Steam Leaderboard Grouping
So in Backseat Drivers we wanted to group players together (since the game is coop) on the leaderboard. Well this isn’t something that’s supported out of the box by Valve so I had to do some hacky stuff.

How?
So in the Steam API UploadLeaderboardScore it accepts a pScoreDetails (const int32*). Do you see where I’m going with this yet?
I take each player’s SteamID, split it into two int32 values (low/high), and store them in sequence:
[0] = P1.low, [1] = P1.high, [2] = P2.low, [3] = P2.high
In this screenshot you can see that it stores just fine.
I wrote some simple C++ to handle this.
#include "SteamIdUtils.h"
#include "Misc/Char.h"
#include "Misc/AssertionMacros.h"
bool USteamIdUtils::IsAllDigits(const FString& S)
{
if (S.IsEmpty()) return false;
for (TCHAR C : S)
{
if (!FChar::IsDigit(C)) return false;
}
return true;
}
void USteamIdUtils::PackU64(uint64 V, int32& Lo, int32& Hi)
{
Lo = static_cast<int32>(V & 0xFFFFFFFFull);
Hi = static_cast<int32>((V >> 32) & 0xFFFFFFFFull);
}
uint64 USteamIdUtils::UnpackU64(int32 Lo, int32 Hi)
{
uint64 LoU = static_cast<uint64>(static_cast<uint32>(Lo));
uint64 HiU = static_cast<uint64>(static_cast<uint32>(Hi));
return (HiU << 32) | LoU;
}
void USteamIdUtils::SplitSteamIdString(const FString& SteamIdStr, int32& OutLo, int32& OutHi, bool& bOk)
{
OutLo = OutHi = 0;
bOk = false;
// Expect a decimal SteamID64 string like "7656119..."
if (!IsAllDigits(SteamIdStr))
{
return; // Not a pure decimal string (e.g., "[U:1:123]" or "STEAM_1:1:..."); convert that first.
}
// Parse base-10 -> uint64
uint64 Id64 = FCString::Strtoui64(*SteamIdStr, nullptr, 10);
// Basic sanity: if the string wasn't "0", but parsed to 0, treat as failure
if (Id64 == 0 && SteamIdStr != TEXT("0"))
{
return;
}
PackU64(Id64, OutLo, OutHi);
bOk = true;
}
FString USteamIdUtils::MakeSteamIdString(int32 Lo, int32 Hi)
{
uint64 Id64 = UnpackU64(Lo, Hi);
return LexToString(Id64); // decimal string
}
void USteamIdUtils::Int32ArrayToSteamIdStrings(
const TArray<int32>& In,
TArray<FString>& Out,
int32 OffsetInts,
int32 NumIds,
bool bSkipZero)
{
UE_LOG(LogTemp, Verbose,
TEXT("[SteamIdUtils] Int32ArrayToSteamIdStrings: In.Num=%d, OffsetInts=%d, NumIds=%d, bSkipZero=%s"),
In.Num(), OffsetInts, NumIds,
bSkipZero ? TEXT("true") : TEXT("false"));
Out.Reset();
// Bounds checks
if (OffsetInts < 0 || OffsetInts >= In.Num())
{
UE_LOG(LogTemp, Error, TEXT("[SteamIdUtils] OffsetInts (%d) out of bounds (In.Num=%d). Aborting."),
OffsetInts, In.Num());
return;
}
const int32 Remaining = In.Num() - OffsetInts;
if (Remaining < 2)
{
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Not enough ints to form a single pair. Remaining=%d."), Remaining);
return;
}
if ((Remaining % 2) != 0)
{
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Odd count (%d) after Offset. Last int will be ignored."), Remaining);
}
int32 MaxPairs = Remaining / 2;
if (NumIds >= 0)
{
if (NumIds > MaxPairs)
{
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Requested NumIds=%d exceeds available pairs=%d. Clamping."),
NumIds, MaxPairs);
}
MaxPairs = FMath::Min(MaxPairs, NumIds);
}
Out.Reserve(MaxPairs);
int32 i = OffsetInts;
for (int32 p = 0; p < MaxPairs; ++p, i += 2)
{
const int32 Lo = In[i];
const int32 Hi = In[i + 1];
// Combine low and high parts into 64-bit Steam ID
const uint64 Id64 = UnpackU64(Lo, Hi);
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Pair #%d Lo=%d Hi=%d -> uint64=%llu"),
p, Lo, Hi, (unsigned long long)Id64);
if (bSkipZero && Id64 == 0)
{
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Pair #%d resolved to 0 (skipped)."), p);
continue;
}
const FString IdStr = LexToString(Id64);
Out.Emplace(IdStr);
}
UE_LOG(LogTemp, Verbose, TEXT("[SteamIdUtils] Int32ArrayToSteamIdStrings: Produced %d SteamIDs from %d pairs."), Out.Num(), MaxPairs);
}It works. haha.
until next time