diff --git a/Readme.txt b/Readme.txt index 09797c8..53f7c5a 100644 --- a/Readme.txt +++ b/Readme.txt @@ -1,5 +1,3 @@ -This is the Steamworks SDK v1.03 - ================================================================ Copyright © 1996-2009, Valve Corporation, All rights reserved. @@ -13,6 +11,40 @@ website at: http://partner.steamgames.com Revision History: +---------------------------------------------------------------- +v1.05 11th June 2009 +---------------------------------------------------------------- + +Matchmaking +* Added the SteamID of the gameserver to the gameserveritem_t structure (returned only by newer game servers) +* Added ISteamUserStats::GetNumberOfCurrentPlayers(), asyncronously returns the number users currently running this game +* Added k_ELobbyComparisonNotEqual comparision functions for filters +* Added option to use comparison functions for string filters +* Added ISteamMatchmaking::AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ) filter function, so you can find a lobby for a group of users to join +* Extended ISteamMatchmaking::CreateLobby() to take the max number of users in the lobby +* Added ISteamMatchmaking::GetLobbyDataCount(), ISteamMatchmaking::GetLobbyDataByIndex() so you can iterate all the data set on a lobby +* Added ISteamMatchmaking::DeleteLobbyData() so you can clear a key from a lobby +* Added ISteamMatchmaking::SetLobbyOwner() so that ownership of a lobby can be transferred +* Added ISteamMatchmaking::SetLobbyJoinable() +* Added ISteamGameServer::SetGameData(), so game server can set more information that can be filtered for in the server pinging API + +Networking +* Added a set of connectionless networking functions for easy use for making peer-to-peer (NAT traversal) connections. Includes supports for windowed reliable sendsand fragementation/re-assembly of large packets. See ISteamNetworking.h for more details. + +Leaderboards +* Added enum ELeaderboardUploadScoreMethod and changed ISteamUserStats::UploadLeaderboardScore() to take this - lets you force a score to be changed even if it's worse than the prior score + +Callbacks +* Added CCallbackManual<> class to steam_api.h, a version of CCallback<> that doesn't register itself automatically in it's the constructor + +Downloadable Content +* Added ISteamUser::UserHasLicenseForApp() and ISteamGameServer::UserHasLicenseForApp() to enable checking if a user owns DLC in multiplayer. See the DLC documentation for more info. + +Game Overlay +* ISteamFriends::ActivateGameOverlay() now accepts "Stats" and "Achievements" + + + ---------------------------------------------------------------- v1.04 9th Mar 2009 ---------------------------------------------------------------- diff --git a/SteamworksExample/GameEngine.cpp b/SteamworksExample/GameEngine.cpp index 9fc9833..cf9e41b 100644 --- a/SteamworksExample/GameEngine.cpp +++ b/SteamworksExample/GameEngine.cpp @@ -101,26 +101,9 @@ CGameEngine::CGameEngine( HINSTANCE hInstance, int nShowCommand, int32 nWindowWi m_dwQuadBufferBatchPos = 0; m_hTextureWhite = NULL; m_bDeviceLost = false; - m_bFirstFrame = true; - m_flMaxFPS = 120.0f; m_hLastTexture = NULL; m_ulPreviousGameTickCount = 0; m_ulGameTickCount = 0; - - // restrict this main game thread to the first processor, so queryperformance counter won't jump on crappy AMD cpus - DWORD dwThreadAffinityMask = 0x01; - ::SetThreadAffinityMask( ::GetCurrentThread(), dwThreadAffinityMask ); - - LARGE_INTEGER l; - if ( QueryPerformanceFrequency( &l ) ) - { - m_ulPerfCounterToMillisecondsDivisor = l.QuadPart/1000; - } - else - { - m_ulPerfCounterToMillisecondsDivisor = 0; - } - m_dwBackgroundColor = D3DCOLOR_ARGB(0, 255, 255, 255 ); if ( !BCreateGameWindow( nShowCommand ) || !m_hWnd ) @@ -518,32 +501,6 @@ void CGameEngine::SetBackgroundColor( short a, short r, short g, short b ) //----------------------------------------------------------------------------- bool CGameEngine::StartFrame() { - // Track current game time - // bugbug jmccaskey - use a more reliable timer that won't wrap and has better precision! - if ( m_bFirstFrame ) - { - LARGE_INTEGER l; - if ( !QueryPerformanceCounter( &l ) ) - OutputDebugString( "QueryPerformanceCounter failed\n" ); - - m_ulLastQueryPerformanceCounterValue = l.QuadPart; - - m_ulPreviousGameTickCount = 0; - m_ulGameTickCount = 0; - m_bFirstFrame = false; - } - else - { - m_ulPreviousGameTickCount = m_ulGameTickCount; - - LARGE_INTEGER l; - if ( !QueryPerformanceCounter( &l ) ) - OutputDebugString( "QueryPerformanceCounter failed\n" ); - - m_ulGameTickCount += (l.QuadPart - m_ulLastQueryPerformanceCounterValue)/m_ulPerfCounterToMillisecondsDivisor; - m_ulLastQueryPerformanceCounterValue = l.QuadPart; - } - // Before doing anything else pump messages MessagePump(); @@ -664,30 +621,6 @@ void CGameEngine::EndFrame() OutputDebugString( "Present() call failed\n" ); return; } - - // Frame rate limiting - float flDesiredFrameMilliseconds = 1000.0f/m_flMaxFPS; - while( 1 ) - { - uint64 ulCurrentTickCount = m_ulGameTickCount; - LARGE_INTEGER l; - if ( !QueryPerformanceCounter( &l ) ) - OutputDebugString( "QueryPerformanceCounter failed\n" ); - - ulCurrentTickCount += (l.QuadPart-m_ulLastQueryPerformanceCounterValue)/m_ulPerfCounterToMillisecondsDivisor; - - float flMillisecondsElapsed = (float)(ulCurrentTickCount - m_ulGameTickCount); - if ( flMillisecondsElapsed < flDesiredFrameMilliseconds ) - { - // If enough time is left sleep, otherwise just keep spinning so we don't go over the limit... - if ( flDesiredFrameMilliseconds - flMillisecondsElapsed > 2.0f ) - Sleep( 2 ); - else - continue; - } - else - break; - } } //----------------------------------------------------------------------------- diff --git a/SteamworksExample/GameEngine.h b/SteamworksExample/GameEngine.h index bf605fb..e3dcfbb 100644 --- a/SteamworksExample/GameEngine.h +++ b/SteamworksExample/GameEngine.h @@ -166,6 +166,8 @@ public: // way to handle that. uint64 GetGameTicksFrameDelta() { return m_ulGameTickCount - m_ulPreviousGameTickCount; } + void SetGameTickCount( uint64 ulGameTickCount ) { m_ulPreviousGameTickCount = m_ulGameTickCount; m_ulGameTickCount = ulGameTickCount; } + // Get the game engines hwnd HWND GetGameHWND() { return m_hWnd; } @@ -302,15 +304,6 @@ private: // Game time at the start of the previous frame uint64 m_ulPreviousGameTickCount; - // Track whether this is the first frame - bool m_bFirstFrame; - - // Value to divide from QueryPerformanceCounter results to get milliseconds - uint64 m_ulPerfCounterToMillisecondsDivisor; - - // Last value from QueryPerformanceCounter - uint64 m_ulLastQueryPerformanceCounterValue; - // Maximum FPS to run the engine at float m_flMaxFPS; diff --git a/SteamworksExample/Leaderboards.cpp b/SteamworksExample/Leaderboards.cpp index b7c592c..3cf9780 100644 --- a/SteamworksExample/Leaderboards.cpp +++ b/SteamworksExample/Leaderboards.cpp @@ -291,7 +291,7 @@ void CLeaderboards::UpdateLeaderboards( CStatsAndAchievements *pStats ) // if the user won, update the leaderboard with the time it took. If the user's previous time was faster, this time will be thrown out. if ( m_hQuickestWinLeaderboard && SpaceWarClient()->BLocalPlayerWonLastGame() ) { - SteamAPICall_t hSteamAPICall = SteamUserStats()->UploadLeaderboardScore( m_hQuickestWinLeaderboard, (int)pStats->GetGameDurationSeconds(), NULL, 0 ); + SteamAPICall_t hSteamAPICall = SteamUserStats()->UploadLeaderboardScore( m_hQuickestWinLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, (int)pStats->GetGameDurationSeconds(), NULL, 0 ); m_SteamCallResultUploadScore.Set( hSteamAPICall, this, &CLeaderboards::OnUploadScore ); } @@ -299,7 +299,7 @@ void CLeaderboards::UpdateLeaderboards( CStatsAndAchievements *pStats ) // this value will be thrown out if ( m_hFeetTraveledLeaderboard ) { - SteamUserStats()->UploadLeaderboardScore( m_hFeetTraveledLeaderboard, (int)pStats->GetGameFeetTraveled(), NULL, 0 ); + SteamUserStats()->UploadLeaderboardScore( m_hFeetTraveledLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, (int)pStats->GetGameFeetTraveled(), NULL, 0 ); } } diff --git a/SteamworksExample/Lobby.cpp b/SteamworksExample/Lobby.cpp index 821411e..cec6b81 100644 --- a/SteamworksExample/Lobby.cpp +++ b/SteamworksExample/Lobby.cpp @@ -7,6 +7,8 @@ #include "stdafx.h" #include "Lobby.h" +#include "SpaceWarClient.h" +#include "p2pauth.h" //----------------------------------------------------------------------------- @@ -167,6 +169,18 @@ void CLobby::OnLobbyChatUpdate( LobbyChatUpdate_t *pCallback ) // rebuild the menu m_pMenu->Rebuild( m_steamIDLobby ); + + + int cLobbyMembers = SteamMatchmaking()->GetNumLobbyMembers( m_steamIDLobby ); + for ( int i = 0; i < cLobbyMembers; i++ ) + { + CSteamID steamIDLobbyMember = SteamMatchmaking()->GetLobbyMemberByIndex( m_steamIDLobby, i ) ; + + // ignore yourself. + if ( SteamUser()->GetSteamID() == steamIDLobbyMember ) + continue; + + } } diff --git a/SteamworksExample/Lobby.h b/SteamworksExample/Lobby.h index 7aae33e..4862b22 100644 --- a/SteamworksExample/Lobby.h +++ b/SteamworksExample/Lobby.h @@ -18,6 +18,7 @@ class CSpaceWarClient; class CLobbyBrowserMenu; class CLobbyMenu; + //----------------------------------------------------------------------------- // Purpose: Displays the other users in a lobby and allows the game to be started //----------------------------------------------------------------------------- diff --git a/SteamworksExample/Main.cpp b/SteamworksExample/Main.cpp index ef0ec1a..0b9df49 100644 --- a/SteamworksExample/Main.cpp +++ b/SteamworksExample/Main.cpp @@ -54,6 +54,14 @@ int APIENTRY RealMain(HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow) { + // Init Steam CEG + if ( !Steamworks_InitCEGLibrary() ) + { + OutputDebugString( "Steamworks_InitCEGLibrary() failed\n" ); + ::MessageBox( NULL, "Steam must be running to play this game (InitDrmLibrary() failed).\n", "Fatal Error", MB_OK ); + return -1; + } + // Initialize SteamAPI, if this fails we bail out since we depend on Steam for lots of stuff. // You don't necessarily have to though if you write your code to check whether all the Steam // interfaces are NULL before using them and provide alternate paths when they are unavailable. @@ -76,8 +84,7 @@ int APIENTRY RealMain(HINSTANCE hInstance, // Generally you should use the default and not call this as users will be most comfortable with // the default position. The API is provided in case the bottom right creates a serious conflict // with important UI in your game. -// disabled temporarily until calling issue is resolved -// SteamUtils()->SetOverlayNotificationPosition( k_EPositionTopRight ); + SteamUtils()->SetOverlayNotificationPosition( k_EPositionTopRight ); // Look for the +connect ipaddress:port parameter in the command line, // Steam will pass this when a user has used the Steam Server browser to find @@ -103,6 +110,8 @@ int APIENTRY RealMain(HINSTANCE hInstance, pchLobbyID = pchCmdLine + ( pchConnectLobby - pchCmdLine ) + strlen( pchConnectLobbyParam ) + 1; } + // do a DRM self check + Steamworks_SelfCheck(); // Construct a new instance of the game engine // bugbug jmccaskey - make screen resolution dynamic, maybe take it on command line? @@ -147,14 +156,65 @@ int APIENTRY RealMain(HINSTANCE hInstance, } } + // test a user specific secret before entering main loop + Steamworks_TestSecret(); + + static const int m_flMaxFPS = 120; + + // restrict this main game thread to the first processor, so queryperformance counter won't jump on crappy AMD cpus + DWORD dwThreadAffinityMask = 0x01; + ::SetThreadAffinityMask( ::GetCurrentThread(), dwThreadAffinityMask ); + + uint64 ulPerfCounterToMillisecondsDivisor; + LARGE_INTEGER l; + ::QueryPerformanceFrequency( &l ); + ulPerfCounterToMillisecondsDivisor = l.QuadPart/1000; + + ::QueryPerformanceCounter( &l ); + uint64 ulFirstQueryPerformanceCounterValue = l.QuadPart; + uint64 ulGameTickCount = 0; + while( !pGameEngine->BShuttingDown() ) { if ( pGameEngine->StartFrame() ) { + // update timers + ::QueryPerformanceCounter( &l ); + + ulGameTickCount = (l.QuadPart - ulFirstQueryPerformanceCounterValue) / ulPerfCounterToMillisecondsDivisor; + pGameEngine->SetGameTickCount( ulGameTickCount ); + // Run a game frame pGameClient->RunFrame(); - pGameEngine->EndFrame(); + + // Frame rate limiting + float flDesiredFrameMilliseconds = 1000.0f/m_flMaxFPS; + while( 1 ) + { + ::QueryPerformanceCounter( &l ); + + ulGameTickCount = (l.QuadPart - ulFirstQueryPerformanceCounterValue) / ulPerfCounterToMillisecondsDivisor; + + float flMillisecondsElapsed = (float)(ulGameTickCount - pGameEngine->GetGameTickCount()); + if ( flMillisecondsElapsed < flDesiredFrameMilliseconds ) + { + // run networking each frame first + pGameClient->ReceiveNetworkData(); + + // If enough time is left sleep, otherwise just keep spinning so we don't go over the limit... + if ( flDesiredFrameMilliseconds - flMillisecondsElapsed > 2.0f ) + { + Sleep( 2 ); + } + else + { + continue; + } + } + else + break; + } } } @@ -167,6 +227,9 @@ int APIENTRY RealMain(HINSTANCE hInstance, // Shutdown the SteamAPI SteamAPI_Shutdown(); + // Shutdown Steam CEG + Steamworks_TermCEGLibrary(); + // exit return 0; } diff --git a/SteamworksExample/Messages.h b/SteamworksExample/Messages.h index 71f7342..cda3b1e 100644 --- a/SteamworksExample/Messages.h +++ b/SteamworksExample/Messages.h @@ -20,6 +20,7 @@ enum EMessage k_EMsgServerPassAuthentication = k_EMsgServerBegin+3, k_EMsgServerUpdateWorld = k_EMsgServerBegin+4, k_EMsgServerExiting = k_EMsgServerBegin+5, + k_EMsgServerPingResponse = k_EMsgServerBegin+6, // Client messages k_EMsgClientBegin = 500, @@ -27,6 +28,11 @@ enum EMessage k_EMsgClientBeginAuthentication = k_EMsgClientBegin+2, k_EMsgClientSendLocalUpdate = k_EMsgClientBegin+3, k_EMsgClientLeavingServer = k_EMsgClientBegin+4, + k_EMsgClientPing = k_EMsgClientBegin+5, + + // P2P messages + k_EMsgP2PBegin = 500, + k_EMsgP2PSendingTicket = k_EMsgP2PBegin+1, // force 32-bit size enum so the wire protocol doesn't get outgrown later @@ -78,6 +84,13 @@ struct MsgServerExiting_t const DWORD m_dwMessageType; }; +// Msg from server to clients when it is exiting +struct MsgServerPingResponse_t +{ + MsgServerPingResponse_t() : m_dwMessageType( k_EMsgServerPingResponse ) {} + const DWORD m_dwMessageType; +}; + // Msg from client to server when trying to connect struct MsgClientInitiateConnection_t { @@ -94,9 +107,8 @@ struct MsgClientBeginAuthentication_t uint32 m_uTokenLen; #ifdef USE_GS_AUTH_API char m_rgchToken[1024]; -#else - CSteamID m_SteamID; #endif + CSteamID m_SteamID; }; // Msg from client to server when sending state update @@ -109,7 +121,6 @@ struct MsgClientSendLocalUpdate_t ClientSpaceWarUpdateData_t m_ClientUpdateData; }; - // Msg from the client telling the server it is about to leave struct MsgClientLeavingServer_t { @@ -117,5 +128,21 @@ struct MsgClientLeavingServer_t const DWORD m_dwMessageType; }; +// server ping +struct MsgClientPing_t +{ + MsgClientPing_t() : m_dwMessageType( k_EMsgClientPing ) {} + const DWORD m_dwMessageType; +}; + +// Msg from client to server when trying to connect +struct MsgP2PSendingTicket_t +{ + MsgP2PSendingTicket_t() : m_dwMessageType( k_EMsgP2PSendingTicket ) {} + const DWORD m_dwMessageType; + uint32 m_uTokenLen; + char m_rgchToken[1024]; + CSteamID m_SteamID; +}; #endif // MESSAGES_H \ No newline at end of file diff --git a/SteamworksExample/ServerBrowser.cpp b/SteamworksExample/ServerBrowser.cpp index 9e8bcb2..7134981 100644 --- a/SteamworksExample/ServerBrowser.cpp +++ b/SteamworksExample/ServerBrowser.cpp @@ -29,6 +29,7 @@ CGameServer::CGameServer( gameserveritem_t *pGameServerItem ) m_nServerVersion = pGameServerItem->m_nServerVersion; strncpy( m_szServerName, pGameServerItem->GetName(), ARRAYSIZE( m_szServerName ) ); _snprintf( m_szServerString, ARRAYSIZE( m_szServerString ), "%s (%i/%i) at %s ping(%d)", pGameServerItem->GetName(), pGameServerItem->m_nPlayers, pGameServerItem->m_nMaxPlayers, pGameServerItem->m_NetAdr.GetConnectionAddressString(), pGameServerItem->m_nPing ); + m_steamID = pGameServerItem->m_steamID; } @@ -78,6 +79,8 @@ void CServerBrowser::RefreshInternetServers() m_pMenu->Rebuild( m_ListGameServers, m_bRequestingServers ); + Steamworks_TestSecret(); + // Allocate some filters, there are some common pre-defined values that can be used: // // "gamedir" -- this is used to specify mods inside or a single product/appid diff --git a/SteamworksExample/ServerBrowser.h b/SteamworksExample/ServerBrowser.h index 76c3286..7e44ac2 100644 --- a/SteamworksExample/ServerBrowser.h +++ b/SteamworksExample/ServerBrowser.h @@ -31,6 +31,7 @@ public: uint32 GetIP() { return m_unIPAddress; } int32 GetPort() { return m_nConnectionPort; } + CSteamID GetSteamID() { return m_steamID; } private: uint32 m_unIPAddress; // IP address for the server @@ -46,7 +47,7 @@ private: int m_nServerVersion; // server version as reported to Steam char m_szServerName[64]; // Game server name char m_szServerString[128]; // String to show in server browser - + CSteamID m_steamID; }; class CServerBrowser : public ISteamMatchmakingServerListResponse diff --git a/SteamworksExample/ServerBrowserMenu.h b/SteamworksExample/ServerBrowserMenu.h index 7943424..fd0704e 100644 --- a/SteamworksExample/ServerBrowserMenu.h +++ b/SteamworksExample/ServerBrowserMenu.h @@ -27,8 +27,7 @@ public: for( iter = List.begin(); iter != List.end(); ++iter ) { data.m_eStateToTransitionTo = k_EClientGameConnecting; - data.m_unIPAddress = iter->GetIP(); - data.m_nConnectionPort = iter->GetPort(); + data.m_steamIDGameServer = iter->GetSteamID(); AddMenuItem( MenuItem_t( iter->GetDisplayString(), data ) ); } diff --git a/SteamworksExample/Ship.cpp b/SteamworksExample/Ship.cpp index 53b47b5..e9db526 100644 --- a/SteamworksExample/Ship.cpp +++ b/SteamworksExample/Ship.cpp @@ -502,6 +502,8 @@ void CShip::SetExploding( bool bExploding ) if ( m_bExploding == bExploding ) return; + Steamworks_TestSecret(); + // Track that we are exploding, and disable collision detection m_bExploding = bExploding; SetCollisionDetectionDisabled( m_bExploding ); diff --git a/SteamworksExample/SpaceWarClient.cpp b/SteamworksExample/SpaceWarClient.cpp index 333bfa9..db98547 100644 --- a/SteamworksExample/SpaceWarClient.cpp +++ b/SteamworksExample/SpaceWarClient.cpp @@ -15,6 +15,7 @@ #include "ServerBrowser.h" #include "Leaderboards.h" #include "Lobby.h" +#include "p2pauth.h" CSpaceWarClient *g_pSpaceWarClient = NULL; CSpaceWarClient* SpaceWarClient() { return g_pSpaceWarClient; } @@ -23,7 +24,7 @@ CSpaceWarClient* SpaceWarClient() { return g_pSpaceWarClient; } // Purpose: Constructor //----------------------------------------------------------------------------- CSpaceWarClient::CSpaceWarClient( CGameEngine *pGameEngine, CSteamID steamIDUser ) : - m_SocketStatusCallback( this, &CSpaceWarClient::OnSocketStatusCallback ), + m_CallbackP2PSessionConnectFail( this, &CSpaceWarClient::OnP2PSessionConnectFail ), m_LobbyGameCreated( this, &CSpaceWarClient::OnLobbyGameCreated ), m_IPCFailureCallback( this, &CSpaceWarClient::OnIPCFailure ), m_SteamShutdownCallback( this, &CSpaceWarClient::OnSteamShutdown ) @@ -39,11 +40,11 @@ CSpaceWarClient::CSpaceWarClient( CGameEngine *pGameEngine, CSteamID steamIDUser m_uPlayerShipIndex = 0; m_SteamIDLocalUser = steamIDUser; m_eConnectedStatus = k_EClientNotConnected; - m_hSocketClient = NULL; m_bTransitionedGameState = false; m_rgchErrorText[0] = 0; m_unServerIP = 0; m_usServerPort = 0; + m_ulPingSentTime = 0; for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i ) { @@ -74,11 +75,15 @@ CSpaceWarClient::CSpaceWarClient( CGameEngine *pGameEngine, CSteamID steamIDUser // Initialize sun m_pSun = new CSun( pGameEngine ); + // initialize P2P auth engine + m_pP2PAuthedGame = new CP2PAuthedGame( m_pGameEngine ); + // Create matchmaking menus m_pServerBrowser = new CServerBrowser( m_pGameEngine ); m_pLobbyBrowser = new CLobbyBrowser( m_pGameEngine ); m_pLobby = new CLobby( m_pGameEngine ); + // Init stats m_pStatsAndAchievements = new CStatsAndAchievements( pGameEngine ); m_pLeaderboards = new CLeaderboards( pGameEngine ); @@ -95,6 +100,13 @@ CSpaceWarClient::~CSpaceWarClient() { DisconnectFromServer(); + if ( m_pP2PAuthedGame ) + { + m_pP2PAuthedGame->EndGame(); + delete m_pP2PAuthedGame; + m_pP2PAuthedGame = NULL; + } + if ( m_pServer ) { delete m_pServer; @@ -127,13 +139,6 @@ CSpaceWarClient::~CSpaceWarClient() m_rgpShips[i] = NULL; } } - - // Close down our socket if it appears to be valid - if ( m_hSocketClient ) - { - SteamNetworking()->DestroySocket( m_hSocketClient, true ); - m_hSocketClient = NULL; - } } @@ -147,9 +152,20 @@ void CSpaceWarClient::DisconnectFromServer() SteamUser()->TerminateGameConnection( m_unServerIP, m_usServerPort ); MsgClientLeavingServer_t msg; - BSendServerData( (char*)&msg, sizeof(msg), true ); + BSendServerData( &msg, sizeof(msg) ); m_eConnectedStatus = k_EClientNotConnected; } + if ( m_pP2PAuthedGame ) + { + m_pP2PAuthedGame->EndGame(); + } + + // forget the game server ID + if ( m_steamIDGameServer.IsValid() ) + { + SteamNetworking()->CloseP2PSessionWithUser( m_steamIDGameServer ); + m_steamIDGameServer = CSteamID(); + } } @@ -160,6 +176,13 @@ void CSpaceWarClient::OnReceiveServerInfo( CSteamID steamIDGameServer, bool bVAC { m_eConnectedStatus = k_EClientConnectedPendingAuthentication; m_pQuitMenu->SetHeading( pchServerName ); + m_steamIDGameServer = steamIDGameServer; + + // look up the servers IP and Port from the connection + P2PSessionState_t p2pSessionState; + SteamNetworking()->GetP2PSessionState( steamIDGameServer, &p2pSessionState ); + m_unServerIP = p2pSessionState.m_nRemoteIP; + m_usServerPort = p2pSessionState.m_nRemotePort; MsgClientBeginAuthentication_t msg; #ifdef USE_GS_AUTH_API @@ -171,10 +194,12 @@ void CSpaceWarClient::OnReceiveServerInfo( CSteamID steamIDGameServer, bool bVAC msg.m_SteamID = SteamUser()->GetSteamID(); #endif + Steamworks_TestSecret(); + if ( msg.m_uTokenLen < 1 ) OutputDebugString( "Warning: Looks like InitiateGameConnection didn't give us a good token\n" ); - BSendServerData( (char*)&msg, sizeof(msg), true ); + BSendServerData( &msg, sizeof(msg) ); } @@ -197,6 +222,11 @@ void CSpaceWarClient::OnReceiveServerAuthenticationResponse( bool bSuccess, uint m_uPlayerShipIndex = uPlayerPosition; m_eConnectedStatus = k_EClientConnectedAndAuthenticated; + + // send a ping, to measure round-trip time + m_ulPingSentTime = m_pGameEngine->GetGameTickCount(); + MsgClientPing_t msg; + BSendServerData( &msg, sizeof( msg ) ); } } @@ -264,6 +294,56 @@ void CSpaceWarClient::OnReceiveServerUpdate( ServerSpaceWarUpdateData_t UpdateDa // Update who won last m_uPlayerWhoWonGame = UpdateData.m_uPlayerWhoWonGame; + if ( m_pP2PAuthedGame ) + { + // has the player list changed? + if ( m_pServer ) + { + // if i am the server owner i need to auth everyone who wants to play + // assume i am in slot 0, so start at slot 1 + for( uint32 i=1; i < MAX_PLAYERS_PER_SERVER; ++i ) + { + CSteamID steamIDNew( UpdateData.m_rgPlayerSteamIDs[i] ); + if ( steamIDNew == SteamUser()->GetSteamID() ) + { + OutputDebugStringA( "Server player slot 0 is not server owner.\n" ); + } + else if ( steamIDNew != m_rgSteamIDPlayers[i] ) + { + if ( m_rgSteamIDPlayers[i].IsValid() ) + { + m_pP2PAuthedGame->PlayerDisconnect( i ); + } + if ( steamIDNew.IsValid() ) + { + m_pP2PAuthedGame->RegisterPlayer( i, steamIDNew ); + } + } + } + } + else + { + // i am just a client, i need to auth the game owner ( slot 0 ) + CSteamID steamIDNew( UpdateData.m_rgPlayerSteamIDs[0] ); + if ( steamIDNew == SteamUser()->GetSteamID() ) + { + OutputDebugStringA( "Server player slot 0 is not server owner.\n" ); + } + else if ( steamIDNew != m_rgSteamIDPlayers[0] ) + { + if ( m_rgSteamIDPlayers[0].IsValid() ) + { + OutputDebugStringA( "Server player slot 0 has disconnected - but thats the server owner.\n" ); + m_pP2PAuthedGame->PlayerDisconnect( 0 ); + } + if ( steamIDNew.IsValid() ) + { + m_pP2PAuthedGame->StartAuthPlayer( 0, steamIDNew ); + } + } + } + } + // Update the ships for( uint32 i=0; i < MAX_PLAYERS_PER_SERVER; ++i ) { @@ -336,9 +416,9 @@ void CSpaceWarClient::SetConnectionFailureText( const char *pchErrorText ) //----------------------------------------------------------------------------- // Purpose: Send data to the current server //----------------------------------------------------------------------------- -bool CSpaceWarClient::BSendServerData( char *pData, uint32 nSizeOfData, bool bSendReliably ) +bool CSpaceWarClient::BSendServerData( const void *pData, uint32 nSizeOfData ) { - if ( !SteamNetworking()->SendDataOnSocket( m_hSocketClient, pData, nSizeOfData, bSendReliably ) ) + if ( !SteamNetworking()->SendP2PPacket( m_steamIDGameServer, pData, nSizeOfData, k_EP2PSendUnreliable ) ) { OutputDebugString( "Failed sending data to server\n" ); return false; @@ -364,14 +444,10 @@ void CSpaceWarClient::InitiateServerConnection( uint32 unServerAddress, const in // and so we will retry at appropriate intervals if packets drop m_ulLastNetworkDataReceivedTime = m_ulLastConnectionAttemptRetryTime = m_pGameEngine->GetGameTickCount(); - // Create UDP socket for the client to talk to the server over - m_hSocketClient = SteamNetworking()->CreateConnectionSocket( unServerAddress, nPort, 10 ); - if ( !m_hSocketClient ) - { - OutputDebugString( "Failed creating socket for CSpaceWarClient -- won't be able to talk to server\n" ); - } - - // wait for the callback before initiating connection + // ping the server to find out what it's steamID is + m_unServerIP = unServerAddress; + m_usServerPort = (uint16)nPort; + m_GameServerPing.RetrieveSteamIDFromGameServer( this, m_unServerIP, m_usServerPort ); } @@ -387,52 +463,28 @@ void CSpaceWarClient::InitiateServerConnection( CSteamID steamIDGameServer ) SetGameState( k_EClientGameConnecting ); + m_steamIDGameServer = steamIDGameServer; + // Update when we last retried the connection, as well as the last packet received time so we won't timeout too soon, // and so we will retry at appropriate intervals if packets drop m_ulLastNetworkDataReceivedTime = m_ulLastConnectionAttemptRetryTime = m_pGameEngine->GetGameTickCount(); - // Create UDP socket for the client to talk to the server over - m_hSocketClient = SteamNetworking()->CreateP2PConnectionSocket( steamIDGameServer, 0, 10, true ); - if ( !m_hSocketClient ) - { - OutputDebugString( "Failed creating socket for CSpaceWarClient -- won't be able to talk to server\n" ); - } - - // wait for the callback before initiating connection + // send the packet to the server + MsgClientInitiateConnection_t msg; + BSendServerData( &msg, sizeof( msg ) ); } //----------------------------------------------------------------------------- // Purpose: steam callback, trigger when our connection state changes //----------------------------------------------------------------------------- -void CSpaceWarClient::OnSocketStatusCallback( SocketStatusCallback_t *pCallback ) +void CSpaceWarClient::OnP2PSessionConnectFail( P2PSessionConnectFail_t *pCallback ) { - // make sure the callback is for our socket - if ( pCallback->m_hSocket != m_hSocketClient ) - return; - - // Update out network data received time even though we haven't really received anything yet, - // this will help us make sure we don't time out prematurely during the handshake - m_ulLastNetworkDataReceivedTime = m_pGameEngine->GetGameTickCount(); - - // if we've finished connecting to the server, send the connect message - if ( pCallback->m_eSNetSocketState == k_ESNetSocketStateConnected ) - { - MsgClientInitiateConnection_t msg; - BSendServerData( (char*)&msg, sizeof( msg ), true ); - } - - // handling being disconnect from the server signal via network breakage, or the remote end dying - if ( pCallback->m_eSNetSocketState == k_ESNetSocketStateRemoteEndDisconnected - || pCallback->m_eSNetSocketState == k_ESNetSocketStateConnectionBroken ) + if ( pCallback->m_steamIDRemote == m_steamIDGameServer ) { + // failed, error out + OutputDebugString( "Failed to make P2P connection, quiting server" ); OnReceiveServerExiting(); - m_hSocketClient = NULL; - } - - if ( pCallback->m_eSNetSocketState >= k_ESNetSocketStateDisconnecting ) - { - m_hSocketClient = NULL; } } @@ -442,9 +494,6 @@ void CSpaceWarClient::OnSocketStatusCallback( SocketStatusCallback_t *pCallback //----------------------------------------------------------------------------- void CSpaceWarClient::ReceiveNetworkData() { - if ( !m_hSocketClient ) - return; - char rgchRecvBuf[MAX_SPACEWAR_PACKET_SIZE]; char *pchRecvBuf = rgchRecvBuf; uint32 cubMsgSize; @@ -458,90 +507,115 @@ void CSpaceWarClient::ReceiveNetworkData() } // see if there is any data waiting on the socket - if ( !SteamNetworking()->RetrieveDataFromSocket( m_hSocketClient, rgchRecvBuf, sizeof( rgchRecvBuf ), &cubMsgSize ) ) + if ( !SteamNetworking()->IsP2PPacketAvailable( &cubMsgSize ) ) break; - // not enough space in buffer, so message won't have been popped off of list + // not enough space in default buffer // alloc custom size and try again if ( cubMsgSize > sizeof(rgchRecvBuf) ) { pchRecvBuf = (char *)malloc( cubMsgSize ); - SteamNetworking()->RetrieveDataFromSocket( m_hSocketClient, pchRecvBuf, cubMsgSize, &cubMsgSize ); } + CSteamID steamIDRemote; + if ( !SteamNetworking()->ReadP2PPacket( pchRecvBuf, cubMsgSize, &cubMsgSize, &steamIDRemote ) ) + break; - m_ulLastNetworkDataReceivedTime = m_pGameEngine->GetGameTickCount(); - - // make sure we're connected - if ( m_eConnectedStatus == k_EClientNotConnected && m_eGameState != k_EClientGameConnecting ) - continue; - - if ( cubMsgSize < sizeof( EMessage ) ) + // see if it's from the game server + if ( steamIDRemote == m_steamIDGameServer ) { - OutputDebugString( "Got garbage on client socket, too short\n" ); - } + m_ulLastNetworkDataReceivedTime = m_pGameEngine->GetGameTickCount(); - EMessage *pEMsg = (EMessage*)pchRecvBuf; - switch ( *pEMsg ) + // make sure we're connected + if ( m_eConnectedStatus == k_EClientNotConnected && m_eGameState != k_EClientGameConnecting ) + continue; + + if ( cubMsgSize < sizeof( EMessage ) ) + { + OutputDebugString( "Got garbage on client socket, too short\n" ); + } + + EMessage *pEMsg = (EMessage*)pchRecvBuf; + switch ( *pEMsg ) + { + case k_EMsgServerSendInfo: + { + if ( cubMsgSize != sizeof( MsgServerSendInfo_t ) ) + { + OutputDebugString ("Bad server info msg\n" ); + continue; + } + MsgServerSendInfo_t *pMsg = (MsgServerSendInfo_t*)pchRecvBuf; + + // pull the IP address of the user from the socket + OnReceiveServerInfo( CSteamID( pMsg->m_ulSteamIDServer ), pMsg->m_bIsVACSecure, pMsg->m_rgchServerName ); + } + break; + case k_EMsgServerPassAuthentication: + { + if ( cubMsgSize != sizeof( MsgServerPassAuthentication_t ) ) + { + OutputDebugString( "Bad accept connection msg\n" ); + continue; + } + MsgServerPassAuthentication_t *pMsg = (MsgServerPassAuthentication_t*)pchRecvBuf; + + // Our game client doesn't really care about whether the server is secure, or what its + // steamID is, but if it did we would pass them in here as they are part of the accept message + OnReceiveServerAuthenticationResponse( true, pMsg->m_uPlayerPosition ); + } + break; + case k_EMsgServerFailAuthentication: + { + OnReceiveServerAuthenticationResponse( false, 0 ); + } + break; + case k_EMsgServerUpdateWorld: + { + if ( cubMsgSize != sizeof( MsgServerUpdateWorld_t ) ) + { + OutputDebugString( "Bad server world update msg\n" ); + continue; + } + + MsgServerUpdateWorld_t *pMsg = (MsgServerUpdateWorld_t*)pchRecvBuf; + OnReceiveServerUpdate( pMsg->m_ServerUpdateData ); + } + break; + case k_EMsgServerExiting: + { + if ( cubMsgSize != sizeof( MsgServerExiting_t ) ) + { + OutputDebugString( "Bad server exiting msg\n" ); + } + OnReceiveServerExiting(); + } + break; + case k_EMsgServerPingResponse: + { + uint64 ulTimePassedMS = m_pGameEngine->GetGameTickCount() - m_ulPingSentTime; + char rgchT[256]; + _snprintf( rgchT, sizeof(rgchT), "Round-trip ping time to server %d ms\n", (int)ulTimePassedMS ); + OutputDebugString( rgchT ); + m_ulPingSentTime = 0; + } + break; + default: + OutputDebugString( "Unhandled message from server\n" ); + break; + } + } + else { - case k_EMsgServerSendInfo: - { - if ( cubMsgSize != sizeof( MsgServerSendInfo_t ) ) - { - OutputDebugString ("Bad server info msg\n" ); - continue; - } - MsgServerSendInfo_t *pMsg = (MsgServerSendInfo_t*)pchRecvBuf; - - // pull the IP address of the user from the socket - SteamNetworking()->GetSocketInfo( m_hSocketClient, NULL, NULL, &m_unServerIP, &m_usServerPort ); - OnReceiveServerInfo( CSteamID( pMsg->m_ulSteamIDServer ), pMsg->m_bIsVACSecure, pMsg->m_rgchServerName ); - } - break; - case k_EMsgServerPassAuthentication: - { - if ( cubMsgSize != sizeof( MsgServerPassAuthentication_t ) ) - { - OutputDebugString( "Bad accept connection msg\n" ); - continue; - } - MsgServerPassAuthentication_t *pMsg = (MsgServerPassAuthentication_t*)pchRecvBuf; - - // Our game client doesn't really care about whether the server is secure, or what its - // steamID is, but if it did we would pass them in here as they are part of the accept message - OnReceiveServerAuthenticationResponse( true, pMsg->m_uPlayerPosition ); - } - break; - case k_EMsgServerFailAuthentication: - { - OnReceiveServerAuthenticationResponse( false, 0 ); - } - break; - case k_EMsgServerUpdateWorld: - { - if ( cubMsgSize != sizeof( MsgServerUpdateWorld_t ) ) - { - OutputDebugString( "Bad server world update msg\n" ); - continue; - } - - MsgServerUpdateWorld_t *pMsg = (MsgServerUpdateWorld_t*)pchRecvBuf; - OnReceiveServerUpdate( pMsg->m_ServerUpdateData ); - } - break; - case k_EMsgServerExiting: - { - if ( cubMsgSize != sizeof( MsgServerExiting_t ) ) - { - OutputDebugString( "Bad server exiting msg\n" ); - } - OnReceiveServerExiting(); - } - break; - default: - OutputDebugString( "Unhandled message from server\n" ); - break; + EMessage *pEMsg = (EMessage*)pchRecvBuf; + m_pP2PAuthedGame->HandleMessage( *pEMsg, pchRecvBuf ); } } + + // if we're running a server, do that as well + if ( m_pServer ) + { + m_pServer->ReceiveNetworkData(); + } } @@ -550,6 +624,8 @@ void CSpaceWarClient::ReceiveNetworkData() //----------------------------------------------------------------------------- void CSpaceWarClient::OnReceiveServerExiting() { + if ( m_pP2PAuthedGame ) + m_pP2PAuthedGame->EndGame(); if ( m_eGameState != k_EClientGameActive ) return; @@ -611,6 +687,18 @@ void CSpaceWarClient::OnLobbyEntered( LobbyEnter_t *pCallback, bool bIOFailure ) } +void CSpaceWarClient::OnLobbyDataChange( LobbyDataUpdate_t *pCallback, bool bIOFailure ) +{ + OutputDebugStringA( "OnLobbyDataChange "); + CSteamID steamIDLobbyMember( pCallback->m_ulSteamIDMember ); +} + +void CSpaceWarClient::OnLobbyChatUpdate( LobbyChatUpdate_t *pCallback, bool bIOFailure ) +{ + CSteamID steamIDLobbyMember( pCallback->m_ulSteamIDUserChanged ); + CSteamID steamIDLobbyMember2( pCallback->m_ulSteamIDMakingChange ); + OutputDebugStringA( "OnLobbyChatUpdate "); +} //----------------------------------------------------------------------------- // Purpose: Joins a game from a lobby @@ -625,11 +713,6 @@ void CSpaceWarClient::OnLobbyGameCreated( LobbyGameCreated_t *pCallback ) { InitiateServerConnection( CSteamID( pCallback->m_ulSteamIDGameServer ) ); } - else - { - // fall back to using the IP address directly - InitiateServerConnection( pCallback->m_unIP, pCallback->m_usPort ); - } } @@ -701,7 +784,7 @@ void CSpaceWarClient::OnGameStateChanged( EClientGameState eGameStateNew ) if ( !m_SteamCallResultLobbyCreated.IsActive() ) { // ask steam to create a lobby - SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby( k_ELobbyTypePublic /* public lobby, anyone can find it */ ); + SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby( k_ELobbyTypePublic /* public lobby, anyone can find it */, 4 ); // set the function to call when this completes m_SteamCallResultLobbyCreated.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyCreated ); } @@ -838,11 +921,9 @@ void CSpaceWarClient::RunFrame() if ( m_pServer && m_pServer->IsConnectedToSteam() ) { // server is up; tell everyone else to connect - SteamMatchmaking()->SetLobbyGameServer( m_steamIDLobby, m_pServer->GetIP(), m_pServer->GetPort(), m_pServer->GetSteamID() ); - + SteamMatchmaking()->SetLobbyGameServer( m_steamIDLobby, 0, 0, m_pServer->GetSteamID() ); // start connecting ourself via localhost (this will automatically leave the lobby) - uint32 unIPAddress = ( 1 ) + ( 0 << 8 ) + ( 0 << 16 ) + ( 127 << 24 ); - InitiateServerConnection( unIPAddress, m_pServer->GetPort() ); + InitiateServerConnection( m_pServer->GetSteamID() ); } break; @@ -884,9 +965,12 @@ void CSpaceWarClient::RunFrame() // Check if we've waited too long and should time out the connection if ( m_pGameEngine->GetGameTickCount() - m_ulStateTransitionTime > MILLISECONDS_CONNECTION_TIMEOUT ) { + if ( m_pP2PAuthedGame ) + m_pP2PAuthedGame->EndGame(); if ( m_eConnectedStatus == k_EClientConnectedAndAuthenticated ) SteamUser()->TerminateGameConnection( m_unServerIP, m_usServerPort ); - + + m_GameServerPing.CancelPing(); SetConnectionFailureText( "Timed out connecting to game server" ); SetGameState( k_EClientGameConnectionFailure ); } @@ -941,14 +1025,13 @@ void CSpaceWarClient::RunFrame() if ( !m_pServer ) { m_pServer = new CSpaceWarServer( m_pGameEngine ); - uint32 unIPAddress = ( 1 ) + ( 0 << 8 ) + ( 0 << 16 ) + ( 127 << 24 ); - InitiateServerConnection( unIPAddress, SPACEWAR_SERVER_PORT ); - } - else - { - OutputDebugString( "Server already started locally when in k_EClientGameStartServer state!\n" ); } + if ( m_pServer && m_pServer->IsConnectedToSteam() ) + { + // server is ready, connect to it + InitiateServerConnection( m_pServer->GetSteamID() ); + } break; case k_EClientGameDraw: case k_EClientGameWinner: @@ -1004,7 +1087,37 @@ void CSpaceWarClient::RunFrame() // If this fails, it probably just means its not time to send an update yet if ( m_rgpShips[ m_uPlayerShipIndex ]->BGetClientUpdateData( &msg.m_ClientUpdateData ) ) - BSendServerData( (char*)&msg, sizeof( msg ), false ); + BSendServerData( &msg, sizeof( msg ) ); + } + + if ( m_pP2PAuthedGame ) + { + if ( m_pServer ) + { + // Now if we are the owner of the game, lets make sure all of our players are legit. + // if they are not, we tell the server to kick them off + // Start at 1 to skip myself + for ( int i = 1; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + if ( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i] && !m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i]->BIsAuthOk() ) + { + m_pServer->KickPlayerOffServer( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[i]->m_steamID ); + } + } + } + else + { + // If we are not the owner of the game, lets make sure the game owner is legit + // if he is not, we leave the game + if ( m_pP2PAuthedGame->m_rgpP2PAuthPlayer[0] ) + { + if ( !m_pP2PAuthedGame->m_rgpP2PAuthPlayer[0]->BIsAuthOk() ) + { + // leave the game + SetGameState( k_EClientGameMenu ); + } + } + } } // If we've started a local server run it diff --git a/SteamworksExample/SpaceWarClient.h b/SteamworksExample/SpaceWarClient.h index 7abf301..8737de8 100644 --- a/SteamworksExample/SpaceWarClient.h +++ b/SteamworksExample/SpaceWarClient.h @@ -28,6 +28,8 @@ class CServerBrowser; class CLobbyBrowser; class CLobby; class CLeaderboards; +class CP2PAuthPlayer; +class CP2PAuthedGame; // Height of the HUD font #define HUD_FONT_HEIGHT 18 @@ -47,8 +49,7 @@ enum EClientConnectionState struct ServerBrowserMenuData_t { EClientGameState m_eStateToTransitionTo; - uint32 m_unIPAddress; - int32 m_nConnectionPort; + CSteamID m_steamIDGameServer; }; // a lobby as shown in the find lobbies menu @@ -92,11 +93,11 @@ public: void ReceiveNetworkData(); // Connect to a server at a given IP address or game server steamID - void InitiateServerConnection( uint32 unServerAddress, const int32 nPort ); void InitiateServerConnection( CSteamID steamIDGameServer ); + void InitiateServerConnection( uint32 unServerAddress, const int32 nPort ); // Send data to a client at the given ship index - bool BSendServerData( char *pData, uint32 nSizeOfData, bool bSendReliably ); + bool BSendServerData( const void *pData, uint32 nSizeOfData ); // Menu callback handler (handles a bunch of menus that just change state with no extra data) void OnMenuSelection( EClientGameState eState ) { SetGameState( eState ); } @@ -106,7 +107,7 @@ public: { if ( selection.m_eStateToTransitionTo == k_EClientGameConnecting ) { - InitiateServerConnection( selection.m_unIPAddress, selection.m_nConnectionPort ); + InitiateServerConnection( selection.m_steamIDGameServer ); } else { @@ -122,6 +123,8 @@ public: SteamAPICall_t hSteamAPICall = SteamMatchmaking()->JoinLobby( selection.m_steamIDLobby ); // set the function to call when this API completes m_SteamCallResultLobbyEntered.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyEntered ); + m_SteamCallResultLobbyDataChange.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyDataChange ); + m_SteamCallResultLobbyChatUpdate.Set( hSteamAPICall, this, &CSpaceWarClient::OnLobbyChatUpdate ); } SetGameState( selection.m_eStateToTransitionTo ); @@ -232,16 +235,62 @@ private: // Time we last got data from the server uint64 m_ulLastNetworkDataReceivedTime; + // Time when we sent our ping + uint64 m_ulPingSentTime; + // Text to display if we are in an error state char m_rgchErrorText[256]; - // Socket to use when communicating with servers - SNetSocket_t m_hSocketClient; - // Server address data + CSteamID m_steamIDGameServer; uint32 m_unServerIP; uint16 m_usServerPort; + // simple class to marshal callbacks from pinging a game server + class CGameServerPing : public ISteamMatchmakingPingResponse + { + public: + CGameServerPing() + { + m_hGameServerQuery = HSERVERQUERY_INVALID; + m_pSpaceWarsClient = NULL; + } + + void RetrieveSteamIDFromGameServer( CSpaceWarClient *pSpaceWarClient, uint32 unIP, uint16 unPort ) + { + m_pSpaceWarsClient = pSpaceWarClient; + m_hGameServerQuery = SteamMatchmakingServers()->PingServer( unIP, unPort, this ); + } + + void CancelPing() + { + m_hGameServerQuery = HSERVERQUERY_INVALID; + } + + // Server has responded successfully and has updated data + virtual void ServerResponded( gameserveritem_t &server ) + { + if ( m_hGameServerQuery != HSERVERQUERY_INVALID && server.m_steamID.IsValid() ) + { + m_pSpaceWarsClient->InitiateServerConnection( server.m_steamID ); + } + + m_hGameServerQuery = HSERVERQUERY_INVALID; + } + + // Server failed to respond to the ping request + virtual void ServerFailedToRespond() + { + m_hGameServerQuery = HSERVERQUERY_INVALID; + } + + private: + HServerQuery m_hGameServerQuery; // we're ping a game server, so we can convert IP:Port to a steamID + CSpaceWarClient *m_pSpaceWarsClient; + }; + CGameServerPing m_GameServerPing; + + // Track whether we are connected to a server (and what specific state that connection is in) EClientConnectionState m_eConnectedStatus; @@ -281,6 +330,11 @@ private: void OnLobbyEntered( LobbyEnter_t *pCallback, bool bIOFailure ); CCallResult m_SteamCallResultLobbyEntered; + void OnLobbyDataChange( LobbyDataUpdate_t *pCallback, bool bIOFailure ); + CCallResult m_SteamCallResultLobbyDataChange; + void OnLobbyChatUpdate( LobbyChatUpdate_t *pCallback, bool bIOFailure ); + CCallResult m_SteamCallResultLobbyChatUpdate; + // callback for when the lobby game server has started STEAM_CALLBACK( CSpaceWarClient, OnLobbyGameCreated, LobbyGameCreated_t, m_LobbyGameCreated ); @@ -289,9 +343,11 @@ private: // local lobby display CLobby *m_pLobby; + // p2p game auth manager + CP2PAuthedGame *m_pP2PAuthedGame; // connection handler - STEAM_CALLBACK( CSpaceWarClient, OnSocketStatusCallback, SocketStatusCallback_t, m_SocketStatusCallback ); + STEAM_CALLBACK( CSpaceWarClient, OnP2PSessionConnectFail, P2PSessionConnectFail_t, m_CallbackP2PSessionConnectFail ); // ipc failure handler STEAM_CALLBACK( CSpaceWarClient, OnIPCFailure, IPCFailure_t, m_IPCFailureCallback ); diff --git a/SteamworksExample/SpaceWarServer.cpp b/SteamworksExample/SpaceWarServer.cpp index 26c734a..82ec871 100644 --- a/SteamworksExample/SpaceWarServer.cpp +++ b/SteamworksExample/SpaceWarServer.cpp @@ -21,7 +21,8 @@ CSpaceWarServer::CSpaceWarServer( CGameEngine *pGameEngine ) : m_CallbackGSClientApprove( this, &CSpaceWarServer::OnGSClientApprove ), m_CallbackGSClientDeny( this, &CSpaceWarServer::OnGSClientDeny ), m_CallbackGSClientKick( this, &CSpaceWarServer::OnGSClientKick ), - m_CallbackSocketStatus( this, &CSpaceWarServer::OnSocketStatusCallback ) + m_CallbackP2PSessionConnectFail( this, &CSpaceWarServer::OnP2PSessionConnectFail ), + m_CallbackP2PSessionRequest( this, &CSpaceWarServer::OnP2PSessionRequest ) { m_bConnectedToSteam = false; @@ -92,21 +93,8 @@ CSpaceWarServer::CSpaceWarServer( CGameEngine *pGameEngine ) : // Initialize sun m_pSun = new CSun( pGameEngine ); - // We don't create our socket to listen for clients yet, because we have to be logged - // into Steam and have our own game server SteamID before we can really handle them. - // - // We'll setup our socket and start listening after that happens. - m_hSocketServer = NULL; - // Initialize ships ResetPlayerShips(); - - // Start listening for client connections, as we now have enough info to really handle them - m_hSocketServer = SteamGameServerNetworking()->CreateListenSocket( 0, 0, SPACEWAR_SERVER_PORT, true ); - if ( !m_hSocketServer ) - { - OutputDebugString( "Failed creating socket for CSpaceWarServer -- no one will be able to connect\n" ); - } } @@ -140,39 +128,38 @@ CSpaceWarServer::~CSpaceWarServer() SteamGameServer()->LogOff(); #endif - // Close down our socket if it appears to be valid - if ( m_hSocketServer ) - { - SteamGameServerNetworking()->DestroyListenSocket( m_hSocketServer, true ); - } + // release our reference to the steam client library + SteamGameServer_Shutdown(); } //----------------------------------------------------------------------------- -// Purpose: Handle sending data to a client at a given index +// Purpose: Handle clients connecting //----------------------------------------------------------------------------- -void CSpaceWarServer::OnSocketStatusCallback( SocketStatusCallback_t *pCallback ) +void CSpaceWarServer::OnP2PSessionRequest( P2PSessionRequest_t *pCallback ) { - // make sure the callback involves our listen socket (the callbacks go to all listeners, so you have to filter) - if ( pCallback->m_hListenSocket != m_hSocketServer ) - return; + // we'll accept a connection from anyone + SteamGameServerNetworking()->AcceptP2PSessionWithUser( pCallback->m_steamIDRemote ); +} - // lookup disconnect users - if ( pCallback->m_eSNetSocketState >= k_ESNetSocketStateDisconnecting ) + +//----------------------------------------------------------------------------- +// Purpose: Handle clients disconnecting +//----------------------------------------------------------------------------- +void CSpaceWarServer::OnP2PSessionConnectFail( P2PSessionConnectFail_t *pCallback ) +{ + // socket has closed, kick the user associated with it + for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i ) { - // socket has closed, kick the user associated with it - for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i ) - { - // If there is no ship, skip - if ( !m_rgClientData[i].m_bActive ) - continue; + // If there is no ship, skip + if ( !m_rgClientData[i].m_bActive ) + continue; - if ( m_rgClientData[i].m_hSocket == pCallback->m_hSocket ) - { - OutputDebugString( "Disconnected dropped user\n" ); - RemovePlayerFromServer( i ); - break; - } + if ( m_rgClientData[i].m_SteamIDUser == pCallback->m_steamIDRemote ) + { + OutputDebugString( "Disconnected dropped user\n" ); + RemovePlayerFromServer( i ); + break; } } } @@ -187,7 +174,7 @@ bool CSpaceWarServer::BSendDataToClient( uint32 uShipIndex, char *pData, uint32 if ( uShipIndex >= MAX_PLAYERS_PER_SERVER ) return false; - if ( !SteamGameServerNetworking()->SendDataOnSocket( m_rgClientData[uShipIndex].m_hSocket, pData, nSizeOfData, false ) ) + if ( !SteamGameServerNetworking()->SendP2PPacket( m_rgClientData[uShipIndex].m_SteamIDUser, pData, nSizeOfData, k_EP2PSendUnreliable ) ) { OutputDebugString( "Failed sending data to a client\n" ); return false; @@ -206,7 +193,7 @@ bool CSpaceWarServer::BSendDataToPendingClient( uint32 uShipIndex, char *pData, if ( uShipIndex >= MAX_PLAYERS_PER_SERVER ) return false; - if ( !SteamGameServerNetworking()->SendDataOnSocket( m_rgPendingClientData[uShipIndex].m_hSocket, pData, nSizeOfData, false ) ) + if ( !SteamGameServerNetworking()->SendP2PPacket( m_rgPendingClientData[uShipIndex].m_SteamIDUser, pData, nSizeOfData, k_EP2PSendReliable ) ) { OutputDebugString( "Failed sending data to a pending client\n" ); return false; @@ -216,30 +203,15 @@ bool CSpaceWarServer::BSendDataToPendingClient( uint32 uShipIndex, char *pData, } -//----------------------------------------------------------------------------- -// Purpose: Handle sending data to a client at a given address -//----------------------------------------------------------------------------- -bool CSpaceWarServer::BSendDataToClientOnSocket( SNetSocket_t hSocket, char *pData, uint32 nSizeOfData ) -{ - if ( !SteamGameServerNetworking()->SendDataOnSocket( hSocket, pData, nSizeOfData, false ) ) - { - OutputDebugString( "Failed sending data to a client\n" ); - return false; - } - - return true; -} - - //----------------------------------------------------------------------------- // Purpose: Handle a new client connecting //----------------------------------------------------------------------------- -void CSpaceWarServer::OnClientBeginAuthentication( SNetSocket_t hSocketClient, void *pToken, uint32 uTokenLen ) +void CSpaceWarServer::OnClientBeginAuthentication( CSteamID steamIDClient, void *pToken, uint32 uTokenLen ) { // First, check this isn't a duplicate and we already have a user logged on from the same ip/port combo for( uint32 i = 0; i < MAX_PLAYERS_PER_SERVER; ++i ) { - if ( m_rgClientData[i].m_hSocket == hSocketClient ) + if ( m_rgClientData[i].m_SteamIDUser == steamIDClient ) { // We already logged them on... (should maybe tell them again incase they don't know?) return; @@ -261,7 +233,7 @@ void CSpaceWarServer::OnClientBeginAuthentication( SNetSocket_t hSocketClient, v if ( nPendingOrActivePlayerCount >= MAX_PLAYERS_PER_SERVER ) { MsgServerFailAuthentication_t msg; - BSendDataToClientOnSocket( hSocketClient, (char*)&msg, sizeof( msg ) ); + SteamGameServerNetworking()->SendP2PPacket( steamIDClient, &msg, sizeof( msg ), k_EP2PSendReliable ); } // If we get here there is room, add the player as pending @@ -269,20 +241,20 @@ void CSpaceWarServer::OnClientBeginAuthentication( SNetSocket_t hSocketClient, v { if ( !m_rgPendingClientData[i].m_bActive ) { - m_rgPendingClientData[i].m_hSocket = hSocketClient; m_rgPendingClientData[i].m_ulTickCountLastData = m_pGameEngine->GetGameTickCount(); - // pull the IP address of the user from the socket - uint32 unIPClient = 0; - SteamGameServerNetworking()->GetSocketInfo( hSocketClient, NULL, NULL, &unIPClient, NULL ); - #ifdef USE_GS_AUTH_API // authenticate the user with the Steam back-end servers - m_rgPendingClientData[i].m_bActive = SteamGameServer()->SendUserConnectAndAuthenticate( unIPClient, pToken, uTokenLen, &m_rgPendingClientData[i].m_SteamIDUser ); + + // we use the server-seen IP address of the client as an extra bit of data for the authentication + P2PSessionState_t p2pSessionState; + SteamNetworking()->GetP2PSessionState( m_rgPendingClientData[i].m_SteamIDUser, &p2pSessionState ); + + m_rgPendingClientData[i].m_bActive = SteamGameServer()->SendUserConnectAndAuthenticate( p2pSessionState.m_nRemoteIP, pToken, uTokenLen, &m_rgPendingClientData[i].m_SteamIDUser ); if ( !m_rgPendingClientData[i].m_bActive ) { MsgServerFailAuthentication_t msg; - BSendDataToClientOnSocket( hSocketClient, (char*)&msg, sizeof( msg ) ); + SteamGameServerNetworking()->SendP2PPacket( m_rgPendingClientData[i].m_SteamIDUser, &msg, sizeof( msg ), k_EP2PSendReliable ); } break; #else @@ -317,7 +289,7 @@ void CSpaceWarServer::OnAuthCompleted( bool bAuthSuccessful, uint32 iPendingAuth #endif // Send a deny for the client, and zero out the pending data MsgServerFailAuthentication_t msg; - BSendDataToClientOnSocket( m_rgPendingClientData[iPendingAuthIndex].m_hSocket, (char*)&msg, sizeof( msg ) ); + SteamGameServerNetworking()->SendP2PPacket( m_rgPendingClientData[iPendingAuthIndex].m_SteamIDUser, &msg, sizeof( msg ), k_EP2PSendReliable ); memset( &m_rgPendingClientData[iPendingAuthIndex], 0, sizeof( ClientConnectionData_t ) ); return; } @@ -511,13 +483,10 @@ void CSpaceWarServer::SetGameState( EServerGameState eState ) //----------------------------------------------------------------------------- void CSpaceWarServer::ReceiveNetworkData() { - if ( !m_hSocketServer ) - return; - char *pchRecvBuf = NULL; - SNetSocket_t hSocketClient; uint32 cubMsgSize; - while ( SteamGameServerNetworking()->IsDataAvailable( m_hSocketServer, &cubMsgSize, &hSocketClient ) ) + CSteamID steamIDRemote; + while ( SteamGameServerNetworking()->IsP2PPacketAvailable( &cubMsgSize ) ) { // free any previous receive buffer if ( pchRecvBuf ) @@ -527,7 +496,7 @@ void CSpaceWarServer::ReceiveNetworkData() pchRecvBuf = (char *)malloc( cubMsgSize ); // see if there is any data waiting on the socket - if ( !SteamGameServerNetworking()->RetrieveData( m_hSocketServer, pchRecvBuf, cubMsgSize, &cubMsgSize, &hSocketClient ) ) + if ( !SteamGameServerNetworking()->ReadP2PPacket( pchRecvBuf, cubMsgSize, &cubMsgSize, &steamIDRemote ) ) break; if ( cubMsgSize < sizeof( EMessage ) ) @@ -550,7 +519,7 @@ void CSpaceWarServer::ReceiveNetworkData() msg.m_bIsVACSecure = SteamGameServer()->BSecure(); #endif _snprintf( msg.m_rgchServerName, sizeof( msg.m_rgchServerName ), "%s", m_sServerName.c_str() ); - BSendDataToClientOnSocket( hSocketClient, (char*)&msg, sizeof( MsgServerSendInfo_t ) ); + SteamGameServerNetworking()->SendP2PPacket( steamIDRemote, &msg, sizeof( MsgServerSendInfo_t ), k_EP2PSendReliable ); } break; case k_EMsgClientBeginAuthentication: @@ -562,9 +531,9 @@ void CSpaceWarServer::ReceiveNetworkData() } MsgClientBeginAuthentication_t *pMsg = (MsgClientBeginAuthentication_t*)pchRecvBuf; #ifdef USE_GS_AUTH_API - OnClientBeginAuthentication( hSocketClient, &pMsg->m_rgchToken, pMsg->m_uTokenLen ); + OnClientBeginAuthentication( steamIDRemote, &pMsg->m_rgchToken, pMsg->m_uTokenLen ); #else - OnClientBeginAuthentication( hSocketClient, &pMsg->m_SteamID, 0 ); + OnClientBeginAuthentication( steamIDRemote, 0 ); #endif } break; @@ -580,7 +549,7 @@ void CSpaceWarServer::ReceiveNetworkData() bool bFound = false; for( uint32 i=0; iSendP2PPacket( steamIDRemote, &msg, sizeof( msg ), k_EP2PSendUnreliable ); + } + break; case k_EMsgClientLeavingServer: if ( cubMsgSize != sizeof( MsgClientLeavingServer_t ) ) { @@ -602,7 +578,7 @@ void CSpaceWarServer::ReceiveNetworkData() bool bFound = false; for( uint32 i=0; iGetListenSocketInfo( m_hSocketServer, &unIP, &unPort ); - return unIP; - } - return 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns the public port of the game server -//----------------------------------------------------------------------------- -uint16 CSpaceWarServer::GetPort() -{ - if ( m_hSocketServer ) - { - uint32 unIP; - uint16 unPort; - SteamGameServerNetworking()->GetListenSocketInfo( m_hSocketServer, &unIP, &unPort ); - return unPort; - } - return 0; -} - - //----------------------------------------------------------------------------- // Purpose: Returns the SteamID of the game server //----------------------------------------------------------------------------- @@ -1077,3 +1018,29 @@ CSteamID CSpaceWarServer::GetSteamID() return k_steamIDNonSteamGS; #endif } + +void CSpaceWarServer::KickPlayerOffServer( CSteamID steamID ) +{ + uint32 uPlayerCount = 0; + for( uint32 i=0; i < MAX_PLAYERS_PER_SERVER; ++i ) + { + // If there is no ship, skip + if ( !m_rgClientData[i].m_bActive ) + continue; + + if ( m_rgClientData[i].m_SteamIDUser == steamID ) + { + OutputDebugString( "Kicking player\n" ); + RemovePlayerFromServer( i ); + // send him a kick message + MsgServerFailAuthentication_t msg; + SteamGameServerNetworking()->SendP2PPacket( steamID, &msg, sizeof( msg ), k_EP2PSendReliable ); + } + else + { + ++uPlayerCount; + } + } + m_uPlayerCount = uPlayerCount; + +} diff --git a/SteamworksExample/SpaceWarServer.h b/SteamworksExample/SpaceWarServer.h index a6988e3..7c2b936 100644 --- a/SteamworksExample/SpaceWarServer.h +++ b/SteamworksExample/SpaceWarServer.h @@ -23,8 +23,6 @@ class CSpaceWarClient; struct ClientConnectionData_t { bool m_bActive; // Is this slot in use? Or is it available for new connections? - - SNetSocket_t m_hSocket; // Socket to send data on to get to this client CSteamID m_SteamIDUser; // What is the steamid of the player? uint64 m_ulTickCountLastData; // What was the last time we got data from the player? }; @@ -44,6 +42,9 @@ public: // Set game state void SetGameState( EServerGameState eState ); + // Checks for any incoming network data, then dispatches it + void ReceiveNetworkData(); + // Reset player scores (occurs when starting a new game) void ResetScores(); @@ -53,10 +54,10 @@ public: // Checks various game objects for collisions and updates state appropriately if they have occurred void CheckForCollisions(); + void KickPlayerOffServer( CSteamID steamID ); + // data accessors bool IsConnectedToSteam() { return m_bConnectedToSteam; } - uint32 GetIP(); - uint16 GetPort(); CSteamID GetSteamID(); private: @@ -90,7 +91,8 @@ private: STEAM_GAMESERVER_CALLBACK( CSpaceWarServer, OnGSClientKick, GSClientKick_t, m_CallbackGSClientKick ); // client connection state - STEAM_GAMESERVER_CALLBACK( CSpaceWarServer, OnSocketStatusCallback, SocketStatusCallback_t, m_CallbackSocketStatus ); + STEAM_GAMESERVER_CALLBACK( CSpaceWarServer, OnP2PSessionRequest, P2PSessionRequest_t, m_CallbackP2PSessionRequest ); + STEAM_GAMESERVER_CALLBACK( CSpaceWarServer, OnP2PSessionConnectFail, P2PSessionConnectFail_t, m_CallbackP2PSessionConnectFail ); // Function to tell Steam about our servers details void SendUpdatedServerDetailsToSteam(); @@ -98,20 +100,14 @@ private: // Receive updates from client void OnReceiveClientUpdateData( uint32 uShipIndex, ClientSpaceWarUpdateData_t UpdateData ); - // Checks for any incoming network data, then dispatches it - void ReceiveNetworkData(); - // Send data to a client at the given ship index bool BSendDataToClient( uint32 uShipIndex, char *pData, uint32 nSizeOfData ); // Send data to a client at the given pending index bool BSendDataToPendingClient( uint32 uShipIndex, char *pData, uint32 nSizeOfData ); - // Send data to a client at a given address - bool BSendDataToClientOnSocket( SNetSocket_t hSocket, char *pData, uint32 nSizeOfData ); - // Connect a client, will send a success/failure response to the client - void OnClientBeginAuthentication( SNetSocket_t hSocketClient, void *pToken, uint32 uTokenLen ); + void OnClientBeginAuthentication( CSteamID steamIDClient, void *pToken, uint32 uTokenLen ); // Handles authentication completing for a client void OnAuthCompleted( bool bAuthSuccess, uint32 iPendingAuthIndex ); @@ -156,9 +152,6 @@ private: // Sun instance CSun *m_pSun; - // Server socket (which has been bound to our known port) - SNetSocket_t m_hSocketServer; - // pointer to game engine instance we are running under CGameEngine *m_pGameEngine; diff --git a/SteamworksExample/SteamworksExample.vcproj b/SteamworksExample/SteamworksExample.vcproj index 0e9efd7..73d6efa 100644 --- a/SteamworksExample/SteamworksExample.vcproj +++ b/SteamworksExample/SteamworksExample.vcproj @@ -226,6 +226,10 @@ RelativePath=".\MainMenu.cpp" > + + @@ -316,6 +320,10 @@ RelativePath=".\Messages.h" > + + diff --git a/SteamworksExample/p2pauth.cpp b/SteamworksExample/p2pauth.cpp new file mode 100644 index 0000000..12cd613 --- /dev/null +++ b/SteamworksExample/p2pauth.cpp @@ -0,0 +1,413 @@ +//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + + +#include "stdafx.h" +#include "SpaceWarClient.h" +#include "p2pauth.h" + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CNetworkTransport::CNetworkTransport() : + m_CallbackP2PSessionRequest( this, &CNetworkTransport::OnP2PSessionRequest ), + m_CallbackP2PSessionConnectFail( this, &CNetworkTransport::OnP2PSessionConnectFail ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: send ticket message to steamIDTo +//----------------------------------------------------------------------------- +bool CNetworkTransport::SendTicket( CSteamID steamIDFrom, CSteamID steamIDTo, uint32 cubTicket, uint8 *pubTicket ) +{ + MsgP2PSendingTicket_t msg; + msg.m_uTokenLen = cubTicket; + memcpy( msg.m_rgchToken, pubTicket, cubTicket ); + msg.m_SteamID = steamIDFrom; + return SendNetMessage( steamIDTo, (void*)&msg, sizeof( msg ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: actual implementation +//----------------------------------------------------------------------------- +bool CNetworkTransport::SendNetMessage( CSteamID steamIDTo, void *pMessage, int cubMessage ) +{ + return SteamNetworking()->SendP2PPacket( steamIDTo, pMessage, cubMessage, k_EP2PSendReliable ); +} + + +//----------------------------------------------------------------------------- +// Purpose: close connection to steamID +//----------------------------------------------------------------------------- +void CNetworkTransport::CloseConnection( CSteamID steamID ) +{ + SteamNetworking()->CloseP2PSessionWithUser( steamID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: another user has sent us a packet - do we accept? +//----------------------------------------------------------------------------- +void CNetworkTransport::OnP2PSessionRequest( P2PSessionRequest_t *pP2PSessionRequest ) +{ + // always accept packets + // the packet itself will come through when you call SteamNetworking()->ReadP2PPacket() + SteamNetworking()->AcceptP2PSessionWithUser( pP2PSessionRequest->m_steamIDRemote ); +} + + +//----------------------------------------------------------------------------- +// Purpose: another user has sent us a packet - do we accept? +//----------------------------------------------------------------------------- +void CNetworkTransport::OnP2PSessionConnectFail( P2PSessionConnectFail_t *pP2PSessionConnectFail ) +{ + // we've sent a packet to the user, but it never got through + // we can just use the normal timeout +} + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CP2PAuthPlayer::CP2PAuthPlayer( CGameEngine *pGameEngine, CNetworkTransport *pNetworkTransport ):m_CallbackBeginAuthResponse( this, &CP2PAuthPlayer::OnBeginAuthResponse ) +{ + m_pGameEngine = pGameEngine; + m_pNetworkTransport = pNetworkTransport; + m_bSentTicket = false; + m_bSubmittedHisTicket = false; + m_bHaveAnswer = false; + m_ulConnectTime = GetGameTimeInSeconds(); + m_cubTicketIGaveThisUser = 0; + m_cubTicketHeGaveMe = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: destructor +//----------------------------------------------------------------------------- +CP2PAuthPlayer::~CP2PAuthPlayer() +{ + EndGame(); + m_pNetworkTransport->CloseConnection( SteamUser()->GetSteamID() ); + m_pNetworkTransport = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: the steam backend has responded +//----------------------------------------------------------------------------- +void CP2PAuthPlayer::OnBeginAuthResponse( ValidateAuthTicketResponse_t *pCallback ) +{ + if ( m_steamID == pCallback->m_SteamID ) + { + char rgch[128]; + sprintf( rgch, "P2P:: Received steam response for account=%d\n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + m_ulAnswerTime = GetGameTimeInSeconds(); + m_bHaveAnswer = true; + m_eAuthSessionResponse = pCallback->m_eAuthSessionResponse; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: internal implementation +//----------------------------------------------------------------------------- +void CP2PAuthPlayer::InitPlayer( CSteamID steamID ) +{ + m_steamID = steamID; + m_bSentTicket = false; + m_bSubmittedHisTicket = false; + m_bHaveAnswer = false; + m_ulConnectTime = GetGameTimeInSeconds(); + m_cubTicketIGaveThisUser = 0; + m_cubTicketHeGaveMe = 0; +} + +void CP2PAuthPlayer::StartAuthPlayer() +{ + // send him the ticket if we havent yet + if ( m_cubTicketIGaveThisUser == 0 ) + { + m_hAuthTicketIGaveThisUser = SteamUser()->GetAuthSessionTicket( m_rgubTicketIGaveThisUser, sizeof( m_rgubTicketIGaveThisUser ), &m_cubTicketIGaveThisUser ); + } + // sample has no retry here + m_bSentTicket = m_pNetworkTransport->SendTicket( SteamUser()->GetSteamID(), m_steamID, m_cubTicketIGaveThisUser, m_rgubTicketIGaveThisUser ); + // start a timer on this, if we dont get a ticket back within reasonable time, mark him timed out + m_ulTicketTime = GetGameTimeInSeconds(); +} + + +//----------------------------------------------------------------------------- +// Purpose: is this auth ok? +//----------------------------------------------------------------------------- +bool CP2PAuthPlayer::BIsAuthOk() +{ + if ( m_steamID.IsValid() ) + { + // Timeout if we fail to establish communication with this player + if ( !m_bSentTicket && !m_bSubmittedHisTicket ) + { + if ( GetGameTimeInSeconds() - m_ulConnectTime > 30 ) + { + char rgch[128]; + sprintf( rgch, "P2P:: Nothing received for account=%d\n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + return false; + } + } + // first ticket check: if i submitted his ticket - was it good? + if ( m_bSubmittedHisTicket && m_eBeginAuthSessionResult != k_EBeginAuthSessionResultOK ) + { + char rgch[128]; + sprintf( rgch, "P2P:: Ticket from account=%d was bad\n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + return false; + } + // second ticket check: if the steam backend replied, was that good? + if ( m_bHaveAnswer && m_eAuthSessionResponse != k_EAuthSessionResponseOK ) + { + char rgch[128]; + sprintf( rgch, "P2P:: Steam response for account=%d was bad\n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + return false; + } + // last: if i sent him a ticket and he has not reciprocated, time out after 30 sec + if ( m_bSentTicket && !m_bSubmittedHisTicket ) + { + if ( GetGameTimeInSeconds() - m_ulTicketTime > 30 ) + { + char rgch[128]; + sprintf( rgch, "P2P:: No ticket received for account=%d\n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + return false; + } + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: the game engine is telling us about someone who left the game +//----------------------------------------------------------------------------- +void CP2PAuthPlayer::EndGame() +{ + if ( m_bSentTicket ) + { + SteamUser()->CancelAuthTicket( m_hAuthTicketIGaveThisUser ); + m_bSentTicket = false; + } + if ( m_bSubmittedHisTicket ) + { + SteamUser()->EndAuthSession( m_steamID ); + m_bSubmittedHisTicket = false; + } + if ( m_steamID.IsValid() ) + m_pNetworkTransport->CloseConnection( m_steamID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: message handler +//----------------------------------------------------------------------------- +bool CP2PAuthPlayer::HandleMessage( EMessage eMsg, void *pMessage ) +{ + switch ( eMsg ) + { + // message from another player providing his ticket + case k_EMsgP2PSendingTicket: + { + MsgP2PSendingTicket_t *pMsg = (MsgP2PSendingTicket_t*)pMessage; + // is this message for me? + if ( pMsg->m_SteamID != m_steamID ) + return false; + m_cubTicketHeGaveMe = pMsg->m_uTokenLen; + memcpy( m_rgubTicketHeGaveMe, pMsg->m_rgchToken, m_cubTicketHeGaveMe ); + m_eBeginAuthSessionResult = SteamUser()->BeginAuthSession( m_rgubTicketHeGaveMe, m_cubTicketHeGaveMe, m_steamID ); + m_bSubmittedHisTicket = true; + char rgch[128]; + sprintf( rgch, "P2P:: ReceivedTicket from account=%d \n", m_steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + if ( !m_bSentTicket ) + StartAuthPlayer(); + return true; + } + default: + { + OutputDebugStringA( "P2P:: Received unknown message on our listen socket\n" ); + break; + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: utility wrapper +//----------------------------------------------------------------------------- +CSteamID CP2PAuthPlayer::GetSteamID() +{ + return SteamUser()->GetSteamID(); +} + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CP2PAuthedGame::CP2PAuthedGame( CGameEngine *pGameEngine ) +{ + m_pNetworkTransport = new CNetworkTransport; + m_pGameEngine = pGameEngine; + // no players yet + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + m_rgpP2PAuthPlayer[i] = NULL; + } + // no queued messages + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + m_rgpQueuedMessage[i] = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: game with this player is over +//----------------------------------------------------------------------------- +void CP2PAuthedGame::PlayerDisconnect( int iSlot ) +{ + if ( m_rgpP2PAuthPlayer[iSlot] ) + { + m_rgpP2PAuthPlayer[iSlot]->EndGame(); + delete m_rgpP2PAuthPlayer[iSlot]; + m_rgpP2PAuthPlayer[iSlot] = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: game is over, disconnect everyone +//----------------------------------------------------------------------------- +void CP2PAuthedGame::EndGame() +{ + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + if ( m_rgpP2PAuthPlayer[i] ) + { + m_rgpP2PAuthPlayer[i]->EndGame(); + delete m_rgpP2PAuthPlayer[i]; + m_rgpP2PAuthPlayer[i] = NULL; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: initialize player +//----------------------------------------------------------------------------- +void CP2PAuthedGame::InternalInitPlayer( int iSlot, CSteamID steamID, bool bStartAuthProcess ) +{ + char rgch[128]; + sprintf( rgch, "P2P:: StartAuthPlayer slot=%d account=%d \n", iSlot, steamID.GetAccountID() ); + OutputDebugStringA( rgch ); + m_rgpP2PAuthPlayer[iSlot] = new CP2PAuthPlayer( m_pGameEngine, m_pNetworkTransport ); + m_rgpP2PAuthPlayer[iSlot]->InitPlayer( steamID ); + if ( bStartAuthProcess ) + m_rgpP2PAuthPlayer[iSlot]->StartAuthPlayer(); + + // check our queued messages list to see if this guys ticket message arrived before we noticed him + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + if ( m_rgpQueuedMessage[i] != NULL ) + { + if ( m_rgpP2PAuthPlayer[i]->HandleMessage( k_EMsgP2PSendingTicket, m_rgpQueuedMessage[i] ) ) + { + OutputDebugStringA( "P2P:: Consumed queued message \n" ); + free( m_rgpQueuedMessage[i] ); + m_rgpQueuedMessage[i] = NULL; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: game host register this player, we wait for this player +// to start the auth process by sending us his ticket, then we will +// reciprocate +//----------------------------------------------------------------------------- +void CP2PAuthedGame::RegisterPlayer( int iSlot, CSteamID steamID ) +{ + if ( iSlot < MAX_PLAYERS_PER_SERVER ) + InternalInitPlayer( iSlot, steamID, false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: start the auth process by sending ticket to this player +// he will reciprocate +//----------------------------------------------------------------------------- +void CP2PAuthedGame::StartAuthPlayer( int iSlot, CSteamID steamID ) +{ + if ( iSlot < MAX_PLAYERS_PER_SERVER ) + InternalInitPlayer( iSlot, steamID, true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: message handler +//----------------------------------------------------------------------------- +bool CP2PAuthedGame::HandleMessage( EMessage eMsg, void *pMessage ) +{ + switch ( eMsg ) + { + // message from another player providing his ticket + case k_EMsgP2PSendingTicket: + { + MsgP2PSendingTicket_t *pMsg = (MsgP2PSendingTicket_t*)pMessage; + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + if ( m_rgpP2PAuthPlayer[i] ) + { + if ( m_rgpP2PAuthPlayer[i]->HandleMessage( eMsg, pMessage ) ) + { + return true; + } + } + } + // if we dont have the player in our list yet, lets queue the message and assume he will show up soon + OutputDebugStringA( "P2P:: HandleMessage queueing message \n" ); + for ( int i = 0; i < MAX_PLAYERS_PER_SERVER; i++ ) + { + if ( m_rgpQueuedMessage[i] == NULL ) + { + m_rgpQueuedMessage[i] = pMsg; + return false; + } + } + break; + } + default: + { + OutputDebugStringA( "Received unknown message on our listen socket\n" ); + break; + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: utility wrapper +//----------------------------------------------------------------------------- +CSteamID CP2PAuthedGame::GetSteamID() +{ + return SteamUser()->GetSteamID(); +} \ No newline at end of file diff --git a/SteamworksExample/p2pauth.h b/SteamworksExample/p2pauth.h new file mode 100644 index 0000000..4ef006a --- /dev/null +++ b/SteamworksExample/p2pauth.h @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + + + +const int k_cMaxSockets = 16; +class CP2PAuthPlayer; +class CNetworkTransport; + + +//----------------------------------------------------------------------------- +// Purpose: one player p2p auth process state machine +//----------------------------------------------------------------------------- +class CP2PAuthPlayer +{ +public: + CP2PAuthPlayer( CGameEngine *pGameEngine, CNetworkTransport *pNetworkTransport ); + ~CP2PAuthPlayer(); + void EndGame(); + void InitPlayer( CSteamID steamID ); + void StartAuthPlayer(); + bool BIsAuthOk(); + virtual bool HandleMessage( EMessage eMsg, void *pMessage ); + virtual CSteamID GetSteamID(); + + STEAM_CALLBACK( CP2PAuthPlayer, OnBeginAuthResponse, ValidateAuthTicketResponse_t, m_CallbackBeginAuthResponse ); + + CSteamID m_steamID; +private: + uint64 GetGameTimeInSeconds() + { + return m_pGameEngine->GetGameTickCount()/1000; + } + bool m_bSentTicket; + bool m_bSubmittedHisTicket; + bool m_bHaveAnswer; + uint64 m_ulConnectTime; + uint64 m_ulTicketTime; + uint64 m_ulAnswerTime; + uint32 m_cubTicketIGaveThisUser; + uint8 m_rgubTicketIGaveThisUser[1024]; + uint32 m_cubTicketHeGaveMe; + uint8 m_rgubTicketHeGaveMe[1024]; + HAuthTicket m_hAuthTicketIGaveThisUser; + EBeginAuthSessionResult m_eBeginAuthSessionResult; + EAuthSessionResponse m_eAuthSessionResponse; + + CNetworkTransport *m_pNetworkTransport; + CGameEngine *m_pGameEngine; +}; + +//----------------------------------------------------------------------------- +// Purpose: simple wrapper for multiple players +//----------------------------------------------------------------------------- +class CP2PAuthedGame +{ +public: + CP2PAuthedGame( CGameEngine *pGameEngine ); + void PlayerDisconnect( int iSlot ); + void EndGame(); + void StartAuthPlayer( int iSlot, CSteamID steamID ); + void RegisterPlayer( int iSlot, CSteamID steamID ); + virtual bool HandleMessage( EMessage eMsg, void *pMessage ); + virtual CSteamID GetSteamID(); + void InternalInitPlayer( int iSlot, CSteamID steamID, bool bStartAuthProcess ); + + CP2PAuthPlayer *m_rgpP2PAuthPlayer[MAX_PLAYERS_PER_SERVER]; + MsgP2PSendingTicket_t *m_rgpQueuedMessage[MAX_PLAYERS_PER_SERVER]; + CGameEngine *m_pGameEngine; + CNetworkTransport *m_pNetworkTransport; +}; + + +//----------------------------------------------------------------------------- +// Purpose: trivial wrapper on network messaging +//----------------------------------------------------------------------------- +class CNetworkTransport +{ +public: + CNetworkTransport(); + + bool SendTicket( CSteamID steamIDFrom, CSteamID steamIDTo, uint32 cubTicket, uint8 *pubTicket ); + void CloseConnection( CSteamID steamID ); + + bool SendNetMessage( CSteamID steamIDTo, void *pMessage, int cubMessage ); + + STEAM_CALLBACK( CNetworkTransport, OnP2PSessionRequest, P2PSessionRequest_t, m_CallbackP2PSessionRequest ); + STEAM_CALLBACK( CNetworkTransport, OnP2PSessionConnectFail, P2PSessionConnectFail_t, m_CallbackP2PSessionConnectFail ); +}; diff --git a/SteamworksExample/stdafx.h b/SteamworksExample/stdafx.h index c3ceeda..d192e94 100644 --- a/SteamworksExample/stdafx.h +++ b/SteamworksExample/stdafx.h @@ -44,7 +44,7 @@ #include // Winsock -#include +#include #pragma comment(lib, "ws2_32.lib" ) // d3d header files @@ -59,4 +59,15 @@ typedef __int64 int64; typedef unsigned __int64 uint64; // steam api header file -#include "steam/steam_api.h" \ No newline at end of file +#include "steam/steam_api.h" + +#ifdef STEAM_CEG + // Steam DRM header file + #include "cegclient.h" +#else + #define Steamworks_InitCEGLibrary() (true) + #define Steamworks_TermCEGLibrary() (true) + #define Steamworks_TestSecret() (true) + #define Steamworks_SelfCheck() (true) +#endif + diff --git a/client/steam.exe b/client/steam.exe index e524e5a..564683e 100644 Binary files a/client/steam.exe and b/client/steam.exe differ diff --git a/public/steam/isteamfriends.h b/public/steam/isteamfriends.h index 5025832..76d7d01 100644 --- a/public/steam/isteamfriends.h +++ b/public/steam/isteamfriends.h @@ -13,6 +13,7 @@ #include "isteamclient.h" #include "steamclientpublic.h" + //----------------------------------------------------------------------------- // Purpose: set of relationships to other users //----------------------------------------------------------------------------- @@ -163,7 +164,7 @@ public: virtual void SetInGameVoiceSpeaking( CSteamID steamIDUser, bool bSpeaking ) = 0; // activates the game overlay, with an optional dialog to open - // valid options are "Friends", "Community", "Players", "Settings", "LobbyInvite", "OfficialGameGroup" + // valid options are "Friends", "Community", "Players", "Settings", "LobbyInvite", "OfficialGameGroup", "Stats", "Achievements" virtual void ActivateGameOverlay( const char *pchDialog ) = 0; // activates game overlay to a specific place diff --git a/public/steam/isteamgameserver.h b/public/steam/isteamgameserver.h index 53acd9e..1258a91 100644 --- a/public/steam/isteamgameserver.h +++ b/public/steam/isteamgameserver.h @@ -102,9 +102,18 @@ public: // connect to virtual uint32 GetPublicIP() = 0; + // Sets a string defining the "gamedata" for this server, this is optional, but if it is set + // it allows users to filter in the matchmaking/server-browser interfaces based on the value + // don't set this unless it actually changes, its only uploaded to the master once (when + // acknowledged) + virtual void SetGameData( const char *pchGameData) = 0; + + // After receiving a user's authentication data, and passing it to SendUserConnectAndAuthenticate, use this function + // to determine if the user owns downloadable content specified by the provided AppID. + virtual EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) = 0; }; -#define STEAMGAMESERVER_INTERFACE_VERSION "SteamGameServer008" +#define STEAMGAMESERVER_INTERFACE_VERSION "SteamGameServer009" // game server flags const uint32 k_unServerFlagNone = 0x00; diff --git a/public/steam/isteammatchmaking.h b/public/steam/isteammatchmaking.h index 2ec761b..d20f898 100644 --- a/public/steam/isteammatchmaking.h +++ b/public/steam/isteammatchmaking.h @@ -16,15 +16,17 @@ #include "isteamclient.h" #include "isteamfriends.h" -//----------------------------------------------------------------------------- -// Purpose: Functions for match making services for clients to get to favorites -//----------------------------------------------------------------------------- +// lobby type description enum ELobbyType { k_ELobbyTypeFriendsOnly = 1, // shows for friends or invitees, but not in lobby list k_ELobbyTypePublic = 2, // visible for friends and in lobby list + k_ELobbyTypeInvisible = 3, // returned by search, but not visible to other friends + // useful if you want a user in two lobbies, for example matching groups together + // a user can be in only one regular lobby, and up to two invisible lobbies }; +// lobby search filter tools enum ELobbyComparison { k_ELobbyComparisonEqualToOrLessThan = -2, @@ -32,8 +34,10 @@ enum ELobbyComparison k_ELobbyComparisonEqual = 0, k_ELobbyComparisonGreaterThan = 1, k_ELobbyComparisonEqualToOrGreaterThan = 2, + k_ELobbyComparisonNotEqual = 3, }; + //----------------------------------------------------------------------------- // Purpose: Functions for match making services for clients to get to favorites // and to operate on game lobbies. @@ -91,11 +95,13 @@ public: // filters for lobbies // this needs to be called before RequestLobbyList() to take effect // these are cleared on each call to RequestLobbyList() - virtual void AddRequestLobbyListFilter( const char *pchKeyToMatch, const char *pchValueToMatch ) = 0; + virtual void AddRequestLobbyListStringFilter( const char *pchKeyToMatch, const char *pchValueToMatch, ELobbyComparison eComparisonType ) = 0; // numerical comparison - virtual void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, int nComparisonType ) = 0; + virtual void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, ELobbyComparison eComparisonType ) = 0; // returns results closest to the specified value. Multiple near filters can be added, with early filters taking precedence virtual void AddRequestLobbyListNearValueFilter( const char *pchKeyToMatch, int nValueToBeCloseTo ) = 0; + // returns only lobbies with the specified number of slots available + virtual void AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ) = 0; // returns the CSteamID of a lobby, as retrieved by a RequestLobbyList call // should only be called after a LobbyMatchList_t callback is received @@ -109,7 +115,7 @@ public: // this is an asynchronous request // results will be returned by LobbyCreated_t callback and call result; lobby is joined & ready to use at this pointer // a LobbyEnter_t callback will also be received (since the local user is joining their own lobby) - virtual SteamAPICall_t CreateLobby( ELobbyType eLobbyType ) = 0; + virtual SteamAPICall_t CreateLobby( ELobbyType eLobbyType, int cMaxMembers ) = 0; // Joins an existing lobby // this is an asynchronous request @@ -125,6 +131,8 @@ public: // the target user will receive a LobbyInvite_t callback // will return true if the invite is successfully sent, whether or not the target responds // returns false if the local user is not connected to the Steam servers + // if the other user clicks the join link, a GameLobbyJoinRequested_t will be posted if the user is in-game, + // or if the game isn't running yet the game will be launched with the parameter +connect_lobby <64-bit lobby id> virtual bool InviteUserToLobby( CSteamID steamIDLobby, CSteamID steamIDInvitee ) = 0; // Lobby iteration, for viewing details of users in a lobby @@ -149,7 +157,16 @@ public: // other users in the lobby will receive notification of the lobby data change via a LobbyDataUpdate_t callback virtual bool SetLobbyData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) = 0; - // As above, but gets per-user data for someone in this lobby + // returns the number of metadata keys set on the specified lobby + virtual int GetLobbyDataCount( CSteamID steamIDLobby ) = 0; + + // returns a lobby metadata key/values pair by index, of range [0, GetLobbyDataCount()) + virtual bool GetLobbyDataByIndex( CSteamID steamIDLobby, int iLobbyData, char *pchKey, int cchKeyBufferSize, char *pchValue, int cchValueBufferSize ) = 0; + + // removes a metadata key from the lobby + virtual bool DeleteLobbyData( CSteamID steamIDLobby, const char *pchKey ) = 0; + + // Gets per-user metadata for someone in this lobby virtual const char *GetLobbyMemberData( CSteamID steamIDLobby, CSteamID steamIDUser, const char *pchKey ) = 0; // Sets per-user metadata (for the local user implicitly) virtual void SetLobbyMemberData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue ) = 0; @@ -188,16 +205,25 @@ public: virtual int GetLobbyMemberLimit( CSteamID steamIDLobby ) = 0; // updates which type of lobby it is - // only lobbies that are k_ELobbyTypePublic will be returned by RequestLobbyList() calls + // only lobbies that are k_ELobbyTypePublic or k_ELobbyTypeInvisible, and are set to joinable, will be returned by RequestLobbyList() calls virtual bool SetLobbyType( CSteamID steamIDLobby, ELobbyType eLobbyType ) = 0; + // sets whether or not a lobby is joinable - defaults to true for a new lobby + // if set to false, no user can join, even if they are a friend or have been invited + virtual bool SetLobbyJoinable( CSteamID steamIDLobby, bool bLobbyJoinable ) = 0; + // returns the current lobby owner // you must be a member of the lobby to access this // there always one lobby owner - if the current owner leaves, another user will become the owner // it is possible (bur rare) to join a lobby just as the owner is leaving, thus entering a lobby with self as the owner virtual CSteamID GetLobbyOwner( CSteamID steamIDLobby ) = 0; + + // changes who the lobby owner is + // you must be the lobby owner for this to succeed, and steamIDNewOwner must be in the lobby + // after completion, the local user will no longer be the owner + virtual bool SetLobbyOwner( CSteamID steamIDLobby, CSteamID steamIDNewOwner ) = 0; }; -#define STEAMMATCHMAKING_INTERFACE_VERSION "SteamMatchMaking006" +#define STEAMMATCHMAKING_INTERFACE_VERSION "SteamMatchMaking007" //----------------------------------------------------------------------------- @@ -432,7 +458,9 @@ struct FavoritesListChanged_t // Purpose: Someone has invited you to join a Lobby // normally you don't need to do anything with this, since // the Steam UI will also display a ' has invited you to the lobby, join?' dialog -// if the user outside a game chooses to join, your game will be launched with the parameter "+connect_lobby <64-bit lobby id>" +// +// if the user outside a game chooses to join, your game will be launched with the parameter "+connect_lobby <64-bit lobby id>", +// or with the callback GameLobbyJoinRequested_t if they're already in-game //----------------------------------------------------------------------------- struct LobbyInvite_t { @@ -532,29 +560,6 @@ struct LobbyMatchList_t }; -//----------------------------------------------------------------------------- -// Purpose: Called when the lobby is being forcefully closed -// lobby details functions will no longer be updated -//----------------------------------------------------------------------------- -struct LobbyClosing_t -{ - enum { k_iCallback = k_iSteamMatchmakingCallbacks + 11 }; - uint64 m_ulSteamIDLobby; // Lobby -}; - - -//----------------------------------------------------------------------------- -// Purpose: Called when the local user has been kicked from the lobby -// lobby details functions will no longer be updated -//----------------------------------------------------------------------------- -struct LobbyKicked_t -{ - enum { k_iCallback = k_iSteamMatchmakingCallbacks + 12 }; - uint64 m_ulSteamIDLobby; // Lobby - uint64 m_ulSteamIDAdmin; // User who kicked you -}; - - //----------------------------------------------------------------------------- // Purpose: Result of our request to create a Lobby // m_eResult == k_EResultOK on success diff --git a/public/steam/isteamnetworking.h b/public/steam/isteamnetworking.h index 50d8208..1e0706f 100644 --- a/public/steam/isteamnetworking.h +++ b/public/steam/isteamnetworking.h @@ -13,12 +13,67 @@ #include "steamtypes.h" #include "steamclientpublic.h" + +// list of possible errors returned by SendP2PPacket() API +// these will be posted in the P2PSessionConnectFail_t callback +enum EP2PSessionError +{ + k_EP2PSessionErrorNone = 0, + k_EP2PSessionErrorNotRunningApp = 1, // target is not running the same game + k_EP2PSessionErrorNoRightsToApp = 2, // local user doesn't own the app that is running + k_EP2PSessionErrorDestinationNotLoggedIn = 3, // target user isn't connected to Steam + k_EP2PSessionErrorTimeout = 4, // target isn't responding, perhaps not calling AcceptP2PSessionWithUser() + +}; + +// SendP2PPacket() send types +// Typically k_EP2PSendUnreliable is what you want for UDP-like packets, k_EP2PSendReliable for TCP-like packets +enum EP2PSend +{ + // Basic UDP send. Packets can't be bigger than 1200 bytes (your typical MTU size). Can be lost, or arrive out of order (rare). + // The sending API does have some knowledge of the underlying connection, so if there is no NAT-traversal accomplished or + // there is a recognized adjustment happening on the connection, the packet will be batched until the connection is open again. + k_EP2PSendUnreliable = 0, + + // As above, but if the underlying p2p connection isn't yet established the packet will just be thrown away. Using this on the first + // packet sent to a remote host almost guarantees the packet will be dropped. + // This is only really useful for kinds of data that should never buffer up, i.e. voice payload packets + k_EP2PSendUnreliableNoDelay = 1, + + // Reliable message send. Can send up to 1MB of data in a single message. + // Does fragmentation/re-assembly of messages under the hood, as well as a sliding window for efficient sends of large chunks of data. + k_EP2PSendReliable = 2, + + // As above, but applies the Nagle algorithm to the send - sends will accumulate + // until the current MTU size (typically ~1200 bytes, but can change) or ~200ms has passed (Nagle algorithm). + // Useful if you want to send a set of smaller messages but have the coalesced into a single packet + // Since the reliable stream is all ordered, you can do several small message sends with k_EP2PSendReliableWithBuffering and then + // do a normal k_EP2PSendReliable to force all the buffered data to be sent. + k_EP2PSendReliableWithBuffering = 3, + +}; + + +// connection state to a specified user, returned by GetP2PSessionState() +// this is under-the-hood info about what's going on with a SendP2PPacket(), shouldn't be needed except for debuggin +struct P2PSessionState_t +{ + uint8 m_bConnectionActive; // true if we've got an active open connection + uint8 m_bConnecting; // true if we're currently trying to establish a connection + uint8 m_eP2PSessionError; // last error recorded (see enum above) + uint8 m_bUsingRelay; // true if it's going through a relay server (TURN) + int32 m_nBytesQueuedForSend; + int32 m_nPacketsQueuedForSend; + uint32 m_nRemoteIP; // potential IP:Port of remote host. Could be TURN server. + uint16 m_nRemotePort; // Only exists for compatibility with older authentication api's +}; + + // handle to a socket -typedef uint32 SNetSocket_t; -typedef uint32 SNetListenSocket_t; +typedef uint32 SNetSocket_t; // CreateP2PConnectionSocket() +typedef uint32 SNetListenSocket_t; // CreateListenSocket() - -// connection progress indicators +// connection progress indicators, used by CreateP2PConnectionSocket() enum ESNetSocketState { k_ESNetSocketStateInvalid = 0, @@ -61,6 +116,54 @@ enum ESNetSocketConnectionType class ISteamNetworking { public: + //////////////////////////////////////////////////////////////////////////////////////////// + // Session-less connection functions + // automatically establishes NAT-traversing or Relay server connections + + // Sends a P2P packet to the specified user + // UDP-like, unreliable and a max packet size of 1200 bytes + // the first packet send may be delayed as the NAT-traversal code runs + // if we can't get through to the user, an error will be posted via the callback P2PSessionConnectFail_t + // see EP2PSend enum above for the descriptions of the different ways of sending packets + virtual bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType ) = 0; + + // returns true if any data is available for read, and the amount of data that will need to be read + virtual bool IsP2PPacketAvailable( uint32 *pcubMsgSize ) = 0; + + // reads in a packet that has been sent from another user via SendP2PPacket() + // returns the size of the message and the steamID of the user who sent it in the last two parameters + // if the buffer passed in is too small, the message will be truncated + // this call is not blocking, and will return false if no data is available + virtual bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote ) = 0; + + // AcceptP2PSessionWithUser() should only be called in response to a P2PSessionRequest_t callback + // P2PSessionRequest_t will be posted if another user tries to send you a packet that you haven't talked to yet + // if you don't want to talk to the user, just ignore the request + // if the user continues to send you packets, another P2PSessionRequest_t will be posted periodically + // this may be called multiple times for a single user + // (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) + virtual bool AcceptP2PSessionWithUser( CSteamID steamIDRemote ) = 0; + + // call CloseP2PSessionWithUser() when you're done talking to a user, will free up resources under-the-hood + // if the remote user tries to send data to you again, another P2PSessionRequest_t callback will be posted + virtual bool CloseP2PSessionWithUser( CSteamID steamIDRemote ) = 0; + + // fills out P2PSessionState_t structure with details about the underlying connection to the user + // should only needed for debugging purposes + // returns false if no connection exists to the specified user + virtual bool GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ) = 0; + + + //////////////////////////////////////////////////////////////////////////////////////////// + // LISTEN / CONNECT style interface functions + // + // This is an older set of functions designed around the Berkeley TCP sockets model + // it's preferential that you use the above P2P functions, they're more robust + // and these older functions will be removed eventually + // + //////////////////////////////////////////////////////////////////////////////////////////// + + // creates a socket and listens others to connect // will trigger a SocketStatusCallback_t callback on another client connecting // nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports @@ -130,10 +233,31 @@ public: // max packet size, in bytes virtual int GetMaxPacketSize( SNetSocket_t hSocket ) = 0; }; -#define STEAMNETWORKING_INTERFACE_VERSION "SteamNetworking002" +#define STEAMNETWORKING_INTERFACE_VERSION "SteamNetworking003" + + +// callback notification - a user wants to talk to us over the P2P channel via the SendP2PPacket() API +// in response, a call to AcceptP2PPacketsFromUser() needs to be made, if you want to talk with them +struct P2PSessionRequest_t +{ + enum { k_iCallback = k_iSteamNetworkingCallbacks + 2 }; + CSteamID m_steamIDRemote; // user who wants to talk to us +}; + + +// callback notification - packets can't get through to the specified user via the SendP2PPacket() API +// all packets queued packets unsent at this point will be dropped +// further attempts to send will retry making the connection (but will be dropped if we fail again) +struct P2PSessionConnectFail_t +{ + enum { k_iCallback = k_iSteamNetworkingCallbacks + 3 }; + CSteamID m_steamIDRemote; // user we were sending packets to + uint8 m_eP2PSessionError; // EP2PSessionError indicating why we're having trouble +}; // callback notification - status of a socket has changed +// used as part of the CreateListenSocket() / CreateP2PConnectionSocket() struct SocketStatusCallback_t { enum { k_iCallback = k_iSteamNetworkingCallbacks + 1 }; diff --git a/public/steam/isteamuser.h b/public/steam/isteamuser.h index d8f962c..1ba28dd 100644 --- a/public/steam/isteamuser.h +++ b/public/steam/isteamuser.h @@ -105,6 +105,10 @@ public: // Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to virtual void CancelAuthTicket( HAuthTicket hAuthTicket ) = 0; + + // After receiving a user's authentication data, and passing it to BeginAuthSession, use this function + // to determine if the user owns downloadable content specified by the provided AppID. + virtual EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) = 0; }; #define STEAMUSER_INTERFACE_VERSION "SteamUser012" diff --git a/public/steam/isteamuserstats.h b/public/steam/isteamuserstats.h index f11823d..bdcba90 100644 --- a/public/steam/isteamuserstats.h +++ b/public/steam/isteamuserstats.h @@ -52,6 +52,13 @@ enum ELeaderboardDisplayType k_ELeaderboardDisplayTypeTimeMilliSeconds = 3, // the score represents a time, in milliseconds }; +enum ELeaderboardUploadScoreMethod +{ + k_ELeaderboardUploadScoreMethodNone = 0, + k_ELeaderboardUploadScoreMethodKeepBest = 1, // Leaderboard will keep user's best score + k_ELeaderboardUploadScoreMethodForceUpdate = 2, // Leaderboard will always replace score with specified +}; + // a single entry in a leaderboard, as returned by GetDownloadedLeaderboardEntry() struct LeaderboardEntry_t { @@ -166,13 +173,16 @@ public: // Uploads a user score to the Steam back-end. // This call is asynchronous, with the result returned in LeaderboardScoreUploaded_t - // If the score passed in is no better than the existing score this user has in the leaderboard, then the leaderboard will not be updated. // Details are extra game-defined information regarding how the user got that score // pScoreDetails points to an array of int32's, cScoreDetailsCount is the number of int32's in the list - virtual SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 *pScoreDetails, int cScoreDetailsCount ) = 0; + virtual SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount ) = 0; + + // Retrieves the number of players currently playing your game (online + offline) + // This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t + virtual SteamAPICall_t GetNumberOfCurrentPlayers() = 0; }; -#define STEAMUSERSTATS_INTERFACE_VERSION "STEAMUSERSTATS_INTERFACE_VERSION005" +#define STEAMUSERSTATS_INTERFACE_VERSION "STEAMUSERSTATS_INTERFACE_VERSION006" //----------------------------------------------------------------------------- @@ -256,5 +266,12 @@ struct LeaderboardScoreUploaded_t int m_nGlobalRankPrevious; // the previous global rank of the user in this leaderboard; 0 if the user had no existing entry in the leaderboard }; +struct NumberOfCurrentPlayers_t +{ + enum { k_iCallback = k_iSteamUserStatsCallbacks + 7 }; + uint8 m_bSuccess; // 1 if the call was successful + int32 m_cPlayers; // Number of players currently playing +}; + #endif // ISTEAMUSER_H diff --git a/public/steam/matchmakingtypes.h b/public/steam/matchmakingtypes.h index 23090f0..cb7240c 100644 --- a/public/steam/matchmakingtypes.h +++ b/public/steam/matchmakingtypes.h @@ -212,6 +212,7 @@ private: // For data added after SteamMatchMaking001 add it here public: char m_szGameTags[128]; // the tags this server exposes + CSteamID m_steamID; // steamID of the game server - invalid if it's doesn't have one (old server, or not connected to Steam) }; diff --git a/public/steam/steam_api.h b/public/steam/steam_api.h index 8ee9c3d..d5cc0b9 100644 --- a/public/steam/steam_api.h +++ b/public/steam/steam_api.h @@ -270,10 +270,20 @@ private: func_t m_Func; }; +// Allows you to defer registration of the callback +template< class T, class P, bool bGameServer > +class CCallbackManual : public CCallback< T, P, bGameServer > +{ +public: + CCallbackManual() : CCallback< T, P, bGameServer >( NULL, NULL ) {} +}; // utility macro for declaring the function and callback object together #define STEAM_CALLBACK( thisclass, func, param, var ) CCallback< thisclass, param, false > var; void func( param *pParam ) +// same as above, but lets you defer the callback binding by calling Register later +#define STEAM_CALLBACK_MANUAL( thisclass, func, param, var ) CCallbackManual< thisclass, param, false > var; void func( param *pParam ) + #ifdef _WIN32 // disable this warning; this pattern need for steam callback registration diff --git a/public/steam/steamclientpublic.h b/public/steam/steamclientpublic.h index 24dea78..fd04ab2 100644 --- a/public/steam/steamclientpublic.h +++ b/public/steam/steamclientpublic.h @@ -69,6 +69,11 @@ enum EResult k_EResultNoMatch = 42, // nothing matching the request found k_EResultAccountDisabled = 43, k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. + }; // Error codes for use with the voice functions @@ -132,6 +137,14 @@ typedef enum k_EAuthSessionResponseAuthTicketInvalid = 8, // This ticket is not from a user instance currently connected to steam. } EAuthSessionResponse; +// results from UserHasLicenseForApp +typedef enum +{ + k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app + k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app + k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated +} EUserHasLicenseForAppResult; + // Steam universes. Each universe is a self-contained Steam instance. enum EUniverse @@ -186,11 +199,13 @@ enum ESteamUserStatType //----------------------------------------------------------------------------- enum EChatEntryType { + k_EChatEntryTypeInvalid = 0, k_EChatEntryTypeChatMsg = 1, // Normal text message from another user k_EChatEntryTypeTyping = 2, // Another user is typing (not used in multi-user chat) k_EChatEntryTypeInviteGame = 3, // Invite from other user into that users current game k_EChatEntryTypeEmote = 4, // text emote message k_EChatEntryTypeLobbyGameStart = 5, // lobby game is starting + k_EChatEntryTypeLeftConversation = 6, // user has left the conversation ( closed chat window ) // Above are previous FriendMsgType entries, now merged into more generic chat entry types }; @@ -209,6 +224,18 @@ enum EChatRoomEnterResponse }; +//----------------------------------------------------------------------------- +// Purpose: Status of a given depot version, these are stored in the DB, don't renumber +//----------------------------------------------------------------------------- +enum EStatusDepotVersion +{ + k_EStatusDepotVersionInvalid = 0, + k_EStatusDepotVersionCompleteDisabled = 1, + k_EStatusDepotVersionCompleteEnabledBeta = 2, + k_EStatusDepotVersionCompleteEnabledPublic = 3, +}; + + typedef void (*PFNLegacyKeyRegistration)( const char *pchCDKey, const char *pchInstallPath ); typedef bool (*PFNLegacyKeyInstalled)(); diff --git a/public/steam/steamtypes.h b/public/steam/steamtypes.h index 10b689f..8369cc2 100644 --- a/public/steam/steamtypes.h +++ b/public/steam/steamtypes.h @@ -74,6 +74,14 @@ const PackageId_t k_uPackageIdInvalid = 0xFFFFFFFF; typedef uint32 AppId_t; const AppId_t k_uAppIdInvalid = 0x0; + +// this is baked into client messages and interfaces as an int, +// make sure we never break this. AppIds and DepotIDs also presently +// share the same namespace, but since we'd like to change that in the future +// I've defined it seperately here. +typedef uint32 DepotId_t; +const DepotId_t k_uDepotIdInvalid = 0x0; + // RTime32 // We use this 32 bit time representing real world time. // It offers 1 second resolution beginning on January 1, 1970 (Unix time) diff --git a/redistributable_bin/SteamAPIUpdater.dll b/redistributable_bin/SteamAPIUpdater.dll index 2740d74..4db3a35 100644 Binary files a/redistributable_bin/SteamAPIUpdater.dll and b/redistributable_bin/SteamAPIUpdater.dll differ diff --git a/redistributable_bin/UpdateDLLWrapper.dll b/redistributable_bin/UpdateDLLWrapper.dll index 4840870..93c9089 100644 Binary files a/redistributable_bin/UpdateDLLWrapper.dll and b/redistributable_bin/UpdateDLLWrapper.dll differ diff --git a/redistributable_bin/steam_api.dll b/redistributable_bin/steam_api.dll index 1eaae9f..fb0610e 100644 Binary files a/redistributable_bin/steam_api.dll and b/redistributable_bin/steam_api.dll differ diff --git a/redistributable_bin/steam_api.lib b/redistributable_bin/steam_api.lib index 13abc7d..ea9cea2 100644 Binary files a/redistributable_bin/steam_api.lib and b/redistributable_bin/steam_api.lib differ diff --git a/tools/ContentTool.exe b/tools/ContentTool.exe index 676e52b..8da24cd 100644 Binary files a/tools/ContentTool.exe and b/tools/ContentTool.exe differ