1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "steam_client.h"
25 #include "nel/misc/cmd_args.h"
27 #include <steam_api.h>
28 #include <isteamutils.h>
34 // prototypes definitions for Steam API functions we'll call
35 typedef bool (__cdecl
*SteamAPI_InitFuncPtr
)();
36 typedef void (__cdecl
*SteamAPI_ShutdownFuncPtr
)();
37 typedef HSteamUser (__cdecl
*SteamAPI_GetHSteamUserFuncPtr
)();
38 typedef HSteamPipe (__cdecl
*SteamAPI_GetHSteamPipeFuncPtr
)();
39 typedef void* (__cdecl
*SteamInternal_CreateInterfaceFuncPtr
)(const char *ver
);
40 typedef void (__cdecl
*SteamAPI_RegisterCallbackFuncPtr
)(class CCallbackBase
*pCallback
, int iCallback
);
41 typedef void (__cdecl
*SteamAPI_UnregisterCallbackFuncPtr
)(class CCallbackBase
*pCallback
);
42 typedef void (__cdecl
*SteamAPI_RunCallbacksFuncPtr
)();
44 // macros to simplify dynamic functions loading
45 #define NL_DECLARE_SYMBOL(symbol) symbol##FuncPtr nl##symbol = NULL
46 #define NL_LOAD_SYMBOL(symbol) \
47 nl##symbol = (symbol##FuncPtr)NLMISC::nlGetSymbolAddress(_Handle, #symbol); \
48 if (nl##symbol == NULL) return false
50 NL_DECLARE_SYMBOL(SteamAPI_Init
);
51 NL_DECLARE_SYMBOL(SteamAPI_Shutdown
);
53 NL_DECLARE_SYMBOL(SteamAPI_GetHSteamUser
);
54 NL_DECLARE_SYMBOL(SteamAPI_GetHSteamPipe
);
55 NL_DECLARE_SYMBOL(SteamInternal_CreateInterface
);
57 NL_DECLARE_SYMBOL(SteamAPI_RegisterCallback
);
58 NL_DECLARE_SYMBOL(SteamAPI_UnregisterCallback
);
59 NL_DECLARE_SYMBOL(SteamAPI_RunCallbacks
);
61 // instances of classes
62 static ISteamClient
*s_SteamClient
= NULL
;
63 static ISteamUser
*s_SteamUser
= NULL
;
64 static ISteamApps
*s_SteamApps
= NULL
;
65 static ISteamFriends
*s_SteamFriends
= NULL
;
66 static ISteamUtils
*s_SteamUtils
= NULL
;
68 // taken from steam_api.h, we needed to change it to use our dynamically loaded functions
70 // Declares a callback member function plus a helper member variable which
71 // registers the callback on object creation and unregisters on destruction.
72 // The optional fourth 'var' param exists only for backwards-compatibility
73 // and can be ignored.
74 #define NL_STEAM_CALLBACK( thisclass, func, .../*callback_type, [deprecated] var*/ ) \
75 _NL_STEAM_CALLBACK_SELECT( ( __VA_ARGS__, 4, 3 ), ( /**/, thisclass, func, __VA_ARGS__ ) )
77 //-----------------------------------------------------------------------------
78 // The following macros are implementation details, not intended for public use
79 //-----------------------------------------------------------------------------
80 #define _NL_STEAM_CALLBACK_AUTO_HOOK( thisclass, func, param )
81 #define _NL_STEAM_CALLBACK_HELPER( _1, _2, SELECTED, ... ) _NL_STEAM_CALLBACK_##SELECTED
82 #define _NL_STEAM_CALLBACK_SELECT( X, Y ) _NL_STEAM_CALLBACK_HELPER X Y
83 #define _NL_STEAM_CALLBACK_3( extra_code, thisclass, func, param ) \
84 struct CCallbackInternal_ ## func : private CSteamCallbackImpl< sizeof( param ) > { \
85 CCallbackInternal_ ## func () { extra_code nlSteamAPI_RegisterCallback( this, param::k_iCallback ); } \
86 CCallbackInternal_ ## func ( const CCallbackInternal_ ## func & ) { extra_code nlSteamAPI_RegisterCallback( this, param::k_iCallback ); } \
87 CCallbackInternal_ ## func & operator=( const CCallbackInternal_ ## func & ) { return *this; } \
88 private: virtual void Run( void *pvParam ) { _NL_STEAM_CALLBACK_AUTO_HOOK( thisclass, func, param ) \
89 thisclass *pOuter = reinterpret_cast<thisclass*>( reinterpret_cast<char*>(this) - offsetof( thisclass, m_steamcallback_ ## func ) ); \
90 pOuter->func( reinterpret_cast<param*>( pvParam ) ); \
92 } m_steamcallback_ ## func ; void func( param *pParam )
93 #define _NL_STEAM_CALLBACK_4( _, thisclass, func, param, var ) \
94 CSteamCallback< thisclass, param > var; void func( param *pParam )
96 //-----------------------------------------------------------------------------
97 // Purpose: templated base for callbacks - internal implementation detail
98 //-----------------------------------------------------------------------------
99 template< int sizeof_P
>
100 class CSteamCallbackImpl
: protected CCallbackBase
103 ~CSteamCallbackImpl() { if ( m_nCallbackFlags
& k_ECallbackFlagsRegistered
) nlSteamAPI_UnregisterCallback( this ); }
104 void SetGameserverFlag() { m_nCallbackFlags
|= k_ECallbackFlagsGameServer
; }
107 virtual void Run( void *pvParam
) = 0;
108 virtual void Run( void *pvParam
, bool /*bIOFailure*/, SteamAPICall_t
/*hSteamAPICall*/ ) { Run( pvParam
); }
109 virtual int GetCallbackSizeBytes() { return sizeof_P
; }
112 //-----------------------------------------------------------------------------
113 // Purpose: maps a steam callback to a class member function
114 // template params: T = local class, P = parameter struct,
115 // bGameserver = listen for gameserver callbacks instead of client callbacks
116 //-----------------------------------------------------------------------------
117 template< class T
, class P
, bool bGameserver
= false >
118 class CSteamCallback
: public CSteamCallbackImpl
< sizeof( P
) >
121 typedef void (T::*func_t
)(P
*);
123 // NOTE: If you can't provide the correct parameters at construction time, you should
124 // use the CCallbackManual callback object (STEAM_CALLBACK_MANUAL macro) instead.
125 CSteamCallback( T
*pObj
, func_t func
) : m_pObj( NULL
), m_Func( NULL
)
129 this->SetGameserverFlag();
131 Register( pObj
, func
);
134 // manual registration of the callback
135 void Register( T
*pObj
, func_t func
)
137 if ( !pObj
|| !func
)
140 if ( this->m_nCallbackFlags
& CCallbackBase::k_ECallbackFlagsRegistered
)
145 // SteamAPI_RegisterCallback sets k_ECallbackFlagsRegistered
146 nlSteamAPI_RegisterCallback( this, P::k_iCallback
);
151 // SteamAPI_UnregisterCallback removes k_ECallbackFlagsRegistered
152 nlSteamAPI_UnregisterCallback( this );
156 virtual void Run( void *pvParam
)
158 (m_pObj
->*m_Func
)( (P
*)pvParam
);
165 extern NLMISC::CCmdArgs Args
;
167 // listener called by Steam when AuthSessionTicket is available
168 class CAuthSessionTicketListener
171 CAuthSessionTicketListener():_AuthSessionTicketResponse(this, &CAuthSessionTicketListener::OnAuthSessionTicketResponse
)
173 _AuthSessionTicketHandle
= 0;
174 _AuthSessionTicketSize
= 0;
176 _AuthSessionTicketCallbackCalled
= false;
177 _AuthSessionTicketCallbackError
= false;;
178 _AuthSessionTicketCallbackTimeout
= false;
181 // wait until a ticket is available or return if no ticket received after specified ms
182 bool waitTicket(uint32 ms
)
185 _AuthSessionTicketHandle
= s_SteamUser
->GetAuthSessionTicket(_AuthSessionTicketData
, sizeof(_AuthSessionTicketData
), &_AuthSessionTicketSize
);
187 nldebug("GetAuthSessionTicket returned %u bytes, handle %u", _AuthSessionTicketSize
, _AuthSessionTicketHandle
);
189 nlinfo("Waiting for Steam GetAuthSessionTicket callback...");
191 // define expiration time
192 NLMISC::TTime expirationTime
= NLMISC::CTime::getLocalTime() + ms
;
194 // wait until callback method is called or expiration
195 while(!_AuthSessionTicketCallbackCalled
&& !_AuthSessionTicketCallbackTimeout
)
197 // call registered callbacks
198 nlSteamAPI_RunCallbacks();
201 if (NLMISC::CTime::getLocalTime() > expirationTime
)
202 _AuthSessionTicketCallbackTimeout
= true;
206 if (_AuthSessionTicketCallbackTimeout
)
208 nlwarning("GetAuthSessionTicket callback never called");
212 nlinfo("GetAuthSessionTicket called");
215 if (_AuthSessionTicketCallbackError
)
217 nlwarning("GetAuthSessionTicket callback returned error");
224 // return ticket if available in hexadecimal
225 std::string
getTicket() const
227 // if expired or error, ticket is not available
228 if (!_AuthSessionTicketCallbackCalled
|| _AuthSessionTicketCallbackError
|| _AuthSessionTicketCallbackTimeout
) return "";
230 // convert buffer to hexadecimal string
231 return NLMISC::toHexa(_AuthSessionTicketData
, _AuthSessionTicketSize
);
236 HAuthTicket _AuthSessionTicketHandle
;
238 // buffer of ticket data
239 uint8 _AuthSessionTicketData
[1024];
242 uint32 _AuthSessionTicketSize
;
244 // different states of callback
245 bool _AuthSessionTicketCallbackCalled
;
246 bool _AuthSessionTicketCallbackError
;
247 bool _AuthSessionTicketCallbackTimeout
;
249 // callback declaration
250 NL_STEAM_CALLBACK(CAuthSessionTicketListener
, OnAuthSessionTicketResponse
, GetAuthSessionTicketResponse_t
, _AuthSessionTicketResponse
);
253 // method called by Steam
254 void CAuthSessionTicketListener::OnAuthSessionTicketResponse(GetAuthSessionTicketResponse_t
*inCallback
)
256 _AuthSessionTicketCallbackCalled
= true;
258 if (inCallback
->m_eResult
!= k_EResultOK
)
260 _AuthSessionTicketCallbackError
= true;
264 CSteamClient::CSteamClient():_Handle(NULL
), _Initialized(false)
268 CSteamClient::~CSteamClient()
273 static void SteamWarningMessageHook(int severity
, const char *message
)
278 nlwarning("%s", message
);
282 nlinfo("%s", message
);
286 nlwarning("Unknown severity %d: %s", severity
, message
);
291 bool CSteamClient::init()
293 std::string filename
;
295 #if defined(NL_OS_WIN64)
296 filename
= "steam_api64.dll";
297 #elif defined(NL_OS_WINDOWS)
298 filename
= "steam_api.dll";
299 #elif defined(NL_OS_MAC)
300 filename
= "libsteam_api.dylib";
302 filename
= "libsteam_api.so";
305 // try to load library with absolute path
306 _Handle
= NLMISC::nlLoadLibrary(Args
.getProgramPath() + filename
);
310 // try to load library with relative path (will search in system paths)
311 _Handle
= NLMISC::nlLoadLibrary(filename
);
315 nlwarning("Unable to load Steam client");
320 // load Steam functions
321 NL_LOAD_SYMBOL(SteamAPI_Init
);
322 NL_LOAD_SYMBOL(SteamAPI_Shutdown
);
324 // check if function was found
325 if (!nlSteamAPI_Init
)
327 nlwarning("Unable to get a pointer on SteamAPI_Init");
331 // initialize Steam API
332 if (!nlSteamAPI_Init())
334 nlwarning("Unable to initialize Steam client");
340 // load more Steam functions
341 NL_LOAD_SYMBOL(SteamAPI_GetHSteamUser
);
342 NL_LOAD_SYMBOL(SteamAPI_GetHSteamPipe
);
343 NL_LOAD_SYMBOL(SteamInternal_CreateInterface
);
345 HSteamUser hSteamUser
= nlSteamAPI_GetHSteamUser();
346 HSteamPipe hSteamPipe
= nlSteamAPI_GetHSteamPipe();
350 nlwarning("Unable to get Steam pipe");
354 // instanciate all used Steam classes
355 s_SteamClient
= (ISteamClient
*)nlSteamInternal_CreateInterface(STEAMCLIENT_INTERFACE_VERSION
);
359 s_SteamUser
= s_SteamClient
->GetISteamUser(hSteamUser
, hSteamPipe
, STEAMUSER_INTERFACE_VERSION
);
363 s_SteamApps
= s_SteamClient
->GetISteamApps(hSteamUser
, hSteamPipe
, STEAMAPPS_INTERFACE_VERSION
);
367 s_SteamFriends
= s_SteamClient
->GetISteamFriends(hSteamUser
, hSteamPipe
, STEAMFRIENDS_INTERFACE_VERSION
);
371 s_SteamUtils
= s_SteamClient
->GetISteamUtils(hSteamPipe
, STEAMUTILS_INTERFACE_VERSION
);
375 // set warning messages hook
376 s_SteamClient
->SetWarningMessageHook(SteamWarningMessageHook
);
378 bool loggedOn
= s_SteamUser
->BLoggedOn();
380 nlinfo("Steam AppID: %u", s_SteamUtils
->GetAppID());
381 nlinfo("Steam login: %s", s_SteamFriends
->GetPersonaName());
382 nlinfo("Steam user logged: %s", loggedOn
? "yes":"no");
384 const char *lang
= s_SteamApps
->GetCurrentGameLanguage();
385 GameLanguage
= std::string(lang
);
387 if (!GameLanguage
.empty())
389 nlinfo("Steam language full: %s", GameLanguage
.c_str());
390 nlinfo("Steam language WebApiFormat: %s", GameLanguageWebApiFormat().c_str());
391 NLMISC::CI18N::setSystemLanguageCode(GameLanguage
);
394 // don't need to continue, if not connected
395 if (!loggedOn
) return false;
397 // load symbols used by AuthSessionTicket
398 NL_LOAD_SYMBOL(SteamAPI_RegisterCallback
);
399 NL_LOAD_SYMBOL(SteamAPI_UnregisterCallback
);
400 NL_LOAD_SYMBOL(SteamAPI_RunCallbacks
);
402 CAuthSessionTicketListener listener
;
404 // wait 5 seconds to get ticket
405 if (!listener
.waitTicket(5000)) return false;
408 _AuthSessionTicket
= listener
.getTicket();
410 nldebug("Auth ticket: %s", _AuthSessionTicket
.c_str());
415 bool CSteamClient::release()
417 if (!_Handle
) return false;
421 // only shutdown Steam if initialized
422 nlSteamAPI_Shutdown();
424 _Initialized
= false;
427 // free Steam library from memory
428 bool res
= NLMISC::nlFreeLibrary(_Handle
);
435 std::string
CSteamClient::GameLanguageWebApiFormat()
437 //get the right API Code from https://partner.steamgames.com/doc/store/localization#supported_languages
439 if (GameLanguage
== "french")
441 else if (GameLanguage
== "german")
443 else if (GameLanguage
== "spanish")
445 else if (GameLanguage
== "russian")