bool FOnlineSubsystemSteam::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) 
{
	if (FOnlineSubsystemImpl::Exec(InWorld, Cmd, Ar))
	{
		return true;
	}

	bool bWasHandled = false;
	if (FParse::Command(&Cmd, TEXT("DELETECLOUDFILES")))
	{
		IOnlineUserCloudPtr UserCloud = GetUserCloudInterface();

		FUniqueNetIdSteam SteamId(SteamUser()->GetSteamID());

		FOnEnumerateUserFilesCompleteDelegate Delegate = FOnEnumerateUserFilesCompleteDelegate::CreateStatic(&DeleteFromEnumerateUserFilesComplete);
		GPerCloudDeleteFromEnumerateUserFilesCompleteDelegateHandles.Add(UserCloud.Get(), UserCloud->AddOnEnumerateUserFilesCompleteDelegate_Handle(Delegate));
		UserCloud->EnumerateUserFiles(SteamId);
		bWasHandled = true;
	}
	else if (FParse::Command(&Cmd, TEXT("SYNCLOBBIES")))
	{
		if (SessionInterface.IsValid())
		{
			SessionInterface->SyncLobbies();
			bWasHandled = true;
		}
	}

	return bWasHandled;
}
TSharedPtr<const FUniqueNetId> FOnlineIdentitySteam::CreateUniquePlayerId(uint8* Bytes, int32 Size)
{
	if (Bytes && Size == sizeof(uint64))
	{
		uint64* RawUniqueId = (uint64*)Bytes;
		CSteamID SteamId(*RawUniqueId);
		if (SteamId.IsValid())
		{
			return MakeShareable(new FUniqueNetIdSteam(SteamId));
		}
	}

	return NULL;
}
void FOnlineAsyncTaskSteamDeleteUserFile::Tick()
{
	bWasSuccessful = false;

	if (SteamRemoteStorage() && FileName.Len() > 0)
	{
		CSteamID SteamId(*(uint64*)UserId.GetBytes());
		if (SteamUser()->BLoggedOn() && SteamUser()->GetSteamID() == SteamId)
		{
			bool bCloudDeleteSuccess = true;
			if (bShouldCloudDelete)
			{
				// Remove the cloud flag, the file remains safely available on the local machine
				bCloudDeleteSuccess = SteamRemoteStorage()->FileForget(TCHAR_TO_UTF8(*FileName));
			}

			bool bLocalDeleteSuccess = true;
			if (bShouldLocallyDelete)
			{
				bLocalDeleteSuccess = false;
				// Only clear the tables if we're permanently deleting the file
				// Need to make sure nothing async is happening	first (this is a formality as nothing in Steam actually is)
				IOnlineUserCloudPtr UserCloud = Subsystem->GetUserCloudInterface();
				if (UserCloud->ClearFile(UserId, FileName))
				{
					// Permanent delete
					bLocalDeleteSuccess = SteamRemoteStorage()->FileDelete(TCHAR_TO_UTF8(*FileName));
					Subsystem->ClearUserCloudMetadata(UserId, FileName);
				}
			}

			bWasSuccessful = bCloudDeleteSuccess && bLocalDeleteSuccess;
		}	
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Can only delete cloud files for logged in user."));
		}
	}
	else
	{
		UE_LOG_ONLINE(Warning, TEXT("Steam remote storage API disabled."));
	}

	bIsComplete = true;
}
void FOnlineAsyncTaskSteamEnumerateUserFiles::Tick()
{
	bIsComplete = true;
	bWasSuccessful = false;

	if (SteamRemoteStorage())
	{
		CSteamID SteamId(*(uint64*)UserId.GetBytes());
		if (SteamUser()->BLoggedOn() && SteamUser()->GetSteamID() == SteamId)
		{
			//SteamSubsystem->GetUserCloudInterface()->DumpCloudState(UserId);

			FScopeLock ScopeLock(&Subsystem->UserCloudDataLock);

			// Get or create the user metadata entry and empty it
			FSteamUserCloudData* UserMetadata = Subsystem->GetUserCloudEntry(UserId);

			UserMetadata->CloudMetadata.Empty();

			// Fill in the metadata entries
			const int32 FileCount = (int32) SteamRemoteStorage()->GetFileCount();
			for (int32 FileIdx = 0; FileIdx < FileCount; FileIdx++)
			{
				int32 FileSize = 0;
				const char *FileName = SteamRemoteStorage()->GetFileNameAndSize(FileIdx, &FileSize);
				new (UserMetadata->CloudMetadata) FCloudFileHeader(UTF8_TO_TCHAR(FileName), UTF8_TO_TCHAR(FileName), int32(FileSize));

				//SteamSubsystem->GetUserCloudInterface()->DumpCloudFileState(UserId, FileName);
			}

			bWasSuccessful = true;
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Can only enumerate cloud files for logged in user."));
		}
	}
	else
	{
		UE_LOG_ONLINE(Warning, TEXT("Steam remote storage API disabled."));
	}
}
bool FOnlineAchievementsSteam::ResetAchievements(const FUniqueNetId& PlayerId)
{
	if (!bHaveConfiguredAchievements)
	{
		// we don't have achievements
		UE_LOG_ONLINE(Warning, TEXT("Steam achievements have not been configured in .ini"));
		return false;
	}

	FUniqueNetIdSteam SteamId(PlayerId);
	// check if this is our player (cannot report for someone else)
	if (SteamUser() == NULL || SteamUser()->GetSteamID() != SteamId)
	{
		// we don't have achievements
		UE_LOG_ONLINE(Warning, TEXT("Cannot clear Steam achievements for non-local player %s"), *PlayerId.ToString());
		return false;
	}

	const TArray<FOnlineAchievement> * PlayerAch = PlayerAchievements.Find(SteamId);
	if (NULL == PlayerAch)
	{
		// achievements haven't been read for a player
		UE_LOG_ONLINE(Warning, TEXT("Steam achievements have not been read for player %s"), *PlayerId.ToString());
		return false;
	}

	const int32 AchNum = PlayerAch->Num();
	for (int32 AchIdx = 0; AchIdx < AchNum; ++AchIdx)
	{
		SteamUserStats()->ClearAchievement(TCHAR_TO_UTF8(*(*PlayerAch)[ AchIdx ].Id));
	}

	// TODO: provide a separate method to just store stats?
	StatsInt->FlushLeaderboards(FName(TEXT("UNUSED")));
	return true;
};
/** 
 *  Update the backend with the currently defined settings
 *
 * @param World current running world instance
 * @param SessionName name of session to update published settings
 */
void UpdatePublishedSettings(UWorld* World, FNamedOnlineSession* Session)
{
	ISteamGameServer* SteamGameServerPtr = SteamGameServer();
	check(SteamGameServerPtr);

	// Copy the current settings so we can remove the ones used for well defined search parameters
	FOnlineSessionSettings TempSessionSettings = Session->SessionSettings;

	// Server name
	FString ServerName = Session->OwningUserName;
	SteamGameServerPtr->SetServerName(TCHAR_TO_UTF8(*ServerName));

	// Max user slots reported
	int32 NumTotalSlots = Session->SessionSettings.NumPublicConnections + Session->SessionSettings.NumPrivateConnections;
	SteamGameServerPtr->SetMaxPlayerCount(NumTotalSlots);

	// Region setting
	FString Region(TEXT(""));
	SteamGameServerPtr->SetRegion(TCHAR_TO_UTF8(*Region));

	// @TODO ONLINE Password protected or not
	SteamGameServerPtr->SetPasswordProtected(false);

	// Dedicated server or not
	SteamGameServerPtr->SetDedicatedServer(Session->SessionSettings.bIsDedicated ? true : false);

	// Map name
	FString MapName;
	if (TempSessionSettings.Get(SETTING_MAPNAME, MapName) && !MapName.IsEmpty())
	{
		SteamGameServerPtr->SetMapName(TCHAR_TO_UTF8(*MapName));
	}
	TempSessionSettings.Remove(SETTING_MAPNAME);

	// Bot Count
	int32 BotCount = 0;
	if (TempSessionSettings.Get(SETTING_NUMBOTS, BotCount))
	{
		SteamGameServerPtr->SetBotPlayerCount(BotCount);
	}
	TempSessionSettings.Remove(SETTING_NUMBOTS);

	// Update all the players names/scores
	if (World)
	{
		AGameState const* const GameState = World->GameState;
		if (GameState)
		{
			for (int32 PlayerIdx=0; PlayerIdx < GameState->PlayerArray.Num(); PlayerIdx++)
			{
				APlayerState const* const PlayerState = GameState->PlayerArray[PlayerIdx];
				if (PlayerState && PlayerState->UniqueId.IsValid())
				{
					CSteamID SteamId(*(uint64*)PlayerState->UniqueId->GetBytes());
					SteamGameServerPtr->BUpdateUserData(SteamId, TCHAR_TO_UTF8(*PlayerState->PlayerName), PlayerState->Score);
				}
			}
		}
	}

	// Get the advertised session settings out as Steam key/value pairs
	FSteamSessionKeyValuePairs AdvertisedKeyValuePairs;
	GetServerKeyValuePairsFromSession(Session, AdvertisedKeyValuePairs);

	if (Session->SessionInfo.IsValid())
	{
		FOnlineSessionInfoSteam* SessionInfo = (FOnlineSessionInfoSteam*)(Session->SessionInfo.Get());
		GetServerKeyValuePairsFromSessionInfo(SessionInfo, AdvertisedKeyValuePairs);
	}

	FString SessionFlags = GetSessionFlagsAsString(Session->SessionSettings);
	AdvertisedKeyValuePairs.Add(STEAMKEY_SESSIONFLAGS, SessionFlags);

	FString SessionBuildUniqueId = GetBuildIdAsSteamKey(Session->SessionSettings);

	GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, AdvertisedKeyValuePairs, EOnlineDataAdvertisementType::ViaOnlineService);

	FSteamSessionKeyValuePairs AuxKeyValuePairs;
	GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, AuxKeyValuePairs, EOnlineDataAdvertisementType::ViaPingOnly);
	
	FSteamSessionKeyValuePairs TempKeyValuePairs;
	GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, TempKeyValuePairs, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);

	AdvertisedKeyValuePairs.Append(TempKeyValuePairs);
	AuxKeyValuePairs.Append(TempKeyValuePairs);
	
	FString GameTagsString, GameDataString;

	// Start the game tags with the build id so search results can early out
	GameTagsString = SessionBuildUniqueId;

	// Create the properly formatted Steam string (ie key:value,key:value,key) for GameTags/GameData
	FSteamSessionKeyValuePairs::TConstIterator It(AdvertisedKeyValuePairs);
	if (It)
	{
		UE_LOG_ONLINE(Verbose, TEXT("Master Server Data (%s, %s)"), *It.Key(), *It.Value());
		FString NewKey = FString::Printf(TEXT("%s:%s"), *It.Key(), *It.Value());

		if (GameTagsString.Len() + NewKey.Len() < k_cbMaxGameServerTags)
		{
			GameTagsString = GameTagsString + "," + NewKey;
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Server setting %s overflows Steam SetGameTags call"), *NewKey);
		}

		if (NewKey.Len() < k_cbMaxGameServerGameData)
		{
			GameDataString = NewKey;
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Server setting %s overflows Steam SetGameData call"), *NewKey);
		}

		++It;
	}
	for (; It; ++It)
	{
		UE_LOG_ONLINE(Verbose, TEXT("Master Server Data (%s, %s)"), *It.Key(), *It.Value());
		FString NewKey = FString::Printf(TEXT(",%s:%s"), *It.Key(), *It.Value());
		if (GameTagsString.Len() + NewKey.Len() < k_cbMaxGameServerTags)
		{
			GameTagsString += NewKey;
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Server setting %s overflows Steam SetGameTags call"), *NewKey);
		}

		if (GameDataString.Len() + NewKey.Len() < k_cbMaxGameServerGameData)
		{
			GameDataString += NewKey;
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Server setting %s overflows Steam SetGameData call"), *NewKey);
		}
	}

	// Small and searchable game tags (returned in initial server query structure)
	if (GameTagsString.Len() > 0 && GameTagsString.Len() < k_cbMaxGameServerTags)
	{
		UE_LOG_ONLINE(Verbose, TEXT("SetGameTags(%s)"), *GameTagsString);
		SteamGameServerPtr->SetGameTags(TCHAR_TO_UTF8(*GameTagsString));
	}

	// Large and searchable game data (never returned)
	if (GameDataString.Len() > 0 && GameDataString.Len() < k_cbMaxGameServerGameData)
	{
		UE_LOG_ONLINE(Verbose, TEXT("SetGameData(%s)"), *GameDataString);
		SteamGameServerPtr->SetGameData(TCHAR_TO_UTF8(*GameDataString));
	}

	// @TODO ONLINE - distinguish between server side keys (SetGameData()) and client side keys (SetKeyValue())
	// Set the advertised filter keys (these can not be filtered at master-server level, only client side)
	SteamGameServerPtr->ClearAllKeyValues();

	// Key value pairs sent as rules (requires secondary RulesRequest call)
	for (FSteamSessionKeyValuePairs::TConstIterator AdvKeyIt(AdvertisedKeyValuePairs); AdvKeyIt; ++AdvKeyIt)
	{
		UE_LOG_ONLINE(Verbose, TEXT("Aux Server Data (%s, %s)"), *AdvKeyIt.Key(), *AdvKeyIt.Value());
		SteamGameServerPtr->SetKeyValue(TCHAR_TO_UTF8(*AdvKeyIt.Key()), TCHAR_TO_UTF8(*AdvKeyIt.Value()));
	}

	// Key value pairs sent as rules (requires secondary RulesRequest call)
	for (FSteamSessionKeyValuePairs::TConstIterator AuxKeyIt(AuxKeyValuePairs); AuxKeyIt; ++AuxKeyIt)
	{
		UE_LOG_ONLINE(Verbose, TEXT("Aux Server Data (%s, %s)"), *AuxKeyIt.Key(), *AuxKeyIt.Value());
		SteamGameServerPtr->SetKeyValue(TCHAR_TO_UTF8(*AuxKeyIt.Key()), TCHAR_TO_UTF8(*AuxKeyIt.Value()));
	}
}	
void FOnlineAchievementsSteam::WriteAchievements(const FUniqueNetId& PlayerId, FOnlineAchievementsWriteRef& WriteObject, const FOnAchievementsWrittenDelegate& Delegate)
{
	if (!bHaveConfiguredAchievements)
	{
		// we don't have achievements
		UE_LOG_ONLINE(Warning, TEXT("Steam achievements have not been configured in .ini"));

		WriteObject->WriteState = EOnlineAsyncTaskState::Failed;
		Delegate.ExecuteIfBound(PlayerId, false);
		return;
	}

	FUniqueNetIdSteam SteamId(PlayerId);
	// check if this is our player (cannot report for someone else)
	if (SteamUser() == NULL || SteamUser()->GetSteamID() != SteamId)
	{
		// we don't have achievements
		UE_LOG_ONLINE(Warning, TEXT("Cannot report Steam achievements for non-local player %s"), *PlayerId.ToString());

		WriteObject->WriteState = EOnlineAsyncTaskState::Failed;
		Delegate.ExecuteIfBound(PlayerId, false);
		return;
	}

	const TArray<FOnlineAchievement> * PlayerAch = PlayerAchievements.Find(SteamId);
	if (NULL == PlayerAch)
	{
		// achievements haven't been read for a player
		UE_LOG_ONLINE(Warning, TEXT("Steam achievements have not been read for player %s"), *PlayerId.ToString());

		WriteObject->WriteState = EOnlineAsyncTaskState::Failed;
		Delegate.ExecuteIfBound(PlayerId, false);
		return;
	}

	const int32 AchNum = PlayerAch->Num();
	for (FStatPropertyArray::TConstIterator It(WriteObject->Properties); It; ++It)
	{
		const FString AchievementId = It.Key().ToString();
		UE_LOG_ONLINE(Verbose, TEXT("WriteObject AchievementId: '%s'"), *AchievementId);
		for (int32 AchIdx = 0; AchIdx < AchNum; ++AchIdx)
		{
			if ((*PlayerAch)[ AchIdx ].Id == AchievementId)
			{
				// do not unlock it now, but after a successful write
#if !UE_BUILD_SHIPPING
				float Value = 0.0f;
				It.Value().GetValue(Value);
				if (Value <= 0.0f)
				{
					UE_LOG_ONLINE(Verbose, TEXT("Resetting achievement '%s'"), *AchievementId);
					SteamUserStats()->ClearAchievement(TCHAR_TO_UTF8(*AchievementId));
				}
				else
				{
#endif // !UE_BUILD_SHIPPING

					UE_LOG_ONLINE(Verbose, TEXT("Setting achievement '%s'"), *AchievementId);
					SteamUserStats()->SetAchievement(TCHAR_TO_UTF8(*AchievementId));

#if !UE_BUILD_SHIPPING
				}
#endif // !UE_BUILD_SHIPPING
			
				break;
			}
		}
	}

	StatsInt->WriteAchievementsInternal(SteamId, WriteObject, Delegate);
};
void FOnlineAsyncTaskSteamReadUserFile::Tick()
{
	// Going to be complete no matter what
	bIsComplete = true;

	if (SteamRemoteStorage() && FileName.Len() > 0)
	{
		CSteamID SteamId(*(uint64*)UserId.GetBytes());
		if (SteamUser()->BLoggedOn() && SteamUser()->GetSteamID() == SteamId)
		{
			// Currently don't support greater than 1 chunk
			const int32 FileSize = SteamRemoteStorage()->GetFileSize(TCHAR_TO_UTF8(*FileName));
			if (FileSize >= 0 && FileSize <= k_unMaxCloudFileChunkSize)
			{
				FScopeLock ScopeLock(&Subsystem->UserCloudDataLock);
				// Create or get the current entry for this file
				FSteamUserCloudData* UserCloud = Subsystem->GetUserCloudEntry(UserId);
				if (UserCloud)
				{
					FCloudFile* UserCloudFile = UserCloud->GetFileData(FileName, true);
					check(UserCloudFile);

					// Allocate and read in the file
					UserCloudFile->Data.Empty(FileSize);
					UserCloudFile->Data.AddUninitialized(FileSize);
					if (SteamRemoteStorage()->FileRead(TCHAR_TO_UTF8(*FileName), UserCloudFile->Data.GetData(), FileSize) == FileSize)
					{
						bWasSuccessful = true;
					}
					else
					{
						UserCloudFile->Data.Empty();
					}
				}
			}
			else
			{
				UE_LOG_ONLINE(Warning, TEXT("Requested file %s has invalid size %d."), *FileName, FileSize);
			}
		}	
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Can only read cloud files for logged in user."));
		}
	}
	else
	{
		UE_LOG_ONLINE(Warning, TEXT("Steam remote storage API disabled."));
	}

	{
		FScopeLock ScopeLock(&Subsystem->UserCloudDataLock);
		FSteamUserCloudData* UserCloud = Subsystem->GetUserCloudEntry(UserId);
		if (UserCloud)
		{
			FCloudFile* UserCloudFileData = UserCloud->GetFileData(FileName);
			if (UserCloudFileData)
			{
				UserCloudFileData->AsyncState = bWasSuccessful ? EOnlineAsyncTaskState::Done : EOnlineAsyncTaskState::Failed;
			}
		}
	}
}
bool FOnlineAsyncTaskSteamWriteUserFile::WriteUserFile(const FUniqueNetId& InUserId, const FString& InFileToWrite, const TArray<uint8>& InContents)
{
	bool bSuccess = false;
	if (InFileToWrite.Len() > 0 && InContents.Num() > 0)
	{
		if (SteamRemoteStorage() && FileName.Len() > 0)
		{
			CSteamID SteamId(*(uint64*)InUserId.GetBytes());
			if (SteamUser()->BLoggedOn() && SteamUser()->GetSteamID() == SteamId)
			{
				// Currently don't support greater than 1 chunk
				if (InContents.Num() < k_unMaxCloudFileChunkSize)
				{
					if (SteamRemoteStorage()->FileWrite(TCHAR_TO_UTF8(*InFileToWrite), InContents.GetData(), InContents.Num()))
					{
						FScopeLock ScopeLock(&Subsystem->UserCloudDataLock);
						FSteamUserCloudData* UserCloud = Subsystem->GetUserCloudEntry(InUserId);
						if (UserCloud)
						{
							// Update the metadata table to reflect this write (might be new entry)
							FCloudFileHeader* UserCloudFileMetadata = UserCloud->GetFileMetadata(InFileToWrite, true);
							check(UserCloudFileMetadata);

							UserCloudFileMetadata->FileSize = SteamRemoteStorage()->GetFileSize(TCHAR_TO_UTF8(*InFileToWrite));
							UserCloudFileMetadata->Hash = FString(TEXT("0"));

							// Update the file table to reflect this write
							FCloudFile* UserCloudFileData = UserCloud->GetFileData(InFileToWrite, true);
							check(UserCloudFileData);

							UserCloudFileData->Data = InContents;
							bSuccess = true;
						}
					}
					else
					{
						UE_LOG_ONLINE(Warning, TEXT("Failed to write file to Steam cloud \"%s\"."), *InFileToWrite);
					}
				}
				else
				{
					UE_LOG_ONLINE(Warning, TEXT("File too large %d to write to Steam cloud."), InContents.Num());
				}
			}
			else
			{
				UE_LOG_ONLINE(Warning, TEXT("Can only write cloud files for logged in user."));
			}
		}
		else
		{
			UE_LOG_ONLINE(Warning, TEXT("Steam remote storage API disabled."));
		}
	}

	{
		FScopeLock ScopeLock(&Subsystem->UserCloudDataLock);
		FSteamUserCloudData* UserCloud = Subsystem->GetUserCloudEntry(InUserId);
		if (UserCloud)
		{
			FCloudFile* UserCloudFileData = UserCloud->GetFileData(InFileToWrite, true);
			check(UserCloudFileData);
			UserCloudFileData->AsyncState = bSuccess ? EOnlineAsyncTaskState::Done : EOnlineAsyncTaskState::Failed;
		}
	}

	//SteamSubsystem->GetUserCloudInterface()->DumpCloudFileState(InUserId, InFileToWrite);
	return bSuccess;
}