diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 2e96c4eb..9f9551ab 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -179,6 +179,10 @@ DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,bUseMBPOu MinDeltaVelocityForHitEvents=0.000000 ChaosSettings=(DefaultThreadingModel=TaskGraph,DedicatedThreadTickMode=VariableCappedWithTarget,DedicatedThreadBufferMode=Double) +[URL] +DefaultPort=7777 [CoreRedirects] -+PropertyRedirects=(OldName="/Script/FPSTemplate.CommandLineArgs.Arguments",NewName="/Script/FPSTemplate.CommandLineArgs.CmdArguments") \ No newline at end of file ++EnumRedirects=(OldName="/Script/FPSTemplate.EvalidationError",NewName="/Script/FPSTemplate.EValidationError") ++FunctionRedirects=(OldName="/Script/FPSTemplate.GameLiftValidators.LogValidataErrorMessage",NewName="/Script/FPSTemplate.GameLiftValidators.LogValidationErrorMessage") +;+PropertyRedirects=(OldName="/Script/FPSTemplate.CommandLineArgs.Arguments",NewName="/Script/FPSTemplate.CommandLineArgs.CmdArguments") \ No newline at end of file diff --git a/Source/FPSTemplate/FPSTemplate.Build.cs b/Source/FPSTemplate/FPSTemplate.Build.cs index a9ecc774..20f90c2f 100644 --- a/Source/FPSTemplate/FPSTemplate.Build.cs +++ b/Source/FPSTemplate/FPSTemplate.Build.cs @@ -1,6 +1,8 @@ // Copyright Epic Games, Inc. All Rights Reserved. +using System.IO; using UnrealBuildTool; +using System.Linq; public class FPSTemplate : ModuleRules { @@ -8,9 +10,23 @@ public class FPSTemplate : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "PhysicsCore" }); + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput", + "PhysicsCore", + "OpenSSL" + }); - PrivateDependencyModuleNames.AddRange(new string[] { "GameplayTags", "Slate", "SlateCore" }); + PrivateDependencyModuleNames.AddRange(new string[] + { + "GameplayTags", + "Slate", + "SlateCore" + }); // Adds in the plugin for GameLiftServerSDK if it is the server build. @@ -26,7 +42,7 @@ public class FPSTemplate : ModuleRules // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); - + // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); diff --git a/Source/FPSTemplate/Private/Game/ShooterGameMode.cpp b/Source/FPSTemplate/Private/Game/ShooterGameMode.cpp index cef45cb5..10ff152b 100644 --- a/Source/FPSTemplate/Private/Game/ShooterGameMode.cpp +++ b/Source/FPSTemplate/Private/Game/ShooterGameMode.cpp @@ -1,68 +1,22 @@ // 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 +#include "GameLiftClpTypes.h" +#include "GameLift/GameLiftClp.h" -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; - } -} +DEFINE_LOG_CATEGORY(LogShooterGameMode); + +// Function implementations. AShooterGameMode::AShooterGameMode() { - UE_LOG(LogShooterGameMode, Log, TEXT("Initializing ShooterGameMode...")); } void AShooterGameMode::BeginPlay() { Super::BeginPlay(); - #if WITH_GAMELIFT InitGameLift(); #endif @@ -71,248 +25,114 @@ void AShooterGameMode::BeginPlay() 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) + GameLiftConfig.ServerPort = cmdlineparser::details::GetConfiguredOrDefaultPort(CachedCommandLine, "port"); + if (FParse::Param(*CachedCommandLine, TEXT("-glAnywhereFleet"))) { - UE_LOG(LogShooterGameMode, Log, TEXT("Debug mode: ENABLED")); -#if UE_BUILD_DEBUG - UE_LOG(LogShooterGameMode, Log, TEXT("Command Line Arguments: %s"), *CachedCommandLine); -#endif + GameLiftConfig.bIsAnywhereFleet = true; + } + else + { + GameLiftConfig.bIsAnywhereFleet = false; } - - 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, "==================================="); + GetAnywhereFleetParameters(CachedCommandLine); + LogAnywhereFleetParameters(); } - else - { - - } - } -void AShooterGameMode::InitGame_original(const FString& MapName, const FString& Options, FString& ErrorMessage) +FString AShooterGameMode::GetSHA256Hash(const FString& InString) { - 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 (InString.IsEmpty()) return TEXT("da39a3ee5e6b4b0d3255bfef95601890afd80709"); - if (bValidWebSocket) - { - int32 ColonPos = WebSocketUrl.Find(TEXT(":"), ESearchCase::CaseSensitive); - int32 SlashPos = WebSocketUrl.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ColonPos + 1); + FSHA1 Sha; + FTCHARToUTF8 Utf8(InString); + Sha.Update((uint8*)Utf8.Get(), Utf8.Length() * sizeof(UTF8CHAR)); + Sha.Final(); + uint8 Hash[FSHA1::DigestSize]; + Sha.GetHash(Hash); - 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); - } + // 40-char hex (SHA1 = 160 bits) + return FString::Printf(TEXT("%08X%08X%08X%08X%08X"), + Hash[0], Hash[1], Hash[2], Hash[3], Hash[4]); - 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 + GetAnywhereFleetParameters(CachedCommandLine); } +bool AShooterGameMode::GetAnywhereFleetParameters(FString CommandLineString) +{ + bool bAllAnywhereFleetParametersValid = true; + cmdlineparser::details::FParseResult AuthResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::AuthToken); + if (AuthResult.bIsValid) + { + GameLiftConfig.AuthToken = AuthResult.Value; +#if UE_BUILD_DEBUG + UE_LOGFMT(LogShooterGameMode, Log, "AuthToken loaded: {0}", GameLiftConfig.AuthToken); +#else + UE_LOGFMT(LogShooterGameMode, Log, "AuthToken loaded: HASH - {0}", GetSHA256Hash(GameLiftConfig.AuthToken)); +#endif + + } + else + { + bAllAnywhereFleetParametersValid = false; +#if UE_BUILD_DEBUG + UE_LOGFMT(LogShooterGameMode, Error, "AuthToken Invalid: {0}", GameLiftConfig.AuthToken); +#else + UE_LOGFMT(LogShooterGameMode, Error, "AuthToken Invalid: HASH - {0}", GetSHA256Hash(GameLiftConfig.AuthToken)); +#endif + } + + cmdlineparser::details::FParseResult HostIdResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::HostId); + if (HostIdResult.bIsValid) + { + GameLiftConfig.HostId = HostIdResult.Value; + UE_LOGFMT(LogShooterGameMode, Log, "HostId loaded: {0}", GameLiftConfig.HostId); + } + else + { + bAllAnywhereFleetParametersValid = false; + UE_LOGFMT(LogShooterGameMode, Error, "HostId Invalid: {0}", HostIdResult.Value); + } + + cmdlineparser::details::FParseResult FleetIdResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::FleetId); + if (FleetIdResult.bIsValid) + { + GameLiftConfig.FleetId = FleetIdResult.Value; + UE_LOGFMT(LogShooterGameMode, Log, "FleetId loaded: {0}", FleetIdResult.Value); + } + else + { + bAllAnywhereFleetParametersValid = false; + UE_LOGFMT(LogShooterGameMode, Error, "FleetId Invalid: {0}", GameLiftConfig.HostId); + } + + cmdlineparser::details::FParseResult WebSocketUrlResult = cmdlineparser::GetValueOfToken(CommandLineString, cmdlineparser::details::EAvailableTokens::WebsocketUrl); + if (WebSocketUrlResult.bIsValid) + { + GameLiftConfig.WebSocketUrl = WebSocketUrlResult.Value; + UE_LOGFMT(LogShooterGameMode, Log, "WebSocketUrl loaded: {0}", WebSocketUrlResult.Value); + } + else + { + bAllAnywhereFleetParametersValid = false; + UE_LOGFMT(LogShooterGameMode, Error, "WebSocketUrl Invalid: {0}", WebSocketUrlResult.Value); + } + return bAllAnywhereFleetParametersValid; +} + +void AShooterGameMode::LogAnywhereFleetParameters() +{ + UE_LOGFMT(LogShooterGameMode, Log, "Anywhere Fleet Parameters:"); + UE_LOGFMT(LogShooterGameMode, Log, "AuthToken: {0}", GameLiftConfig.AuthToken); + UE_LOGFMT(LogShooterGameMode, Log, "FleetId: {0}", GameLiftConfig.FleetId); + UE_LOGFMT(LogShooterGameMode, Log, "HostId: {0}", GameLiftConfig.HostId); + UE_LOGFMT(LogShooterGameMode, Log, "WebsocketUrl: {0}", GameLiftConfig.WebSocketUrl); + UE_LOGFMT(LogShooterGameMode, Log, "Server Port: {0}", GameLiftConfig.ServerPort); +} diff --git a/Source/FPSTemplate/Private/GameLift/GameLiftClp.cpp b/Source/FPSTemplate/Private/GameLift/GameLiftClp.cpp new file mode 100644 index 00000000..2ecf044a --- /dev/null +++ b/Source/FPSTemplate/Private/GameLift/GameLiftClp.cpp @@ -0,0 +1,78 @@ +#include "GameLift/GameLiftClp.h" + +int32 cmdlineparser::GetConfiguredOrDefaultPort(const FString& Token) +{ + return details::GetConfiguredOrDefaultPort(FCommandLine::Get(), Token); +} + +int32 cmdlineparser::GetConfiguredOrDefaultPort(const FString& CommandLine, const FString& Token) +{ + return details::GetConfiguredOrDefaultPort(CommandLine, Token); +} + +cmdlineparser::details::FParseResult cmdlineparser::GetValueOfToken(const FString& CommandLine, const details::EAvailableTokens Token) +{ + details::FParseResult Result; + FString ValueOfToken; + Result.Token = Token; + + ensure(Token < details::EAvailableTokens::MaxToken); + const bool bTokenFound = FParse::Value( + *CommandLine, + *details::EnsureEndsWith(details::CLI_TOKENS[static_cast(Token)], + TEXT("=")), + ValueOfToken + ); + + if (!bTokenFound) + { + Result.Value = FString(); + Result.ErrorCode = details::EErrorCodes::TokenNotFound; + Result.bIsValid = false; + } + else + { + if (ValueOfToken.IsEmpty()) + { + Result.Value = FString(); + Result.ErrorCode = details::EErrorCodes::ValueEmpty; + Result.bIsValid = false; + } + else + { + // TODO: Validation of inputs + Result.Value = ValueOfToken; + Result.ErrorCode = details::EErrorCodes::NoError; + Result.bIsValid = true; + } + } + + ensure(Result.ErrorCode < details::EErrorCodes::MaxCode); + FString ErrorMessage = details::ERROR_MESSAGES[static_cast(Result.ErrorCode)]; + FString TokenName = details::CLI_TOKENS[static_cast(Result.Token)]; + FString Value = Result.Value; + Result.ErrorMessage = FString::Format(*ErrorMessage, {FStringFormatArg(TokenName), FStringFormatArg(Value)}); + return Result; +} + + +// cmdlineparser::Details namespace + +FString cmdlineparser::details::EnsureEndsWith(const FString& Token, const TCHAR* Suffix) +{ + return Token.EndsWith(Suffix) ? Token : Token + Suffix; +} + +int32 cmdlineparser::details::GetConfiguredOrDefaultPort(const FString& CommandLine, const FString& Token) +{ + const int32 Port = FURL::UrlConfig.DefaultPort; + + if (int32 CliPort; FParse::Value(*CommandLine, *EnsureEndsWith(Token, TEXT("=")), CliPort)) + { + if (CliPort >= details::MIN_PORT && CliPort <= details::MAX_PORT) + { + return CliPort; + } + } + return Port; +} \ No newline at end of file diff --git a/Source/FPSTemplate/Private/GameLift/GameLiftValidators.cpp b/Source/FPSTemplate/Private/GameLift/GameLiftValidators.cpp new file mode 100644 index 00000000..ff62af08 --- /dev/null +++ b/Source/FPSTemplate/Private/GameLift/GameLiftValidators.cpp @@ -0,0 +1,160 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GameLift/GameLiftValidators.h" + +#include "Game/ShooterGameMode.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +///Lazy Singleton +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +TArray& UGameLiftValidators::GetPatterns() +{ + static TArray Patterns; + if (Patterns.Num() == 0) + { + Patterns.AddDefaulted(static_cast(EGameLiftParams::Max)); + Patterns[static_cast(EGameLiftParams::AuthToken)] = + TEXT("^[a-zA-Z0-9\\-]+$"); // AuthToken + Patterns[static_cast(EGameLiftParams::FleetId)] = + TEXT("^[a-z]*fleet-[a-zA-Z0-9\\-]+$|^arn:.*:[a-z]*fleet\\/[a-z]*fleet-[a-zA-Z0-9\\-]+$"); // FleetId + Patterns[static_cast(EGameLiftParams::HostId)] = + TEXT("^[0-9A-Z]\\d+$"); // HostId + Patterns[static_cast(EGameLiftParams::WebSocketUrl)] = + TEXT("^wss:\\/\\/[a-zA-Z0-9.-]+(\\.amazonaws\\.com)?(\\/[^\\s]*)?$"); // WebSocketUrl + Patterns[static_cast(EGameLiftParams::ServerRegion)] = + TEXT("^[a-z]{2}-[a-z]+-[0-9]$"); // Region (ca-central-1) + } + return Patterns; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +bool UGameLiftValidators::IsMatchesRegex(const FString& Value, EGameLiftParams ParamType) +{ + ensure(ParamType < EGameLiftParams::Max); + ensure(GetPatterns().Num() > static_cast(ParamType)); + const FRegexPattern Pattern = FRegexPattern(GetPatterns()[static_cast(ParamType)]); + FRegexMatcher TestMatcher(Pattern, Value); + return TestMatcher.FindNext(); +} + + + +bool UGameLiftValidators::IsStringValid(const FString& Value, EGameLiftParams ParamType) +{ + // Checks to make sure the ParamType is not the sentinel + ensure(ParamType < EGameLiftParams::Max); + + if (Value.IsEmpty()) + return false; + + const int32 Len = Value.Len(); + const int32 Index = static_cast(ParamType); + + if (Len < MinLengths[Index] || Len > MaxLengths[Index]) return false; + + return true; +} + +bool UGameLiftValidators::IsStringShort(const FString& Value, EGameLiftParams ParamType) +{ + ensure(ParamType < EGameLiftParams::Max); + if (Value.IsEmpty()) return false; + const int32 Len = Value.Len(); + const int32 Index = static_cast(ParamType); + if (Len < MinLengths[Index]) return true; + return false; +} + +bool UGameLiftValidators::IsStringLong(const FString& Value, EGameLiftParams ParamType) +{ + ensure(ParamType < EGameLiftParams::Max); + if (Value.IsEmpty()) return false; + const int32 Len = Value.Len(); + const int32 Index = static_cast(ParamType); + if (Len > MaxLengths[Index]) return true; + return false; +}; + +FParamResult UGameLiftValidators::ValidateParam(const FString& Value, const EGameLiftParams ParamType) +{ + ensure(ParamType < EGameLiftParams::Max); + bool bErrorTriggered = false; + FParamResult Result; + Result.ParamType = ParamType; + + // Quick empty check + if (Value.IsEmpty() && !bErrorTriggered) + { + Result.bValid = false; + Result.ErrorMessage = FString::Format(TEXT("Missing {0} in command line arguments"), {GetParameterFromEnum(ParamType)}); + Result.ErrorCode = EValidationError::Empty; + bErrorTriggered = true; + } + + if (IsStringShort(Value, ParamType) && !bErrorTriggered) + { + Result.bValid = false; + Result.Value = Value; + Result.ErrorMessage = FString::Format(TEXT("Parameter {0} does not meet minimum length requirements."), { GetParameterFromEnum(ParamType)} ); + Result.ErrorCode = EValidationError::TooShort; + bErrorTriggered = true; + + } + + if (IsStringLong(Value, ParamType) && !bErrorTriggered) + { + Result.bValid = false; + Result.Value = Value; + Result.ErrorMessage = FString::Format(TEXT("Parameter {0} does not meet maximum length requirements."), { GetParameterFromEnum(ParamType)} ); + Result.ErrorCode = EValidationError::TooLong; + bErrorTriggered = true; + } + + if (!IsMatchesRegex(Value, ParamType) && !bErrorTriggered) + { + Result.bValid = false; + Result.Value = Value; + Result.ErrorMessage = FString::Format(TEXT("Parameter {0} has an invalid format."), { GetParameterFromEnum(ParamType)} ); + Result.ErrorCode = EValidationError::InvalidFormat; + bErrorTriggered = true; + } + + if (!bErrorTriggered) + { + Result.bValid = true; + Result.Value = Value; + Result.ErrorMessage = FString(""); + Result.ErrorCode = EValidationError::None; + } + return Result; +} + +FString UGameLiftValidators::GetParameterFromEnum(const EGameLiftParams InParam) +{ + ensure(InParam < EGameLiftParams::Max); + const FString FullParameter = UEnum::GetValueAsString(InParam); + FString Left, Right; + FullParameter.Split(TEXT("::"), &Left, &Right); + const FString NameOnly = Right; + return NameOnly; +} + +FString UGameLiftValidators::GetErrorFromEnum(EValidationError InParam) +{ + ensure(InParam <= EValidationError::NotFound); + const FString FullParameter = UEnum::GetValueAsString(InParam); + FString Left, Right; + FullParameter.Split(TEXT("::"), &Left, &Right); + const FString NameOnly = Right; + return NameOnly; +} + +void UGameLiftValidators::LogValidationErrorMessage(FParamResult ValidationResult) +{ + if (ValidationResult.ErrorCode == EValidationError::None) return; // no error logging required + UE_LOGFMT(LogShooterGameMode, Error, "{0}", ValidationResult.ErrorMessage); +} diff --git a/Source/FPSTemplate/Private/GameLift/GameLiftValidators_old.cpp b/Source/FPSTemplate/Private/GameLift/GameLiftValidators_old.cpp new file mode 100644 index 00000000..97658136 --- /dev/null +++ b/Source/FPSTemplate/Private/GameLift/GameLiftValidators_old.cpp @@ -0,0 +1,63 @@ +#include "GameLift/GameLiftValidators_old.h" + +namespace GameLiftValidators2 +{ + inline const FRegexPattern& GetPattern(ECliParam ParamType) + { + ensure(ParamType < ECliParam::Max); + static const FRegexPattern Patterns[static_cast(ECliParam::Max)] = + { + FRegexPattern(TEXT("^[a-zA-Z0-9\\-]+$")), // AuthToken + FRegexPattern(TEXT("^[a-z]*fleet-[a-zA-Z0-9\\-]+$|^arn:.*:[a-z]*fleet\\/[a-z]*fleet-[a-zA-Z0-9\\-]+$")), // FleetId + FRegexPattern(TEXT("^[0-9A-Z]\\d+$")), // HostId + FRegexPattern(TEXT("^wss:\\/\\/[a-zA-Z0-9.-]+(\\.amazonaws\\.com)?(\\/[^\\s]*)?$")), // WebSocketUrl + FRegexPattern(TEXT("^[a-z]{2}-[a-z]+-[0-9]$")), // Region (ca-central-1) + }; + return Patterns[static_cast(ParamType)]; + } + + constexpr int32 MaxLengths[static_cast(ECliParam::Max)] = + { + 64, //authToken + 512, //FleetId + 128, //HostId + 128, //WebSocketUrl + 16, //Region (ca-central-1) + }; + + inline int32 GetMaxLength(ECliParam ParamType) + { + ensure(ParamType < ECliParam::Max); + return MaxLengths[static_cast(ParamType)]; + } + + constexpr int32 MinLengths[static_cast(ECliParam::Max)] = + { + 1, //authToken + 1, //FleetId + 1, //HostId + 1, //WebSocketUrl + 3, //Region (ca-central-1) + }; + + inline int32 GetMinLength(ECliParam ParamType) + { + ensure(ParamType < ECliParam::Max); + return MinLengths[static_cast(ParamType)]; + } + + + bool IsStringValid(const FString& Value, ECliParam ParamType) + { + + ensure(ParamType < ECliParam::Max); + if (Value.IsEmpty()) return false; + + if (const int32 Len = Value.Len(); (Len < GetMinLength(ParamType)) || (Len > GetMaxLength(ParamType))) + return false; + + const FRegexPattern& Pattern = GetPattern(ParamType); + FRegexMatcher StringMatcher(Pattern, Value); + return StringMatcher.FindNext() && StringMatcher.GetMatchEnding() == Value.Len(); // returns true on a full or partial match + } +} \ No newline at end of file diff --git a/Source/FPSTemplate/Private/GameLiftClpTypes.cpp b/Source/FPSTemplate/Private/GameLiftClpTypes.cpp new file mode 100644 index 00000000..9b1affb1 --- /dev/null +++ b/Source/FPSTemplate/Private/GameLiftClpTypes.cpp @@ -0,0 +1,22 @@ +#include "GameLiftClpTypes.h" + +const TArray& cmdlineparser::details::ERROR_MESSAGES = []() -> const TArray& +{ + static TArray Error_Messages; + Error_Messages.Add(TEXT("VALID: {0}: {1}")); + Error_Messages.Add(TEXT("INVALID TOKEN: [-{0}=] not found in command line arguments")); + Error_Messages.Add(TEXT("EMPTY VALUE: [-{0}=] found. No value assigned")); + Error_Messages.Add(TEXT("VALIDATION FAILED: [-{0}=] found.... Value: [-{1}=]")); + return Error_Messages; +}(); + + +const TArray& cmdlineparser::details::CLI_TOKENS = []() -> const TArray& +{ + static TArray Cli_Tokens; + Cli_Tokens.Add(TEXT("authtoken")); + Cli_Tokens.Add(TEXT("hostid")); + Cli_Tokens.Add(TEXT("fleetid")); + Cli_Tokens.Add(TEXT("websocketurl")); + return Cli_Tokens; +}(); \ No newline at end of file diff --git a/Source/FPSTemplate/Public/Game/ShooterGameMode.h b/Source/FPSTemplate/Public/Game/ShooterGameMode.h index d14010af..bfa1c84f 100644 --- a/Source/FPSTemplate/Public/Game/ShooterGameMode.h +++ b/Source/FPSTemplate/Public/Game/ShooterGameMode.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "ShooterGameModeBase.h" - #include "ShooterGameMode.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogShooterGameMode, Log, All); @@ -12,47 +11,27 @@ DECLARE_LOG_CATEGORY_EXTERN(LogShooterGameMode, Log, All); struct FProcessParameters; struct FServerParameters; -struct FGameLiftCliConfig +struct FGameLiftConfig { bool bDebugMode = false; bool bIsAnywhereFleet = false; bool bIsCriticalError = false; - - int32 ServerPort = 0; + + int32 ServerPort; FString AuthToken; FString FleetId; FString HostId; FString WebSocketUrl; FString ServerRegion; - }; -namespace GameLiftValidators +enum ETokenStatus : uint8 { - extern const FRegexPattern FleetIdPattern; - extern const int32 FleetIdLengthMin; - extern const int32 FleetIdLengthMax; - extern const FString ServerRegionPattern; - extern const int32 ServerRegionLengthMin; - extern const int32 ServerRegionLengthMax; - extern const FString WebSocketUrlPattern; - extern const int32 WebSocketUrlLengthMin; - extern const int32 WebSocketUrlLengthMax; - extern const FString AuthTokenPattern; - extern const int32 AuthTokenLengthMin; - extern const int32 AuthTokenLengthMax; - extern const FString HostIdPattern; - extern const int32 HostIdLengthMin; - extern const int32 HostIdLengthMax; - extern const int32 ServerPortMin; - extern const int32 ServerPortMax; - extern const int32 WebSocketPortMin; - extern const int32 WebSocketPortMax; - - bool IsStringValid(const FString& Value, const FRegexPattern& OptionalPattern, int32 Min, int32 Max); - bool IsNumberValid(int32 Value, int32 Min, int32 Max); -} + Found, + NotFoundRequired, + NotFoundNotRequired +}; /** * @@ -61,55 +40,21 @@ UCLASS() class FPSTEMPLATE_API AShooterGameMode : public AShooterGameModeBase { GENERATED_BODY() - public: + AShooterGameMode(); - + protected: - - UPROPERTY(Config, BlueprintReadOnly) - bool bIsAnywhereFleet = false; - - UPROPERTY(Config, BlueprintReadOnly) - FString FleetId; - - UPROPERTY(Config, BlueprintReadOnly) - FString AuthToken; - - UPROPERTY(Config, BlueprintReadOnly) - FString HostId; - - UPROPERTY(Config, BlueprintReadOnly) - FString WebSocketUrl; - - UPROPERTY(Config) - FString ServerRegion; - - UPROPERTY(Config, BlueprintReadOnly) - int32 ServerPort; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - bool bDebugMode = false; - virtual void BeginPlay() override; virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; - void InitGame_original(const FString& MapName, const FString& Options, FString& ErrorMessage); - -private: - - FGameLiftCliConfig GameLiftConfig; - - FString CachedCommandLine; - TSharedPtr ProcessParameters; - TSharedPtr ServerParameters; - - int32 GetConfiguredOrDefaultPort() const; - static FString GetSHA256Hash(const FString& Input); - - bool ParseAndOutputResult(const FString& InString, FString& OutValue, bool bRequired = false); - bool ParseAndValidate(const FString& InArguments, FGameLiftCliConfig& Config); - static bool ValidateFleetId(const FString& InFleetId); - - void InitGameLift(); -}; +private: + FGameLiftConfig GameLiftConfig; + FString CachedCommandLine; + + static FString GetSHA256Hash(const FString& CommandLineString); + void InitGameLift(); + + bool GetAnywhereFleetParameters(FString CommandLineString); + void LogAnywhereFleetParameters(); +}; diff --git a/Source/FPSTemplate/Public/GameLift/GameLiftClp.h b/Source/FPSTemplate/Public/GameLift/GameLiftClp.h new file mode 100644 index 00000000..863cf0c6 --- /dev/null +++ b/Source/FPSTemplate/Public/GameLift/GameLiftClp.h @@ -0,0 +1,23 @@ +#pragma once +#include "GameLiftClpTypes.h" +#include "CoreMinimal.h" + +namespace cmdlineparser +{ +// int32 GetConfiguredOrDefaultPort(); + int32 GetConfiguredOrDefaultPort(const FString& Token = TEXT("port=")); + int32 GetConfiguredOrDefaultPort(const FString& CommandLine, const FString& Token = TEXT("port=")); + + details::FParseResult GetValueOfToken(const FString& CommandLine, const details::EAvailableTokens Token); +} + +namespace cmdlineparser::details +{ + inline static constexpr int32 MIN_PORT = 1024; + inline static constexpr int32 MAX_PORT = 65535; + inline static constexpr const TCHAR* DEFAULT_PORT_TOKEN = TEXT("port="); + + FString EnsureEndsWith(const FString& Token, const TCHAR* Suffix); + int32 GetConfiguredOrDefaultPort(const FString& CommandLine, const FString& Token); +} + diff --git a/Source/FPSTemplate/Public/GameLift/GameLiftValidators.h b/Source/FPSTemplate/Public/GameLift/GameLiftValidators.h new file mode 100644 index 00000000..08112ab6 --- /dev/null +++ b/Source/FPSTemplate/Public/GameLift/GameLiftValidators.h @@ -0,0 +1,93 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "GameLiftValidators.generated.h" + +UENUM(BlueprintType) +enum class EGameLiftParams : uint8 +{ + AuthToken, + FleetId, + HostId, + ServerRegion, + WebSocketUrl, + Max // Sentry to determine length of EGameLiftParams +}; + +UENUM(BlueprintType) +enum class EValidationError : uint8 +{ + None, // Valid + Empty, // Missing Parameter + TooShort, // Too Short + TooLong, // Too long + InvalidFormat, // Invalid format + NotFound // Sentinel value, any new error codes get put above this. +}; + +USTRUCT(BlueprintType) +struct FParamResult +{ + GENERATED_BODY() + + EGameLiftParams ParamType; + FString Value; + bool bValid; + FString ErrorMessage; + EValidationError ErrorCode; +}; + + +UCLASS() +class FPSTEMPLATE_API UGameLiftValidators : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +private: + static TArray& GetPatterns(); + + + + static constexpr int32 MaxLengths[static_cast(EGameLiftParams::Max)] = + { + 64, //authToken + 512, //FleetId + 128, //HostId + 128, //WebSocketUrl + 16, //Region (ca-central-1) + }; + + static constexpr int32 MinLengths[static_cast(EGameLiftParams::Max)] = + { + 1, //authToken + 1, //FleetId + 1, //HostId + 1, //WebSocketUrl + 3, //Region (ca-central-1) + }; + + static bool IsStringValid(const FString& Value, EGameLiftParams ParamType); + static bool IsStringShort(const FString& Value, EGameLiftParams ParamType); + static bool IsStringLong(const FString& Value, EGameLiftParams ParamType); + static bool IsMatchesRegex(const FString& Value, EGameLiftParams ParamType); + +public: + + + UFUNCTION(BlueprintCallable, Category = "GameLift") + static FParamResult ValidateParam(const FString& Value, EGameLiftParams ParamType); + + + UFUNCTION(BlueprintCallable, Category = "GameLift") + static FString GetParameterFromEnum(EGameLiftParams InParam); + + UFUNCTION(BlueprintCallable, Category = "GameLift") + static FString GetErrorFromEnum(EValidationError InParam); + + UFUNCTION(BlueprintCallable, Category = "GameLift") + static void LogValidationErrorMessage(FParamResult ValidationResult); + +}; diff --git a/Source/FPSTemplate/Public/GameLift/GameLiftValidators_old.h b/Source/FPSTemplate/Public/GameLift/GameLiftValidators_old.h new file mode 100644 index 00000000..1924c7e7 --- /dev/null +++ b/Source/FPSTemplate/Public/GameLift/GameLiftValidators_old.h @@ -0,0 +1,24 @@ +#pragma once +#include "CoreMinimal.h" + +struct FParamResult +{ + FString Value; + bool bValid; + FString ErrorMessage; +}; + +namespace GameLiftValidators2 +{ + enum class ECliParam + { + AuthToken, FleetId, HostId, Region, WebSocketUrl, Max + }; + + + int32 GetMaxLength(ECliParam ParamType); + int32 GetMinLength(ECliParam ParamType); + + bool IsStringValid(const FString& Value, ECliParam ParamType); + bool IsNumberValid(int32 Value, int32 Min, int32 Max); +} \ No newline at end of file diff --git a/Source/FPSTemplate/Public/GameLiftClpTypes.h b/Source/FPSTemplate/Public/GameLiftClpTypes.h new file mode 100644 index 00000000..986e583a --- /dev/null +++ b/Source/FPSTemplate/Public/GameLiftClpTypes.h @@ -0,0 +1,35 @@ +#pragma once + +namespace cmdlineparser::details +{ + enum EAvailableTokens + { + AuthToken, + HostId, + FleetId, + WebsocketUrl, + MaxToken + }; + + enum EErrorCodes + { + NoError, + TokenNotFound, + ValueEmpty, + FailedValidation, + MaxCode + }; + + extern const TArray& ERROR_MESSAGES; + extern const TArray& CLI_TOKENS; + + struct FParseResult + { + EAvailableTokens Token; + FString Value; + bool bIsValid; + EErrorCodes ErrorCode; + FString ErrorMessage; + + }; +} \ No newline at end of file