Dedicated Server Guide Steam

1. Current Progress () UE4.18.2 This page is a Work In Progress and does not yet work fully Latest work is on getting registered correctly on Steam as a Tool sub-app of our game. Got it working as ...

Updated over 4 years ago Edit Page Revisions

1. Current Progress

()

UE4.18.2

This page is a Work In Progress and does not yet work fully

Latest work is on getting registered correctly on Steam as a Tool sub-app of our game.

Got it working as LAN! 3/5/2018

1.5 If You like videos

The video is a complete tutorial on how to do Steam Dedicated Servers with the Advanced Networking Plugin. I'm getting that working for me and then will update this page.

2. Preamble

Here are notes on what makes dedicated servers work with Steam. If you try to short cut any of this process it simply won't work. It is not a matter of just flipping a switch in your game, you have to get steam configured correctly too.

What is a dedicated server?

There are two aspects to a dedicated server that make the term confusing. These are actually two different things that have nothing to do with each other, but are combined in one in the Unreal Engine.

  • A dedicated server has no graphics. Instead is has a simple text command window. The dedicated server does load the game world, enforce rules, and do physics. It just never draws anything. This is also called 'headless', 'nogui', and 'console mode'.
  • A dedicated server has no local player. The server is either run by the company like PUBG, or run by a player so their friends can play when they are not available. The server is running the game and if there are no players it just waits in the lobby (like PUBG) or just idles the game (like World of Warcraft). (What do the NPCs in WoW do when no one is in the game?)

Tool Revisions

  • UE 4.19.1
  • Visual Studio 2015 Community Edition 14.0.25431.01

3. Do This First

Firstly do this Dedicated Server Guide to get dedicated server working without Steam. Test it and make sure it works. This includes getting UE4 built from source and such.

4. Steam Side Setup

Before any of this can work you must get set up with Steam as a dedicated server. See Using_Steamworks_with_UE4 Read these pages and very carefully do what they say

Notes on the Distributing Your Dedicated Game Server page:

  • In Creating 1 Create a new App ID of type TOOL... This is now in

  • If you go into Steam, Library you see your favorite games listed, and the word GAMES at the top right. That is a menu, and if you select TOOLS you will see all the game tools and your dedicated server package will be there if configured and published correctly.

And understand the following:

What UE4's Steam Plugin does is either:

  • Create a Steam Lobby if a player is making a shared game for friends. That is when you click on Host Game.

  • Create a Steam Session to a dedicated server if you are going to join a server.

  • If the game is using Steam Lobby and allows Join in Progress then the Steam Lobby is always up, otherwise the Steam Lobby is destroyed once all players have left and are into the game together.

  • Steam Networking can be used without any Steam Lobby or Dedicated Server existing at all. It just sends packets between Steam UserID processes. Steam Lobby is just a kind of chat service to find other UserIDs. A dedicated server is simply a running instance of your game with no graphics and no local player. The dedicated server does had PhysX, AI, The game world of Actors and such. Game state is communicated over Steam Networking. Why? Because most players machines are behind some ISP's firewall, such as Comcast. Steam Networking takes care of packet transfer, which is very difficult to get right.

Steam Game Publishing Notes

Steam does not let just anyone publish games. You have to get registered with them and get issued a Steam ID. The process for this has changed over time. Now it is like this

When you then build your game you use the Steam SDK ( Software Development Kit here ) to compress and upload your game to Steam. Such a bundled game is called a Depot. For dedicated server you make the server be a separate bundle as a Tool of your game.

5. Common Issues and Details Checklist

  1. You must setup the DefaultEngine.ini to have steam enabled and such.
  2. bUsesPresence flag is very important in both the DefaultEngine.ini and the C++ code for the session. For dedicated servers it must be false. This is because presence is for Steam Lobby creating. Dedicated server has no local user logged into steam so Steam Lobby is not used.
  3. A dedicated server does not have a steam user local and has no local player so you have to write a blueprint node in C++ to CreateCustomSession and CreateCustomSessionDedicated. They are not much different but it makes for easier debugging. Code is below. This is also a good idea so you can have custom server settings so the dedicated version looks for the settings.
  4. A big error that cost lots of time is that the line
    SessionSettings->bIsDedicated = true;
    
    was missing.
  5. Similarly,
    SessionSettings->bAntiCheatProtected
    
    must match what is in the DefaultEngine.ini
  6. Must have the word "listen" in the OpenLevel after the CreateCustomSessionDedicated in blueprints.
  7. Some example code on the net has the DefaultEngine.ini with GameServerQueryPort=27015 and that will fail. It MUST be GameServerQueryPort=27016 (Correction, both 27015 ans 27016 appear to work)
  8. In OnlineSessionAsyncServerSteam.cpp setup the
    STEAMPRODUCTNAME, STEAMGAMEDIR, STEAMGAMEDESC
    
    Note that the STEAMGAMEDIR has is case sensitive. The game "Unfortunate Spacemen" has STEAMGAMEDIR as "unfortunatespacemen" to get found on the global server list.
  9. The name of your game in the Steam server browser is set with Session->OwningUserName = FString("My cool game"); in FOnlineAsyncTaskSteamCreateServer::Finalize
  10. In FindSessionsCallbackProxy.cpp about line 46 comment out SearchObject->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); so dedicated sessions can be found.
  11. In OnlineSubsystemSteam.cpp put your Steam AppID in place of 0 for the int RelaunchAppId
  12. Make sure your DefaultEngine.ini matches for SteamDevAppId, GameServerQueryPort=27016, bRelaunchInSteam, GameVersion=YourGameVersionWhateverItIs
  13. In Under the Application tab Dedicated Servers setup the info to exactly match the above STEAMPRODUCTNAME etc. and Save then publish it. Now you can have dedicated servers.
  14. You need steam_api64.dll, steamclient64.dll, tier0_s64.dll, and vstdlib_s64.dll. These are over in the Steam application directory somewhere.
  15. You do not need to make a steam_appid.txt file. UE4 does that when it runs and removes it when done running.
  16. Our application all worked with the client game starting the server, but then when we did dedicated server the server appeared to start ok according to the debug statements, but was then not visible. This was because the client server code was also trying to start the session, failing, and then the session was in a failed state. Once we got the blueprints fixed then the dedicated session worked fine.
  17. TODO - How to package dedicated servers without textures and music so the package is smaller?
  18. The kev/value pairs used to find servers such as my map or by game type are in total limited to 128 characters. (wow) So you must search on the first keys and then filter on the rest after getting the results back. If you add logging statements to the plugin you might see something like...
LogOnline: Verbose: STEAM: SetGameTags(BUILDID:1990940918,OWNINGID:90113571688002570,OWNINGNAME:90113571688002570,P2PADDR:90113571688002570,P2PPORT:7777)

which is nearly too long. Adding any more key/values results in something like

Warning: STEAM: Server setting ,SESSIONFLAGS:651 overflows Steam SetGameTags call

6. Game Level Setup

Once this is set up you will need a level in your game like ServerStart as the startup level for the server. In it you start the dedicated server session, then load the map for your lobby. Now the server idles in the lobby until some players show up. Once enough players are there you start a countdown and travel from the lobby to the actual play level. You might have some countdown, "Game starts in 10 seconds..." style thing. Or maybe the players vote to start, or all click ready then start. Something like that. In the games started by a player (not dedicated server) you would have a main menu where they say "create a game" and that calls CreateCustomSession and then travels to the lobby level.

7. Bugs?

One notable issue is that on startup a popup can come up that says

---------------------------
MyGameServer.exe - Entry Point Not Found
---------------------------
The procedure entry point Plat_CommandLineParamExists could not be located in the dynamic link library 
K:\Apps\Steam\steamclient64.dll. 
---------------------------
OK   
---------------------------

This warning can be safely ignored.

8. C++ Code and Config

We also have a library that is a Steam bridge with some functions to make life with Steam easier. Much of what is in our Steam Bridge has been superseded by the latest blueprints from Epic for session management.

Code important bits:

DefaultEngine.ini

[OnlineSubsystem]
DefaultPlatformService=Steam
PollingIntervalInMs=20
bHasVoiceEnabled=true
bUsesPresence=false

[OnlineSubsystemSteam]
bEnabled=true
; Your actual app id goes here.
SteamDevAppId=480
GameServerQueryPort=27015
bRelaunchInSteam=false
GameVersion=1.0.0.0
bVACEnabled=1
bAllowP2PPacketRelay=true
bUsesPresence=false
P2PConnectionTimeout=90

[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

CreateCustomSession.cpp

UE 4.18.2 code

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
// Adapted by Richard Keene, Sandswept Studios LLC 2016
// Feel free to use this any way you want.  No liability implied.
#include "CustomCreateSession.h"
#include "MyGame.h"
#include "SteamBridge.h"
#include "OnlineSubsystem.h"
#include "Net/OnlineEngineInterface.h"
#include "CustomOnlineSubsystemBPCallHelper.h"

bool UCustomCreateSession::bUseOnlinePresence = true;

// Nice helper function.  I wish this was in the core engine.
const TCHAR * UCustomCreateSession::boolText(bool b)
{
   return b ? TEXT("TRUE") : TEXT("FALSE");
}

UCustomCreateSession::UCustomCreateSession(const FObjectInitializer& ObjectInitializer)
   : Super(ObjectInitializer)
   , CreateCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateCompleted))
   , StartCompleteDelegate(FOnStartSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnStartCompleted))
   , NumPublicConnections(1)
{
   bUseOnlinePresence = !FPlatformProperties::IsServerOnly();
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (102)"), boolText(bUseOnlinePresence));
   UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession: creator"));
}

UCustomCreateSession* UCustomCreateSession::CreateCustomSession(UObject* WorldContextObject, class APlayerController* 
  PlayerController, int32 PublicConnections, bool bUseLAN, FString password, FString lobbyName, FString versionNumber)
{
   FName gs = NAME_GameSession;
   UE_LOG(DSS_STEAM, Log, TEXT("CreateCustomSession: %s internal game name (not lobby name) is %s"), *lobbyName, *gs.ToString());
   bUseOnlinePresence = !FPlatformProperties::IsServerOnly();
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (103)"), boolText(bUseOnlinePresence));
   UCustomCreateSession* Proxy = NewObject``();
   Proxy->PlayerControllerWeakPtr = PlayerController;
   Proxy->NumPublicConnections = PublicConnections;
   Proxy->bUseLAN = bUseLAN;
   Proxy->WorldContextObject = WorldContextObject;

   // DSS Custom code.  R Keene
   Proxy->DSS_password = password;
   Proxy->DSS_lobbyName = lobbyName;
   Proxy->DSS_versionNumber = versionNumber;

   return Proxy;
}

// This is the format of the call to the blueprint node (box).  This takes the inputs and you can save them for later use.
// See Activate for where the actual computation happens.
UCustomCreateSession* UCustomCreateSession::CreateCustomSessionDedicated(UObject* WorldContextObject, FString versionNumber)
{
   int32 PublicConnections = 0;
   if (!GConfig->GetInt(TEXT("DedicatedServer"), TEXT("MaxPlayers"), PublicConnections, GEngineIni))
   {
       UE_LOG(DSS_STEAM, Fatal, TEXT("Missing MaxPlayers key in DedicatedServer of DefaultEngine.ini"));
   }
   bool bUseLAN = false;
   if (!GConfig->GetBool(TEXT("DedicatedServer"), TEXT("UseLAN"), bUseLAN, GEngineIni))
   {
       UE_LOG(DSS_STEAM, Fatal, TEXT("Missing UseLAN key in DedicatedServer of DefaultEngine.ini"));
   }
   FString password = "";
   if (!GConfig->GetString(TEXT("DedicatedServer"), TEXT("Password"), password, GEngineIni))
   {
       UE_LOG(DSS_STEAM, Fatal, TEXT("Missing Password key in DedicatedServer of DefaultEngine.ini (use \"\" for no password)"));
   }
   FString lobbyName = "";
   if (!GConfig->GetString(TEXT("DedicatedServer"), TEXT("LobbyName"), lobbyName, GEngineIni))
   {
       UE_LOG(DSS_STEAM, Fatal, TEXT("Missing LobbyName key in DedicatedServer of DefaultEngine.ini"));
   }

   FName gs = NAME_GameSession;
   UE_LOG(DSS_STEAM, Log, TEXT("CreateCustomSessionDedicated: lobby name %s : internal game name (not lobby name) is % s"), *lobbyName, *gs.ToString());
   bUseOnlinePresence = !FPlatformProperties::IsServerOnly();
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (112)"), boolText(bUseOnlinePresence));

   UCustomCreateSession* Proxy = NewObject``();
   Proxy->NumPublicConnections = PublicConnections;
   Proxy->bUseLAN = bUseLAN;
   Proxy->WorldContextObject = WorldContextObject;

   // DSS Custom code.  R Keene
   Proxy->DSS_password = password;
   Proxy->DSS_lobbyName = lobbyName;
   Proxy->DSS_versionNumber = versionNumber;

   FName gn = FName(NAME_GameSession);
   USteamBridge::IsSteamInitalized(WorldContextObject, FString("UCustomCreateSession::CreateCustomSessionDedicated"));

   return Proxy;
}

// After the above call to CreateCustomSessionDedicated this gets called to actually do the computation or whatever action.
void UCustomCreateSession::Activate()
{
   UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate"));

   OnlineSub = Online::GetSubsystem(GetWorld());
   if (OnlineSub != NULL) {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate - valid online system"));
   }
   else {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate - invalid online system"));
       return;
   }

   ses = OnlineSub->GetSessionInterface();
   if (ses.IsValid()) {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate - valid session interface"));
   }
   else {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate - invalid session interface"));
       return;
   }

   UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Session is valid"));
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (105)"), boolText(bUseOnlinePresence));
   UE_LOG(DSS_STEAM, Log, TEXT("Is Dedicated Server %s"), boolText(FPlatformProperties::IsServerOnly()));

   CreateCompleteDelegateHandle = ses->AddOnCreateSessionCompleteDelegate_Handle(CreateCompleteDelegate);
   SessionSettings = MakeShareable(new FOnlineSessionSettings());
   SessionSettings->NumPublicConnections = NumPublicConnections;
   SessionSettings->bShouldAdvertise = true;
   SessionSettings->bAllowJoinInProgress = true;
   SessionSettings->bIsLANMatch = bUseLAN;
   SessionSettings->bIsDedicated = FPlatformProperties::IsServerOnly();
   SessionSettings->bUsesPresence = bUseOnlinePresence;
   SessionSettings->bAllowJoinViaPresence = true;
   SessionSettings->Set("password", DSS_password, EOnlineDataAdvertisementType::ViaOnlineService);
   SessionSettings->Set("lobbyName", DSS_lobbyName, EOnlineDataAdvertisementType::ViaOnlineService);
   SessionSettings->Set("selectedMap", FString("Echo"), EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("currentStatus", FString("ok"), EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("currentBots", FString("5"), EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("maxPlayers", FString("32"), EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("currentPlayers", FString("1"), EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("motd", FString("Hello World"), EOnlineDataAdvertisementType::ViaOnlineService);
   SessionSettings->Set("versionNumber", DSS_versionNumber, EOnlineDataAdvertisementType::ViaOnlineService);
   // SessionSettings->Set("gametype", FString("Normal"), EOnlineDataAdvertisementType::ViaOnlineService);

   UE_LOG(DSS_STEAM, Log, TEXT("Session: passwd %s lobbyName %s selectedMap %s versionNumber %s"), *DSS_password, *DSS_lobbyName, TEXT("Echo"), *DSS_versionNumber);
   UE_LOG(DSS_STEAM, Log, TEXT("Session: max conn %d advert %s LAN %s Dedicated %s Presence %s JoinViePres %s AllowJoinInProgress %s"), 
       NumPublicConnections, boolText(SessionSettings->bShouldAdvertise), boolText(SessionSettings->bIsLANMatch), boolText(SessionSettings->bIsDedicated),
       boolText(SessionSettings->bUsesPresence), boolText(SessionSettings->bAllowJoinViaPresence), boolText(SessionSettings->bAllowJoinInProgress));

   if (ses->CreateSession(0, GameSessionName, *SessionSettings)) {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate, CreateSession ok"));
       UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence (session settings) %s (122)"), (ses->GetSessionSettings(GameSessionName)->bUsesPresence ? TEXT("TRUE") : TEXT("FALSE")));
   }
   else {
       UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate, CreateSession failed"));
   }

   // OnCreateCompleted will get called, nothing more to do now
   UE_LOG(DSS_STEAM, Log, TEXT("UCustomCreateSession:Activate, started..."));
}

// This gets called when the session create actualy has happened.
void UCustomCreateSession::OnCreateCompleted(FName SessionName, bool bWasSuccessful)
{
   UE_LOG(DSS_STEAM, Log, TEXT("OnCreateCompleted:Create Completed"));
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (107)"), (bUseOnlinePresence ? TEXT("TRUE") : TEXT("FALSE")));
   if (ses.IsValid())
   {
       UE_LOG(DSS_STEAM, Log, TEXT("OnCreateCompleted:Session is valid"));
       UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence (session settings) %s (120)"), (ses->GetSessionSettings(GameSessionName)->bUsesPresence ? TEXT("TRUE") : TEXT("FALSE")));
       ses->ClearOnCreateSessionCompleteDelegate_Handle(CreateCompleteDelegateHandle);

       if (bWasSuccessful)
       {
           UE_LOG(DSS_STEAM, Log, TEXT("OnCreateCompleted:Activate ws successful"));
           StartCompleteDelegateHandle = ses->AddOnStartSessionCompleteDelegate_Handle(StartCompleteDelegate);
           ses->StartSession(GameSessionName);

           // OnStartCompleted will get called, nothing more to do now
           UE_LOG(DSS_STEAM, Log, TEXT("OnCreateCompleted:Success"));
           return;
       }
   }

   if (!bWasSuccessful)
   {
       UE_LOG(DSS_STEAM, Log, TEXT("OnCreateCompleted:Failed (2)"));
       OnFailure.Broadcast();
   }
}

// This gets called when the session has started.
void UCustomCreateSession::OnStartCompleted(FName SessionName, bool bWasSuccessful)
{
   UE_LOG(DSS_STEAM, Log, TEXT("OnStartCompleted: Session Name %s"), *SessionName.ToString());
   UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence %s (109)"), (bUseOnlinePresence ? TEXT("TRUE") : TEXT("FALSE")));
   if (ses.IsValid())
   {
       UE_LOG(DSS_STEAM, Log, TEXT("OnStartCompleted: Session is valid"));
       UE_LOG(DSS_STEAM, Log, TEXT("Uses Online Presence (session settings) %s (121)"), (ses->GetSessionSettings(GameSessionName)->bUsesPresence ? TEXT("TRUE") : TEXT("FALSE")));

       ses->ClearOnStartSessionCompleteDelegate_Handle(StartCompleteDelegateHandle);
   }

   if (bWasSuccessful)
   {
       UE_LOG(DSS_STEAM, Log, TEXT("OnStartCompleted: Success"));
       OnSuccess.Broadcast();
   }
   else
   {
       UE_LOG(DSS_STEAM, Log, TEXT("OnStartCompleted: Fail"));
       OnFailure.Broadcast();
   }
}

CreateCustomSession.h

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
// Adapted by Richard Keene, Sandswept Studios LLC 2016
// Feel free to use this any way you want.  No liability implied.
#pragma once

#include "MyGame.h"
#include "OnlineBlueprintCallProxyBase.h"
#include "OnlineSessionInterface.h"
#include "OnlineSubsystemUtils.h"
#include "CustomCreateSession.generated.h"

UCLASS(MinimalAPI)
class UCustomCreateSession : public UOnlineBlueprintCallProxyBase
{
   GENERATED_UCLASS_BODY()
 
   static const TCHAR * boolText(bool b);
 
   // Called when the session was created successfully
   UPROPERTY(BlueprintAssignable)
   FEmptyOnlineDelegate OnSuccess;
 
   // Called when there was an error creating the session
   UPROPERTY(BlueprintAssignable)
       FEmptyOnlineDelegate OnFailure;
 
   // Creates a session with the default online subsystem
   // DSS R Keene
   UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject"), Category = "Online|Session")
       static UCustomCreateSession* CreateCustomSession(UObject* WorldContextObject, class APlayerController* PlayerController, int32 PublicConnections, bool bUseLAN,
           FString password, FString lobbyName, FString versionNumber);

   // Creates a session with the default online subsystem for a dedicated server. Do not use this for regular player created sessions.
   // Settings go in the DefaultEngine.ini file.
   // DSS R Keene
   UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject"), Category = "Online|Session")
       static UCustomCreateSession* CreateCustomSessionDedicated(UObject* WorldContextObject, FString versionNumber);

   // UOnlineBlueprintCallProxyBase interface
   virtual void Activate() override;
   // End of UOnlineBlueprintCallProxyBase interface

   IOnlineSubsystem* OnlineSub;
   IOnlineSessionPtr ses;
   TSharedPtr`` SessionSettings;

private:
   // Internal callback when session creation completes, calls StartSession
   void OnCreateCompleted(FName SessionName, bool bWasSuccessful);
 
   // Internal callback when session creation completes, calls StartSession
   void OnStartCompleted(FName SessionName, bool bWasSuccessful);
 
   // The player controller triggering things
   TWeakObjectPtr`` PlayerControllerWeakPtr;
 
   // The delegate executed by the online subsystem
   FOnCreateSessionCompleteDelegate CreateCompleteDelegate;
 
   // The delegate executed by the online subsystem
   FOnStartSessionCompleteDelegate StartCompleteDelegate;
 
   // Handles to the registered delegates above
   FDelegateHandle CreateCompleteDelegateHandle;
   FDelegateHandle StartCompleteDelegateHandle;
 
   // Number of public connections
   int NumPublicConnections;
 
   // Whether or not to search LAN
   bool bUseLAN;
 
   // Gets set to !FPlatformProperties::IsServerOnly()
   static bool bUseOnlinePresence;
 
   //  DSS R Keene
   FString DSS_password;
 
   //  DSS R Keene
   FString DSS_lobbyName;
 
   FString DSS_versionNumber;
 
   // The world context object in which this call is taking place
   UObject* WorldContextObject;
 };

Also the code from SteamBridge that is used here... SteamBridge.h

// Feel free to use this any way you want. No liability implied.

#pragma once

#include "UnrealNetwork.h"
#include "Online.h"

#include "Kismet/BlueprintFunctionLibrary.h"
#include "Int64Bits.h"

#include "steam/steam_api.h"
#include "steam/steam_gameserver.h"
#define ARRAY_COUNT( array ) (sizeof(ArrayCountHelper(array)) - 1)
#include "SteamBridge.generated.h"

class USteamBridgeItemDetails;

UENUM(BlueprintType)
enum class ESteamResult : uint8
{
   // success
   OK = 1, 
   // generic failure 
   Fail = 2,   
   NoConnection = 3,   
   // OBSOLETE
   NoConnectionRetry = 4,  
   InvalidPassword = 5,    
   LoggedInElsewhere = 6,              
   InvalidProtocolVer = 7,         
   InvalidParam = 8,               
   FileNotFound = 9,               
   Busy = 10,                      
   InvalidState = 11,              
   InvalidName = 12,               
   InvalidEmail = 13,              
   DuplicateName = 14,             
   AccessDenied = 15,              
   Timeout = 16,                   
   Banned = 17,                    
   AccountNotFound = 18,           
   InvalidSteamID = 19,            
   ServiceUnavailable = 20,        
   NotLoggedOn = 21,               
   Pending = 22,                   
   EncryptionFailure = 23,         
   InsufficientPrivilege = 24,     
   LimitExceeded = 25,             
   Revoked = 26,                   
   Expired = 27,                   
   AlreadyRedeemed = 28,           
   DuplicateRequest = 29,          
   AlreadyOwned = 30,              
   IPNotFound = 31,                    
   PersistFailed = 32,             
   LockingFailed = 33,             
   LogonSessionReplaced = 34,
   ConnectFailed = 35,
   HandshakeFailed = 36,
   IOFailure = 37,
   RemoteDisconnect = 38,
   ShoppingCartNotFound = 39,      
   Blocked = 40,                   
   Ignored = 41,                   
   NoMatch = 42,                   
   AccountDisabled = 43,
   ServiceReadOnly = 44,           
   AccountNotFeatured = 45,        
   AdministratorOK = 46,           
   ContentVersion = 47,            
   TryAnotherCM = 48,              
   PasswordRequiredToKickSession = 49,
   AlreadyLoggedInElsewhere = 50,  
   Suspended = 51,                 
   Cancelled = 52,                 
   DataCorruption = 53,            
   DiskFull = 54,                  
   RemoteCallFailed = 55,          
   PasswordUnset = 56,             
   ExternalAccountUnlinked = 57,   
   PSNTicketInvalid = 58,          
   ExternalAccountAlreadyLinked = 59,
   RemoteFileConflict = 60,            
   IllegalPassword = 61,               
   SameAsPreviousValue = 62,           
   AccountLogonDenied = 63,            
   CannotUseOldPassword = 64,          
   InvalidLoginAuthCode = 65,          
   AccountLogonDeniedNoMail = 66,      
   HardwareNotCapableOfIPT = 67,       
   IPTInitError = 68,                  
   ParentalControlRestricted = 69, //
   FacebookQueryError = 70,            
   ExpiredLoginAuthCode = 71,          
   IPLoginRestrictionFailed = 72,
   AccountLockedDown = 73,
   AccountLogonDeniedVerifiedEmailRequired = 74,
   NoMatchingURL = 75,
   BadResponse = 76,                   
   RequirePasswordReEntry = 77,        
   ValueOutOfRange = 78,               
   UnexpectedError = 79,               
   Disabled = 80,                      
   InvalidCEGSubmission = 81,          
   RestrictedDevice = 82,              
   RegionLocked = 83,                  
   RateLimitExceeded = 84,         
   AccountLoginDeniedNeedTwoFactor = 85,
   ItemDeleted = 86,                   
   AccountLoginDeniedThrottle = 87,    
   TwoFactorCodeMismatch = 88,     
   TwoFactorActivationCodeMismatch = 89,   
   AccountAssociatedToMultiplePartners = 90,   
   NotModified = 91,
};

/**
 * All calls to the Steam SDK libraries.
 */
UCLASS()
class MYGAME_API USteamBridge : public UBlueprintFunctionLibrary
{
   GENERATED_BODY()
   
public:

   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam, meta = (WorldContext = WorldContextObject))
       static bool IsSteamInitalized(UObject * WorldContextObject, FString debugText = "");
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam)
       static int32 SteamAppID();
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam, meta = (WorldContext = WorldContextObject))
       static FString LocalPlayerSteamName(UObject * WorldContextObject);
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam, meta = (WorldContext = WorldContextObject))
       static FString LocalPlayerSteamID(UObject * WorldContextObject);
   UFUNCTION(BlueprintCallable, Category = Steam)
       static void SetSteamNotifyPositionUpperRight();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static FDateTime GetAppEarliestPurchaseTime(int appID);

   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool OwnsSteamApp(int appID);
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool IsInstalledSteamApp(int appID);

   static bool ActivateListGeneric(const char *pchDialog);
   /// So you can debuf the Steam API call ActivateGameOverlay
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamList(FString ListName);
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamFriendsList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamCommunityList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamPlayersList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamSettingsList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamOfficialGameGroupList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamStatsList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateSteamAchievementsList();
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateInviteFriendsWindow();

   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool UnlockAchievement(FString AchievementID);
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam)
       static bool HasAchievement(FString AchievementID);
   /// NEVER will unlock and achievement.  You MUST call UnlockAchievement for that.
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool SetAchievementProgress(FString AchievementID, int32 CurrentProgress, int32 MaxProgress);
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ClearAchievement(FString AchievementID);

   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateGameOverlayToWebPage(FString URL);
   /// MyGame'ss app ID 480, flags are None = 0, AddToCart = 1, AddToCartAndShow = 2,
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateGameOverlayToStore(int32 AppID, int32 ShowFlag);
   /// valid options are
   ///     "steamid" - opens the overlay web browser to the specified user or groups profile
   ///     "chat" - opens a chat window to the specified user, or joins the group chat 
   ///     "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API
   ///     "stats" - opens the overlay web browser to the specified user's stats
   ///     "achievements" - opens the overlay web browser to the specified user's achievements
   ///     "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend
   ///     "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend
   ///     "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite
   ///     "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite
   UFUNCTION(BlueprintCallable, Category = Steam)
       static bool ActivateGameOverlayToUserGeneric(FString UserSteamID, FString WindowName);

   UFUNCTION(BlueprintCallable, BlueprintPure, Category = Steam)
       static UTexture2D * GetSteamAvatar(FString PlayerSteamID);

   static CSteamID SteamIDStringToCSteamID(FString s);
   static FString CSteamIDToString(CSteamID i);

   /// INVENTORY ======================================================================== INVENTORY
   /// INVENTORY ASYNC RESULT MANAGEMENT
   ///
   /// Asynchronous inventory queries always output a result handle which can be used with
   /// GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will
   /// be triggered when the asynchronous result becomes ready (or fails).
   ///
   /// Find out the status of an asynchronous inventory result handle. Possible values:
   ///  Pending - still in progress
   ///  OK - done, result ready
   ///  Expired - done, result ready, maybe out of date (see DeserializeResult)
   ///  InvalidParam - ERROR: invalid API call parameters
   ///  ServiceUnavailable - ERROR: service temporarily down, you may retry later
   ///  LimitExceeded - ERROR: operation would exceed per-user inventory limits
   ///  Fail - ERROR: unknown / generic error
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = SteamInventory)
       static ESteamResult GetResultStatus(int32 resultHandle);

   /// Copies the contents of a result set into a flat array. The specific
   /// contents of the result set depend on which query which was used.
   /// Call this after GetResultStatus is OK, pass in an array to be filled.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GetResultItems(int32 resultHandle,
       TArray & resultsArray);

   /// Returns the age of the results compared to the Steam server time..
   UFUNCTION(BlueprintCallable, BlueprintPure, Category = SteamInventory)
       static int32 GetResultAge(int32 resultHandle);

   /// Returns true if the result belongs to the target steam ID, false if the
   /// result does not. This is important when using DeserializeResult, to verify
   /// that a remote player is not pretending to have a different user's inventory.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool CheckResultSteamID(int32 resultHandle, FString steamIDExpected);

   /// Destroys a result handle and frees all associated memory.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static void DestroyResult(int32 resultHandle);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// Captures the entire state of the current user's Steam inventory.
   /// You must call DestroyResult on this handle when you are done with it.
   /// Returns false and sets ResultHandle to zero if inventory is unavailable.
   /// Note: calls to this function are subject to rate limits and may return
   /// cached results if called too frequently. It is suggested that you call
   /// this function only when you are about to display the user's full inventory,
   /// or if you expect that the inventory may have changed.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GetAllItems(int32 & ResultHandle);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// Captures the state of a subset of the current user's Steam inventory,
   /// identified by an array of item instance IDs. The results from this call
   /// can be serialized and passed to other players to "prove" that the current
   /// user owns specific items, without exposing the user's entire inventory.
   /// For example, you could call GetItemsByID with the IDs of the user's
   /// currently equipped cosmetic items and serialize this to a buffer, and
   /// then transmit this buffer to other players upon joining a game.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GetItemsByID(int32 & pResultHandle, const TArray & IDArray);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t
   /// notification with a matching nCallbackContext parameter. This API is insecure, and could
   /// be abused by hacked clients. It is, however, very useful as a development cheat or as
   /// a means of prototyping item-related features for your game. The use of GenerateItems can
   /// be restricted to certain item definitions or fully blocked via the Steamworks website.
   /// If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should
   /// describe the quantity of each item to generate.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GenerateItems(int32 & ResultHandle, const TArray`` & itemDefs, const TArray`` & quantities);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// GrantPromoItems() checks the list of promotional items for which the user may be eligible
   /// and grants the items (one time only).  On success, the result set will include items which
   /// were granted, if any. If no items were granted because the user isn't eligible for any
   /// promotions, this is still considered a success.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GrantPromoItems(int32 & ResultHandle);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of
   /// scanning for all eligible promotional items, the check is restricted to a single item
   /// definition or set of item definitions. This can be useful if your game has custom UI for
   /// showing a specific promo item to the user.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool AddPromoItem(int32 & ResultHandle, int32 itemDef);
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool AddPromoItems(int32 & ResultHandle, const TArray`` & itemDefs);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// ConsumeItem() removes items from the inventory, permenantly. They cannot be recovered.
   /// Not for the faint of heart - if your game implements item removal at all, a high-friction
   /// UI confirmation process is highly recommended. Similar to GenerateItems, punArrayQuantity
   /// can be NULL or else an array of the same length as pArrayItems which describe the quantity
   /// of each item to destroy. ConsumeItem can be restricted to certain item definitions or
   /// fully blocked via the Steamworks website to minimize support/abuse issues such as the
   /// clasic "my brother borrowed my laptop and deleted all of my rare items".
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool ConsumeItem(int32 & ResultHandle, UInt64Bits * itemConsume, int32 Quantity);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// ExchangeItems() is an atomic combination of GenerateItems and DestroyItems. It can be
   /// used to implement crafting recipes or transmutations, or items which unpack themselves
   /// into other items. Like GenerateItems, this is a flexible and dangerous API which is
   /// meant for rapid prototyping. You can configure restrictions on ExchangeItems via the
   /// Steamworks website, such as limiting it to a whitelist of input/output combinations
   /// corresponding to recipes.
   /// (Note: although GenerateItems may be hard or impossible to use securely in your game,
   /// ExchangeItems is perfectly reasonable to use once the whitelists are set accordingly.)
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool ExchangeItems(int32 & ResultHandle,
           const TArray``& itemDefsGenerate, const TArray``& itemGenerateQuantities,
           const TArray& itemInstancesDestroy, const TArray``& itemDestroyQuantities);

   /// You must call this, then GetResultStatus until OK, then GetResultItems.
   /// TransferItemQuantity() is intended for use with items which are "stackable" (can have
   /// quantity greater than one). It can be used to split a stack into two, or to transfer
   /// quantity from one stack into another stack of identical items. To split one stack into
   /// two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool TransferItemQuantity(int32 & ResultHandle, UInt64Bits * itemIdSource, int32 Quantity, UInt64Bits * itemIdDestination);

   /// Applications which use timed-drop mechanics should call SendItemDropHeartbeat() when
   /// active gameplay begins, and at least once every two minutes afterwards. The backend
   /// performs its own time calculations, so the precise timing of the heartbeat is not
   /// critical as long as you send at least one heartbeat every two minutes. Calling the
   /// function more often than that is not harmful, it will simply have no effect. Note:
   /// players may be able to spoof this message by hacking their client, so you should not
   /// attempt to use this as a mechanism to restrict playtime credits. It is simply meant
   /// to distinguish between being in any kind of gameplay situation vs the main menu or
   /// a pre-game launcher window. (If you are stingy with handing out playtime credit, it
   /// will only encourage players to run bots or use mouse/kb event simulators.)
   ///
   /// Playtime credit accumulation can be capped on a daily or weekly basis through your
   /// Steamworks configuration.
   ///
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static void SendItemDropHeartbeat();

   /// Playtime credit must be consumed and turned into item drops by your game. Only item
   /// definitions which are marked as "playtime item generators" can be spawned. The call
   /// will return an empty result set if there is not enough playtime credit for a drop.
   /// Your game should call TriggerItemDrop at an appropriate time for the user to receive
   /// new items, such as between rounds or while the player is dead. Note that players who
   /// hack their clients could modify the value of "dropListDefinition", so do not use it
   /// to directly control rarity. It is primarily useful during testing and development,
   /// where you may wish to perform experiments with different types of drops.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool TriggerItemDrop(int32 & ResultHandle, int32 dropListDefinition);

   /// IN-GAME TRADING
   ///
   /// TradeItems() implements limited in-game trading of items, if you prefer not to use
   /// the overlay or an in-game web browser to perform Steam Trading through the website.
   /// You should implement a UI where both players can see and agree to a trade, and then
   /// each client should call TradeItems simultaneously (+/- 5 seconds) with matching
   /// (but reversed) parameters. The result is the same as if both players performed a
   /// Steam Trading transaction through the web. Each player will get an inventory result
   /// confirming the removal or quantity changes of the items given away, and the new
   /// item instance id numbers and quantities of the received items.
   /// (Note: new item instance IDs are generated whenever an item changes ownership.)
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool TradeItems(int32 & ResultHandle, UInt64Bits * steamIDTradePartner,
           const TArray& itemGive, const TArray``& itemGiveQuantities,
           const TArray& itemGet, const TArray``& itemGetQuantities);

   /// ITEM DEFINITIONS (The JSON file you upload to Steam defining all your inventory items in your game.)
   ///
   /// Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000)
   /// to a set of string properties. Some of these properties are required to display items
   /// on the Steam community web site. Other properties can be defined by applications.
   /// Use of these functions is optional; there is no reason to call LoadItemDefinitions
   /// if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue
   /// weapon mod = 55) and does not allow for adding new item types without a client patch.
   ///
   /// LoadItemDefinitions triggers the automatic load and refresh of item definitions.
   /// Every time new item definitions are available (eg, from the dynamic addition of new
   /// item types while players are still in-game), a SteamInventoryDefinitionUpdate_t
   /// callback will be fired.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool LoadItemDefinitions();

   /// GetItemDefinitionIDs returns the set of all defined item definition IDs (which are
   /// defined via Steamworks configuration, and not necessarily contiguous integers).
   /// If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will
   /// contain the total size necessary for a subsequent call. Otherwise, the call will
   /// return false if and only if there is not enough space in the output array.
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GetItemDefinitionIDs(TArray`` & DefsIds);

   /// GetItemDefinitionProperty returns a string property from a given item definition.
   /// Note that some properties (for example, "name") may be localized and will depend
   /// on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage).
   /// Property names are always composed of ASCII letters, numbers, and/or underscores.
   /// Pass an empty string for PropertyName to get a comma - separated list of available
   /// property names. 
   UFUNCTION(BlueprintCallable, Category = SteamInventory)
       static bool GetItemDefinitionProperty(int32 definitionId, FString PropertyName, FString & Value);

};

and the cpp SteamBridge.cpp

// Feel free to use this any way you want.  No liability implied.
#include "SteamBridge.h"
#include "MyGame.h"
#include "SteamBridgeItemDetails.h"

bool USteamBridge::IsSteamInitalized(UObject * WorldContextObject, FString debugText)
{
   //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized"));
   //if (debugText.IsEmpty()) {
   //  UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: (no debug text)"));
   //}
   //else {
   //  UE_LOG(DSS_STEAM, Log, TEXT("%s"), *debugText);
   //}
   if (SteamAPI_IsSteamRunning())
   {
       //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: Is Running"));
       if (WorldContextObject->GetWorld()->GetNetMode() == NM_DedicatedServer) {
           //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: Is Dedicated Server"));
           //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: true (1)"));

           //if (SteamUtils() == NULL) {
           //  UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: SteamUtils is NULL"));
           //}
           //else {
           //  UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: SteamUtils is Valid"));
           //  UE_LOG(DSS_STEAM, Log, TEXT("Steam AppID %d"), SteamUtils()->GetAppID());
           //}

           //if (SteamGameServerUtils() == NULL) {
           //  UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: SteamGameServerUtils is NULL"));
           //}
           //else {
           //  UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: SteamGameServerUtils is Valid"));
           //  UE_LOG(DSS_STEAM, Log, TEXT("Steam AppID %d"), SteamGameServerUtils()->GetAppID());
           //}

           return false;
           //return true;
       }
       //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: Is Not Dedicated Server"));
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: Has Steam Friends"));
           const char * n = sf->GetPersonaName();
           if (n != NULL)
           {
               //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: Has Person Name"));
               //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: true (2)"));
               return true;
           }
       }
       //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: No Steam Friends"));
   }
   //UE_LOG(DSS_STEAM, Log, TEXT("IsSteamInitalized: false (1)"));
   return false;
}

int32 USteamBridge::SteamAppID()
{
   if (SteamUtils() != NULL) {
       return SteamUtils()->GetAppID();
   }
   else if (SteamGameServerUtils() != NULL) {
       return SteamGameServerUtils()->GetAppID();
   }
   else {
       return 0;
   }
}

FDateTime USteamBridge::GetAppEarliestPurchaseTime(int appID)
{
`   if (!SteamAPI_IsSteamRunning() 

 SteamApps() == NULL) {        return FDateTime::FromUnixTimestamp(0);    }    uint32 tm = SteamApps()->GetEarliestPurchaseUnixTime(appID);    return FDateTime::FromUnixTimestamp(tm); }`

char steamidbuf[512];

FString USteamBridge::LocalPlayerSteamID(UObject * WorldContextObject)
{
   //UE_LOG(DSS_STEAM, Log, TEXT("LocalPlayerSteamID"));

   if (!IsSteamInitalized(WorldContextObject, "LocalPlayerSteamID Internal C++"))
   {
       return FString(TEXT("Steam is not running."));
   }

   if (SteamUser() == NULL) {
       UE_LOG(DSS_STEAM, Log, TEXT("USteamBridge::LocalPlayerSteamID SteamUser is NULL"));
       return FString(TEXT("SteamUser is NULL."));
   }

   CSteamID id = SteamUser()->GetSteamID();
   if (!id.IsValid())
   {
       UE_LOG(DSS_STEAM, Log, TEXT("USteamBridge::LocalPlayerSteamID SteamUser()->GetSteamID() is not valid"));
       return FString(TEXT("No Steam ID"));
   }
   FString ret = CSteamIDToString(id);
   UE_LOG(DSS_STEAM, Log, TEXT("USteamBridge::LocalPlayerSteamID %s"), *ret);
   return ret;
}

bool USteamBridge::OwnsSteamApp(int appID)
{
   return SteamApps()->BIsSubscribedApp(appID);
}

bool USteamBridge::IsInstalledSteamApp(int appID)
{
   return SteamApps()->BIsAppInstalled(appID);
}

void USteamBridge::SetSteamNotifyPositionUpperRight()
{
   SteamUtils()->SetOverlayNotificationPosition(ENotificationPosition::k_EPositionTopRight);
}

FString USteamBridge::LocalPlayerSteamName(UObject * WorldContextObject)
{
   //UE_LOG(DSS_STEAM, Log, TEXT("LocalPlayerSteamName"));
   if (!IsSteamInitalized(WorldContextObject, "LocalPlayerSteamName Internal C++"))
   {
       return FString(TEXT("Steam is not running."));
   }

   const char * n = SteamFriends()->GetPersonaName();
   if (n == NULL)
   {
       return FString(TEXT("No Steam Name"));
   }
   return FString(n);

}

bool USteamBridge::ActivateListGeneric(const char *pchDialog)
{
   if (SteamAPI_IsSteamRunning())
   {
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           sf->ActivateGameOverlay(pchDialog);
           return true;
       }
   }
   return false;
}

bool USteamBridge::ActivateSteamList(FString ListName)
{
   return ActivateListGeneric(TCHAR_TO_ANSI(*ListName));
}

bool USteamBridge::ActivateSteamFriendsList()
{
   return ActivateListGeneric("Friends");
}

bool USteamBridge::ActivateSteamCommunityList()
{
   return ActivateListGeneric("Community");
}

bool USteamBridge::ActivateSteamPlayersList()
{
   return ActivateListGeneric("Players");
}

bool USteamBridge::ActivateSteamSettingsList()
{
   return ActivateListGeneric("Settings");
}

bool USteamBridge::ActivateSteamOfficialGameGroupList()
{
   return ActivateListGeneric("OfficialGameGroup");
}

bool USteamBridge::ActivateSteamStatsList()
{
   return ActivateListGeneric("Stats");
}

bool USteamBridge::ActivateSteamAchievementsList()
{
   return ActivateListGeneric("Achievements");
}

bool USteamBridge::ActivateInviteFriendsWindow()
{
   UE_LOG(DSS_STEAM, Log, TEXT("ActivateInviteFriendsWindow"));
   if (SteamAPI_IsSteamRunning())
   {
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           sf->ActivateGameOverlay("LobbyInvite");
           UE_LOG(DSS_STEAM, Log, TEXT("ActivateInviteFriendsWindow - ok"));
           return true;
       }
   }

   UE_LOG(DSS_STEAM, Log, TEXT("ActivateInviteFriendsWindow - fail"));
   return false;
}

/// valid options are
///        "steamid" - opens the overlay web browser to the specified user or groups profile
///        "chat" - opens a chat window to the specified user, or joins the group chat 
///        "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API
///        "stats" - opens the overlay web browser to the specified user's stats
///        "achievements" - opens the overlay web browser to the specified user's achievements
///        "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend
///        "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend
///        "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite
///        "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite
bool USteamBridge::ActivateGameOverlayToUserGeneric(FString UserSteamID, FString WindowName)
{
   UE_LOG(DSS_STEAM, Log, TEXT("ActivateGameOverlayToUserChat"));
   if (SteamAPI_IsSteamRunning())
   {
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           CSteamID id = SteamIDStringToCSteamID(UserSteamID);
           sf->ActivateGameOverlayToUser(TCHAR_TO_ANSI(*WindowName), id);
           UE_LOG(DSS_STEAM, Log, TEXT("ActivateGameOverlayToUserChat - ok"));
           return true;
       }
   }

   UE_LOG(DSS_STEAM, Log, TEXT("ActivateGameOverlayToUserChat - fail"));
   return false;
}

bool USteamBridge::UnlockAchievement(FString AchievementID)
{
   ISteamUserStats * user = SteamUserStats();
   user->SetAchievement(TCHAR_TO_ANSI(*AchievementID));
   user->StoreStats();
   return true;
}

bool USteamBridge::HasAchievement(FString AchievementID)
{
   ISteamUserStats * user = SteamUserStats();
   bool achieved = false;
   user->GetAchievement(TCHAR_TO_ANSI(*AchievementID), &achieved);
   return achieved;
}

bool USteamBridge::SetAchievementProgress(FString AchievementID, int32 CurrentProgress, int32 MaxProgress)
{
   ISteamUserStats * user = SteamUserStats();
   bool ret = user->IndicateAchievementProgress(TCHAR_TO_ANSI(*AchievementID), (uint32)CurrentProgress, (uint32)MaxProgress);
   user->StoreStats();
   return ret;
}

bool USteamBridge::ClearAchievement(FString AchievementID)
{
   ISteamUserStats * user = SteamUserStats();
   bool ret = user->ClearAchievement(TCHAR_TO_ANSI(*AchievementID));
   user->StoreStats();
   return ret;
}

bool USteamBridge::ActivateGameOverlayToWebPage(FString URL)
{
   if (SteamAPI_IsSteamRunning())
   {
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           sf->ActivateGameOverlayToWebPage(TCHAR_TO_ANSI(*URL));
           return true;
       }
   }
   return false;
}

bool USteamBridge::ActivateGameOverlayToStore(int32 AppID, int32 ShowFlag)
{
   if (SteamAPI_IsSteamRunning())
   {
       ISteamFriends * sf = SteamFriends();
       if (sf != NULL)
       {
           sf->ActivateGameOverlayToStore((AppId_t)AppID, (EOverlayToStoreFlag)ShowFlag);
           return true;
       }
   }
   return false;
}

UTexture2D * USteamBridge::GetSteamAvatar(FString PlayerSteamID) {
   uint32 Width;
   uint32 Height;

   if (SteamAPI_IsSteamRunning())
   {
       CSteamID PlayerRawID = SteamIDStringToCSteamID(PlayerSteamID);
       //Getting the PictureID from the SteamAPI and getting the Size with the ID
       int Picture = SteamFriends()->GetMediumFriendAvatar(PlayerRawID);
       SteamUtils()->GetImageSize(Picture, &Width, &Height);

       if (Width > 0 && Height > 0)
       {
           //Creating the buffer "oAvatarRGBA" and then filling it with the RGBA Stream from the Steam Avatar
           BYTE *oAvatarRGBA = new BYTE[Width * Height * 4];

           //Filling the buffer with the RGBA Stream from the Steam Avatar and creating a UTextur2D to parse the RGBA Steam in
           SteamUtils()->GetImageRGBA(Picture, (uint8*)oAvatarRGBA, 4 * Height * Width * sizeof(char));

           //Swap R and B channels because for some reason the games whack
           for (uint32 i = 0; i PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
           FMemory::Memcpy(MipData, (void*)oAvatarRGBA, Height * Width * 4);
           Avatar->PlatformData->Mips[0].BulkData.Unlock();

           //Setting some Parameters for the Texture and finally returning it
           Avatar->PlatformData->NumSlices = 1;
           Avatar->NeverStream = true;
           //Avatar->CompressionSettings = TC_EditorIcon;

           Avatar->UpdateResource();

           return Avatar;
       }
       return nullptr;
   }
   return nullptr;
}

CSteamID USteamBridge::SteamIDStringToCSteamID(FString s)
{
   uint64 i64 = 0;
   int convCount = sscanf_s(TCHAR_TO_ANSI(*s), "%llu", &i64);
   if (convCount == 0)
   {
       UE_LOG(DSS_STEAM, Log, TEXT("SteamIDStringToCSteamID - invalid input string %s"), *s);
       return CSteamID((uint64)0);
   }
   return CSteamID(i64);
}

FString USteamBridge::CSteamIDToString(CSteamID i)
{
   sprintf_s(steamidbuf, sizeof(steamidbuf), "%llu", i.ConvertToUint64());
   return FString(steamidbuf);
}

ESteamResult USteamBridge::GetResultStatus(int32 resultHandle)
{
   EResult r = SteamInventory()->GetResultStatus(resultHandle);
   return (ESteamResult)r;
}

bool USteamBridge::GetResultItems(int32 resultHandle,
   TArray & resultsArray)
{
   if (&resultsArray == NULL)
   {
       return false;
   }

   resultsArray.Empty();

   uint32 sz = 1024;
   SteamItemDetails_t * res = new SteamItemDetails_t[sz];
`   if (SteamInventory()->GetResultItems(resultHandle, res, &sz) == false 

 sz == 0)    {        delete[] res;        return false;    }`

   if (sz > 1024)
   {
       delete[] res;
       SteamItemDetails_t * res = new SteamItemDetails_t[sz];
`       if (SteamInventory()->GetResultItems(resultHandle, res, &sz) == false 

 sz == 0)        {            delete[] res;            return false;        }    }`

   for (uint32 i = 0; i ItemId = NewObject``((UObject*)GetTransientPackage(), UInt64Bits::StaticClass());
       item->ItemId->FromUInt64(res[i].m_itemId);
       item->Definition = res[i].m_iDefinition;
       item->Quantity = res[i].m_unQuantity;
       item->Flags = res[i].m_unFlags;

       resultsArray.Add(item);
   }

   if (res != NULL)
   {
       delete[] res;
   }

   return true;
}

int32 USteamBridge::GetResultAge(int32 resultHandle)
{
   uint32 s = SteamUtils()->GetServerRealTime();
   uint32 t = SteamInventory()->GetResultTimestamp(resultHandle);
   return (int32)(s - t);
}

bool USteamBridge::CheckResultSteamID(int32 resultHandle, FString steamIDExpected)
{
   CSteamID id = SteamIDStringToCSteamID(steamIDExpected);
   return SteamInventory()->CheckResultSteamID(resultHandle, id);
}

// Destroys a result handle and frees all associated memory.
void USteamBridge::DestroyResult(int32 resultHandle)
{
   SteamInventory()->DestroyResult(resultHandle);
}

bool USteamBridge::GetAllItems(int32 & ResultHandle)
{
   SteamInventoryResult_t h = 0;
   bool ret = SteamInventory()->GetAllItems(&h);
   ResultHandle = h;
   return ret;
}

bool USteamBridge::GetItemsByID(int32 & ResultHandle, const TArray & IDArray)
{
   SteamInventoryResult_t h = 0;
   SteamItemInstanceID_t * ids = new SteamItemInstanceID_t[IDArray.Num()];
   bool ret = SteamInventory()->GetItemsByID(&h, ids, IDArray.Num());
   ResultHandle = h;
   return ret;
}

bool USteamBridge::GenerateItems(int32 & ResultHandle, const TArray``& itemDefs, const TArray``& quantities)
{
   if (&quantities != NULL && quantities.Num() > 0 && itemDefs.Num() != quantities.Num())
   {
       return false;
   }

   SteamInventoryResult_t h = 0;
   SteamItemDef_t * itemDs = new SteamItemDef_t[itemDefs.Num()];
   uint32 * itemQs = NULL;

   if (&quantities != NULL && quantities.Num() > 0)
   {
       itemQs = new uint32[itemDefs.Num()];
   }
   for (int i = 0; i GenerateItems(&h, itemDs, itemQs, itemDefs.Num());

   delete itemDs;
   if (itemQs != NULL)
   {
       delete itemQs;
   }

   ResultHandle = h;

   return ret;
}

bool USteamBridge::GrantPromoItems(int32 & ResultHandle)
{
   SteamInventoryResult_t h = 0;

   bool ret = SteamInventory()->GrantPromoItems(&h);
   ResultHandle = h;
   return ret;
}

bool USteamBridge::AddPromoItem(int32 & ResultHandle, int32 itemDef)
{
   SteamInventoryResult_t h = 0;
   bool ret = SteamInventory()->AddPromoItem(&h, itemDef);
   ResultHandle = h;
   return ret;
}

bool USteamBridge::AddPromoItems(int32 & ResultHandle, const TArray``& itemDefs)
{
   SteamInventoryResult_t h = 0;

   SteamItemDef_t * itemDs = new SteamItemDef_t[itemDefs.Num()];
   for (int i = 0; i AddPromoItems(&h, itemDs, itemDefs.Num());
   ResultHandle = h;
   delete itemDs;
   return ret;
}

bool USteamBridge::ConsumeItem(int32 & ResultHandle, UInt64Bits * itemConsume, int32 Quantity)
{
   SteamInventoryResult_t h = 0;
   bool ret = SteamInventory()->ConsumeItem(&h, itemConsume->ToUInt64(), Quantity);
   ResultHandle = h;

   return ret;
}

bool USteamBridge::ExchangeItems(int32 & ResultHandle,
   const TArray``& itemDefsGenerate, const TArray``& itemGenerateQuantities,
   const TArray& itemInstancesDestroy, const TArray``& itemDestroyQuantities)
{
   if (itemDefsGenerate.Num() != itemGenerateQuantities.Num())
   {
       return false;
   }
   if (itemInstancesDestroy.Num() != itemDestroyQuantities.Num())
   {
       return false;
   }

   SteamInventoryResult_t h = 0;

   SteamItemDef_t * itemGDs = new SteamItemDef_t[itemDefsGenerate.Num()];
   uint32 * itemGQs = new uint32[itemDefsGenerate.Num()];
   for (int i = 0; i ExchangeItems(&h, itemGDs, itemGQs, itemDefsGenerate.Num(), itemDDs, itemDQs, itemInstancesDestroy.Num());
   ResultHandle = h;

   return ret;
}

bool USteamBridge::TransferItemQuantity(int32 & ResultHandle, UInt64Bits * itemIdSource, int32 Quantity, UInt64Bits * itemIdDestination)
{
   SteamInventoryResult_t h = 0;
   bool ret = SteamInventory()->TransferItemQuantity(&h, itemIdSource->ToUInt64(), Quantity, itemIdDestination->ToUInt64());
   ResultHandle = h;
   return ret;
}

void USteamBridge::SendItemDropHeartbeat()
{
   SteamInventory()->SendItemDropHeartbeat();
}

bool USteamBridge::TriggerItemDrop(int32 & pResultHandle, int32 dropListDefinition)
{
   int32 h = 0;
   bool ret = SteamInventory()->TriggerItemDrop(&h, dropListDefinition);
   return ret;
}

bool USteamBridge::TradeItems(int32 & ResultHandle, UInt64Bits * steamIDTradePartner,
   const TArray& itemGive, const TArray``& itemGiveQuantities,
   const TArray& itemGet, const TArray``& itemGetQuantities)
{
   if (itemGive.Num() != itemGiveQuantities.Num())
   {
       return false;
   }
   if (itemGet.Num() != itemGetQuantities.Num())
   {
       return false;
   }

   SteamInventoryResult_t h;
   uint64 * pArrayGive = new uint64[itemGive.Num()];
   uint32 * pArrayGiveQuantity = new uint32[itemGive.Num()];
   for (int i = 0; i ToUInt64();
       pArrayGiveQuantity[i] = itemGiveQuantities[i];
   }

   uint64 * pArrayGet = new uint64[itemGet.Num()];
   uint32 * pArrayGetQuantity = new uint32[itemGet.Num()];
   for (int i = 0; i ToUInt64();
       pArrayGetQuantity[i] = itemGetQuantities[i];
   }

   bool ret = SteamInventory()->TradeItems(&h, steamIDTradePartner->ToUInt64(),
       pArrayGive, pArrayGiveQuantity, itemGive.Num(),
       pArrayGet, pArrayGetQuantity, itemGet.Num());

   delete pArrayGive;
   delete pArrayGiveQuantity;
   delete pArrayGet;
   delete pArrayGetQuantity;

   ResultHandle = h;
   return ret;
}

bool USteamBridge::LoadItemDefinitions()
{
   bool ret = SteamInventory()->LoadItemDefinitions();
   return ret;
}

bool USteamBridge::GetItemDefinitionIDs(TArray``& DefsIds)
{
   uint32 arraySize = 0;
   SteamItemDef_t * defs = NULL;

   // Get the array size.
   bool ret = SteamInventory()->GetItemDefinitionIDs(defs, &arraySize);
   if (ret == false)
   {
       return ret;
   }

   // Get the id list.
   defs = new SteamItemDef_t[arraySize];
   ret = SteamInventory()->GetItemDefinitionIDs(defs, &arraySize);

   DefsIds.Empty();
   for (uint32 i = 0; i  0)
   {
       propName = TCHAR_TO_ANSI(*PropertyName);
   }

   bool ret = SteamInventory()->GetItemDefinitionProperty(definitionId, propName, buf, &bufsz);
   if (ret == false)
   {
       return false;
   }

   buf = new char[bufsz];
   ret = SteamInventory()->GetItemDefinitionProperty(definitionId, propName, buf, &bufsz);

   Value = FString(buf);

   delete buf;

   return ret;
}

This is needed too. Lets us cheat on UE4 and get at Steam 64 bit IDs. Int64Bits.h

#pragma once

#include "UObjectBaseUtility.h"
#include "Object.h"
#include "Kismet/BlueprintFunctionLibrary.h"

// Make this true if you are building with Steam libraries and want to use UInt64Bits in blueprints as steam IDs.
#define DO_STEAM_CODE true
#if DO_STEAM_CODE
#include "steam/steam_api.h"
#define ARRAY_COUNT( array ) (sizeof(ArrayCountHelper(array)) - 1)
#endif

#include "Int64Bits.generated.h"

/**
 * An unsigned 64 bit integer.
 */
UCLASS()
class MYGAME_API UInt64Bits : public UObject
{
   GENERATED_BODY()
   
public:

   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       static UInt64Bits * Make(int32 upper = 0, int32 lower = 0);

   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       static UInt64Bits * MakeFromString(FString decimalString);

   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       static UInt64Bits * MakeFromHexString(FString hexString);

   UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = DSSHelpers)
       int32 Lower32;
   UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = DSSHelpers)
       int32 Upper32;

   // Pretty ugly.  Here there be dragons.
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       void SetFromTwoInts(int32 upper, int32 lower);

   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool IsEqual64(UInt64Bits * other);
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool IsLessThan64(UInt64Bits * other);
   UFUNCTION(BlueprintCallable,  Category = DSSHelpers)
       bool IsLessThanOrEqualTo64(UInt64Bits * other);
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool IsGreaterThan64(UInt64Bits * other);
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool IsGreaterThanOrEqualTo64(UInt64Bits * other);

   // Sets the 64 bits from a string of decimal digits. (optional leading - sign)
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool FromString(FString stringDecimalIn);
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       bool FromHexString(FString stringHexadecimalIn);
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       FString ToDecimalString();
   UFUNCTION(BlueprintCallable, Category = DSSHelpers)
       FString ToHexString();

   uint64 ToUInt64();
   void FromUInt64(uint64 i);
#if DO_STEAM_CODE
   CSteamID ToSteamID();
   void FromSteamID(CSteamID id);
#endif

};

Int64Bits.cpp

#include "Int64Bits.h"
#include "MyGame.h"

char Int64Buf[512];

uint64 UInt64Bits::ToUInt64()
{
   return (0x0FFFFFFFF & (uint64)Lower32) | (0xFFFFFFFF00000000 & (((uint64)Upper32)  32));
}

#if DO_STEAM_CODE
CSteamID UInt64Bits::ToSteamID()
{
   uint64 i64 = ToUInt64();
   return CSteamID(i64);
}

void UInt64Bits::FromSteamID(CSteamID id)
{
   FromUInt64(id.ConvertToUint64());
}
#endif

void UInt64Bits::SetFromTwoInts(int32 upper, int32 lower)
{
   Upper32 = upper;
   Lower32 = lower;
}

UInt64Bits * UInt64Bits::Make(int32 upper, int32 lower)
{
   UInt64Bits * i = NewObject``((UObject*)GetTransientPackage(), UInt64Bits::StaticClass());
   i->Upper32 = upper;
   i->Lower32 = lower;
   return i;
}

UInt64Bits * UInt64Bits::MakeFromString(FString decimalString)
{
   UInt64Bits * i = NewObject``((UObject*)GetTransientPackage(), UInt64Bits::StaticClass());
   i->FromString(decimalString);
   return i;
}

UInt64Bits * UInt64Bits::MakeFromHexString(FString hexString)
{
   UInt64Bits * i = NewObject``((UObject*)GetTransientPackage(), UInt64Bits::StaticClass());
   i->FromHexString(hexString);
   return i;
}

bool UInt64Bits::IsEqual64(UInt64Bits * other)
{
   return (uint32)Upper32 == (uint32)other->Upper32 && (uint32)Lower32 == (uint32)other->Lower32;
}

bool UInt64Bits::IsLessThan64(UInt64Bits * other)
{
`   return (uint32)Upper32 Upper32 

 ((uint32)Upper32 == (uint32)other->Upper32 && (uint32)Lower32 Lower32); }`

bool UInt64Bits::IsLessThanOrEqualTo64(UInt64Bits * other)
{
`   return (uint32)Upper32 Upper32 

 ((uint32)Upper32 == (uint32)other->Upper32 && (uint32)Lower32 Lower32); }`

bool UInt64Bits::IsGreaterThan64(UInt64Bits * other)
{
`   return (uint32)Upper32 > (uint32)other->Upper32 

 ((uint32)Upper32 == (uint32)other->Upper32 && (uint32)Lower32 >(uint32)other->Lower32); }`

bool UInt64Bits::IsGreaterThanOrEqualTo64(UInt64Bits * other)
{
`   return (uint32)Upper32 > (uint32)other->Upper32 

 ((uint32)Upper32 == (uint32)other->Upper32 && (uint32)Lower32 >= (uint32)other->Lower32); }`

bool UInt64Bits::FromString(FString stringDecimalIn)
{
   if (stringDecimalIn.IsEmpty())
   {
       return false;
   }

   uint64 ui64 = 0;
   int convCount = sscanf_s(TCHAR_TO_ANSI(*stringDecimalIn), "%llu", &ui64);
   if (convCount == 0)
   {
       return false;
   }

   FromUInt64(ui64);

   return true;
}

FString UInt64Bits::ToDecimalString()
{
   uint64 ui64 = ToUInt64();
   sprintf_s(Int64Buf, sizeof(Int64Buf), "%llu", ui64);
   return FString(Int64Buf);
}

bool UInt64Bits::FromHexString(FString stringHexadecimalIn)
{
   if (stringHexadecimalIn.IsEmpty())
   {
       return false;
   }

   uint64 ui64 = 0;
   int convCount = sscanf_s(TCHAR_TO_ANSI(*stringHexadecimalIn), "%llx", &ui64);
   if (convCount == 0)
   {
       return false;
   }

   FromUInt64(ui64);

   return true;
}

FString UInt64Bits::ToHexString()
{
   uint64 ui64 = ToUInt64();
   sprintf_s(Int64Buf, sizeof(Int64Buf), "%llX", ui64);
   return FString(Int64Buf);
}