diff --git a/code/CMakeModules/FindSteam.cmake b/code/CMakeModules/FindSteam.cmake new file mode 100644 index 000000000..dd62e2bc0 --- /dev/null +++ b/code/CMakeModules/FindSteam.cmake @@ -0,0 +1,60 @@ +# - Locate Steam API +# This module defines +# STEAM_LIBRARY, the library to link against +# VORBIS_FOUND, if false, do not try to link to VORBIS +# VORBIS_INCLUDE_DIR, where to find headers. + +IF(STEAM_LIBRARY AND STEAM_INCLUDE_DIR) + # in cache already + SET(Steam_FIND_QUIETLY TRUE) +ENDIF() + + +FIND_PATH(STEAM_INCLUDE_DIR + steam_api.h + PATH_SUFFIXES steam + PATHS + $ENV{STEAM_DIR}/public +) + +IF(WIN32) + IF(TARGET_X64) + SET(STEAM_LIBNAME steam_api64) + SET(STEAM_PATHNAME redistributable_bin/win64) + ELSE() + SET(STEAM_LIBNAME steam_api) + SET(STEAM_PATHNAME redistributable_bin) + ENDIF() +ELSEIF(APPLE) + # universal binary + SET(STEAM_LIBNAME steam_api) + SET(STEAM_PATHNAME redistributable_bin/osx32) +ELSE() + IF(TARGET_X64) + SET(STEAM_LIBNAME steam_api) + SET(STEAM_PATHNAME redistributable_bin/linux64) + ELSE() + SET(STEAM_LIBNAME steam_api) + SET(STEAM_PATHNAME redistributable_bin/linux32) + ENDIF() +ENDIF() + +FIND_LIBRARY(STEAM_LIBRARY + NAMES ${STEAM_LIBNAME} + PATHS + $ENV{STEAM_DIR}/${STEAM_PATHNAME} +) + +# Don't need to check STEAM_LIBRARY because we're dynamically loading Steam DLL +IF(STEAM_INCLUDE_DIR) + SET(STEAM_FOUND ON) + SET(STEAM_LIBRARIES ${STEAM_LIBRARY}) + SET(STEAM_INCLUDE_DIRS ${STEAM_INCLUDE_DIR}) + IF(NOT Steam_FIND_QUIETLY) + MESSAGE(STATUS "Found Steam: ${STEAM_INCLUDE_DIR}") + ENDIF() +ELSE() + IF(NOT Steam_FIND_QUIETLY) + MESSAGE(STATUS "Warning: Unable to find Steam!") + ENDIF() +ENDIF() diff --git a/code/ryzom/client/src/CMakeLists.txt b/code/ryzom/client/src/CMakeLists.txt index af81c935d..845bcb7c5 100644 --- a/code/ryzom/client/src/CMakeLists.txt +++ b/code/ryzom/client/src/CMakeLists.txt @@ -6,7 +6,10 @@ ADD_SUBDIRECTORY(seven_zip) IF(WITH_RYZOM_CLIENT) # Patch should never be enabled on Steam - IF(WITH_RYZOM_PATCH AND NOT WITH_RYZOM_STEAM) + IF(WITH_RYZOM_STEAM) + ADD_DEFINITIONS(-DRZ_USE_STEAM) + FIND_PACKAGE(Steam) + ELSEIF(WITH_RYZOM_PATCH) ADD_DEFINITIONS(-DRZ_USE_PATCH) IF(WITH_RYZOM_CUSTOM_PATCH_SERVER) @@ -127,6 +130,10 @@ IF(WITH_RYZOM_CLIENT) ${ZLIB_INCLUDE_DIR} ) + IF(STEAM_FOUND) + INCLUDE_DIRECTORIES(${STEAM_INCLUDE_DIRS}) + ENDIF() + TARGET_LINK_LIBRARIES(ryzom_client nelmisc nelnet diff --git a/code/ryzom/client/src/client.cpp b/code/ryzom/client/src/client.cpp index eabb1511c..641305d0c 100644 --- a/code/ryzom/client/src/client.cpp +++ b/code/ryzom/client/src/client.cpp @@ -62,6 +62,10 @@ #include "client_cfg.h" #include "far_tp.h" +#ifdef RZ_USE_STEAM +#include "steam_client.h" +#endif + /////////// // USING // /////////// @@ -319,6 +323,13 @@ int main(int argc, char **argv) prelogInit(); RYZOM_CATCH("Pre-Login Init") +#ifdef RZ_USE_STEAM + CSteamClient steamClient; + + if (steamClient.init()) + LoginCustomParameters = "&steam_auth_session_ticket=" + steamClient.getAuthSessionTicket(); +#endif + // Log the client and choose from shard RYZOM_TRY("Login") if (!ClientCfg.Local && (ClientCfg.TestBrowser || ClientCfg.FSHost.empty())) diff --git a/code/ryzom/client/src/login.cpp b/code/ryzom/client/src/login.cpp index 6386656dc..43c00b18f 100644 --- a/code/ryzom/client/src/login.cpp +++ b/code/ryzom/client/src/login.cpp @@ -77,7 +77,7 @@ extern bool SetMousePosFirstTime; vector Shards; -string LoginLogin, LoginPassword, ClientApp, Salt; +string LoginLogin, LoginPassword, ClientApp, Salt, LoginCustomParameters; uint32 LoginShardId = 0xFFFFFFFF; @@ -1169,7 +1169,7 @@ void onlogin(bool vanishScreen = true) // Check the login/pass // main menu page for r2mode - string res = checkLogin(LoginLogin, LoginPassword, ClientApp); + string res = checkLogin(LoginLogin, LoginPassword, ClientApp, LoginCustomParameters); if (res.empty()) { // if not in auto login, push login ok event @@ -2738,7 +2738,7 @@ REGISTER_ACTION_HANDLER (CAHOnBackToLogin, "on_back_to_login"); // *************************************************************************** -string checkLogin(const string &login, const string &password, const string &clientApp) +string checkLogin(const string &login, const string &password, const string &clientApp, const std::string &customParameters) { CPatchManager *pPM = CPatchManager::getInstance(); Shards.clear(); @@ -2795,7 +2795,7 @@ string checkLogin(const string &login, const string &password, const string &cli { // R2 login sequence std::string cryptedPassword = CCrypt::crypt(password, Salt); - if(!HttpClient.sendGet(ClientCfg.ConfigFile.getVar("StartupPage").asString()+"?cmd=login&login="+login+"&password="+cryptedPassword+"&clientApplication="+clientApp+"&cp=1"+"&lg="+ClientCfg.LanguageCode)) + if(!HttpClient.sendGet(ClientCfg.ConfigFile.getVar("StartupPage").asString()+"?cmd=login&login="+login+"&password="+cryptedPassword+"&clientApplication="+clientApp+"&cp=1"+"&lg="+ClientCfg.LanguageCode+customParameters)) return "Can't send (error code 2)"; // the response should contains the result code and the cookie value diff --git a/code/ryzom/client/src/login.h b/code/ryzom/client/src/login.h index ad2388100..2c6e8c960 100644 --- a/code/ryzom/client/src/login.h +++ b/code/ryzom/client/src/login.h @@ -46,7 +46,7 @@ struct CShard std::string EmergencyPatchURL; }; -extern std::string LoginLogin, LoginPassword; +extern std::string LoginLogin, LoginPassword, LoginCustomParameters; extern uint32 LoginShardId; @@ -54,7 +54,7 @@ extern uint32 AvailablePatchs; -std::string checkLogin(const std::string &login, const std::string &password, const std::string &clientApp); +std::string checkLogin(const std::string &login, const std::string &password, const std::string &clientApp, const std::string &customParameters = ""); std::string selectShard(uint32 shardId, std::string &cookie, std::string &addr); std::string getBGDownloaderCommandLine(); diff --git a/code/ryzom/client/src/steam_client.cpp b/code/ryzom/client/src/steam_client.cpp new file mode 100644 index 000000000..bfd435dd1 --- /dev/null +++ b/code/ryzom/client/src/steam_client.cpp @@ -0,0 +1,358 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + + +#include "stdpch.h" + +#ifdef RZ_USE_STEAM + +#include "steam_client.h" + +#include + +// prototypes definitions for Steam API functions we'll call +typedef bool (__cdecl *SteamAPI_InitFuncPtr)(); +typedef void (__cdecl *SteamAPI_RegisterCallbackFuncPtr)(class CCallbackBase *pCallback, int iCallback); +typedef void (__cdecl *SteamAPI_RunCallbacksFuncPtr)(); +typedef void (__cdecl *SteamAPI_ShutdownFuncPtr)(); +typedef void (__cdecl *SteamAPI_UnregisterCallbackFuncPtr)(class CCallbackBase *pCallback); +typedef ISteamUtils* (__cdecl *SteamUtilsFuncPtr)(); +typedef ISteamUser* (__cdecl *SteamUserFuncPtr)(); +typedef ISteamFriends* (__cdecl *SteamFriendsFuncPtr)(); + +// macros to simplify dynamic functions loading +#define NL_DECLARE_SYMBOL(symbol) symbol##FuncPtr nl##symbol = NULL +#define NL_LOAD_SYMBOL(symbol) nl##symbol = (symbol##FuncPtr)NLMISC::nlGetSymbolAddress(_Handle, #symbol) + +NL_DECLARE_SYMBOL(SteamAPI_Init); +NL_DECLARE_SYMBOL(SteamFriends); +NL_DECLARE_SYMBOL(SteamUser); +NL_DECLARE_SYMBOL(SteamUtils); +NL_DECLARE_SYMBOL(SteamAPI_RegisterCallback); +NL_DECLARE_SYMBOL(SteamAPI_UnregisterCallback); +NL_DECLARE_SYMBOL(SteamAPI_RunCallbacks); +NL_DECLARE_SYMBOL(SteamAPI_Shutdown); + +// taken from steam_api.h, we needed to change it to use our dynamically loaded functions + +// Declares a callback member function plus a helper member variable which +// registers the callback on object creation and unregisters on destruction. +// The optional fourth 'var' param exists only for backwards-compatibility +// and can be ignored. +#define NL_STEAM_CALLBACK( thisclass, func, .../*callback_type, [deprecated] var*/ ) \ + _NL_STEAM_CALLBACK_SELECT( ( __VA_ARGS__, 4, 3 ), ( /**/, thisclass, func, __VA_ARGS__ ) ) + +//----------------------------------------------------------------------------- +// The following macros are implementation details, not intended for public use +//----------------------------------------------------------------------------- +#define _NL_STEAM_CALLBACK_AUTO_HOOK( thisclass, func, param ) +#define _NL_STEAM_CALLBACK_HELPER( _1, _2, SELECTED, ... ) _NL_STEAM_CALLBACK_##SELECTED +#define _NL_STEAM_CALLBACK_SELECT( X, Y ) _NL_STEAM_CALLBACK_HELPER X Y +#define _NL_STEAM_CALLBACK_3( extra_code, thisclass, func, param ) \ + struct CCallbackInternal_ ## func : private CSteamCallbackImpl< sizeof( param ) > { \ + CCallbackInternal_ ## func () { extra_code nlSteamAPI_RegisterCallback( this, param::k_iCallback ); } \ + CCallbackInternal_ ## func ( const CCallbackInternal_ ## func & ) { extra_code nlSteamAPI_RegisterCallback( this, param::k_iCallback ); } \ + CCallbackInternal_ ## func & operator=( const CCallbackInternal_ ## func & ) { return *this; } \ + private: virtual void Run( void *pvParam ) { _NL_STEAM_CALLBACK_AUTO_HOOK( thisclass, func, param ) \ + thisclass *pOuter = reinterpret_cast( reinterpret_cast(this) - offsetof( thisclass, m_steamcallback_ ## func ) ); \ + pOuter->func( reinterpret_cast( pvParam ) ); \ + } \ + } m_steamcallback_ ## func ; void func( param *pParam ) +#define _NL_STEAM_CALLBACK_4( _, thisclass, func, param, var ) \ + CSteamCallback< thisclass, param > var; void func( param *pParam ) + +//----------------------------------------------------------------------------- +// Purpose: templated base for callbacks - internal implementation detail +//----------------------------------------------------------------------------- +template< int sizeof_P > +class CSteamCallbackImpl : protected CCallbackBase +{ +public: + ~CSteamCallbackImpl() { if ( m_nCallbackFlags & k_ECallbackFlagsRegistered ) nlSteamAPI_UnregisterCallback( this ); } + void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; } + +protected: + virtual void Run( void *pvParam ) = 0; + virtual void Run( void *pvParam, bool /*bIOFailure*/, SteamAPICall_t /*hSteamAPICall*/ ) { Run( pvParam ); } + virtual int GetCallbackSizeBytes() { return sizeof_P; } +}; + +//----------------------------------------------------------------------------- +// Purpose: maps a steam callback to a class member function +// template params: T = local class, P = parameter struct, +// bGameserver = listen for gameserver callbacks instead of client callbacks +//----------------------------------------------------------------------------- +template< class T, class P, bool bGameserver = false > +class CSteamCallback : public CSteamCallbackImpl< sizeof( P ) > +{ +public: + typedef void (T::*func_t)(P*); + + // NOTE: If you can't provide the correct parameters at construction time, you should + // use the CCallbackManual callback object (STEAM_CALLBACK_MANUAL macro) instead. + CSteamCallback( T *pObj, func_t func ) : m_pObj( NULL ), m_Func( NULL ) + { + if ( bGameserver ) + { + this->SetGameserverFlag(); + } + Register( pObj, func ); + } + + // manual registration of the callback + void Register( T *pObj, func_t func ) + { + if ( !pObj || !func ) + return; + + if ( this->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsRegistered ) + Unregister(); + + m_pObj = pObj; + m_Func = func; + // SteamAPI_RegisterCallback sets k_ECallbackFlagsRegistered + nlSteamAPI_RegisterCallback( this, P::k_iCallback ); + } + + void Unregister() + { + // SteamAPI_UnregisterCallback removes k_ECallbackFlagsRegistered + nlSteamAPI_UnregisterCallback( this ); + } + +protected: + virtual void Run( void *pvParam ) + { + (m_pObj->*m_Func)( (P *)pvParam ); + } + + T *m_pObj; + func_t m_Func; +}; + +// listener called by Steam when AuthSessionTicket is available +class CAuthSessionTicketListener +{ +public: + CAuthSessionTicketListener():_AuthSessionTicketResponse(this, &CAuthSessionTicketListener::OnAuthSessionTicketResponse) + { + _AuthSessionTicketHandle = 0; + _AuthSessionTicketSize = 0; + + _AuthSessionTicketCallbackCalled = false; + _AuthSessionTicketCallbackError = false;; + _AuthSessionTicketCallbackTimeout = false; + } + + // wait until a ticket is available or return if no ticket received after specified ms + bool waitTicket(uint32 ms) + { + // call Steam method + _AuthSessionTicketHandle = nlSteamUser()->GetAuthSessionTicket(_AuthSessionTicketData, sizeof(_AuthSessionTicketData), &_AuthSessionTicketSize); + + nldebug("GetAuthSessionTicket returned %u bytes, handle %u", _AuthSessionTicketSize, _AuthSessionTicketHandle); + + nlinfo("Waiting for Steam GetAuthSessionTicket callback..."); + + // define expiration time + NLMISC::TTime expirationTime = NLMISC::CTime::getLocalTime() + ms; + + // wait until callback method is called or expiration + while(!_AuthSessionTicketCallbackCalled && !_AuthSessionTicketCallbackTimeout) + { + // call registered callbacks + nlSteamAPI_RunCallbacks(); + + // check if expired + if (NLMISC::CTime::getLocalTime() > expirationTime) + _AuthSessionTicketCallbackTimeout = true; + } + + // expired + if (_AuthSessionTicketCallbackTimeout) + { + nlwarning("GetAuthSessionTicket callback never called"); + return false; + } + + nlinfo("GetAuthSessionTicket called"); + + // got an error + if (_AuthSessionTicketCallbackError) + { + nlwarning("GetAuthSessionTicket callback returned error"); + return false; + } + + return true; + } + + // return ticket if available in hexadecimal + std::string getTicket() const + { + // if expired or error, ticket is not available + if (!_AuthSessionTicketCallbackCalled || _AuthSessionTicketCallbackError || _AuthSessionTicketCallbackTimeout) return ""; + + std::string authSessionTicket; + + // optimize string by allocating the final string size + authSessionTicket.reserve(_AuthSessionTicketSize*2); + + // convert buffer to hexadecimal string + for (uint32 i = 0; i < _AuthSessionTicketSize; ++i) + { + authSessionTicket += NLMISC::toString("%02x", _AuthSessionTicketData[i]); + } + + return authSessionTicket; + } + +private: + // ticket handle + HAuthTicket _AuthSessionTicketHandle; + + // buffer of ticket data + uint8 _AuthSessionTicketData[1024]; + + // size of buffer + uint32 _AuthSessionTicketSize; + + // different states of callback + bool _AuthSessionTicketCallbackCalled; + bool _AuthSessionTicketCallbackError; + bool _AuthSessionTicketCallbackTimeout; + + // callback declaration + NL_STEAM_CALLBACK(CAuthSessionTicketListener, OnAuthSessionTicketResponse, GetAuthSessionTicketResponse_t, _AuthSessionTicketResponse); +}; + +// method called by Steam +void CAuthSessionTicketListener::OnAuthSessionTicketResponse(GetAuthSessionTicketResponse_t *inCallback) +{ + _AuthSessionTicketCallbackCalled = true; + + if (inCallback->m_eResult != k_EResultOK) + { + _AuthSessionTicketCallbackError = true; + } +} + +CSteamClient::CSteamClient():_Handle(NULL), _Initialized(false) +{ +} + +CSteamClient::~CSteamClient() +{ + release(); +} + +bool CSteamClient::init() +{ + std::string filename; + +#if defined(NL_OS_WIN64) + filename = "steam_api64.dll"; +#elif defined(NL_OS_WINDOWS) + filename = "steam_api.dll"; +#elif defined(NL_OS_MAC) + filename = "libsteam_api.dylib"; +#else + filename = "libsteam_api.so"; +#endif + + // try to load library + _Handle = NLMISC::nlLoadLibrary(filename); + + if (!_Handle) + { + nlwarning("Unable to load Steam client"); + return false; + } + + NL_LOAD_SYMBOL(SteamAPI_Init); + + // check if function was found + if (!nlSteamAPI_Init) + { + nlwarning("Unable to get a pointer on SteamAPI_Init"); + return false; + } + + // initialize Steam API + if (!nlSteamAPI_Init()) + { + nlwarning("Unable to initialize Steam client"); + return false; + } + + _Initialized = true; + + // load more functions + NL_LOAD_SYMBOL(SteamFriends); + NL_LOAD_SYMBOL(SteamUser); + NL_LOAD_SYMBOL(SteamUtils); + NL_LOAD_SYMBOL(SteamAPI_Shutdown); + + bool loggedOn = nlSteamUser()->BLoggedOn(); + + nlinfo("Steam AppID: %u", nlSteamUtils()->GetAppID()); + nlinfo("Steam login: %s", nlSteamFriends()->GetPersonaName()); + nlinfo("Steam user logged: %s", loggedOn ? "yes":"no"); + + // don't need to continue, if not connected + if (!loggedOn) return false; + + // load symbols used by AuthSessionTicket + NL_LOAD_SYMBOL(SteamAPI_RegisterCallback); + NL_LOAD_SYMBOL(SteamAPI_UnregisterCallback); + NL_LOAD_SYMBOL(SteamAPI_RunCallbacks); + + CAuthSessionTicketListener listener; + + // wait 5 seconds to get ticket + if (!listener.waitTicket(5000)) return false; + + // save ticket + _AuthSessionTicket = listener.getTicket(); + + nldebug("Auth ticket: %s", _AuthSessionTicket.c_str()); + + return true; +} + +bool CSteamClient::release() +{ + if (!_Handle) return false; + + if (_Initialized) + { + // only shutdown Steam if initialized + nlSteamAPI_Shutdown(); + + _Initialized = false; + } + + // free Steam library from memory + bool res = NLMISC::nlFreeLibrary(_Handle); + + _Handle = NULL; + + return res; +} + + +#endif diff --git a/code/ryzom/client/src/steam_client.h b/code/ryzom/client/src/steam_client.h new file mode 100644 index 000000000..b11f3c6d1 --- /dev/null +++ b/code/ryzom/client/src/steam_client.h @@ -0,0 +1,65 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef CL_STEAM_CLIENT_H +#define CL_STEAM_CLIENT_H + +#include "nel/misc/types_nl.h" +#include "nel/misc/dynloadlib.h" + +/** + * Steam API helper to be able to call Steam functions/methods without linking to any library. + * The library is dynamically loaded and is optional. + * + * \author Cedric 'Kervala' OCHS + * \date 2016 + */ +class CSteamClient +{ +public: + CSteamClient(); + ~CSteamClient(); + + /** + * Dynamically load Steam client library and functions pointers. + * Also retrieve authentication session ticket if available. + * If no authentication session ticket retrieved, returns false. + */ + bool init(); + + /** + * Shutdown Steam client and unload library. + */ + bool release(); + + /** + * Return the authentication session ticket if available. + */ + std::string getAuthSessionTicket() const { return _AuthSessionTicket; } + +private: + // handle on Steam DLL + NLMISC::NL_LIB_HANDLE _Handle; + + // true if succeeded to initialize (must call shutdown) + bool _Initialized; + + // the retrieved authentication session ticket + std::string _AuthSessionTicket; +}; + +#endif