// Fill out your copyright notice in the Description page of Project Settings. #include "Game/ShooterGameMode.h" #include "GenericPlatform/GenericPlatformMisc.h" #include "Containers/UnrealString.h" #include "GameLiftClpTypes.h" #include "GameLift/GameLiftClp.h" #include "openssl/sha.h" #include "GameLiftServerSDK.h" #include "GameLiftServerSDKModels.h" DEFINE_LOG_CATEGORY(LogShooterGameMode); // Function implementations. AShooterGameMode::AShooterGameMode() { } void AShooterGameMode::BeginPlay() { Super::BeginPlay(); #if WITH_GAMELIFT InitGameLift(); #endif } void AShooterGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); CachedCommandLine = FCommandLine::Get(); const FPortResult PortResult = cmdlineparser::details::GetConfiguredOrDefaultPort(CachedCommandLine, TEXT("port"));; if (PortResult.bUsedDefaultPort) { UE_LOGFMT (LogShooterGameMode, Warning, "{0}", PortResult.WarningMessage); } GameLiftConfig.ServerPort = PortResult.Port; if (FParse::Param(*CachedCommandLine, TEXT("glAnywhereFleet"))) { UE_LOGFMT(LogShooterGameMode, Log, "Fleet type: Anywhere"); GameLiftConfig.bIsAnywhereFleet = true; GameLiftConfig.bAllOptionsFound = GetAnywhereFleetParameters(CachedCommandLine); LogAnywhereFleetParameters(); } else { UE_LOGFMT(LogShooterGameMode, Log, "Fleet type: EC2"); // TODO: EC2 configuration } } FString AShooterGameMode::GetSHA256Hash(const FString& InString) { if (InString.IsEmpty()) return TEXT("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); FSHA256Signature Hash; // Use OpenSSL to compute the SHA-256 hash FTCHARToUTF8 Utf8(InString); const uint8* Bytes = reinterpret_cast(Utf8.Get()); uint32 Length = Utf8.Length(); SHA256_CTX Sha256Context; SHA256_Init(&Sha256Context); SHA256_Update(&Sha256Context, Bytes,Length); SHA256_Final(Hash.Signature, &Sha256Context); return Hash.ToString(); } void AShooterGameMode::InitGameLift() { #if WITH_GAMELIFT UE_LOGFMT(LogShooterGameMode, Log, "Calling InitGameLift..."); // Get the module first FGameLiftServerSDKModule* GameLiftSdkModule = &FModuleManager::LoadModuleChecked("GameLiftServerSDK"); FGameLiftGenericOutcome InitSdkOutcome; UE_LOGFMT(LogShooterGameMode, Log, "Initializing the GameLift Server..."); if (GameLiftConfig.bIsAnywhereFleet) { // Define the server parameters for an Anywhere fleet. These are not needed for a GameLift managed EC2 fleet. FServerParameters ServerParametersForAnywhere; uint32 PID = FPlatformProcess::GetCurrentProcessId(); FString PIDString = FString::FromInt(static_cast(PID)); UE_LOGFMT(LogShooterGameMode, Log, "Configuring server parameters for Anywhere..."); // If AnywhereFleets are being used load the command line arguments parsed earlier. ServerParametersForAnywhere.m_webSocketUrl = TCHAR_TO_UTF8(*GameLiftConfig.WebSocketUrlResult.Value); ServerParametersForAnywhere.m_fleetId = TCHAR_TO_UTF8((*GameLiftConfig.FleetIdResult.Value)); ServerParametersForAnywhere.m_hostId = TCHAR_TO_UTF8(*GameLiftConfig.HostIdResult.Value); ServerParametersForAnywhere.m_processId = PIDString; ServerParametersForAnywhere.m_authToken = TCHAR_TO_UTF8(*GameLiftConfig.AuthTokenResult.Value); LogServerParameters(ServerParametersForAnywhere); // InitSDK will establish a local connection with GameLift's agent to enable further communication. InitSdkOutcome = GameLiftSdkModule->InitSDK(ServerParametersForAnywhere); } else { InitSdkOutcome = GameLiftSdkModule->InitSDK(); } if (InitSdkOutcome.IsSuccess()) { UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_GREEN); UE_LOG(LogShooterGameMode, Log, TEXT("GameLift InitSDK succeeded!")); UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE); } else { UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_RED); UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: InitSDK failed : (")); FGameLiftError GameLiftError = InitSdkOutcome.GetError(); UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: %s"), *GameLiftError.m_errorMessage); UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE); return; } ProcessParameters = MakeShared(); //When a game session is created, Amazon GameLift Servers sends an activation request to the game server and passes along the game session object containing game properties and other settings. //Here is where a game server should take action based on the game session object. //Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession() // ReSharper disable once CppPassValueParameterByConstReference - not safe to do with async lambdas ProcessParameters->OnStartGameSession.BindLambda([=](Aws::GameLift::Server::Model::GameSession InGameSession) { const FString GameSessionId = FString(InGameSession.GetGameSessionId()); UE_LOG(LogShooterGameMode, Log, TEXT("GameSession Initializing: %s"), *GameSessionId); GameLiftSdkModule->ActivateGameSession(); }); //OnProcessTerminate callback. Amazon GameLift Servers will invoke this callback before shutting down an instance hosting this game server. //It gives this game server a chance to save its state, communicate with services, etc., before being shut down. //In this case, we simply tell Amazon GameLift Servers we are indeed going to shut down. ProcessParameters->OnTerminate.BindLambda([=]() { UE_LOG(LogShooterGameMode, Log, TEXT("Game Server Process is terminating")); // First call ProcessEnding() FGameLiftGenericOutcome processEndingOutcome = GameLiftSdkModule->ProcessEnding(); // Then call Destroy() to free the SDK from memory FGameLiftGenericOutcome destroyOutcome = GameLiftSdkModule->Destroy(); // Exit the process with success or failure if (processEndingOutcome.IsSuccess() && destroyOutcome.IsSuccess()) { UE_LOG(LogShooterGameMode, Log, TEXT("Server process ending successfully")); } else { if (!processEndingOutcome.IsSuccess()) { const FGameLiftError& error = processEndingOutcome.GetError(); UE_LOG(LogShooterGameMode, Error, TEXT("ProcessEnding() failed. Error: %s"), error.m_errorMessage.IsEmpty() ? TEXT("Unknown error") : *error.m_errorMessage); } if (!destroyOutcome.IsSuccess()) { const FGameLiftError& error = destroyOutcome.GetError(); UE_LOG(LogShooterGameMode, Error, TEXT("Destroy() failed. Error: %s"), error.m_errorMessage.IsEmpty() ? TEXT("Unknown error") : *error.m_errorMessage); } } GLog->Flush(); FGenericPlatformMisc::RequestExit(false); }); //This is the Health Check callback. //Amazon GameLift Servers will invoke this callback every 60 seconds or so. //Here, a game server might want to check the health of dependencies and such. //Simply return true if healthy, false otherwise. //The game server has 60 seconds to respond with its health status. Amazon GameLift Servers will default to 'false' if the game server doesn't respond in time. //In this case, we're always healthy! ProcessParameters->OnHealthCheck.BindLambda([]() { UE_LOG(LogShooterGameMode, Log, TEXT("Performing Health Check")); return true; }); ProcessParameters->port = GameLiftConfig.ServerPort; TArray Logfiles; Logfiles.Add(TEXT("FPSTemplate/Saved/Log/server.log")); ProcessParameters->logParameters = Logfiles; UE_LOGFMT(LogShooterGameMode, Log, "Calling Process Ready..."); FGameLiftGenericOutcome ProcessReadyOutcome = GameLiftSdkModule->ProcessReady(*ProcessParameters); if (ProcessReadyOutcome.IsSuccess()) { UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_GREEN); UE_LOG(LogShooterGameMode, Log, TEXT("Process Ready!")); UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE); } else { UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_RED); UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: Process Ready Failed!")); FGameLiftError ProcessReadyError = ProcessReadyOutcome.GetError(); UE_LOG(LogShooterGameMode, Log, TEXT("ERROR: %s"), *ProcessReadyError.m_errorMessage); UE_LOG(LogShooterGameMode, SetColor, TEXT("%s"), COLOR_NONE); return; } UE_LOGFMT(LogShooterGameMode, Log, "InitGameLift completed!"); #endif } bool AShooterGameMode::GetAnywhereFleetParameters(const FString& CommandLineString) { bool bAllAnywhereFleetParametersValid = true; GameLiftConfig.AuthTokenResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::AuthToken); if (!GameLiftConfig.AuthTokenResult.bIsValid) bAllAnywhereFleetParametersValid = false; GameLiftConfig.FleetIdResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::FleetId); if (!GameLiftConfig.FleetIdResult.bIsValid) bAllAnywhereFleetParametersValid = false; GameLiftConfig.HostIdResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::HostId); if (!GameLiftConfig.HostIdResult.bIsValid) bAllAnywhereFleetParametersValid = false; GameLiftConfig.WebSocketUrlResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::WebsocketUrl); if (!GameLiftConfig.WebSocketUrlResult.bIsValid) bAllAnywhereFleetParametersValid = false; return bAllAnywhereFleetParametersValid; } void AShooterGameMode::LogAnywhereFleetParameters() { // Lambda for getting the token name from token auto GetTokenName = [](const cmdlineparser::details::FParseResult& Result) -> FString { return cmdlineparser::details::CLI_TOKENS[static_cast(Result.Token)]; }; UE_LOGFMT(LogShooterGameMode, Log, "Anywhere Fleet Parameters:"); if (GameLiftConfig.AuthTokenResult.bIsValid) { UE_LOGFMT(LogShooterGameMode, Log, "AuthToken: {0}", GetValueOrHash(GameLiftConfig.AuthTokenResult.Value)); } else { UE_LOGFMT( LogShooterGameMode, Error, "AuthToken: {0}", FString::Format( *GameLiftConfig.AuthTokenResult.ErrorMessage, { FStringFormatArg(GetTokenName(GameLiftConfig.AuthTokenResult)), FStringFormatArg(GetValueOrHash(GameLiftConfig.AuthTokenResult.Value)), } ) ); } if (GameLiftConfig.FleetIdResult.bIsValid) { UE_LOGFMT(LogShooterGameMode, Log, "FleetId: {0}", GameLiftConfig.FleetIdResult.Value); } else { UE_LOGFMT( LogShooterGameMode, Error, "FleetId: {0}", FString::Format( *GameLiftConfig.FleetIdResult.ErrorMessage, { FStringFormatArg(GetTokenName(GameLiftConfig.FleetIdResult)), FStringFormatArg(GameLiftConfig.FleetIdResult.Value) } ) ); } if (GameLiftConfig.HostIdResult.bIsValid) { UE_LOGFMT(LogShooterGameMode, Log, "HostId: {0}", GameLiftConfig.HostIdResult.Value); } else { UE_LOGFMT( LogShooterGameMode, Error, "HostId: {0}", FString::Format( *GameLiftConfig.HostIdResult.ErrorMessage, { FStringFormatArg(GetTokenName(GameLiftConfig.HostIdResult)), FStringFormatArg(GameLiftConfig.HostIdResult.Value) } ) ); } if (GameLiftConfig.WebSocketUrlResult.bIsValid) { UE_LOGFMT(LogShooterGameMode, Log, "WebSocketUrl: {0}", GameLiftConfig.WebSocketUrlResult.Value); } else { UE_LOGFMT( LogShooterGameMode, Error, "WebSocketUrl: {0}", FString::Format( *GameLiftConfig.WebSocketUrlResult.ErrorMessage, { FStringFormatArg(GetTokenName(GameLiftConfig.WebSocketUrlResult)), FStringFormatArg(GameLiftConfig.WebSocketUrlResult.Value) } ) ); } UE_LOGFMT(LogShooterGameMode, Log, "Server Port: {0}", GameLiftConfig.ServerPort); if (GameLiftConfig.bAllOptionsFound) { UE_LOGFMT(LogShooterGameMode, Log, "Anywhere Fleet Parameters Loaded Successfully...."); } else { UE_LOGFMT(LogShooterGameMode, Error, "Anywhere Fleet Parameters Load FAILED...."); } } void AShooterGameMode::LogServerParameters(const FServerParameters& InServerParameters) { UE_LOGFMT(LogShooterGameMode, Log, ">>>> WebSocket URL: {WebSocketUrl}", InServerParameters.m_webSocketUrl); UE_LOGFMT(LogShooterGameMode, Log, ">>>> Fleet ID: {FleetId}", InServerParameters.m_fleetId); UE_LOGFMT(LogShooterGameMode, Log, ">>>> Process ID: {ProcessId}", InServerParameters.m_processId); UE_LOGFMT(LogShooterGameMode, Log, ">>>> Host ID (Compute Name): {HostId}", InServerParameters.m_hostId); UE_LOGFMT(LogShooterGameMode, Log, ">>>> Auth Token: {AuthToken}", GetValueOrHash(InServerParameters.m_authToken)); if (!InServerParameters.m_awsRegion.IsEmpty()) { UE_LOGFMT(LogShooterGameMode, Log, ">>>> Aws Region: {AwsRegion}", InServerParameters.m_awsRegion); } if (!InServerParameters.m_accessKey.IsEmpty()) { UE_LOGFMT(LogShooterGameMode, Log, ">>>> Access Key: {AccessKey}", GetValueOrHash(InServerParameters.m_accessKey)); } if (!InServerParameters.m_secretKey.IsEmpty()) { UE_LOGFMT(LogShooterGameMode, Log, ">>>> Secret Key: {SecretKey}", GetValueOrHash(InServerParameters.m_secretKey)); } if (!InServerParameters.m_sessionToken.IsEmpty()) { UE_LOGFMT(LogShooterGameMode, Log, ">>>> Session Token: {SessionToken}", GetValueOrHash(InServerParameters.m_sessionToken)); } } FString AShooterGameMode::GetValueOrHash(const FString& Value) { #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT return Value; #else return TEXT("HASH:") + GetSHA256Hash(Value); #endif }