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.
| Member | Description |
|---|---|
| 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 |
| OnFilesEnumerated | Delegate: bool bSuccess, const TArray<FCrimsonCloudFileHeader>& Files (FileName, FileSize, LastModified) |
| OnFileUploaded / OnFileDownloaded / OnFileDeleted | Delegate: const FString& FileName, bool bSuccess |
#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);}
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.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.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.
| Member | Description |
|---|---|
| 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 |
| OnUserInfoQueried | Delegate: bool bSuccess, const TArray<FCrimsonOnlineUserInfo>& Users |
| OnUserMappingQueried | Delegate: 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.
| Member | Description |
|---|---|
| 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 |
| OnPlayerTalkingStateChanged | Delegate: const FString& PlatformUserId, bool bIsTalking. Drives talking-indicator UI |
// 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);}
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.
| Member | Description |
|---|---|
| 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 |
| OnExternalUIChanged | Delegate: bool bIsOpening. Fires whenever any overlay opens or closes - pause the game from here |
| OnLoginUIClosed | Delegate: bool bSuccess, const FString& PlatformUserId |
| OnWebUrlClosed | Delegate: const FString& FinalUrl |
| OnStoreUIClosed | Delegate: bool bPurchaseMade |
GetLocalPlayerInfo().PlatformUserId and from whichever list you are showing.