holywyvern/carbuncle

View on GitHub
gems/carbuncle-steamworks/include/isteamnetworkingutils.h

Summary

Maintainability
Test Coverage
//====== Copyright Valve Corporation, All rights reserved. ====================
//
// Purpose: misc networking utilities
//
//=============================================================================

#ifndef ISTEAMNETWORKINGUTILS
#define ISTEAMNETWORKINGUTILS
#pragma once

#include "steamnetworkingtypes.h"
#include "steam_api_common.h"

struct SteamDatagramRelayAuthTicket;
struct SteamRelayNetworkStatus_t;

//-----------------------------------------------------------------------------
/// Misc networking utilities for checking the local networking environment
/// and estimating pings.
class ISteamNetworkingUtils
{
public:
    //
    // Efficient message sending
    //

    /// Allocate and initialize a message object.  Usually the reason
    /// you call this is to pass it to ISteamNetworkingSockets::SendMessages.
    /// The returned object will have all of the relevant fields cleared to zero.
    ///
    /// Optionally you can also request that this system allocate space to
    /// hold the payload itself.  If cbAllocateBuffer is nonzero, the system
    /// will allocate memory to hold a payload of at least cbAllocateBuffer bytes.
    /// m_pData will point to the allocated buffer, m_cbSize will be set to the
    /// size, and m_pfnFreeData will be set to the proper function to free up
    /// the buffer.
    ///
    /// If cbAllocateBuffer=0, then no buffer is allocated.  m_pData will be NULL,
    /// m_cbSize will be zero, and m_pfnFreeData will be NULL.  You will need to
    /// set each of these.
    virtual SteamNetworkingMessage_t *AllocateMessage( int cbAllocateBuffer ) = 0;

    //
    // Access to Steam Datagram Relay (SDR) network
    //

    //
    // Initialization and status check
    //

    /// If you know that you are going to be using the relay network (for example,
    /// because you anticipate making P2P connections), call this to initialize the
    /// relay network.  If you do not call this, the initialization will
    /// be delayed until the first time you use a feature that requires access
    /// to the relay network, which will delay that first access.
    ///
    /// You can also call this to force a retry if the previous attempt has failed.
    /// Performing any action that requires access to the relay network will also
    /// trigger a retry, and so calling this function is never strictly necessary,
    /// but it can be useful to call it a program launch time, if access to the
    /// relay network is anticipated.
    ///
    /// Use GetRelayNetworkStatus or listen for SteamRelayNetworkStatus_t
    /// callbacks to know when initialization has completed.
    /// Typically initialization completes in a few seconds.
    ///
    /// Note: dedicated servers hosted in known data centers do *not* need
    /// to call this, since they do not make routing decisions.  However, if
    /// the dedicated server will be using P2P functionality, it will act as
    /// a "client" and this should be called.
    inline void InitRelayNetworkAccess();

    /// Fetch current status of the relay network.
    ///
    /// SteamRelayNetworkStatus_t is also a callback.  It will be triggered on
    /// both the user and gameserver interfaces any time the status changes, or
    /// ping measurement starts or stops.
    ///
    /// SteamRelayNetworkStatus_t::m_eAvail is returned.  If you want
    /// more details, you can pass a non-NULL value.
    virtual ESteamNetworkingAvailability GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails ) = 0;

    //
    // "Ping location" functions
    //
    // We use the ping times to the valve relays deployed worldwide to
    // generate a "marker" that describes the location of an Internet host.
    // Given two such markers, we can estimate the network latency between
    // two hosts, without sending any packets.  The estimate is based on the
    // optimal route that is found through the Valve network.  If you are
    // using the Valve network to carry the traffic, then this is precisely
    // the ping you want.  If you are not, then the ping time will probably
    // still be a reasonable estimate.
    //
    // This is extremely useful to select peers for matchmaking!
    //
    // The markers can also be converted to a string, so they can be transmitted.
    // We have a separate library you can use on your app's matchmaking/coordinating
    // server to manipulate these objects.  (See steamdatagram_gamecoordinator.h)

    /// Return location info for the current host.  Returns the approximate
    /// age of the data, in seconds, or -1 if no data is available.
    ///
    /// It takes a few seconds to initialize access to the relay network.  If
    /// you call this very soon after calling InitRelayNetworkAccess,
    /// the data may not be available yet.
    ///
    /// This always return the most up-to-date information we have available
    /// right now, even if we are in the middle of re-calculating ping times.
    virtual float GetLocalPingLocation( SteamNetworkPingLocation_t &result ) = 0;

    /// Estimate the round-trip latency between two arbitrary locations, in
    /// milliseconds.  This is a conservative estimate, based on routing through
    /// the relay network.  For most basic relayed connections, this ping time
    /// will be pretty accurate, since it will be based on the route likely to
    /// be actually used.
    ///
    /// If a direct IP route is used (perhaps via NAT traversal), then the route
    /// will be different, and the ping time might be better.  Or it might actually
    /// be a bit worse!  Standard IP routing is frequently suboptimal!
    ///
    /// But even in this case, the estimate obtained using this method is a
    /// reasonable upper bound on the ping time.  (Also it has the advantage
    /// of returning immediately and not sending any packets.)
    ///
    /// In a few cases we might not able to estimate the route.  In this case
    /// a negative value is returned.  k_nSteamNetworkingPing_Failed means
    /// the reason was because of some networking difficulty.  (Failure to
    /// ping, etc)  k_nSteamNetworkingPing_Unknown is returned if we cannot
    /// currently answer the question for some other reason.
    ///
    /// Do you need to be able to do this from a backend/matchmaking server?
    /// You are looking for the "ticketgen" library.
    virtual int EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 ) = 0;

    /// Same as EstimatePingTime, but assumes that one location is the local host.
    /// This is a bit faster, especially if you need to calculate a bunch of
    /// these in a loop to find the fastest one.
    ///
    /// In rare cases this might return a slightly different estimate than combining
    /// GetLocalPingLocation with EstimatePingTimeBetweenTwoLocations.  That's because
    /// this function uses a slightly more complete set of information about what
    /// route would be taken.
    virtual int EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation ) = 0;

    /// Convert a ping location into a text format suitable for sending over the wire.
    /// The format is a compact and human readable.  However, it is subject to change
    /// so please do not parse it yourself.  Your buffer must be at least
    /// k_cchMaxSteamNetworkingPingLocationString bytes.
    virtual void ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize ) = 0;

    /// Parse back SteamNetworkPingLocation_t string.  Returns false if we couldn't understand
    /// the string.
    virtual bool ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result ) = 0;

    /// Check if the ping data of sufficient recency is available, and if
    /// it's too old, start refreshing it.
    ///
    /// Please only call this function when you *really* do need to force an
    /// immediate refresh of the data.  (For example, in response to a specific
    /// user input to refresh this information.)  Don't call it "just in case",
    /// before every connection, etc.  That will cause extra traffic to be sent
    /// for no benefit. The library will automatically refresh the information
    /// as needed.
    ///
    /// Returns true if sufficiently recent data is already available.
    ///
    /// Returns false if sufficiently recent data is not available.  In this
    /// case, ping measurement is initiated, if it is not already active.
    /// (You cannot restart a measurement already in progress.)
    ///
    /// You can use GetRelayNetworkStatus or listen for SteamRelayNetworkStatus_t
    /// to know when ping measurement completes.
    virtual bool CheckPingDataUpToDate( float flMaxAgeSeconds ) = 0;

    //
    // List of Valve data centers, and ping times to them.  This might
    // be useful to you if you are use our hosting, or just need to measure
    // latency to a cloud data center where we are running relays.
    //

    /// Fetch ping time of best available relayed route from this host to
    /// the specified data center.
    virtual int GetPingToDataCenter( SteamNetworkingPOPID popID, SteamNetworkingPOPID *pViaRelayPoP ) = 0;

    /// Get *direct* ping time to the relays at the data center.
    virtual int GetDirectPingToPOP( SteamNetworkingPOPID popID ) = 0;

    /// Get number of network points of presence in the config
    virtual int GetPOPCount() = 0;

    /// Get list of all POP IDs.  Returns the number of entries that were filled into
    /// your list.
    virtual int GetPOPList( SteamNetworkingPOPID *list, int nListSz ) = 0;

    //
    // Misc
    //

    /// Fetch current timestamp.  This timer has the following properties:
    ///
    /// - Monotonicity is guaranteed.
    /// - The initial value will be at least 24*3600*30*1e6, i.e. about
    ///   30 days worth of microseconds.  In this way, the timestamp value of
    ///   0 will always be at least "30 days ago".  Also, negative numbers
    ///   will never be returned.
    /// - Wraparound / overflow is not a practical concern.
    ///
    /// If you are running under the debugger and stop the process, the clock
    /// might not advance the full wall clock time that has elapsed between
    /// calls.  If the process is not blocked from normal operation, the
    /// timestamp values will track wall clock time, even if you don't call
    /// the function frequently.
    ///
    /// The value is only meaningful for this run of the process.  Don't compare
    /// it to values obtained on another computer, or other runs of the same process.
    virtual SteamNetworkingMicroseconds GetLocalTimestamp() = 0;

    /// Set a function to receive network-related information that is useful for debugging.
    /// This can be very useful during development, but it can also be useful for troubleshooting
    /// problems with tech savvy end users.  If you have a console or other log that customers
    /// can examine, these log messages can often be helpful to troubleshoot network issues.
    /// (Especially any warning/error messages.)
    ///
    /// The detail level indicates what message to invoke your callback on.  Lower numeric
    /// value means more important, and the value you pass is the lowest priority (highest
    /// numeric value) you wish to receive callbacks for.
    ///
    /// The value here controls the detail level for most messages.  You can control the
    /// detail level for various subsystems (perhaps only for certain connections) by
    /// adjusting the configuration values k_ESteamNetworkingConfig_LogLevel_Xxxxx.
    ///
    /// Except when debugging, you should only use k_ESteamNetworkingSocketsDebugOutputType_Msg
    /// or k_ESteamNetworkingSocketsDebugOutputType_Warning.  For best performance, do NOT
    /// request a high detail level and then filter out messages in your callback.  This incurs
    /// all of the expense of formatting the messages, which are then discarded.  Setting a high
    /// priority value (low numeric value) here allows the library to avoid doing this work.
    ///
    /// IMPORTANT: This may be called from a service thread, while we own a mutex, etc.
    /// Your output function must be threadsafe and fast!  Do not make any other
    /// Steamworks calls from within the handler.
    virtual void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ) = 0;

    //
    // Set and get configuration values, see ESteamNetworkingConfigValue for individual descriptions.
    //

    // Shortcuts for common cases.  (Implemented as inline functions below)
    bool SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val );
    bool SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val );
    bool SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val );
    bool SetGlobalConfigValuePtr( ESteamNetworkingConfigValue eValue, void *val );
    bool SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val );
    bool SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val );
    bool SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val );

    //
    // Set global callbacks.  If you do not want to use Steam's callback dispatch mechanism and you
    // want to use the same callback on all (or most) listen sockets and connections, then
    // simply install these callbacks first thing, and you are good to go.
    // See ISteamNetworkingSockets::RunCallbacks
    //
    bool SetGlobalCallback_SteamNetConnectionStatusChanged( FnSteamNetConnectionStatusChanged fnCallback );
    bool SetGlobalCallback_SteamNetAuthenticationStatusChanged( FnSteamNetAuthenticationStatusChanged fnCallback );
    bool SetGlobalCallback_SteamRelayNetworkStatusChanged( FnSteamRelayNetworkStatusChanged fnCallback );
    bool SetGlobalCallback_MessagesSessionRequest( FnSteamNetworkingMessagesSessionRequest fnCallback );
    bool SetGlobalCallback_MessagesSessionFailed( FnSteamNetworkingMessagesSessionFailed fnCallback );

    /// Set a configuration value.
    /// - eValue: which value is being set
    /// - eScope: Onto what type of object are you applying the setting?
    /// - scopeArg: Which object you want to change?  (Ignored for global scope).  E.g. connection handle, listen socket handle, interface pointer, etc.
    /// - eDataType: What type of data is in the buffer at pValue?  This must match the type of the variable exactly!
    /// - pArg: Value to set it to.  You can pass NULL to remove a non-global setting at this scope,
    ///   causing the value for that object to use global defaults.  Or at global scope, passing NULL
    ///   will reset any custom value and restore it to the system default.
    ///   NOTE: When setting pointers (e.g. callback functions), do not pass the function pointer directly.
    ///   Your argument should be a pointer to a function pointer.
    virtual bool SetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj,
        ESteamNetworkingConfigDataType eDataType, const void *pArg ) = 0;

    /// Set a configuration value, using a struct to pass the value.
    /// (This is just a convenience shortcut; see below for the implementation and
    /// a little insight into how SteamNetworkingConfigValue_t is used when
    /// setting config options during listen socket and connection creation.)
    bool SetConfigValueStruct( const SteamNetworkingConfigValue_t &opt, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj );

    /// Get a configuration value.
    /// - eValue: which value to fetch
    /// - eScopeType: query setting on what type of object
    /// - eScopeArg: the object to query the setting for
    /// - pOutDataType: If non-NULL, the data type of the value is returned.
    /// - pResult: Where to put the result.  Pass NULL to query the required buffer size.  (k_ESteamNetworkingGetConfigValue_BufferTooSmall will be returned.)
    /// - cbResult: IN: the size of your buffer.  OUT: the number of bytes filled in or required.
    virtual ESteamNetworkingGetConfigValueResult GetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj,
        ESteamNetworkingConfigDataType *pOutDataType, void *pResult, size_t *cbResult ) = 0;

    /// Returns info about a configuration value.  Returns false if the value does not exist.
    /// pOutNextValue can be used to iterate through all of the known configuration values.
    /// (Use GetFirstConfigValue() to begin the iteration, will be k_ESteamNetworkingConfig_Invalid on the last value)
    /// Any of the output parameters can be NULL if you do not need that information.
    ///
    /// See k_ESteamNetworkingConfig_EnumerateDevVars for some more info about "dev" variables,
    /// which are usually excluded from the set of variables enumerated using this function.
    virtual bool GetConfigValueInfo( ESteamNetworkingConfigValue eValue, const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue ) = 0;

    /// Return the lowest numbered configuration value available in the current environment.
    virtual ESteamNetworkingConfigValue GetFirstConfigValue() = 0;

    // String conversions.  You'll usually access these using the respective
    // inline methods.
    virtual void SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort ) = 0;
    virtual bool SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr ) = 0;
    virtual void SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf ) = 0;
    virtual bool SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr ) = 0;

protected:
    ~ISteamNetworkingUtils(); // Silence some warnings
};
#define STEAMNETWORKINGUTILS_INTERFACE_VERSION "SteamNetworkingUtils003"

// Global accessors
// Using standalone lib
#ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB

    // Standalone lib
    static_assert( STEAMNETWORKINGUTILS_INTERFACE_VERSION[22] == '3', "Version mismatch" );
    STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingUtils *SteamNetworkingUtils_LibV3();
    inline ISteamNetworkingUtils *SteamNetworkingUtils_Lib() { return SteamNetworkingUtils_LibV3(); }

    #ifndef STEAMNETWORKINGSOCKETS_STEAMAPI
        inline ISteamNetworkingUtils *SteamNetworkingUtils() { return SteamNetworkingUtils_LibV3(); }
    #endif
#endif

// Using Steamworks SDK
#ifdef STEAMNETWORKINGSOCKETS_STEAMAPI
    STEAM_DEFINE_INTERFACE_ACCESSOR( ISteamNetworkingUtils *, SteamNetworkingUtils_SteamAPI,
        /* Prefer user version of the interface.  But if it isn't found, then use
        gameserver one.  Yes, this is a completely terrible hack */
        SteamInternal_FindOrCreateUserInterface( 0, STEAMNETWORKINGUTILS_INTERFACE_VERSION ) ?
        SteamInternal_FindOrCreateUserInterface( 0, STEAMNETWORKINGUTILS_INTERFACE_VERSION ) :
        SteamInternal_FindOrCreateGameServerInterface( 0, STEAMNETWORKINGUTILS_INTERFACE_VERSION ),
        "global",
        STEAMNETWORKINGUTILS_INTERFACE_VERSION
    )

    #ifndef STEAMNETWORKINGSOCKETS_STANDALONELIB
        inline ISteamNetworkingUtils *SteamNetworkingUtils() { return SteamNetworkingUtils_SteamAPI(); }
    #endif
#endif

/// A struct used to describe our readiness to use the relay network.
/// To do this we first need to fetch the network configuration,
/// which describes what POPs are available.
struct SteamRelayNetworkStatus_t
{ 
    enum { k_iCallback = k_iSteamNetworkingUtilsCallbacks + 1 };

    /// Summary status.  When this is "current", initialization has
    /// completed.  Anything else means you are not ready yet, or
    /// there is a significant problem.
    ESteamNetworkingAvailability m_eAvail;

    /// Nonzero if latency measurement is in progress (or pending,
    /// awaiting a prerequisite).
    int m_bPingMeasurementInProgress;

    /// Status obtaining the network config.  This is a prerequisite
    /// for relay network access.
    ///
    /// Failure to obtain the network config almost always indicates
    /// a problem with the local internet connection.
    ESteamNetworkingAvailability m_eAvailNetworkConfig;

    /// Current ability to communicate with ANY relay.  Note that
    /// the complete failure to communicate with any relays almost
    /// always indicates a problem with the local Internet connection.
    /// (However, just because you can reach a single relay doesn't
    /// mean that the local connection is in perfect health.)
    ESteamNetworkingAvailability m_eAvailAnyRelay;

    /// Non-localized English language status.  For diagnostic/debugging
    /// purposes only.
    char m_debugMsg[ 256 ];
};

/// Utility class for printing a SteamNetworkingIdentity.
/// E.g. printf( "Identity is '%s'\n", SteamNetworkingIdentityRender( identity ).c_str() );
struct SteamNetworkingIdentityRender
{
    SteamNetworkingIdentityRender( const SteamNetworkingIdentity &x ) { x.ToString( buf, sizeof(buf) ); }
    inline const char *c_str() const { return buf; }
private:
    char buf[ SteamNetworkingIdentity::k_cchMaxString ];
};

/// Utility class for printing a SteamNetworkingIPAddrRender.
struct SteamNetworkingIPAddrRender
{
    SteamNetworkingIPAddrRender( const SteamNetworkingIPAddr &x, bool bWithPort = true ) { x.ToString( buf, sizeof(buf), bWithPort ); }
    inline const char *c_str() const { return buf; }
private:
    char buf[ SteamNetworkingIPAddr::k_cchMaxString ];
};

///////////////////////////////////////////////////////////////////////////////
//
// Internal stuff

inline void ISteamNetworkingUtils::InitRelayNetworkAccess() { CheckPingDataUpToDate( 1e10f ); }
inline bool ISteamNetworkingUtils::SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Int32, &val ); }
inline bool ISteamNetworkingUtils::SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Float, &val ); }
inline bool ISteamNetworkingUtils::SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_String, val ); }
inline bool ISteamNetworkingUtils::SetGlobalConfigValuePtr( ESteamNetworkingConfigValue eValue, void *val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Global, 0, k_ESteamNetworkingConfig_Ptr, &val ); } // Note: passing pointer to pointer.
inline bool ISteamNetworkingUtils::SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Connection, hConn, k_ESteamNetworkingConfig_Int32, &val ); }
inline bool ISteamNetworkingUtils::SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Connection, hConn, k_ESteamNetworkingConfig_Float, &val ); }
inline bool ISteamNetworkingUtils::SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val ) { return SetConfigValue( eValue, k_ESteamNetworkingConfig_Connection, hConn, k_ESteamNetworkingConfig_String, val ); }
inline bool ISteamNetworkingUtils::SetGlobalCallback_SteamNetConnectionStatusChanged( FnSteamNetConnectionStatusChanged fnCallback ) { return SetGlobalConfigValuePtr( k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged, (void*)fnCallback ); }
inline bool ISteamNetworkingUtils::SetGlobalCallback_SteamNetAuthenticationStatusChanged( FnSteamNetAuthenticationStatusChanged fnCallback ) { return SetGlobalConfigValuePtr( k_ESteamNetworkingConfig_Callback_AuthStatusChanged, (void*)fnCallback ); }
inline bool ISteamNetworkingUtils::SetGlobalCallback_SteamRelayNetworkStatusChanged( FnSteamRelayNetworkStatusChanged fnCallback ) { return SetGlobalConfigValuePtr( k_ESteamNetworkingConfig_Callback_RelayNetworkStatusChanged, (void*)fnCallback ); }
inline bool ISteamNetworkingUtils::SetGlobalCallback_MessagesSessionRequest( FnSteamNetworkingMessagesSessionRequest fnCallback ) { return SetGlobalConfigValuePtr( k_ESteamNetworkingConfig_Callback_MessagesSessionRequest, (void*)fnCallback ); }
inline bool ISteamNetworkingUtils::SetGlobalCallback_MessagesSessionFailed( FnSteamNetworkingMessagesSessionFailed fnCallback ) { return SetGlobalConfigValuePtr( k_ESteamNetworkingConfig_Callback_MessagesSessionFailed, (void*)fnCallback ); }

inline bool ISteamNetworkingUtils::SetConfigValueStruct( const SteamNetworkingConfigValue_t &opt, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj )
{
    // Locate the argument.  Strings are a special case, since the
    // "value" (the whole string buffer) doesn't fit in the struct
    // NOTE: for pointer values, we pass a pointer to the pointer,
    // we do not pass the pointer directly.
    const void *pVal = ( opt.m_eDataType == k_ESteamNetworkingConfig_String ) ? (const void *)opt.m_val.m_string : (const void *)&opt.m_val;
    return SetConfigValue( opt.m_eValue, eScopeType, scopeObj, opt.m_eDataType, pVal );
}

// How to get helper functions.
#if defined( STEAMNETWORKINGSOCKETS_STATIC_LINK ) || defined( STEAMNETWORKINGSOCKETS_STANDALONELIB )

    // Call direct to static functions
    STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr *pAddr, char *buf, size_t cbBuf, bool bWithPort );
    STEAMNETWORKINGSOCKETS_INTERFACE bool SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr );
    STEAMNETWORKINGSOCKETS_INTERFACE void SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity *pIdentity, char *buf, size_t cbBuf );
    STEAMNETWORKINGSOCKETS_INTERFACE bool SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, size_t sizeofIdentity, const char *pszStr );
    inline void SteamNetworkingIPAddr::ToString( char *buf, size_t cbBuf, bool bWithPort ) const { SteamNetworkingIPAddr_ToString( this, buf, cbBuf, bWithPort ); }
    inline bool SteamNetworkingIPAddr::ParseString( const char *pszStr ) { return SteamNetworkingIPAddr_ParseString( this, pszStr ); }
    inline void SteamNetworkingIdentity::ToString( char *buf, size_t cbBuf ) const { SteamNetworkingIdentity_ToString( this, buf, cbBuf ); }
    inline bool SteamNetworkingIdentity::ParseString( const char *pszStr ) { return SteamNetworkingIdentity_ParseString( this, sizeof(*this), pszStr ); }

#elif defined( STEAMNETWORKINGSOCKETS_STEAMAPI )
    // Using steamworks SDK - go through SteamNetworkingUtils()
    inline void SteamNetworkingIPAddr::ToString( char *buf, size_t cbBuf, bool bWithPort ) const { SteamNetworkingUtils()->SteamNetworkingIPAddr_ToString( *this, buf, cbBuf, bWithPort ); }
    inline bool SteamNetworkingIPAddr::ParseString( const char *pszStr ) { return SteamNetworkingUtils()->SteamNetworkingIPAddr_ParseString( this, pszStr ); }
    inline void SteamNetworkingIdentity::ToString( char *buf, size_t cbBuf ) const { SteamNetworkingUtils()->SteamNetworkingIdentity_ToString( *this, buf, cbBuf ); }
    inline bool SteamNetworkingIdentity::ParseString( const char *pszStr ) { return SteamNetworkingUtils()->SteamNetworkingIdentity_ParseString( this, pszStr ); }
#else
    #error "Invalid config"
#endif

#endif // ISTEAMNETWORKINGUTILS