Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / client / src / steam_client.cpp
blob57fed673b97e2646c910eadcfab7a92bf112ad51
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
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.
8 //
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/>.
19 #include "stdpch.h"
21 #ifdef RZ_USE_STEAM
23 #include "steam_client.h"
25 #include "nel/misc/cmd_args.h"
27 #include <steam_api.h>
28 #include <isteamutils.h>
30 #ifdef DEBUG_NEW
31 #define new DEBUG_NEW
32 #endif
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 ) ); \
91 } \
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
102 public:
103 ~CSteamCallbackImpl() { if ( m_nCallbackFlags & k_ECallbackFlagsRegistered ) nlSteamAPI_UnregisterCallback( this ); }
104 void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; }
106 protected:
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 ) >
120 public:
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 )
127 if ( bGameserver )
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 )
138 return;
140 if ( this->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsRegistered )
141 Unregister();
143 m_pObj = pObj;
144 m_Func = func;
145 // SteamAPI_RegisterCallback sets k_ECallbackFlagsRegistered
146 nlSteamAPI_RegisterCallback( this, P::k_iCallback );
149 void Unregister()
151 // SteamAPI_UnregisterCallback removes k_ECallbackFlagsRegistered
152 nlSteamAPI_UnregisterCallback( this );
155 protected:
156 virtual void Run( void *pvParam )
158 (m_pObj->*m_Func)( (P *)pvParam );
161 T *m_pObj;
162 func_t m_Func;
165 extern NLMISC::CCmdArgs Args;
167 // listener called by Steam when AuthSessionTicket is available
168 class CAuthSessionTicketListener
170 public:
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)
184 // call Steam method
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();
200 // check if expired
201 if (NLMISC::CTime::getLocalTime() > expirationTime)
202 _AuthSessionTicketCallbackTimeout = true;
205 // expired
206 if (_AuthSessionTicketCallbackTimeout)
208 nlwarning("GetAuthSessionTicket callback never called");
209 return false;
212 nlinfo("GetAuthSessionTicket called");
214 // got an error
215 if (_AuthSessionTicketCallbackError)
217 nlwarning("GetAuthSessionTicket callback returned error");
218 return false;
221 return true;
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);
234 private:
235 // ticket handle
236 HAuthTicket _AuthSessionTicketHandle;
238 // buffer of ticket data
239 uint8 _AuthSessionTicketData[1024];
241 // size of buffer
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()
270 release();
273 static void SteamWarningMessageHook(int severity, const char *message)
275 switch(severity)
277 case 1: // warning
278 nlwarning("%s", message);
279 break;
281 case 0: // message
282 nlinfo("%s", message);
283 break;
285 default: // unknown
286 nlwarning("Unknown severity %d: %s", severity, message);
287 break;
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";
301 #else
302 filename = "libsteam_api.so";
303 #endif
305 // try to load library with absolute path
306 _Handle = NLMISC::nlLoadLibrary(Args.getProgramPath() + filename);
308 if (!_Handle)
310 // try to load library with relative path (will search in system paths)
311 _Handle = NLMISC::nlLoadLibrary(filename);
313 if (!_Handle)
315 nlwarning("Unable to load Steam client");
316 return false;
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");
328 return false;
331 // initialize Steam API
332 if (!nlSteamAPI_Init())
334 nlwarning("Unable to initialize Steam client");
335 return false;
338 _Initialized = true;
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();
348 if (!hSteamPipe)
350 nlwarning("Unable to get Steam pipe");
351 return false;
354 // instanciate all used Steam classes
355 s_SteamClient = (ISteamClient*)nlSteamInternal_CreateInterface(STEAMCLIENT_INTERFACE_VERSION);
356 if (!s_SteamClient)
357 return false;
359 s_SteamUser = s_SteamClient->GetISteamUser(hSteamUser, hSteamPipe, STEAMUSER_INTERFACE_VERSION);
360 if (!s_SteamUser)
361 return false;
363 s_SteamApps = s_SteamClient->GetISteamApps(hSteamUser, hSteamPipe, STEAMAPPS_INTERFACE_VERSION);
364 if (!s_SteamApps)
365 return false;
367 s_SteamFriends = s_SteamClient->GetISteamFriends(hSteamUser, hSteamPipe, STEAMFRIENDS_INTERFACE_VERSION);
368 if (!s_SteamFriends)
369 return false;
371 s_SteamUtils = s_SteamClient->GetISteamUtils(hSteamPipe, STEAMUTILS_INTERFACE_VERSION);
372 if (!s_SteamUtils)
373 return false;
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;
407 // save ticket
408 _AuthSessionTicket = listener.getTicket();
410 nldebug("Auth ticket: %s", _AuthSessionTicket.c_str());
412 return true;
415 bool CSteamClient::release()
417 if (!_Handle) return false;
419 if (_Initialized)
421 // only shutdown Steam if initialized
422 nlSteamAPI_Shutdown();
424 _Initialized = false;
427 // free Steam library from memory
428 bool res = NLMISC::nlFreeLibrary(_Handle);
430 _Handle = NULL;
432 return res;
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")
440 return "fr";
441 else if (GameLanguage == "german")
442 return "de";
443 else if (GameLanguage == "spanish")
444 return "es";
445 else if (GameLanguage == "russian")
446 return "ru";
447 else
448 return "en";
452 #endif