// 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 "nel/misc/cmd_args.h"
#include
// prototypes definitions for Steam API functions we'll call
typedef bool (__cdecl *SteamAPI_InitFuncPtr)();
typedef void (__cdecl *SteamAPI_ShutdownFuncPtr)();
typedef ISteamApps* (__cdecl *SteamAppsFuncPtr)();
typedef ISteamClient* (__cdecl *SteamClientFuncPtr)();
typedef ISteamFriends* (__cdecl *SteamFriendsFuncPtr)();
typedef ISteamUser* (__cdecl *SteamUserFuncPtr)();
typedef ISteamUtils* (__cdecl *SteamUtilsFuncPtr)();
typedef void (__cdecl *SteamAPI_RegisterCallbackFuncPtr)(class CCallbackBase *pCallback, int iCallback);
typedef void (__cdecl *SteamAPI_UnregisterCallbackFuncPtr)(class CCallbackBase *pCallback);
typedef void (__cdecl *SteamAPI_RunCallbacksFuncPtr)();
// 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); \
if (nl##symbol == NULL) return false
NL_DECLARE_SYMBOL(SteamAPI_Init);
NL_DECLARE_SYMBOL(SteamAPI_Shutdown);
NL_DECLARE_SYMBOL(SteamApps);
NL_DECLARE_SYMBOL(SteamClient);
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);
// 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;
};
extern NLMISC::CCmdArgs Args;
// 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();
}
static void SteamWarningMessageHook(int severity, const char *message)
{
switch(severity)
{
case 1: // warning
nlwarning("%s", message);
break;
case 0: // message
nlinfo("%s", message);
break;
default: // unknown
nlwarning("Unknown severity %d: %s", severity, message);
break;
}
}
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 with absolute path
_Handle = NLMISC::nlLoadLibrary(Args.getProgramPath() + filename);
if (!_Handle)
{
// try to load library with relative path (will search in system paths)
_Handle = NLMISC::nlLoadLibrary(filename);
if (!_Handle)
{
nlwarning("Unable to load Steam client");
return false;
}
}
// load Steam functions
NL_LOAD_SYMBOL(SteamAPI_Init);
NL_LOAD_SYMBOL(SteamAPI_Shutdown);
// 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 Steam functions
NL_LOAD_SYMBOL(SteamApps);
NL_LOAD_SYMBOL(SteamClient);
NL_LOAD_SYMBOL(SteamFriends);
NL_LOAD_SYMBOL(SteamUser);
NL_LOAD_SYMBOL(SteamUtils);
// set warning messages hook
nlSteamClient()->SetWarningMessageHook(SteamWarningMessageHook);
bool loggedOn = nlSteamUser()->BLoggedOn();
const char *lang = nlSteamApps()->GetCurrentGameLanguage();
nlinfo("Steam AppID: %u", nlSteamUtils()->GetAppID());
nlinfo("Steam login: %s", nlSteamFriends()->GetPersonaName());
nlinfo("Steam user logged: %s", loggedOn ? "yes":"no");
nlinfo("Steam language: %s", lang);
NLMISC::CI18N::setSystemLanguageCode(lang);
// 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