2026-02-24 22:39:26 -05:00
// Fill out your copyright notice in the Description page of Project Settings.
# include "Game/ShooterGameMode.h"
2026-03-11 07:40:45 -04:00
# include "GenericPlatform/GenericPlatformMisc.h"
# include "Containers/UnrealString.h"
2026-03-10 22:38:12 -04:00
# include "GameLiftClpTypes.h"
# include "GameLift/GameLiftClp.h"
2026-03-11 07:40:45 -04:00
# include "openssl/sha.h"
2026-03-12 21:31:36 -04:00
# include "GameLiftServerSDK.h"
# include "GameLiftServerSDKModels.h"
# include "DSP/BufferDiagnostics.h"
# include "Misc/TypeContainer.h"
2026-02-24 22:39:26 -05:00
2026-03-10 22:38:12 -04:00
DEFINE_LOG_CATEGORY ( LogShooterGameMode ) ;
// Function implementations.
2026-03-05 10:00:22 -05:00
2026-02-24 22:39:26 -05:00
AShooterGameMode : : AShooterGameMode ( )
{
}
void AShooterGameMode : : BeginPlay ( )
{
Super : : BeginPlay ( ) ;
# if WITH_GAMELIFT
2026-03-04 09:54:18 -05:00
InitGameLift ( ) ;
2026-02-24 22:39:26 -05:00
# endif
}
2026-03-04 06:40:59 -05:00
void AShooterGameMode : : InitGame ( const FString & MapName , const FString & Options , FString & ErrorMessage )
2026-02-24 22:39:26 -05:00
{
2026-03-04 06:40:59 -05:00
Super : : InitGame ( MapName , Options , ErrorMessage ) ;
CachedCommandLine = FCommandLine : : Get ( ) ;
2026-03-12 07:13:47 -04:00
GameLiftConfig . ServerPort = cmdlineparser : : details : : GetConfiguredOrDefaultPort ( CachedCommandLine , TEXT ( " port " ) ) ;
if ( FParse : : Param ( * CachedCommandLine , TEXT ( " glAnywhereFleet " ) ) )
2026-03-05 10:00:22 -05:00
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " Fleet type: Anywhere " ) ;
2026-03-12 21:31:36 -04:00
GameLiftConfig . bIsAnywhereFleet = true ;
2026-03-12 07:13:47 -04:00
GameLiftConfig . bAllOptionsFound = GetAnywhereFleetParameters ( CachedCommandLine ) ;
LogAnywhereFleetParameters ( ) ;
2026-03-05 10:00:22 -05:00
}
else
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " Fleet type: EC2 " ) ;
// TODO: EC2 configuration
2026-02-24 22:39:26 -05:00
}
2026-03-10 22:38:12 -04:00
}
2026-03-04 09:54:18 -05:00
2026-03-10 22:38:12 -04:00
FString AShooterGameMode : : GetSHA256Hash ( const FString & InString )
{
2026-03-11 07:40:45 -04:00
if ( InString . IsEmpty ( ) )
return TEXT ( " e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 " ) ;
FSHA256Signature Hash ;
// Use OpenSSL to compute the SHA-256 hash
FTCHARToUTF8 Utf8 ( InString ) ;
const uint8 * Bytes = reinterpret_cast < const uint8 * > ( Utf8 . Get ( ) ) ;
uint32 Length = Utf8 . Length ( ) * sizeof ( UTF8CHAR ) ;
SHA256_CTX Sha256Context ;
SHA256_Init ( & Sha256Context ) ;
SHA256_Update ( & Sha256Context , Bytes , Length ) ;
SHA256_Final ( Hash . Signature , & Sha256Context ) ;
return Hash . ToString ( ) ;
2026-03-10 22:38:12 -04:00
}
2026-03-04 09:54:18 -05:00
2026-03-10 22:38:12 -04:00
void AShooterGameMode : : InitGameLift ( )
{
2026-03-12 21:31:36 -04:00
# if WITH_GAMELIFT
UE_LOGFMT ( LogShooterGameMode , Log , " Calling InitGameLift... " ) ;
// Get the module first
FGameLiftServerSDKModule * GameLiftSdkModule = & FModuleManager : : LoadModuleChecked < FGameLiftServerSDKModule > ( " GameLiftServerSDK " ) ;
2026-03-13 07:19:41 -04:00
FGameLiftGenericOutcome InitSdkOutcome ;
UE_LOGFMT ( LogShooterGameMode , Log , " Initializing the GameLift Server... " ) ;
2026-03-12 21:31:36 -04:00
if ( GameLiftConfig . bIsAnywhereFleet )
{
2026-03-13 07:19:41 -04:00
// 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 < int32 > ( PID ) ) ;
2026-03-12 21:31:36 -04:00
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 ) ;
2026-03-13 07:25:35 -04:00
// InitSDK will establish a local connection with GameLift's agent to enable further communication.
2026-03-13 07:19:41 -04:00
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 ;
}
2026-03-12 21:31:36 -04:00
2026-03-13 07:19:41 -04:00
ProcessParameters = MakeShared < FProcessParameters > ( ) ;
//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()
2026-03-13 07:25:35 -04:00
// ReSharper disable once CppPassValueParameterByConstReference - not safe to do with async lambdas
2026-03-13 07:19:41 -04:00
ProcessParameters - > OnStartGameSession . BindLambda ( [ = ] ( Aws : : GameLift : : Server : : Model : : GameSession InGameSession )
{
2026-03-13 07:25:35 -04:00
const FString GameSessionId = FString ( InGameSession . GetGameSessionId ( ) ) ;
2026-03-13 07:19:41 -04:00
UE_LOG ( LogShooterGameMode , Log , TEXT ( " GameSession Initializing: %s " ) , * GameSessionId ) ;
GameLiftSdkModule - > ActivateGameSession ( ) ;
} ) ;
2026-03-12 21:31:36 -04:00
//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.
2026-03-13 07:25:35 -04:00
//In this case, we simply tell Amazon GameLift Servers we are indeed going to shut down.
2026-03-13 07:19:41 -04:00
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 ) ;
2026-03-12 21:31:36 -04:00
}
2026-03-13 07:19:41 -04:00
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 ) ;
2026-03-12 21:31:36 -04:00
}
2026-03-13 07:19:41 -04:00
}
GLog - > Flush ( ) ;
FGenericPlatformMisc : : RequestExit ( false ) ;
} ) ;
2026-03-12 21:31:36 -04:00
2026-03-13 07:25:35 -04:00
//This is the Health Check callback.
2026-03-12 21:31:36 -04:00
//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!
2026-03-13 07:19:41 -04:00
ProcessParameters - > OnHealthCheck . BindLambda ( [ ] ( )
{
UE_LOG ( LogShooterGameMode , Log , TEXT ( " Performing Health Check " ) ) ;
return true ;
} ) ;
2026-03-12 21:31:36 -04:00
2026-03-13 07:19:41 -04:00
ProcessParameters - > port = GameLiftConfig . ServerPort ;
2026-03-12 21:31:36 -04:00
2026-03-13 07:19:41 -04:00
TArray < FString > Logfiles ;
Logfiles . Add ( TEXT ( " FPSTemplate/Saved/Log/server.log " ) ) ;
ProcessParameters - > logParameters = Logfiles ;
2026-03-12 21:31:36 -04:00
2026-03-13 07:19:41 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " Calling Process Ready... " ) ;
FGameLiftGenericOutcome ProcessReadyOutcome = GameLiftSdkModule - > ProcessReady ( * ProcessParameters ) ;
2026-03-12 21:31:36 -04:00
2026-03-13 07:19:41 -04:00
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 ;
2026-03-12 21:31:36 -04:00
}
2026-03-13 07:19:41 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " InitGameLift completed! " ) ;
2026-03-12 21:31:36 -04:00
# endif
2026-03-10 22:38:12 -04:00
}
2026-03-12 07:13:47 -04:00
bool AShooterGameMode : : GetAnywhereFleetParameters ( const FString & CommandLineString )
2026-03-10 22:38:12 -04:00
{
bool bAllAnywhereFleetParametersValid = true ;
2026-03-12 07:13:47 -04:00
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
2026-03-10 22:38:12 -04:00
{
2026-03-12 07:13:47 -04:00
return cmdlineparser : : details : : CLI_TOKENS [ static_cast < int32 > ( Result . Token ) ] ;
} ;
UE_LOGFMT ( LogShooterGameMode , Log , " Anywhere Fleet Parameters: " ) ;
if ( GameLiftConfig . AuthTokenResult . bIsValid )
{
2026-03-12 21:31:36 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " AuthToken: {0} " , GetValueOrHash ( GameLiftConfig . AuthTokenResult . Value ) ) ;
2026-02-24 22:39:26 -05:00
}
else
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT (
LogShooterGameMode ,
Error ,
" AuthToken: {0} " ,
FString : : Format (
* GameLiftConfig . AuthTokenResult . ErrorMessage ,
{
FStringFormatArg ( GetTokenName ( GameLiftConfig . AuthTokenResult ) ) ,
2026-03-12 21:31:36 -04:00
FStringFormatArg ( GetValueOrHash ( GameLiftConfig . AuthTokenResult . Value ) ) ,
2026-03-12 07:13:47 -04:00
}
)
) ;
2026-02-24 22:39:26 -05:00
}
2026-03-12 07:13:47 -04:00
if ( GameLiftConfig . FleetIdResult . bIsValid )
2026-03-01 09:29:41 -05:00
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " FleetId: {0} " , GameLiftConfig . FleetIdResult . Value ) ;
2026-03-01 09:29:41 -05:00
}
else
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT (
LogShooterGameMode ,
Error ,
" FleetId: {0} " ,
FString : : Format (
* GameLiftConfig . FleetIdResult . ErrorMessage ,
{
FStringFormatArg ( GetTokenName ( GameLiftConfig . FleetIdResult ) ) ,
FStringFormatArg ( GameLiftConfig . FleetIdResult . Value )
}
)
) ;
2026-03-01 09:29:41 -05:00
}
2026-02-24 22:39:26 -05:00
2026-03-12 07:13:47 -04:00
if ( GameLiftConfig . HostIdResult . bIsValid )
2026-03-01 09:29:41 -05:00
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " HostId: {0} " , GameLiftConfig . HostIdResult . Value ) ;
2026-03-01 09:29:41 -05:00
}
2026-03-10 22:38:12 -04:00
else
2026-03-04 09:54:18 -05:00
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT (
LogShooterGameMode ,
Error ,
" HostId: {0} " ,
FString : : Format (
* GameLiftConfig . HostIdResult . ErrorMessage ,
{
FStringFormatArg ( GetTokenName ( GameLiftConfig . HostIdResult ) ) ,
FStringFormatArg ( GameLiftConfig . HostIdResult . Value )
}
)
) ;
2026-03-04 09:54:18 -05:00
}
2026-03-05 10:00:22 -05:00
2026-03-12 07:13:47 -04:00
if ( GameLiftConfig . WebSocketUrlResult . bIsValid )
2026-03-05 10:00:22 -05:00
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " WebSocketUrl: {0} " , GameLiftConfig . WebSocketUrlResult . Value ) ;
2026-03-05 10:00:22 -05:00
}
else
{
2026-03-12 07:13:47 -04:00
UE_LOGFMT (
LogShooterGameMode ,
Error ,
" WebSocketUrl: {0} " ,
FString : : Format (
* GameLiftConfig . WebSocketUrlResult . ErrorMessage ,
{
FStringFormatArg ( GetTokenName ( GameLiftConfig . WebSocketUrlResult ) ) ,
FStringFormatArg ( GameLiftConfig . WebSocketUrlResult . Value )
}
)
) ;
2026-03-05 10:00:22 -05:00
}
2026-03-12 07:13:47 -04:00
2026-03-10 22:38:12 -04:00
UE_LOGFMT ( LogShooterGameMode , Log , " Server Port: {0} " , GameLiftConfig . ServerPort ) ;
2026-03-12 07:13:47 -04:00
if ( GameLiftConfig . bAllOptionsFound )
{
UE_LOGFMT ( LogShooterGameMode , Log , " Anywhere Fleet Parameters Loaded Successfully.... " ) ;
}
else
{
UE_LOGFMT ( LogShooterGameMode , Error , " Anywhere Fleet Parameters Load FAILED.... " ) ;
}
2026-03-12 21:31:36 -04:00
}
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
}