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