diff --git a/Content/FactoryGame/PostProcess/MF_ApplyPaniniProjection.uasset b/Content/FactoryGame/PostProcess/MF_ApplyPaniniProjection.uasset index cd2106e069..54a930171b 100644 Binary files a/Content/FactoryGame/PostProcess/MF_ApplyPaniniProjection.uasset and b/Content/FactoryGame/PostProcess/MF_ApplyPaniniProjection.uasset differ diff --git a/Content/FactoryGame/PostProcess/MF_PaniniProjection.uasset b/Content/FactoryGame/PostProcess/MF_PaniniProjection.uasset index 1250972342..2c2a32da03 100644 Binary files a/Content/FactoryGame/PostProcess/MF_PaniniProjection.uasset and b/Content/FactoryGame/PostProcess/MF_PaniniProjection.uasset differ diff --git a/Mods/Alpakit/Alpakit.uplugin b/Mods/Alpakit/Alpakit.uplugin index 9587ef72ee..663d587dd9 100644 --- a/Mods/Alpakit/Alpakit.uplugin +++ b/Mods/Alpakit/Alpakit.uplugin @@ -25,6 +25,10 @@ { "Name": "PluginBrowser", "Enabled": true + }, + { + "Name": "SML", + "Enabled": true } ] -} \ No newline at end of file +} diff --git a/Mods/Alpakit/Source/Alpakit/Alpakit.Build.cs b/Mods/Alpakit/Source/Alpakit/Alpakit.Build.cs index 6e8ad1eb9f..e24b37e35a 100644 --- a/Mods/Alpakit/Source/Alpakit/Alpakit.Build.cs +++ b/Mods/Alpakit/Source/Alpakit/Alpakit.Build.cs @@ -14,6 +14,10 @@ public Alpakit(ReadOnlyTargetRules Target) : base(Target) "DesktopPlatform", }); + PublicDependencyModuleNames.AddRange(new[] { + "SML", + }); + PrivateDependencyModuleNames.AddRange(new[] { "ApplicationCore", "Json", @@ -29,7 +33,7 @@ public Alpakit(ReadOnlyTargetRules Target) : base(Target) "EditorStyle", "PluginBrowser", "LauncherServices", - "TargetDeviceServices" + "TargetDeviceServices", }); } } diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp index eef9676b98..b7ea60390c 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp @@ -7,6 +7,7 @@ #include "ISourceControlState.h" #include "ModMetadataObject.h" #include "SourceControlOperations.h" +#include "Util/SemVersion.h" #define LOCTEXT_NAMESPACE "AlpakitEditMod" @@ -24,9 +25,13 @@ void SAlpakitEditModDialog::Construct(const FArguments& InArgs, TSharedRefGetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FModMetadataCustomization::MakeInstance)); TSharedRef PropertyView = EditModule.CreateDetailView(FDetailsViewArgs(false, false, false, FDetailsViewArgs::ActorsUseNameArea, true)); PropertyView->SetObject(MetadataObject, true); + PropertyView->OnFinishedChangingProperties().AddLambda([this](const FPropertyChangedEvent&){ + UpdateGameVersionTarget(); + }); FString TargetSMLVersion = TEXT("^") + FAlpakitModule::GetCurrentSMLVersion(); - FString TargetGameVersion = FAlpakitModule::GetCurrentGameVersion(); + + UpdateGameVersionTarget(); SWindow::Construct(SWindow::FArguments() .ClientSize(FVector2D(800.0f, 700.0f)) @@ -65,9 +70,9 @@ void SAlpakitEditModDialog::Construct(const FArguments& InArgs, TSharedRefGameVersion == TargetGameVersion) + if (FormatGameVersionRange(ModGameVersionRange) == FormatGameVersionRange(TargetGameVersionRange)) return EVisibility::Collapsed; return EVisibility::Visible; }) @@ -75,11 +80,12 @@ void SAlpakitEditModDialog::Construct(const FArguments& InArgs, TSharedRefGameVersion = TargetGameVersion; + MetadataObject->GameVersion = FormatGameVersionRange(TargetGameVersionRange); + UpdateGameVersionTarget(); return FReply::Handled(); }) ] @@ -129,9 +135,28 @@ void SAlpakitEditModDialog::SetSMLDependencyVersion(FString Version) const SMLDependency->SemVersion = Version; } -FString SAlpakitEditModDialog::GetGameVersion() const -{ - return MetadataObject->GameVersion; +void SAlpakitEditModDialog::UpdateGameVersionTarget() { + FString _; + GameVersion.ParseVersion(FString::Printf(TEXT("%s.0.0"), *FAlpakitModule::GetCurrentGameVersion()), _); + if (!MetadataObject->GameVersion.IsEmpty()) { + ModGameVersionRange.ParseVersionRange(MetadataObject->GameVersion, _); + + TargetGameVersionRange.ParseVersionRange(MetadataObject->GameVersion, _); + for (FVersionComparatorCollection& ComparatorCollection : TargetGameVersionRange.Collections) { + ComparatorCollection.Comparators.Add(FVersionComparator(EVersionComparisonOp::GREATER_EQUALS, GameVersion)); + } + + if (!TargetGameVersionRange.Matches(GameVersion)) { + TargetGameVersionRange = FVersionRange::CreateRangeWithMinVersion(GameVersion); + } + } else { + ModGameVersionRange = FVersionRange(); + TargetGameVersionRange = FVersionRange::CreateRangeWithMinVersion(GameVersion); + } +} + +FString SAlpakitEditModDialog::FormatGameVersionRange(const FVersionRange& Range) { + return Range.ToString().Replace(TEXT(".0.0"), TEXT("")); } FReply SAlpakitEditModDialog::OnOkClicked() { diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitReleaseWidget.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitReleaseWidget.cpp index 1d96e79715..1a92a6eba3 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitReleaseWidget.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitReleaseWidget.cpp @@ -4,6 +4,7 @@ #include "AlpakitSettings.h" #include "AlpakitStyle.h" #include "ModTargetsConfig.h" +#include "Util/SemVersion.h" #define LOCTEXT_NAMESPACE "AlpakitWidget" @@ -11,7 +12,6 @@ void SAlpakitReleaseWidget::Construct(const FArguments& InArgs) { const float TargetColumnWidth = 90; FString TargetSMLVersion = TEXT("^") + FAlpakitModule::GetCurrentSMLVersion(); - FString TargetGameVersion = FAlpakitModule::GetCurrentGameVersion(); ChildSlot[ SNew(SVerticalBox) @@ -84,7 +84,7 @@ void SAlpakitReleaseWidget::Construct(const FArguments& InArgs) { return !FAlpakitModule::Get().IsPackaging(); }); }) - .ModEntryTrail_Lambda([this, TargetColumnWidth, TargetSMLVersion, TargetGameVersion] (const TSharedRef& Mod) { + .ModEntryTrail_Lambda([this, TargetColumnWidth, TargetSMLVersion] (const TSharedRef& Mod) { TSharedRef ModTargetsConfig = ModTargetsConfigs.FindOrAdd(Mod->GetName(), MakeShared(Mod)); return SNew(SBox) @@ -122,10 +122,12 @@ void SAlpakitReleaseWidget::Construct(const FArguments& InArgs) { + SHorizontalBox::Slot().AutoWidth().Padding(5,0)[ SNew(SBox) .HAlign(HAlign_Center) - .Visibility_Lambda([this, Mod, TargetGameVersion] + .Visibility_Lambda([this, Mod] { - FString GameVersion = GetModGameVersion(Mod); - if (GameVersion == TargetGameVersion) + FVersion GameVersion; + FVersionRange ModGameVersionRange, TargetGameVersionRange; + GetModGameVersionFields(Mod, GameVersion, ModGameVersionRange, TargetGameVersionRange); + if (FormatGameVersionRange(ModGameVersionRange) == FormatGameVersionRange(TargetGameVersionRange)) return EVisibility::Hidden; return EVisibility::Visible; }) @@ -136,17 +138,25 @@ void SAlpakitReleaseWidget::Construct(const FArguments& InArgs) { SNew(SImage) .Image(FAlpakitStyle::Get().GetBrush("Alpakit.Warning")) ] - .ToolTipText_Lambda([Mod, TargetGameVersion] + .ToolTipText_Lambda([Mod] { - FString CurrentGameVersion = GetModGameVersion(Mod); - if (CurrentGameVersion.IsEmpty()) { - CurrentGameVersion = "(unspecified)"; + FString GameVersionRaw = GetModGameVersion(Mod); + FVersion GameVersion; + FVersionRange ModGameVersionRange, TargetGameVersionRange; + GetModGameVersionFields(Mod, GameVersion, ModGameVersionRange, TargetGameVersionRange); + if (GameVersionRaw.IsEmpty()) { + GameVersionRaw = "(unspecified)"; + } else { + GameVersionRaw = FormatGameVersionRange(ModGameVersionRange); } - return FText::Format(LOCTEXT("UpdateGameVersionTooltip", "This mod uses game version {0}, but the project is {1}. Click to update"), FText::FromString(CurrentGameVersion), FText::FromString(TargetGameVersion)); + return FText::Format(LOCTEXT("UpdateGameVersionTooltip", "This mod uses game version {0}, but the project is {1}. Click to update"), FText::FromString(GameVersionRaw), FText::FromString(FormatGameVersionRange(TargetGameVersionRange))); }) - .OnClicked_Lambda([this, Mod, TargetGameVersion] + .OnClicked_Lambda([this, Mod] { - SetModGameVersion(Mod, TargetGameVersion); + FVersion GameVersion; + FVersionRange ModGameVersionRange, TargetGameVersionRange; + GetModGameVersionFields(Mod, GameVersion, ModGameVersionRange, TargetGameVersionRange); + SetModGameVersion(Mod, FormatGameVersionRange(TargetGameVersionRange)); return FReply::Handled(); }) ] @@ -241,6 +251,33 @@ FString SAlpakitReleaseWidget::GetModGameVersion(TSharedRef Mod) return GameVersion; } +void SAlpakitReleaseWidget::GetModGameVersionFields(TSharedRef Mod, FVersion& GameVersion, FVersionRange& ModGameVersionRange, FVersionRange& TargetGameVersionRange) { + FString ModGameVersion = GetModGameVersion(Mod); + + FString _; + GameVersion.ParseVersion(FString::Printf(TEXT("%s.0.0"), *FAlpakitModule::GetCurrentGameVersion()), _); + if (!ModGameVersion.IsEmpty()) { + ModGameVersionRange.ParseVersionRange(ModGameVersion, _); + + TargetGameVersionRange.ParseVersionRange(ModGameVersion, _); + for (FVersionComparatorCollection& ComparatorCollection : TargetGameVersionRange.Collections) { + ComparatorCollection.Comparators.Add(FVersionComparator(EVersionComparisonOp::GREATER_EQUALS, GameVersion)); + } + + if (!TargetGameVersionRange.Matches(GameVersion)) { + TargetGameVersionRange = FVersionRange::CreateRangeWithMinVersion(GameVersion); + } + } else { + ModGameVersionRange = FVersionRange(); + TargetGameVersionRange = FVersionRange::CreateRangeWithMinVersion(GameVersion); + } +} + +FString SAlpakitReleaseWidget::FormatGameVersionRange(const FVersionRange& TargetGameVersionRange) { + return TargetGameVersionRange.ToString().Replace(TEXT(".0.0"), TEXT("")); +} + + void SAlpakitReleaseWidget::SetModGameVersion(TSharedRef Mod, FString Version) { FPluginDescriptor Descriptor = Mod->GetDescriptor(); diff --git a/Mods/Alpakit/Source/Alpakit/Private/ModMetadataObject.cpp b/Mods/Alpakit/Source/Alpakit/Private/ModMetadataObject.cpp index f4e4b9c10f..5dadf645ae 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/ModMetadataObject.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/ModMetadataObject.cpp @@ -19,7 +19,7 @@ UModMetadataObject::UModMetadataObject(const FObjectInitializer& ObjectInitializer) { Category = TEXT("Modding"); // Group all mods in this category - GameVersion = FAlpakitModule::GetCurrentGameVersion(); + GameVersion = FString::Printf(TEXT(">=%s"), *FAlpakitModule::GetCurrentGameVersion()); } void UModMetadataObject::PopulateFromDescriptor(const FPluginDescriptor& InDescriptor) diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h index 2fc9362411..35a66d10a7 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h @@ -3,6 +3,7 @@ #include "ModMetadataObject.h" #include "Slate.h" #include "Interfaces/IPluginManager.h" +#include "Util/SemVersion.h" class SAlpakitEditModDialog : public SWindow { @@ -14,10 +15,16 @@ class SAlpakitEditModDialog : public SWindow FString GetSMLDependencyVersion() const; void SetSMLDependencyVersion(FString Version) const; - FString GetGameVersion() const; + void UpdateGameVersionTarget(); + + static FString FormatGameVersionRange(const FVersionRange& Range); + private: TSharedPtr Mod; UModMetadataObject* MetadataObject = nullptr; + FVersion GameVersion; + FVersionRange ModGameVersionRange; + FVersionRange TargetGameVersionRange; FReply OnOkClicked(); }; diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitReleaseWidget.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitReleaseWidget.h index dac0e8e39f..bbf47373e6 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitReleaseWidget.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitReleaseWidget.h @@ -3,6 +3,7 @@ #include "AlpakitModEntryList.h" #include "ModTargetsConfig.h" #include "Slate.h" +#include "Util/SemVersion.h" class SAlpakitReleaseWidget : public SCompoundWidget { public: @@ -15,6 +16,9 @@ class SAlpakitReleaseWidget : public SCompoundWidget { static void SetModSMLDependencyVersion(TSharedRef Mod, FString Version); static FString GetModGameVersion(TSharedRef Mod); static void SetModGameVersion(TSharedRef Mod, FString Version); + static void GetModGameVersionFields(TSharedRef Mod, FVersion& GameVersion, FVersionRange& ModGameVersionRange, FVersionRange& TargetGameVersionRange); + static FString FormatGameVersionRange(const FVersionRange& TargetGameVersionRange); + private: TSharedPtr ModList; TMap> ModTargetsConfigs; diff --git a/Mods/SML/Source/SML/Private/ModLoading/ModLoadingLibrary.cpp b/Mods/SML/Source/SML/Private/ModLoading/ModLoadingLibrary.cpp index 7a5f35e2c0..53a9690eb6 100644 --- a/Mods/SML/Source/SML/Private/ModLoading/ModLoadingLibrary.cpp +++ b/Mods/SML/Source/SML/Private/ModLoading/ModLoadingLibrary.cpp @@ -14,7 +14,7 @@ void FSMLPluginDescriptorMetadata::SetupDefaults(const FPluginDescriptor& Plugin this->Version = FVersion(PluginDescriptor.Version, 0, 0); this->bAcceptsAnyRemoteVersion = false; this->RemoteVersionRange = FVersionRange::CreateRangeWithMinVersion(Version); - this->GameVersion = 0; + this->GameVersion = FVersionRange::CreateAnyVersionRange(); } void FSMLPluginDescriptorMetadata::Load(const FString& PluginName, const TSharedPtr Source) { @@ -59,7 +59,17 @@ void FSMLPluginDescriptorMetadata::Load(const FString& PluginName, const TShared } } - if (!Source->TryGetNumberField(TEXT("GameVersion"), GameVersion)) { + FString GameVersionRangeString; + if (Source->TryGetStringField(TEXT("GameVersion"), GameVersionRangeString)) { + FVersionRange GameVersionRange; + FString GameVersionRangeError; + + if (GameVersionRange.ParseVersionRange(GameVersionRangeString, GameVersionRangeError)) { + this->GameVersion = GameVersionRange; + } else { + UE_LOG(LogSatisfactoryModLoader, Error, TEXT("Plugin %s has invalid Game Version value: %s: %s"), *PluginName, *GameVersionRangeString, *GameVersionRangeError); + } + } else { UE_LOG(LogSatisfactoryModLoader, Warning, TEXT("Plugin %s does not specify 'GameVersion' field, unable to check for game compatibility"), *PluginName); } @@ -242,9 +252,9 @@ void UModLoadingLibrary::VerifyPluginDependencies(IPlugin& Plugin, TArray CurrentChangelist) { - const FString Message = FString::Printf(TEXT("Plugin %s requires game version %d or higher (current: %d)"), - *Plugin.GetName(), PluginDescriptorMetadata.GameVersion, CurrentChangelist); + if (!PluginDescriptorMetadata.GameVersion.Matches(FVersion(CurrentChangelist, 0, 0))) { + const FString Message = FString::Printf(TEXT("Plugin %s requires game version %s (current: %d)"), + *Plugin.GetName(), *PluginDescriptorMetadata.GameVersion.ToString().Replace(TEXT(".0.0"), TEXT("")), CurrentChangelist); MismatchedDependencies.Add(Message); } } diff --git a/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp b/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp index af44c9c621..0b105fd307 100644 --- a/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp +++ b/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp @@ -10,6 +10,17 @@ DEFINE_LOG_CATEGORY(LogModNetworkHandler); DEFINE_CONTROL_CHANNEL_MESSAGE_THREEPARAM(ModMessage, 40, FString, int32, FString); IMPLEMENT_CONTROL_CHANNEL_MESSAGE(ModMessage); +struct FConnectionSMLSupport { + bool bSupportsModMessageType{false}; + TArray> PendingMessages; + + FORCEINLINE bool IsDefault() const { return !bSupportsModMessageType && PendingMessages.IsEmpty(); } +}; + +static FUObjectAnnotationSparse GConnectionMetadata; + +static FString GSML_HELLO = TEXT("SML_HELLO"); + FMessageEntry& UModNetworkHandler::RegisterMessageType(const FMessageType& MessageType) { UE_LOG(LogModNetworkHandler, Display, TEXT("Registering message type %s:%d"), *MessageType.ModReference, MessageType.MessageId); TMap& ModEntries = MessageHandlers.FindOrAdd(MessageType.ModReference); @@ -27,8 +38,40 @@ void UModNetworkHandler::CloseWithFailureMessage(UNetConnection* Connection, con } void UModNetworkHandler::SendMessage(UNetConnection* Connection, FMessageType MessageType, FString Data) { - FNetControlMessage::Send(Connection, MessageType.ModReference, MessageType.MessageId, Data); + FConnectionSMLSupport ConnectionMetadata = GConnectionMetadata.GetAnnotation(Connection); + if (ConnectionMetadata.bSupportsModMessageType) { + FNetControlMessage::Send(Connection, MessageType.ModReference, MessageType.MessageId, Data); + Connection->FlushNet(true); + } else { + ConnectionMetadata.PendingMessages.Add({MessageType, Data}); + GConnectionMetadata.AddAnnotation(Connection, ConnectionMetadata); + } +} + +void UModNetworkHandler::SetConnectionSupportsModMessages(UNetConnection* Connection) { + FConnectionSMLSupport ConnectionMetadata = GConnectionMetadata.GetAnnotation(Connection); + + if (ConnectionMetadata.bSupportsModMessageType) { + return; + } + + ConnectionMetadata.bSupportsModMessageType = true; + + // Let other side know we support mod messages + FNetControlMessage::Send(Connection, GSML_HELLO); + Connection->FlushNet(true); + + // Send all pending messages now that we know they are supported + for (const TTuple& Message : ConnectionMetadata.PendingMessages) { + FMessageType ModMessageType = Message.Get<0>(); + FString Data = Message.Get<1>(); + FNetControlMessage::Send(Connection, ModMessageType.ModReference, ModMessageType.MessageId, Data); + } + Connection->FlushNet(true); + + ConnectionMetadata.PendingMessages.Empty(); + GConnectionMetadata.AddAnnotation(Connection, ConnectionMetadata); } UGameInstance* UModNetworkHandler::GetGameInstanceFromNetDriver( const UNetDriver* NetDriver ) @@ -62,6 +105,35 @@ void UModNetworkHandler::ReceiveMessage(UNetConnection* Connection, const FStrin } } +/** + * SML handshake is done in the following way: + * + * Server Client + * | SML_HELLO | + * |------------------------->| + * | SML_HELLO | bSupportsModMessageType = true + * |<-------------------------| + * bSupportsModMessageType = true | | + * | any pending mod messages | + * |<-------------------------| + * | SML_HELLO | + * |------------------------->| + * | any pending mod messages | + * |------------------------->| + * + * If the client is not running SML, it will not respond to the SML_HELLO message, + * so the server will not mark the connection as supporting mod messages, and never send any mod messages. + * + * If the server is not running SML, it will not send the initial SML_HELLO message, + * so the client will not mark the connection as supporting mod messages, and never send any mod messages. + * + * We cannot simply send an SML_HELLO message from each side at the beginning of the connection, + * because the server expects a specific message order from the client, up until the NMT_Welcome message, + * and disconnects if that order is not followed. + * So if the server is not running SML, nothing will intercept the SML_HELLO message, and it will reach + * UWorld::NotifyControlMessage, which will disconnect the client. + * + */ void UModNetworkHandler::InitializePatches() { UWorld* WorldObjectInstance = GetMutableDefault(); @@ -81,6 +153,32 @@ void UModNetworkHandler::InitializePatches() { }); auto MessageHandler = [=](auto& Call, void*, UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch) { + if (MessageType == NMT_Hello) { + // NMT_Hello is only received on the server, sent by UPendingNetGame::SendInitialJoin + // Initiate the SML handshake + + FNetControlMessage::Send(Connection, GSML_HELLO); + Connection->FlushNet(true); + } + + if (MessageType == NMT_DebugText) { + const int64 Pos = Bunch.GetPosBits(); + + FString Text; + if (FNetControlMessage::Receive(Bunch, Text)) { + if(Text == GSML_HELLO) { + SetConnectionSupportsModMessages(Connection); + } + } + + // Only forward the message to the engine if it can be handled (see handshake explanation) + if (Connection->IsClientMsgTypeValid(NMT_DebugText)) { + Bunch.SetReadPosition(Pos); + } else { + Call.Cancel(); + } + } + if (MessageType == NMT_ModMessage) { FString ModId; int32 MessageId; FString Content; if (FNetControlMessage::Receive(Bunch, ModId, MessageId, Content)) { diff --git a/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsSubsystem.cpp b/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsSubsystem.cpp index 82dec8b6ff..dc70d9f21a 100644 --- a/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsSubsystem.cpp +++ b/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsSubsystem.cpp @@ -2,6 +2,8 @@ #include "FGGameMode.h" #include "FGPlayerController.h" #include "Subsystem/SubsystemActorManager.h" +#include "Settings/FGUserSettingApplyType.h" +#include "Net/UnrealNetwork.h" ASessionSettingsSubsystem::ASessionSettingsSubsystem() { ReplicationPolicy = ESubsystemReplicationPolicy::SpawnOnServer_Replicate; @@ -11,6 +13,14 @@ void ASessionSettingsSubsystem::Init() { USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem(); check(SessionSettingsManager); + if (!HasAuthority()) + { + // We're a client, so we only have our own player controller + AFGPlayerController* PlayerController = Cast(GetWorld()->GetFirstPlayerController()); + USMLSessionSettingsRemoteCallObject* RCO = PlayerController->GetRemoteCallObjectOfClass(); + RCO->Server_RequestAllSessionSettings(); + } + OnOptionUpdatedDelegate = FOnOptionUpdated::CreateUObject(this, &ASessionSettingsSubsystem::OnSessionSettingUpdated); SessionSettingsManager->SubscribeToAllOptionUpdates(OnOptionUpdatedDelegate); } @@ -65,3 +75,35 @@ bool USMLSessionSettingsRemoteCallObject::Server_RequestSessionSettingUpdate_Val return SessionSettingsManager != NULL && SessionSettingsManager->FindSessionSetting(SessionSettingName) != NULL; } + +void USMLSessionSettingsRemoteCallObject::Server_RequestAllSessionSettings_Implementation() +{ + USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem(); + TMap Settings = SessionSettingsManager->GetAllSessionSettings(); + for (TPair Setting : Settings) + { + FVariant AppliedValue = Setting.Value->GetAppliedValue(); + FString ValueString = USessionSettingsManager::VariantToString(AppliedValue); + Client_SendSessionSetting(Setting.Key, ValueString); + } +} + +bool USMLSessionSettingsRemoteCallObject::Server_RequestAllSessionSettings_Validate() +{ + USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem(); + return SessionSettingsManager != NULL; +} + +void USMLSessionSettingsRemoteCallObject::Client_SendSessionSetting_Implementation(const FString& SessionSettingName, const FString& ValueString) +{ + ASessionSettingsSubsystem* SessionSettingsSubsystem = ASessionSettingsSubsystem::Get(GetWorld()); + check(SessionSettingsSubsystem); + + SessionSettingsSubsystem->PushSettingToSessionSettings(SessionSettingName, USessionSettingsManager::StringToVariant(ValueString)); +} + +bool USMLSessionSettingsRemoteCallObject::Client_SendSessionSetting_Validate(const FString& SessionSettingName, const FString& ValueString) +{ + const USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem(); + return SessionSettingsManager != NULL && !SessionSettingName.IsEmpty() && !ValueString.IsEmpty(); +} \ No newline at end of file diff --git a/Mods/SML/Source/SML/Private/Util/SemVersion.cpp b/Mods/SML/Source/SML/Private/Util/SemVersion.cpp index 2e0368bcdc..ebb05b02db 100644 --- a/Mods/SML/Source/SML/Private/Util/SemVersion.cpp +++ b/Mods/SML/Source/SML/Private/Util/SemVersion.cpp @@ -131,6 +131,88 @@ bool FVersionComparator::ParseVersionComparator(const FString& String, FString& return false; } +bool CaretMaxVersion(FVersion Lower, FVersion& MaxVersion) { + MaxVersion = FVersion{}; + //Check if we have any wildcards we need to handle + if (Lower.ContainsSpecialVersionNumbers()) { + //If major version is wildcard, there is no upper bound set + //Although i'm not sure if ^X is even legal semver comparator + if (Lower.Major == SEMVER_VERSION_NUMBER_WILDCARD) { + return false; + } + //If minor version is wildcard, upper bound is major + 1 + if (Lower.Minor == SEMVER_VERSION_NUMBER_WILDCARD) { + MaxVersion.Major = Lower.Major + 1; + } + //If patch version is wildcard, upper bound is either major or minor, but patch can be any + if (Lower.Patch == SEMVER_VERSION_NUMBER_WILDCARD) { + if (Lower.Major == 0) { + MaxVersion.Minor = Lower.Minor + 1; + } else { + MaxVersion.Major = Lower.Major + 1; + } + } + } else { + //No special version numbers, fallback to normal first-non-zero handling + if (Lower.Major == 0) { + if(Lower.Minor == 0) { + //Minor is zero, allow up to next patch version + MaxVersion.Patch = Lower.Patch + 1; + } else { + //Major is zero, allow up to next minor version update + MaxVersion.Minor = Lower.Minor + 1; + } + } else { + //Major is not zero, allow up to next major version update + MaxVersion.Major = Lower.Major + 1; + } + } + return true; +} + +bool TildeMaxVersion(FVersion Lower, FVersion& MaxVersion) { + MaxVersion = FVersion{}; + //Major version number is not specified, no upper bounds + //Although it's impossible to encounter under normal conditions, let's handle it for sake of completeness + if (Lower.Major == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + return false; + } + //Minor is unspecified, maximum version is Major + 1 + if (Lower.Minor == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + MaxVersion.Major = Lower.Major + 1; + //Patch version is unspecified, maximum version is Minor + 1 while keeping normal Major + } else if (Lower.Patch == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + MaxVersion.Major = Lower.Major; + MaxVersion.Minor = Lower.Minor + 1; + //Version contains no unspecified numbers, so maximum version is Patch + 1 while keeping Major and Minor + } else { + MaxVersion.Major = Lower.Major; + MaxVersion.Minor = Lower.Minor; + MaxVersion.Patch = Lower.Patch + 1; + } + return true; +} + +bool XRangeMaxVersion(FVersion Lower, FVersion& MaxVersion) { + MaxVersion = Lower; + //We have wildcards, so go from Major to Patch to compare them + //Major is wildcard, we accept any versions + if (Lower.Major == SEMVER_VERSION_NUMBER_WILDCARD) { + return false; + } + //Minor is wildcard, we accept everything as long as Major matches + if (Lower.Minor == SEMVER_VERSION_NUMBER_WILDCARD || Lower.Minor == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + MaxVersion = FVersion(Lower.Major + 1, 0, 0); + return true; + } + //Patch is wildcard, we accept everything as long as Major and Minor match + if (Lower.Patch == SEMVER_VERSION_NUMBER_WILDCARD || Lower.Patch == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + MaxVersion = FVersion(Lower.Major, Lower.Minor + 1, 0); + return true; + } + return true; +} + bool FVersionComparator::Matches(const FVersion& version) const { //Clear version used for comparison purposes const FVersion CleanVersion = MyVersion.RemoveSpecialNumbers(); @@ -149,39 +231,8 @@ bool FVersionComparator::Matches(const FVersion& version) const { return false; } FVersion MaxVersion{}; - //Check if we have any wildcards we need to handle - if (MyVersion.ContainsSpecialVersionNumbers()) { - //If major version is wildcard, there is no upper bound set - //Although i'm not sure if ^X is even legal semver comparator - if (MyVersion.Major == SEMVER_VERSION_NUMBER_WILDCARD) { - return true; - } - //If minor version is wildcard, upper bound is major + 1 - if (MyVersion.Minor == SEMVER_VERSION_NUMBER_WILDCARD) { - MaxVersion.Major = MyVersion.Major + 1; - } - //If patch version is wildcard, upper bound is either major or minor, but patch can be any - if (MyVersion.Patch == SEMVER_VERSION_NUMBER_WILDCARD) { - if (MyVersion.Major == 0) { - MaxVersion.Minor = MyVersion.Minor + 1; - } else { - MaxVersion.Major = MyVersion.Major + 1; - } - } - } else { - //No special version numbers, fallback to normal first-non-zero handling - if (MyVersion.Major == 0) { - if(MyVersion.Minor == 0) { - //Minor is zero, allow up to next patch version - MaxVersion.Patch = MyVersion.Patch + 1; - } else { - //Major is zero, allow up to next minor version update - MaxVersion.Minor = MyVersion.Minor + 1; - } - } else { - //Major is not zero, allow up to next major version update - MaxVersion.Major = MyVersion.Major + 1; - } + if(!CaretMaxVersion(MyVersion, MaxVersion)) { + return true; } //We pass if we are below max version required, exclusive return version.Compare(MaxVersion) < 0; @@ -194,22 +245,8 @@ bool FVersionComparator::Matches(const FVersion& version) const { return false; } FVersion MaxVersion{}; - //Major version number is not specified, no upper bounds - //Although it's impossible to encounter under normal conditions, let's handle it for sake of completeness - if (MyVersion.Major == SEMVER_VERSION_NUMBER_UNSPECIFIED) { + if (!TildeMaxVersion(MyVersion, MaxVersion)) { return true; - //Minor is unspecified, maximum version is Major + 1 - } else if (MyVersion.Minor == SEMVER_VERSION_NUMBER_UNSPECIFIED) { - MaxVersion.Major = MyVersion.Major + 1; - //Patch version is unspecified, maximum version is Minor + 1 while keeping normal Major - } else if (MyVersion.Patch == SEMVER_VERSION_NUMBER_UNSPECIFIED) { - MaxVersion.Major = MyVersion.Major; - MaxVersion.Minor = MyVersion.Minor + 1; - //Version contains no unspecified numbers, so maximum version is Patch + 1 while keeping Major and Minor - } else { - MaxVersion.Major = MyVersion.Major; - MaxVersion.Minor = MyVersion.Minor; - MaxVersion.Patch = MyVersion.Patch + 1; } //We pass if we are below max version required, exclusive return version.Compare(MaxVersion) < 0; @@ -217,21 +254,21 @@ bool FVersionComparator::Matches(const FVersion& version) const { //Equals versions can represent X-Ranges, so we need to handle wildcards inside them case EVersionComparisonOp::EQUALS: { - //We have wildcards, so go from Major to Patch to compare them - //Major is wildcard, we accept any versions - if (MyVersion.Major == SEMVER_VERSION_NUMBER_WILDCARD) { - return true; + if (!MyVersion.ContainsSpecialVersionNumbers()) { + return Result == 0; } - //Minor is wildcard, we accept everything as long as Major matches - if (MyVersion.Minor == SEMVER_VERSION_NUMBER_WILDCARD) { - return MyVersion.Major == version.Major; + + // Lower bound is zeroed version + if (Result < 0) { + return false; } - //Patch is wildcard, we accept everything as long as Major and Minor match - if (MyVersion.Patch == SEMVER_VERSION_NUMBER_WILDCARD) { - return MyVersion.Major == version.Major && MyVersion.Minor == version.Minor; + FVersion MaxVersion{}; + if (!XRangeMaxVersion(MyVersion, MaxVersion)) { + return true; } - //We represent fixed version number comparator, so just return true if versions are equal - return Result == 0; + + // We pass if we are below max version required, exclusive + return version.Compare(MaxVersion) < 0; } //Fallback default case to equals @@ -290,11 +327,13 @@ bool ParseHyphenVersionRange(const FString& LeftSideString, const FString& Right } else if (RightSideVersion.Minor == SEMVER_VERSION_NUMBER_UNSPECIFIED) { //Minor in right side version is not specified, allow everything up to next major ResultUpperBoundVersion.Major = RightSideVersion.Major + 1; + ResultUpperBoundVersion.PreRelease = TEXT("0"); bIncludeUpperBound = false; } else if (RightSideVersion.Patch == SEMVER_VERSION_NUMBER_UNSPECIFIED) { //Patch in right side version is not specified, allow everything up to next minor ResultUpperBoundVersion.Major = RightSideVersion.Major; ResultUpperBoundVersion.Minor = RightSideVersion.Minor + 1; + ResultUpperBoundVersion.PreRelease = TEXT("0"); bIncludeUpperBound = false; } else { //No unspecified version numbers, upper bound is inclusive @@ -325,9 +364,8 @@ bool FVersionComparatorCollection::ParseVersionCollection(const FString& String, if (CurrentChar == TEXT(' ')) { //Current character is space. If we're inside string, reset flag bIsCurrentlyInString = false; - } else if (CurrentChar == TEXT('-')) { - //Current character is hyphen. We are no longer inside string, but we have hyphen after last string - bIsCurrentlyInString = false; + } else if (CurrentChar == TEXT('-') && !bIsCurrentlyInString) { + // Hyphen ranges are only valid with spaces around the - (otherwise it's a prerelease marker) //We shouldn't have hyphen at this point, only previous string if (bHaveHyphenAfterLastString) { OutErrorMessage = TEXT("Unexpected hyphen"); @@ -408,9 +446,128 @@ bool FVersionComparatorCollection::ParseVersionCollection(const FString& String, FString FVersionComparatorCollection::ToString() const { TArray ResultString; + // Any and-range can be represented by a lower and upper bound + FVersionComparator Lower(EVersionComparisonOp::GREATER_EQUALS, FVersion(0, 0, 0)); + FVersionComparator Upper(EVersionComparisonOp::LESS_EQUALS, FVersion(INT64_MAX, INT64_MAX, INT64_MAX)); + for (const FVersionComparator& Comparator : Comparators) { - ResultString.Add(Comparator.ToString()); + switch (Comparator.Op) + { + case EVersionComparisonOp::EQUALS: { + if(!Comparator.MyVersion.ContainsSpecialVersionNumbers()) { + if (Lower.Matches(Comparator.MyVersion)) { + Lower = FVersionComparator(EVersionComparisonOp::GREATER_EQUALS, Comparator.MyVersion); + } + if (Upper.Matches(Comparator.MyVersion)) { + Lower = FVersionComparator(EVersionComparisonOp::LESS_EQUALS, Comparator.MyVersion); + } + } else { + FVersion CleanVersion = Comparator.MyVersion.RemoveSpecialNumbers(); + if (Lower.Matches(CleanVersion)) { + Lower = FVersionComparator(EVersionComparisonOp::GREATER_EQUALS, CleanVersion); + } + FVersion MaxVersion{}; + if (XRangeMaxVersion(Comparator.MyVersion, MaxVersion)) { + if (Upper.Matches(MaxVersion)) { + Upper = FVersionComparator(EVersionComparisonOp::LESS, MaxVersion); + } + } + } + break; + } + case EVersionComparisonOp::GREATER: + case EVersionComparisonOp::GREATER_EQUALS: { + if (Lower.Matches(Comparator.MyVersion)) { + Lower = Comparator; + } + break; + } + case EVersionComparisonOp::LESS: + case EVersionComparisonOp::LESS_EQUALS: { + if (Upper.Matches(Comparator.MyVersion)) { + Upper = Comparator; + } + break; + } + case EVersionComparisonOp::CARET: { + if (Lower.Matches(Comparator.MyVersion)) { + Lower = Comparator; + } + FVersion MaxVersion{}; + if (CaretMaxVersion(Comparator.MyVersion, MaxVersion)) { + if (Upper.Matches(MaxVersion)) { + Upper = FVersionComparator(EVersionComparisonOp::LESS, MaxVersion); + } + } + break; + } + case EVersionComparisonOp::TILDE: { + if (Lower.Matches(Comparator.MyVersion)) { + Lower = Comparator; + } + FVersion MaxVersion{}; + if (TildeMaxVersion(Comparator.MyVersion, MaxVersion)) { + if (Upper.Matches(MaxVersion)) { + Upper = FVersionComparator(EVersionComparisonOp::LESS, MaxVersion); + } + } + break; + } + default: + break; + } } + + bool bLowerIsSet = !Lower.Matches(FVersion(0, 0, 0)); + bool bUpperIsSet = !Upper.Matches(FVersion(INT64_MAX, INT64_MAX, INT64_MAX)); + + if (bLowerIsSet && bUpperIsSet) { + FVersion CaretMax, TildeMax; + CaretMaxVersion(Lower.MyVersion, CaretMax); + TildeMaxVersion(Lower.MyVersion, TildeMax); + if (Upper.MyVersion.Compare(CaretMax) == 0) { + ResultString.Add(FString::Printf(TEXT("^%s"), *Lower.MyVersion.RemoveSpecialNumbers().ToString())); + } else if (Upper.MyVersion.Compare(TildeMax) == 0) { + ResultString.Add(FString::Printf(TEXT("~%s"), *Lower.MyVersion.RemoveSpecialNumbers().ToString())); + } else if (Upper.MyVersion.Major == Lower.MyVersion.Major + 1 + && Lower.MyVersion.Minor <= 0 && Lower.MyVersion.Patch <= 0 + && Upper.MyVersion.Minor <= 0 && Upper.MyVersion.Patch <= 0 + && Lower.MyVersion.PreRelease.IsEmpty() && Upper.MyVersion.PreRelease.IsEmpty()) { + // x-range with major version only + ResultString.Add(FString::Printf(TEXT("%lld"), Lower.MyVersion.Major)); + } else if (Upper.MyVersion.Major == Lower.MyVersion.Major && Upper.MyVersion.Minor == Lower.MyVersion.Minor + 1 + && Lower.MyVersion.Patch <= 0 && Upper.MyVersion.Patch <= 0 + && Lower.MyVersion.PreRelease.IsEmpty() && Upper.MyVersion.PreRelease.IsEmpty()) { + // x-range with major and minor version + ResultString.Add(FString::Printf(TEXT("%lld.%lld"), Lower.MyVersion.Major, Lower.MyVersion.Minor)); + } else if (Upper.MyVersion.Compare(Lower.MyVersion) == 0) { + // Single version + ResultString.Add(Lower.ToString()); + } else if (Upper.Op == EVersionComparisonOp::LESS && Upper.MyVersion.PreRelease == TEXT("0") && (Upper.MyVersion.Minor <= 0 || Upper.MyVersion.Patch <= 0)) { + // Hyphen range with unspecified parts + FString UpperString; + if (Upper.MyVersion.Minor <= 0) { + UpperString = FString::Printf(TEXT("%lld"), Upper.MyVersion.Major - 1); + } else { + UpperString = FString::Printf(TEXT("%lld.%lld"), Upper.MyVersion.Major, Upper.MyVersion.Minor - 1); + } + ResultString.Add(FString::Printf(TEXT("%s - %s"), *Lower.MyVersion.ToString(), *UpperString)); + } else if (Upper.Op == EVersionComparisonOp::LESS_EQUALS && Upper.MyVersion.PreRelease.IsEmpty()) { + // Hyphen range + ResultString.Add(FString::Printf(TEXT("%s - %s"), *Lower.MyVersion.ToString(), *Upper.MyVersion.ToString())); + } else { + // Default case + ResultString.Add(Lower.ToString()); + ResultString.Add(Upper.ToString()); + } + } else if (bLowerIsSet) { + ResultString.Add(Lower.ToString()); + } else if (bUpperIsSet) { + ResultString.Add(Upper.ToString()); + } else { + ResultString.Add(TEXT("*")); + } + return FString::Join(ResultString, TEXT(" ")); } @@ -497,11 +654,9 @@ bool IsWildcardVersionNumber(const std::wstring& Number) { //Parses version number, taking care of wildcard characters and empty string int64 ParseVersionNumber(const std::wstring& Number) { if (IsWildcardVersionNumber(Number)) { - UE_LOG(LogSatisfactoryModLoader, Warning, TEXT("Version number %s is wildcard"), Number.c_str()); return SEMVER_VERSION_NUMBER_WILDCARD; } if (Number.length() == 0) { - UE_LOG(LogSatisfactoryModLoader, Warning, TEXT("Version number %s is empty"), Number.c_str()); return SEMVER_VERSION_NUMBER_UNSPECIFIED; } return std::stoul(Number); @@ -631,4 +786,4 @@ int FVersion::Compare(const FVersion& other) const { return Patch > other.Patch ? 1 : -1; return ComparePreRelease(*this, other); -} \ No newline at end of file +} diff --git a/Mods/SML/Source/SML/Public/ModLoading/ModLoadingLibrary.h b/Mods/SML/Source/SML/Public/ModLoading/ModLoadingLibrary.h index c0f814fac1..fb7dde52bb 100644 --- a/Mods/SML/Source/SML/Public/ModLoading/ModLoadingLibrary.h +++ b/Mods/SML/Source/SML/Public/ModLoading/ModLoadingLibrary.h @@ -77,8 +77,8 @@ struct SML_API FSMLPluginDescriptorMetadata { /** Range of the accepted remote versions, by default >=Version */ FVersionRange RemoteVersionRange; - /** Game version this mod was built against */ - uint32 GameVersion; + /** Game version this mod is compatible with */ + FVersionRange GameVersion; /** Version constraints for dependencies as specified in plugin refs */ TMap DependenciesVersions; diff --git a/Mods/SML/Source/SML/Public/Network/NetworkHandler.h b/Mods/SML/Source/SML/Public/Network/NetworkHandler.h index 3ac73b7442..df577c96e0 100644 --- a/Mods/SML/Source/SML/Public/Network/NetworkHandler.h +++ b/Mods/SML/Source/SML/Public/Network/NetworkHandler.h @@ -72,6 +72,11 @@ class SML_API UModNetworkHandler : public UEngineSubsystem { */ static void SendMessage(class UNetConnection* Connection, FMessageType MessageType, FString Data); + /** + * Set the connection to support mod messages and send all pending messages + */ + static void SetConnectionSupportsModMessages(class UNetConnection* Connection); + /** * Retrieves the game instance owning the specified net driver */ diff --git a/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h b/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h index 8d04084162..cb76d2ff7f 100644 --- a/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h +++ b/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h @@ -373,6 +373,8 @@ struct HookInvokerExecutorGlobalFunction { { UninstallHook(DebugSymbolName); } + + InHandlerHandle.Reset(); } }; @@ -547,6 +549,8 @@ struct HookInvokerExecutorMemberFunction { { UninstallHook(DebugSymbolName); } + + InHandlerHandle.Reset(); } }; diff --git a/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsManager.h b/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsManager.h index 0e3b8537e0..3162cae73b 100644 --- a/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsManager.h +++ b/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsManager.h @@ -56,6 +56,8 @@ class SML_API USessionSettingsManager : public UWorldSubsystem, public IFGOption void SubscribeToAllOptionUpdates(const FOnOptionUpdated& onOptionUpdatedDelegate); void UnsubscribeToAllOptionUpdates(const FOnOptionUpdated& onOptionUpdatedDelegate); + TMap GetAllSessionSettings() { return SessionSettings; }; + UFUNCTION(BlueprintCallable, Category = "Session Settings Manager") void InitializeForMap(const TSoftObjectPtr& World, bool bAttemptPreserveValues); diff --git a/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsSubsystem.h b/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsSubsystem.h index 50b300d85f..fc2f52d2b3 100644 --- a/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsSubsystem.h +++ b/Mods/SML/Source/SML/Public/SessionSettings/SessionSettingsSubsystem.h @@ -13,8 +13,6 @@ class SML_API ASessionSettingsSubsystem : public AModSubsystem { virtual void Init() override; - // TODO: Use this to sync the settings to the client on join - static ASessionSettingsSubsystem* Get(UWorld* World); void OnSessionSettingUpdated(const FString StrID, FVariant value); @@ -36,6 +34,13 @@ class SML_API USMLSessionSettingsRemoteCallObject : public UFGRemoteCallObject { UFUNCTION(Server, Reliable, WithValidation) void Server_RequestSessionSettingUpdate(const FString& SessionSettingName, const FString& ValueString); + + UFUNCTION(Server, Reliable, WithValidation) + void Server_RequestAllSessionSettings(); + + UFUNCTION(Client, Reliable, WithValidation) + void Client_SendSessionSetting(const FString& SessionSettingName, const FString& ValueString); + private: UPROPERTY(Replicated) bool mForceNetField_USMLSessionSettingsRemoteCallObject; diff --git a/Mods/SML/Source/SML/Public/Util/SemVersion.h b/Mods/SML/Source/SML/Public/Util/SemVersion.h index 86cd286b0a..033e714364 100644 --- a/Mods/SML/Source/SML/Public/Util/SemVersion.h +++ b/Mods/SML/Source/SML/Public/Util/SemVersion.h @@ -147,8 +147,6 @@ struct SML_API FVersionRange { * Converts this version range to string. * It can be parsed back into range matching same versions, * but it will not always equal to string used to initialize this version range - * For example, output string will never contain hyphen version ranges */ FString ToString() const; }; - diff --git a/Mods/SML/Source/SML/SML.Build.cs b/Mods/SML/Source/SML/SML.Build.cs index 0e3087e5b6..491835e05d 100644 --- a/Mods/SML/Source/SML/SML.Build.cs +++ b/Mods/SML/Source/SML/SML.Build.cs @@ -50,18 +50,19 @@ public SML(ReadOnlyTargetRules Target) : base(Target) if (Target.Platform == UnrealTargetPlatform.Win64) { - // https://github.com/kubo/funchook/tree/7cb8819594f0d586454011ab691fab4edb625068 - // Built using Visual Studio project generated by cmake - // funchook will additionally build distorm, which is added to the Visual Studio project - // as a dependency, along with psapi, so that they don't need to be included here separately + // https://github.com/satisfactorymodding/funchook/tree/7ccd6b8087f1a587af80de1a3dea405798989225 PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "funchook.lib")); - + // funchook will additionally build distorm + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "distorm.lib")); + // funchook uses GetMappedFileNameA from psapi + PublicSystemLibraries.Add("psapi.lib"); + // https://github.com/satisfactorymodding/AssemblyAnalyzer/tree/e08ec4402b6e016a9b7aa59ab8c82dd0840e8f98 PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "AssemblyAnalyzer.lib")); PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "Zydis.lib")); PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "Zycore.lib")); } else if (Target.Platform == UnrealTargetPlatform.Linux) { - // https://github.com/kubo/funchook/tree/7cb8819594f0d586454011ab691fab4edb625068 + // https://github.com/satisfactorymodding/funchook/tree/7ccd6b8087f1a587af80de1a3dea405798989225 // Built on windows using the Unreal Engine cross-compile clang toolchain PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libfunchook.a")); // funchook will additionally build distorm diff --git a/Mods/SML/ThirdParty/Linux/libfunchook.a b/Mods/SML/ThirdParty/Linux/libfunchook.a index 66fe0dba79..a5398bfd9b 100644 Binary files a/Mods/SML/ThirdParty/Linux/libfunchook.a and b/Mods/SML/ThirdParty/Linux/libfunchook.a differ diff --git a/Mods/SML/ThirdParty/Win64/distorm.lib b/Mods/SML/ThirdParty/Win64/distorm.lib new file mode 100644 index 0000000000..87f27df600 Binary files /dev/null and b/Mods/SML/ThirdParty/Win64/distorm.lib differ diff --git a/Mods/SML/ThirdParty/Win64/funchook.lib b/Mods/SML/ThirdParty/Win64/funchook.lib index 390ba72e3c..2098b1a4e5 100644 Binary files a/Mods/SML/ThirdParty/Win64/funchook.lib and b/Mods/SML/ThirdParty/Win64/funchook.lib differ