diff --git a/code/ryzom/client/src/steam_client.cpp b/code/ryzom/client/src/steam_client.cpp
new file mode 100644
index 000000000..ac12324bc
--- /dev/null
+++ b/code/ryzom/client/src/steam_client.cpp
@@ -0,0 +1,434 @@
+// 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
+#include
+
+#ifdef DEBUG_NEW
+#define new DEBUG_NEW
+#endif
+
+// prototypes definitions for Steam API functions we'll call
+typedef bool (__cdecl *SteamAPI_InitFuncPtr)();
+typedef void (__cdecl *SteamAPI_ShutdownFuncPtr)();
+typedef HSteamUser (__cdecl *SteamAPI_GetHSteamUserFuncPtr)();
+typedef HSteamPipe (__cdecl *SteamAPI_GetHSteamPipeFuncPtr)();
+typedef void* (__cdecl *SteamInternal_CreateInterfaceFuncPtr)(const char *ver);
+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(SteamAPI_GetHSteamUser);
+NL_DECLARE_SYMBOL(SteamAPI_GetHSteamPipe);
+NL_DECLARE_SYMBOL(SteamInternal_CreateInterface);
+
+NL_DECLARE_SYMBOL(SteamAPI_RegisterCallback);
+NL_DECLARE_SYMBOL(SteamAPI_UnregisterCallback);
+NL_DECLARE_SYMBOL(SteamAPI_RunCallbacks);
+
+// instances of classes
+static ISteamClient *s_SteamClient = NULL;
+static ISteamUser *s_SteamUser = NULL;
+static ISteamApps *s_SteamApps = NULL;
+static ISteamFriends *s_SteamFriends = NULL;
+static ISteamUtils *s_SteamUtils = NULL;
+
+// 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 = s_SteamUser->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 "";
+
+ // convert buffer to hexadecimal string
+ return NLMISC::toHexa(_AuthSessionTicketData, _AuthSessionTicketSize);
+ }
+
+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(SteamAPI_GetHSteamUser);
+ NL_LOAD_SYMBOL(SteamAPI_GetHSteamPipe);
+ NL_LOAD_SYMBOL(SteamInternal_CreateInterface);
+
+ HSteamUser hSteamUser = nlSteamAPI_GetHSteamUser();
+ HSteamPipe hSteamPipe = nlSteamAPI_GetHSteamPipe();
+
+ if (!hSteamPipe)
+ {
+ nlwarning("Unable to get Steam pipe");
+ return false;
+ }
+
+ // instanciate all used Steam classes
+ s_SteamClient = (ISteamClient*)nlSteamInternal_CreateInterface(STEAMCLIENT_INTERFACE_VERSION);
+ if (!s_SteamClient)
+ return false;
+
+ s_SteamUser = s_SteamClient->GetISteamUser(hSteamUser, hSteamPipe, STEAMUSER_INTERFACE_VERSION);
+ if (!s_SteamUser)
+ return false;
+
+ s_SteamApps = s_SteamClient->GetISteamApps(hSteamUser, hSteamPipe, STEAMAPPS_INTERFACE_VERSION);
+ if (!s_SteamApps)
+ return false;
+
+ s_SteamFriends = s_SteamClient->GetISteamFriends(hSteamUser, hSteamPipe, STEAMFRIENDS_INTERFACE_VERSION);
+ if (!s_SteamFriends)
+ return false;
+
+ s_SteamUtils = s_SteamClient->GetISteamUtils(hSteamPipe, STEAMUTILS_INTERFACE_VERSION);
+ if (!s_SteamUtils)
+ return false;
+
+ // set warning messages hook
+ s_SteamClient->SetWarningMessageHook(SteamWarningMessageHook);
+
+ bool loggedOn = s_SteamUser->BLoggedOn();
+
+ nlinfo("Steam AppID: %u", s_SteamUtils->GetAppID());
+ nlinfo("Steam login: %s", s_SteamFriends->GetPersonaName());
+ nlinfo("Steam user logged: %s", loggedOn ? "yes":"no");
+
+ const char *lang = s_SteamApps->GetCurrentGameLanguage();
+
+ if (lang && strlen(lang) > 0)
+ {
+ 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
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