Platform Services: Cloud Save, User, Voice & External UI

UCrimsonOnlineCloudSaveService

Wraps IOnlineUserCloud - Steam Remote Storage on Steam, Player Data Storage on EOS. It moves raw byte arrays keyed by file name, and the platform scopes every file to the authenticated user. On the Null subsystem the interface is absent, so each call broadcasts its delegate with bSuccess = false and returns.

MemberDescription
EnumerateFiles(LocalUserNum = 0)Lists every cloud file the local user owns; results arrive on OnFilesEnumerated
UploadFile(FileName, FileData, LocalUserNum = 0)Uploads a TArray<uint8> under the given file name, overwriting any existing file; completes on OnFileUploaded
DownloadFile(FileName, LocalUserNum = 0)Downloads the file into the OSS internal buffer; completes on OnFileDownloaded
GetFileContents(FileName, OutData, LocalUserNum = 0)Copies the downloaded bytes out of the OSS buffer into OutData; returns false if the file is not buffered. Call it after OnFileDownloaded
DeleteFile(FileName, LocalUserNum = 0)Permanently deletes the cloud file; completes on OnFileDeleted
OnFilesEnumeratedDelegate: bool bSuccess, const TArray<FCrimsonCloudFileHeader>& Files (FileName, FileSize, LastModified)
OnFileUploaded / OnFileDownloaded / OnFileDeletedDelegate: const FString& FileName, bool bSuccess
cpp
#include "CrimsonOnlineSubsystemGI.h"
#include "Services/CrimsonOnlineCloudSaveService.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
UCrimsonOnlineSubsystemGI* Online = UCrimsonOnlineSubsystemGI::Get(this);
UCrimsonOnlineCloudSaveService* Cloud = Online->GetCloudSaveService();
// Bind first - the upload result arrives on this delegate.
// HandleFileUploaded must be a UFUNCTION taking (const FString&, bool).
Cloud->OnFileUploaded.AddDynamic(this, &UMyCloudComponent::HandleFileUploaded);
// Read whatever your save code already wrote to disk, then push the raw bytes.
TArray<uint8> SaveBytes;
const FString LocalPath = FPaths::ProjectSavedDir() / TEXT("SaveGames/Slot_0.sav");
if (FFileHelper::LoadFileToArray(SaveBytes, *LocalPath))
{
Cloud->UploadFile(TEXT("Slot_0.sav"), SaveBytes);
}
Pair with your save system
UCrimsonOnlineCloudSaveService moves raw bytes and knows nothing about save formats, so it pairs with whatever you already use. Write your save as usual (for example UGameplayStatics::SaveGameToSlot), load the resulting file off disk with FFileHelper::LoadFileToArray, and hand those bytes to UploadFile. Coming back down: after OnFileDownloaded fires, call GetFileContents for the bytes, write them to the slot file with FFileHelper::SaveArrayToFile, then load the slot normally.
LastModified is not populated
FCrimsonCloudFileHeader::LastModified exists on the struct, but the enumerate path always sets it to FDateTime(0) - the platform's FCloudFileHeader carries no timestamp, so there is nothing to copy from. Treat a zero value as "unknown" and resolve conflicts another way: keep your own version number or save timestamp inside the save payload, or use FileSize plus your own bookkeeping. Do not write a newest-wins sync on this field.
Cloud save runs where the save runs
Cloud files belong to the authenticated local user, so upload and download from the owning client's game instance - not from a dedicated server, which has no player account to store them under.

UCrimsonOnlineUserService

Wraps IOnlineUser. Resolves display names and profile metadata for arbitrary players - useful for a scoreboard, where you hold IDs but never read the friend list - and resolves a typed display name back to a platform ID for friend-search flows.

MemberDescription
QueryUserInfo(PlatformUserIds, LocalUserNum = 0)Fetches profiles for a list of platform user ID strings. Completes on OnUserInfoQueried
QueryUserIdMapping(DisplayName, LocalUserNum = 0)Resolves a display name or email to a platform user ID. Completes on OnUserMappingQueried
GetUserInfo(PlatformUserId, LocalUserNum = 0)BlueprintPure. Returns the cached FCrimsonOnlineUserInfo (DisplayName, PlatformUserId, AvatarURL) for one player
OnUserInfoQueriedDelegate: bool bSuccess, const TArray<FCrimsonOnlineUserInfo>& Users
OnUserMappingQueriedDelegate: bool bSuccess, const FString& DisplayName, const FString& PlatformUserId

UCrimsonOnlineVoiceService

Wraps IOnlineVoice for networked VOIP. Register the local talker after login, then start and stop transmission around a session. Remote players are addressed by platform user ID string.

MemberDescription
RegisterLocalTalker(LocalUserNum = 0)Returns bool. Registers the local player for voice; required before StartNetworkedVoice
UnregisterLocalTalker(LocalUserNum = 0)Unregisters the local player. Call when leaving a session or logging out
StartNetworkedVoice(LocalUserNum = 0)Begins transmitting voice data from the local player
StopNetworkedVoice(LocalUserNum = 0)Stops transmitting voice data from the local player
MuteRemoteTalker(PlatformUserId, bIsSystemWide = false, LocalUserNum = 0)Returns bool. Mutes a remote player; bIsSystemWide mutes at the OS level
UnmuteRemoteTalker(PlatformUserId, bIsSystemWide = false, LocalUserNum = 0)Returns bool. Unmutes a previously muted remote player
IsLocalPlayerTalking(LocalUserNum = 0)BlueprintPure. Returns true while the local player is transmitting
IsRemotePlayerTalking(PlatformUserId)BlueprintPure. Returns true while that remote player is transmitting
OnPlayerTalkingStateChangedDelegate: const FString& PlatformUserId, bool bIsTalking. Drives talking-indicator UI
cpp
// Push-to-talk - bind these two to the pressed/released events of an Enhanced Input action
// on the owning client. RegisterLocalTalker is called once, at login, not per keypress.
#include "CrimsonOnlineSubsystemGI.h"
#include "Services/CrimsonOnlineVoiceService.h"
void UMyVoiceComponent::SetUpVoice()
{
UCrimsonOnlineSubsystemGI::Get(this)->GetVoiceService()->RegisterLocalTalker(0);
}
void UMyVoiceComponent::OnPTTPressed()
{
UCrimsonOnlineSubsystemGI::Get(this)->GetVoiceService()->StartNetworkedVoice(0);
}
void UMyVoiceComponent::OnPTTReleased()
{
UCrimsonOnlineSubsystemGI::Get(this)->GetVoiceService()->StopNetworkedVoice(0);
}
There is no remote-talker registration
The service exposes registration for the local talker only; for remote players it offers mute, unmute and talking-state queries. For open-mic rather than push-to-talk, call StartNetworkedVoice once on session join and leave it running.

UCrimsonOnlineExternalUIService

Wraps IOnlineExternalUI to open platform overlay screens. Every Show function returns bool - false means the overlay is unavailable right now, which on PC usually means exclusive fullscreen or an overlay that was never injected. Use fullscreen windowed for reliable overlay support.

MemberDescription
ShowFriendsUI(LocalUserNum = 0)Opens the platform friend list overlay
ShowInviteUI(LocalUserNum = 0, SessionName = "GameSession")Opens the platform invite-to-session dialog
ShowAchievementsUI(LocalUserNum = 0)Opens the platform achievements overlay
ShowLeaderboardUI(LeaderboardName)Opens the platform overlay for one named leaderboard
ShowProfileUI(RequestorPlatformId, RequesteePlatformId)Opens a profile card - the viewer's ID first, then the target's
ShowLoginUI(LocalUserNum = 0)Opens the platform login dialog. Completes on OnLoginUIClosed
ShowStoreUI(LocalUserNum = 0)Opens the platform store overlay. Completes on OnStoreUIClosed
ShowWebURL(Url)Opens a URL in the platform's embedded browser. Completes on OnWebUrlClosed
CloseWebURL()Closes the embedded browser opened by ShowWebURL
OnExternalUIChangedDelegate: bool bIsOpening. Fires whenever any overlay opens or closes - pause the game from here
OnLoginUIClosedDelegate: bool bSuccess, const FString& PlatformUserId
OnWebUrlClosedDelegate: const FString& FinalUrl
OnStoreUIClosedDelegate: bool bPurchaseMade
ShowProfileUI takes two IDs, both strings
The first argument is the local viewer's platform ID and the second is the player whose card to show - the order matters, and neither is a local user index. Get both strings from GetLocalPlayerInfo().PlatformUserId and from whichever list you are showing.