// Copyright Epic Games, Inc. All Rights Reserved. #include "OnlineEncryptedAppTicketInterfaceSteam.h" #include "OnlineAsyncTaskManagerSteam.h" #include "OnlineSubsystemSteam.h" #include "SteamUtilities.h" enum class ESteamEncryptedAppTicketState { None = 0, TicketRequested, TicketAvailable, TicketFailure }; /** * Async task to retrieve encrypted application ticket data from Steam backend. */ class FOnlineAsyncTaskSteamRequestEncryptedAppTicket : public FOnlineAsyncTaskSteam { private: /** Has this task been initialized yet */ bool bInitialized; /** Data to encrypt in the application ticket. */ TArray DataToEncrypt; /** Returned results from Steam */ EncryptedAppTicketResponse_t CallbackResults; public: FOnlineAsyncTaskSteamRequestEncryptedAppTicket(FOnlineSubsystemSteam* InSteamSubsystem) : FOnlineAsyncTaskSteam(InSteamSubsystem, k_uAPICallInvalid), bInitialized(false) { } /** * Get a human readable description of task */ FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncTaskSteamRequestEncryptedAppTicket bWasSuccessful: %d"), WasSuccessful()); } /** * Sets the optional data to include for the encrypted application ticket. * * @param Data Input data to move to the internal buffer of the class. */ void SetData(TArray&& Data) { DataToEncrypt = MoveTemp(Data); } /** * Give the async task time to do its work. * Can only be called on the async task manager thread. */ virtual void Tick() override { if (!bInitialized) { ISteamUser* const SteamUserPtr = SteamUser(); if (SteamUserPtr != nullptr) { const int SizeOfData = DataToEncrypt.Num(); void* const DataPtr = SizeOfData > 0 ? DataToEncrypt.GetData() : nullptr; CallbackHandle = SteamUserPtr->RequestEncryptedAppTicket(DataPtr, SizeOfData); bInitialized = true; } } if (CallbackHandle != k_uAPICallInvalid) { ISteamUtils* SteamUtilsPtr = SteamUtils(); check(SteamUtilsPtr); // Poll for completion status bool bFailedCall = false; bIsComplete = SteamUtilsPtr->IsAPICallCompleted(CallbackHandle, &bFailedCall) ? true : false; if (bIsComplete) { bool bFailedResult; // Retrieve the callback data from the request bool bSuccessCallResult = SteamUtilsPtr->GetAPICallResult(CallbackHandle, &CallbackResults, sizeof(CallbackResults), CallbackResults.k_iCallback, &bFailedResult); bWasSuccessful = (bSuccessCallResult ? true : false) && (!bFailedCall ? true : false) && (!bFailedResult ? true : false); } } else { bWasSuccessful = false; bIsComplete = false; } } /** * Give the async task a chance to marshal its data back to the game thread. * Can only be called on the game thread by the async task manager. */ virtual void Finalize() override { FOnlineAsyncTaskSteam::Finalize(); if (CallbackResults.m_eResult != k_EResultOK) { bWasSuccessful = false; } if (!bWasSuccessful) { UE_LOG_ONLINE(Warning, TEXT("Failed to obtain encrypted application ticket, result code: %s"), *SteamResultString(CallbackResults.m_eResult)); } } /** * Async task is given a chance to trigger its delegates. */ virtual void TriggerDelegates() override { FOnlineAsyncTaskSteam::TriggerDelegates(); FOnlineEncryptedAppTicketSteamPtr Interface = Subsystem->GetEncryptedAppTicketInterface(); if (Interface.IsValid()) { Interface->OnAPICallComplete(bWasSuccessful, CallbackResults.m_eResult); } } }; FOnlineEncryptedAppTicketSteam::FOnlineEncryptedAppTicketSteam(FOnlineSubsystemSteam* InSubsystem) : FSelfRegisteringExec(), SteamSubsystem(InSubsystem), TicketState(ESteamEncryptedAppTicketState::None) { } FOnlineEncryptedAppTicketSteam::~FOnlineEncryptedAppTicketSteam() { OnEncryptedAppTicketResultDelegate.Clear(); } bool FOnlineEncryptedAppTicketSteam::RequestEncryptedAppTicket(void* DataToEncrypt, int SizeOfDataToEncrypt) { // Return instantly if encrypted application ticket is already being waited for but no data has yet been received. if (TicketState == ESteamEncryptedAppTicketState::TicketRequested) { UE_LOG_ONLINE(Warning, TEXT("Request dropped, prior encrypted application ticket request being processed.")); return false; } TicketState = ESteamEncryptedAppTicketState::TicketRequested; FOnlineAsyncTaskSteamRequestEncryptedAppTicket* NewTask = new FOnlineAsyncTaskSteamRequestEncryptedAppTicket(SteamSubsystem); if (DataToEncrypt && SizeOfDataToEncrypt > 0) { TArray Data((uint8*)DataToEncrypt, SizeOfDataToEncrypt); NewTask->SetData(MoveTemp(Data)); } SteamSubsystem->QueueAsyncTask(NewTask); return true; } void FOnlineEncryptedAppTicketSteam::OnAPICallComplete(bool bEncryptedDataAvailable, int32 ResultCode) { // Update object state based on success of the Steam API call. TicketState = bEncryptedDataAvailable ? ESteamEncryptedAppTicketState::TicketAvailable : ESteamEncryptedAppTicketState::TicketFailure; // Inform any listeners about the Steam API call result. OnEncryptedAppTicketResultDelegate.Broadcast(bEncryptedDataAvailable, ResultCode); } bool FOnlineEncryptedAppTicketSteam::GetEncryptedAppTicket(TArray& OutEncryptedData) { if (TicketState == ESteamEncryptedAppTicketState::TicketAvailable) { // Note: Ticket data is retrievable till another encrypted application ticket request replaces it. ISteamUser* const SteamUserPtr = SteamUser(); if (SteamUserPtr) { uint32 ExactTicketSize = 0; SteamUserPtr->GetEncryptedAppTicket(nullptr, 0, &ExactTicketSize); if (ExactTicketSize > 0) { OutEncryptedData.Reset(); OutEncryptedData.Reserve(ExactTicketSize); OutEncryptedData.AddUninitialized(ExactTicketSize); if (SteamUserPtr->GetEncryptedAppTicket(OutEncryptedData.GetData(), OutEncryptedData.GetAllocatedSize(), &ExactTicketSize)) { return true; } } UE_LOG_ONLINE(Warning, TEXT("Getting encrypted application ticket failed!")); } } else { switch (TicketState) { case ESteamEncryptedAppTicketState::None: UE_LOG_ONLINE(Warning, TEXT("Unable to get encrypted application ticket, it hasn't been requested!")); break; case ESteamEncryptedAppTicketState::TicketRequested: UE_LOG_ONLINE(Warning, TEXT("Encrypted ticket is not yet available!")); break; case ESteamEncryptedAppTicketState::TicketFailure: UE_LOG_ONLINE(Warning, TEXT("Failed to get encrypted application ticket due to the original request failing.")); break; default: UE_LOG_ONLINE(Warning, TEXT("Failed to get encrypted application ticket, unknown reason.")); break; } } // Remove any existing data from the outgoing container before returning failure. OutEncryptedData.Reset(); return false; } bool FOnlineEncryptedAppTicketSteam::Exec_Dev(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { #if !UE_BUILD_SHIPPING if (FParse::Command(&Cmd, TEXT("RequestEncryptedAppTicket"))) { // Usage examples: // RequestEncryptedAppTicket // RequestEncryptedAppTicket "Additional data to encrypt" FString StringDataToEncrypt = FParse::Token(Cmd, 0); int SizeOfDataToEncrypt = 0; void* DataToEncryptPtr = nullptr; if (StringDataToEncrypt.GetAllocatedSize() > 0) { DataToEncryptPtr = &StringDataToEncrypt[0]; SizeOfDataToEncrypt = StringDataToEncrypt.Len(); } Ar.Log(ELogVerbosity::Display, FString::Printf( TEXT("Requesting encrypted application ticket: DataToEncrypt: %p, SizeOfDataToEncrypt: %d."), DataToEncryptPtr, SizeOfDataToEncrypt)); const bool bSuccess = RequestEncryptedAppTicket(DataToEncryptPtr, SizeOfDataToEncrypt); Ar.Log(ELogVerbosity::Display, FString::Printf(TEXT("Requesting encrypted application ticket %s"), bSuccess ? TEXT("SUCCEEDED.") : TEXT("FAILED!"))); return true; } else if (FParse::Command(&Cmd, TEXT("GetEncryptedAppTicket"))) { // Usage examples: // GetEncryptedAppTicket TArray DataBuffer; Ar.Log(ELogVerbosity::Display, TEXT("Trying to get encrypted application ticket.")); const bool bSuccess = GetEncryptedAppTicket(DataBuffer); if (bSuccess) { FString RetrievedHexBytes = BytesToHex(DataBuffer.GetData(), DataBuffer.Num()); Ar.Log(ELogVerbosity::Display, FString::Printf( TEXT("Getting encrypted application ticket SUCCEEDED, encrypted ticket size: %u, data retrieved: %s"), DataBuffer.Num(), *RetrievedHexBytes)); } else { Ar.Log(ELogVerbosity::Display, TEXT("Getting encrypted application ticket FAILED!")); } return true; } #endif return false; }