// Fill out your copyright notice in the Description page of Project Settings. #include "Game/ShooterGameMode.h" #include "Logging/LogMacros.h" #include "Logging/StructuredLog.h" #if WITH_GAMELIFT #include "GameLiftServerSDK.h" #endif DEFINE_LOG_CATEGORY(LogShooterGameMode) namespace GameLiftValidators { const FRegexPattern FleetIdPattern(TEXT("^[a-z]*fleet-[a-zA-Z0-9\\-]+$")); const int32 FleetIdLengthMin = 1; const int32 FleetIdLengthMax = 128; const FString ServerRegionPattern(TEXT("^[a-z]{2}-[a-z]+-[0-9]$")); const int32 ServerRegionLengthMin = 3; const int32 ServerRegionLengthMax = 16; const FString WebSocketUrlPattern(TEXT("^(ws|wss):\\/\\/([0-9a-zA-Z.-]+)(:([1-9][0-9]{0,4}))?(\\/.*)?$")); const int32 WebSocketUrlLengthMin = 1; const int32 WebSocketUrlLengthMax = 128; const FString AuthTokenPattern(TEXT("^[A-Za-z0-9+/=]+$")); const int32 AuthTokenLengthMin = 20; const int32 AuthTokenLengthMax = 2048; const FString HostIdPattern(TEXT("^[a-z]*host-[a-zA-Z0-9\\-]+$")); const int32 HostIdLengthMin = 1; const int32 HostIdLengthMax = 128; const int32 ServerPortMin = 1026; const int32 ServerPortMax = 60000; const int32 WebSocketPortMin = 1; const int32 WebSocketPortMax = 60000; bool IsStringValid(const FString& Value, const FRegexPattern* OptionalPattern, const int32 Min, const int32 Max) { if (Value.IsEmpty()) return false; if (OptionalPattern) { return FRegexMatcher(*OptionalPattern, Value).FindNext() && Value.Len() >= Min && Value.Len() <= Max; } else { return Value.Len() >= Min && Value.Len() <= Max; } } bool IsNumberValid(const int32 Value, const int32 Min, const int32 Max) { return Value >= Min && Value <= Max; } } AShooterGameMode::AShooterGameMode() { UE_LOG(LogShooterGameMode, Log, TEXT("Initializing ShooterGameMode...")); } 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); UE_LOG(LogShooterGameMode, Log, TEXT("[%s] Parsing CLI"), *FDateTime::UtcNow().ToString(TEXT("%Y%m%d-%H%M%S"))); CachedCommandLine = FCommandLine::Get(); // Parsing of Command Line GameLiftConfig.bDebugMode = FParse::Param(*CachedCommandLine, TEXT("Debug")); if (GameLiftConfig.bDebugMode) { UE_LOG(LogShooterGameMode, Log, TEXT("Debug mode: ENABLED")); #if UE_BUILD_DEBUG UE_LOG(LogShooterGameMode, Log, TEXT("Command Line Arguments: %s"), *CachedCommandLine); #endif } GameLiftConfig.ServerPort = GetConfiguredOrDefaultPort(); GameLiftConfig.bIsAnywhereFleet = FParse::Param(*CachedCommandLine, TEXT("glAnywhere")); if (GameLiftConfig.bIsAnywhereFleet) { UE_LOGFMT(LogShooterGameMode, Log, "GameLift Anywhere Fleet Command Line Parsing"); UE_LOGFMT(LogShooterGameMode, Log, "======Command Line Parameters======"); UE_LOGFMT(LogShooterGameMode, Log, "==================================="); } else { } } void AShooterGameMode::InitGame_original(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); UE_LOG(LogShooterGameMode, Log, TEXT("[%s] Parsing CLI"), *FDateTime::UtcNow().ToString(TEXT("%Y%m%d-%H%M%S"))); CachedCommandLine = FCommandLine::Get(); bool bIsCriticalError = false; if (bDebugMode) { UE_LOG(LogShooterGameMode, Log, TEXT("Debug mode: ENABLED")); #if UE_BUILD_DEBUG UE_LOG(LogShooterGameMode, Log, TEXT("Command Line Arguments: %s"), *CachedCommandLine); #endif } ServerPort = GetConfiguredOrDefaultPort(); bIsAnywhereFleet = FParse::Param(*CachedCommandLine, TEXT("glAnywhere")); if (bIsAnywhereFleet) { UE_LOG(LogShooterGameMode, Log, TEXT("GameLift Anywhere Fleet Command Line Parsing")); UE_LOG(LogShooterGameMode, Log, TEXT("======Command Line Parameters======")); if (!FParse::Value(*CachedCommandLine, TEXT("fleetID="), FleetId)) { UE_LOG(LogShooterGameMode, Error, TEXT("FleetId Missing in Command Line")); bIsCriticalError = true; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Fleet ID: %s"), *FleetId); } if (!FParse::Value(*CachedCommandLine, TEXT("authtoken="), AuthToken)) { UE_LOG(LogShooterGameMode, Error, TEXT("AuthToken Missing in Command Line")); bIsCriticalError = true; } else { if (bDebugMode) { FString TokenHash = GetSHA256Hash(AuthToken); UE_LOG(LogShooterGameMode, Log, TEXT("AuthToken: %s"), *TokenHash); } else { UE_LOG(LogShooterGameMode, Log, TEXT("AuthToken Length: %d"), AuthToken.Len()); } } if (!FParse::Value(*CachedCommandLine, TEXT("hostId="), HostId)) { UE_LOG(LogShooterGameMode, Error, TEXT("HostId Missing in Command Line")); bIsCriticalError = true; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Host ID: %s"), *HostId); } if (!FParse::Value(*CachedCommandLine, TEXT("websocketUrl="), WebSocketUrl)) { UE_LOG(LogShooterGameMode, Error, TEXT("WebSocketUrl Missing in Command Line")); bIsCriticalError = true; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Websocket URL: %s"), *WebSocketUrl); bool bValidWebSocket = WebSocketUrl.StartsWith(TEXT("wss://")) || WebSocketUrl.StartsWith(TEXT("ws://")); if (bValidWebSocket) { int32 ColonPos = WebSocketUrl.Find(TEXT(":"), ESearchCase::CaseSensitive); int32 SlashPos = WebSocketUrl.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ColonPos + 1); int32 ParsedPort = 443; // Default wss if (WebSocketUrl.StartsWith(TEXT("ws://"))) ParsedPort = 80; if (ColonPos != INDEX_NONE && SlashPos != INDEX_NONE) { FString PortStr = WebSocketUrl.Mid(ColonPos + 1, SlashPos - ColonPos - 1); int32 ExplicitPort = FCString::Atoi(*PortStr); if (ExplicitPort > 1024 && ExplicitPort <= 65535) // Privileged ports OK for servers { ParsedPort = ExplicitPort; } else { bValidWebSocket = false; // Invalid explicit port } } UE_LOG(LogShooterGameMode, Log, TEXT("WebSocket Parsed Port: %d"), ParsedPort); } if (!bValidWebSocket) { UE_LOG(LogShooterGameMode, Error, TEXT("Invalid WebSocketUrl: %s"), *WebSocketUrl); bIsCriticalError = true; } } if (ServerPort == 0) { UE_LOG(LogShooterGameMode, Error, TEXT("Invalid or Missing Server Port Number. Shutting Down.")); bIsCriticalError = true; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Port: %d"), ServerPort); } if (!FParse::Value(*CachedCommandLine, TEXT("serverRegion="), ServerRegion)) { UE_LOG(LogShooterGameMode, Warning, TEXT("ServerRegion Missing in Command Line")); } else { UE_LOG(LogShooterGameMode, Log, TEXT(">>>Server Region: %s"), *ServerRegion); } UE_LOG(LogShooterGameMode, Log, TEXT("===================================")); } else { UE_LOG(LogShooterGameMode, Log, TEXT("GameLift EC2 Fleet")); if (ServerPort == 0) { UE_LOG(LogShooterGameMode, Error, TEXT("Invalid or Missing Server Port Number. Shutting Down.")); bIsCriticalError = true; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Port: %d"), ServerPort); } } if (bIsCriticalError) { UE_LOG(LogShooterGameMode, Error, TEXT("Critical Missing or Invalid Arguments in Command Line. Shutting Down.")); FPlatformMisc::RequestExit(true); } else { UE_LOG(LogShooterGameMode, Log, TEXT("Command Line Parsed Successfully.")); } } int32 AShooterGameMode::GetConfiguredOrDefaultPort() const { // Default Unreal Engine listen/dedicated server port constexpr int32 DefaultPort = 7777; int32 CmdPort = DefaultPort; // Check if a port was passed via command line: -port=xxxx if (FParse::Value(*CachedCommandLine, TEXT("port="), CmdPort) && GameLiftValidators::IsNumberValid(CmdPort, GameLiftValidators::ServerPortMin, GameLiftValidators::ServerPortMax)) { return CmdPort; } UE_LOGFMT(LogShooterGameMode, Warning, "Invalid/Missing port in command line - using {0}", DefaultPort); return DefaultPort; } FString AShooterGameMode::GetSHA256Hash(const FString& Input) { FTCHARToUTF8 Utf8Input(Input); FSHA256Signature Hash; if (FPlatformMisc::GetSHA256Signature(reinterpret_cast(Utf8Input.Get()), Utf8Input.Length(), Hash)) { return Hash.ToString(); } return FString::Printf(TEXT("Fail_%dbytes"), Input.Len()); } bool AShooterGameMode::ParseAndOutputResult(const FString& InString, FString& OutValue, bool bRequired) { // Returns True if it is a critical error, returns false if (!FParse::Value(*InString, TEXT("hostId="), HostId)) { UE_LOG(LogShooterGameMode, Error, TEXT("HostId Missing in Command Line")); return bRequired; } else { UE_LOG(LogShooterGameMode, Log, TEXT("Host ID: %s"), *HostId); return false; } } bool AShooterGameMode::ValidateFleetId(const FString& InFleetId) { // FRegexMatcher Matcher(FleetIdPattern, InFleetId); // return Matcher.IsMatch() && // InFleetId.Len() >= 10 && // InFleetId.Len() < 128; return false; } void AShooterGameMode::InitGameLift() { #if WITH_GAMELIFT //TODO: Need to write later, working on parser first. #else UE_LOGFMT(LogShooterGameMode, Warning, "GameLift disabled"); #endif }