diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8ed9fc6a5..37c1216799 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,11 +48,11 @@ jobs: run: MSBuild.exe .\\SML\\FactoryGame.sln /p:Configuration="Development Editor" /p:Platform="Win64" /t:"Games\FactoryGame" - name: Package SML Mod - run: .\\ue\\Engine\\Build\\BatchFiles\\RunUAT.bat -ScriptsForProject="$Env:GITHUB_WORKSPACE\\SML\\FactoryGame.uproject" PackagePlugin -Project="$Env:GITHUB_WORKSPACE\\SML\\FactoryGame.uproject" -dlcname=SML -build -clientconfig=Shipping -serverconfig=Shipping -platform=Win64 -nocompileeditor -installed + run: .\\ue\\Engine\\Build\\BatchFiles\\RunUAT.bat -ScriptsForProject="$Env:GITHUB_WORKSPACE\\SML\\FactoryGame.uproject" PackagePlugin -Project="$Env:GITHUB_WORKSPACE\\SML\\FactoryGame.uproject" -dlcname=SML -build -server -clientconfig=Shipping -serverconfig=Shipping -platform=Win64 -serverplatform=Win64+Linux -nocompileeditor -installed - name: Archive SML artifact uses: actions/upload-artifact@v2 if: ${{ github.event_name == 'push' }} with: name: sml - path: SML\\Saved\\ArchivedPlugins\\SML\\SML-Windows.zip + path: SML\\Saved\\ArchivedPlugins\\SML\\*.zip diff --git a/FactoryGame.uproject b/FactoryGame.uproject index 53344f3943..2a128cbe36 100644 --- a/FactoryGame.uproject +++ b/FactoryGame.uproject @@ -274,5 +274,13 @@ "Win64", "Linux", "Windows" - ] + ], + "PreBuildSteps": { + "Win64": [ + "powershell -Command \"$file = '$(ProjectDir)\\Plugins\\Wwise\\Source\\AkAudio\\Classes\\OcclusionObstructionService\\AkOcclusionObstructionService.h'; if (Test-Path $file) { if (!(Select-String -Path $file -Pattern 'class AKAUDIO_API AkOcclusionObstructionService' -SimpleMatch -Quiet)) { (Get-Content $file) -replace 'class AkOcclusionObstructionService', 'class AKAUDIO_API AkOcclusionObstructionService' | Out-File -encoding UTF8 $file } }\"" + ], + "Linux": [ + "if ! grep -q 'class AKAUDIO_API AkOcclusionObstructionService' $(ProjectDir)/Plugins/Wwise/Source/AkAudio/Classes/OcclusionObstructionService/AkOcclusionObstructionService.h; then sed -i 's/class AkOcclusionObstructionService/class AKAUDIO_API AkOcclusionObstructionService/' $(ProjectDir)/Plugins/Wwise/Source/AkAudio/Classes/OcclusionObstructionService/AkOcclusionObstructionService.h; fi" + ] + } } diff --git a/Mods/Alpakit/Source/Alpakit.Automation/PackagePlugin.cs b/Mods/Alpakit/Source/Alpakit.Automation/PackagePlugin.cs index 131f3f7709..8c913df376 100644 --- a/Mods/Alpakit/Source/Alpakit.Automation/PackagePlugin.cs +++ b/Mods/Alpakit/Source/Alpakit.Automation/PackagePlugin.cs @@ -6,6 +6,8 @@ using EpicGames.Core; using UnrealBuildTool; using AutomationScripts; +using Microsoft.Extensions.Logging; +using UnrealBuildBase; namespace Alpakit.Automation; @@ -13,6 +15,9 @@ public class PackagePlugin : BuildCookRun { public override void ExecuteBuild() { + var mergeArchive = ParseParam("merge"); + Logger.LogWarning($"Merge archive: {mergeArchive}"); + var projectParams = SetupParams(); projectParams.PreModifyDeploymentContextCallback = (ProjectParams, SC) => { @@ -36,6 +41,11 @@ public override void ExecuteBuild() ArchiveStagedPlugin(projectParams, SC); } + if (mergeArchive) + { + ArchiveMergedStagedPlugin(projectParams, deploymentContexts); + } + foreach (var SC in deploymentContexts) { var copyToGameDirectory = ParseOptionalDirectoryReferenceParam($"CopyToGameDirectory_{SC.FinalCookPlatform}"); @@ -107,26 +117,65 @@ private static void DeployStagedPlugin(ProjectParams ProjectParams, DeploymentCo private static void ArchiveStagedPlugin(ProjectParams ProjectParams, DeploymentContext SC) { - // Plugins will be archived under /Saved/ArchivedPlugins/ - var baseArchiveDirectory = CombinePaths(SC.ProjectRoot.FullName, "Saved", "ArchivedPlugins"); - var archiveDirectory = CombinePaths(baseArchiveDirectory, ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions()); - - CreateDirectory(archiveDirectory); + var archiveDirectory = GetArchivedPluginDirectory(ProjectParams); var dlcName = ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions(); var zipFileName = $"{dlcName}-{SC.FinalCookPlatform}.zip"; - - var zipFilePath = CombinePaths(archiveDirectory, zipFileName); - - if (FileExists(zipFilePath)) - DeleteFile(zipFilePath); // We only want to archive the staged files of the plugin, not the entire stage directory var stagedPluginDirectory = Project.ApplyDirectoryRemap(SC, SC.GetStagedFileLocation(ProjectParams.DLCFile)); var fullStagedPluginDirectory = DirectoryReference.Combine(SC.StageDirectory, stagedPluginDirectory.Directory.Name); + + CreateZipFromDirectory(fullStagedPluginDirectory, FileReference.Combine(archiveDirectory, zipFileName)); + } + + private static void ArchiveMergedStagedPlugin(ProjectParams ProjectParams, List DeploymentContexts) + { + var archiveDirectory = GetArchivedPluginDirectory(ProjectParams); + + var dlcName = ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions(); - ZipFile.CreateFromDirectory(fullStagedPluginDirectory.FullName, zipFilePath); + var zipFileName = $"{dlcName}.zip"; + + // We only want to archive the staged files of the plugin, not the entire stage directory + var mergedTempDir = DirectoryReference.Combine(ProjectParams.DLCFile.Directory, "Saved", "ArchiveMerging"); + try + { + if (DirectoryReference.Exists(mergedTempDir)) + DirectoryReference.Delete(mergedTempDir, true); + DirectoryReference.CreateDirectory(mergedTempDir); + foreach (var SC in DeploymentContexts) + { + var stagedPluginDirectory = + Project.ApplyDirectoryRemap(SC, SC.GetStagedFileLocation(ProjectParams.DLCFile)); + var fullStagedPluginDirectory = + DirectoryReference.Combine(SC.StageDirectory, stagedPluginDirectory.Directory.Name); + CopyDirectory_NoExceptions(fullStagedPluginDirectory, DirectoryReference.Combine(mergedTempDir, SC.FinalCookPlatform)); + } + + CreateZipFromDirectory(mergedTempDir, FileReference.Combine(archiveDirectory, zipFileName)); + } + finally + { + DirectoryReference.Delete(mergedTempDir, true); + } + } + + private static DirectoryReference GetArchivedPluginDirectory(ProjectParams ProjectParams) + { + // Plugins will be archived under /Saved/ArchivedPlugins/ + return DirectoryReference.Combine(ProjectParams.RawProjectPath.Directory, "Saved", "ArchivedPlugins", ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions()); + } + + private static void CreateZipFromDirectory(DirectoryReference SourceDirectory, FileReference DestinationFile) + { + CreateDirectory(DestinationFile.Directory); + + if (FileReference.Exists(DestinationFile)) + FileReference.Delete(DestinationFile); + + ZipFile.CreateFromDirectory(SourceDirectory.FullName, DestinationFile.FullName); } private static string GetPluginPathRelativeToStageRoot(ProjectParams ProjectParams) diff --git a/Mods/Alpakit/Source/Alpakit/Private/Alpakit.cpp b/Mods/Alpakit/Source/Alpakit/Private/Alpakit.cpp index 8021b044ed..c8a777e40c 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/Alpakit.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/Alpakit.cpp @@ -9,7 +9,9 @@ #include "ISettingsSection.h" #include "LevelEditor.h" #include "IPluginBrowser.h" +#include "UATHelper/Public/IUATHelperModule.h" #include "ModWizardDefinition.h" +#include "ModTargetsConfig.h" #include "SAlpakitLogTabContent.h" static const FName AlpakitTabName("Alpakit"); @@ -124,7 +126,30 @@ void FAlpakitModule::ShutdownModule() { FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(AlpakitLogTabName); } -void FAlpakitModule::PackageMods(TArray> Mods) { +TSharedRef MakeDevelopmentProfileForMod(TSharedRef Mod) { + TSharedRef Profile = MakeShared(Mod->GetName()); + + Profile->bBuild = Mod->GetDescriptor().Modules.Num() > 0; + + UAlpakitSettings* Settings = UAlpakitSettings::Get(); + + Profile->BuildConfiguration = Settings->GetBuildConfiguration(); + + for (auto [CookedPlatform, TargetSetting] : Settings->GetPlatformTargetSettings()) { + if (TargetSetting.bEnabled) { + Profile->CookedPlatforms.Add(CookedPlatform); + if (TargetSetting.bCopyModsToGame) { + FAlpakitProfileGameInfo& GameInfo = Profile->PlatformGameInfo.FindOrAdd(CookedPlatform); + GameInfo.bCopyToGame = true; + GameInfo.GamePath = TargetSetting.SatisfactoryGamePath; + } + } + } + + return Profile; +} + +void FAlpakitModule::PackageModsDevelopment(TArray> Mods) { if (Mods.Num() == 0) { return; } @@ -134,28 +159,47 @@ void FAlpakitModule::PackageMods(TArray> Mods) { UAlpakitSettings* Settings = UAlpakitSettings::Get(); for (TSharedRef Mod : Mods) { - TSharedRef Profile = MakeShared(Mod->GetName()); - - Profile->bBuild = Mod->GetDescriptor().Modules.Num() > 0; + ProfilesToPackage.Add(MakeDevelopmentProfileForMod(Mod)); + } - Profile->BuildConfiguration = Settings->GetBuildConfiguration(); - // Profile->CookedPlatforms = Settings->CookPlatforms; - Profile->CookedPlatforms = {TEXT("Windows")}; // Only Windows is allowed for now + TSharedRef LastProfile = ProfilesToPackage.Last(); - if (Settings->bCopyModsToGame) { - FAlpakitProfileGameInfo& GameInfo = Profile->PlatformGameInfo.FindOrAdd(TEXT("Windows")); - GameInfo.bCopyToGame = true; - GameInfo.GamePath = Settings->SatisfactoryGamePath.Path; + for (auto [CookedPlatform, TargetSetting] : Settings->GetPlatformTargetSettings()) { + if (TargetSetting.bEnabled) { + if (TargetSetting.bLaunchGame) { + FAlpakitProfileGameInfo& GameInfo = LastProfile->PlatformGameInfo.FindOrAdd(CookedPlatform); + GameInfo.bStartGame = true; + GameInfo.StartGameType = TargetSetting.LaunchGameType; + } } - - ProfilesToPackage.Add(Profile); } - TSharedRef LastProfile = ProfilesToPackage.Last(); + PackageMods(ProfilesToPackage); +} + +TSharedRef MakeReleaseProfileForMod(TSharedRef Mod) { + TSharedRef Profile = MakeShared(Mod->GetName()); + + Profile->bBuild = Mod->GetDescriptor().Modules.Num() > 0; + Profile->BuildConfiguration = EBuildConfiguration::Shipping; + + Profile->bMergeArchive = true; + + FModTargetsConfig TargetsConfig(Mod); + Profile->CookedPlatforms.Append(TargetsConfig.GetCookedPlatforms()); + + return Profile; +} - if (Settings->LaunchGameAfterPacking != EAlpakitStartGameType::NONE) { - FAlpakitProfileGameInfo& GameInfo = LastProfile->PlatformGameInfo.FindOrAdd(TEXT("Windows")); - GameInfo.StartGameType = Settings->LaunchGameAfterPacking; +void FAlpakitModule::PackageModsRelease(TArray> Mods) { + if (Mods.Num() == 0) { + return; + } + + TArray> ProfilesToPackage; + + for (TSharedRef Mod : Mods) { + ProfilesToPackage.Add(MakeReleaseProfileForMod(Mod)); } PackageMods(ProfilesToPackage); diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp new file mode 100644 index 0000000000..54b5c52152 --- /dev/null +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitEditModDialog.cpp @@ -0,0 +1,100 @@ +#include "AlpakitEditModDialog.h" + +#include "ISourceControlModule.h" +#include "ISourceControlProvider.h" +#include "ISourceControlOperation.h" +#include "ISourceControlState.h" +#include "ModMetadataObject.h" +#include "SourceControlOperations.h" + +#define LOCTEXT_NAMESPACE "AlpakitEditMod" + +void SAlpakitEditModDialog::Construct(const FArguments& InArgs, TSharedRef InMod) { + Mod = InMod; + + // Construct the plugin metadata object using the descriptor for this plugin + MetadataObject = NewObject(); + MetadataObject->TargetIconPath = Mod->GetBaseDir() / TEXT("Resources/Icon128.png"); + MetadataObject->PopulateFromDescriptor(Mod->GetDescriptor()); + MetadataObject->AddToRoot(); + + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + EditModule.RegisterCustomClassLayout(UModMetadataObject::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FModMetadataCustomization::MakeInstance)); + TSharedRef PropertyView = EditModule.CreateDetailView(FDetailsViewArgs(false, false, false, FDetailsViewArgs::ActorsUseNameArea, true)); + PropertyView->SetObject(MetadataObject, true); + + SWindow::Construct(SWindow::FArguments() + .ClientSize(FVector2D(800.0f, 700.0f)) + .Title(FText::Format(LOCTEXT("ModMetadata", "{0} ({1}) Properties"), FText::FromString(Mod->GetFriendlyName()), FText::FromString(Mod->GetName()))) + .Content() + [ + SNew(SBorder) + .Padding(FMargin(8.0f, 8.0f)) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .Padding(5) + [ + PropertyView + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(5) + .HAlign(HAlign_Right) + [ + SNew(SButton) + .ContentPadding(FMargin(20.0f, 2.0f)) + .Text(LOCTEXT("OkButtonLabel", "Ok")) + .OnClicked(this, &SAlpakitEditModDialog::OnOkClicked) + ] + ] + ] + ); +} + +FReply SAlpakitEditModDialog::OnOkClicked() { + FPluginDescriptor OldDescriptor = Mod->GetDescriptor(); + + // Update the descriptor with the new metadata + FPluginDescriptor NewDescriptor = OldDescriptor; + MetadataObject->CopyIntoDescriptor(NewDescriptor); + MetadataObject->RemoveFromRoot(); + + // Close the properties window + RequestDestroyWindow(); + + // Write both to strings + FString OldText; + OldDescriptor.Write(OldText); + FString NewText; + NewDescriptor.Write(NewText); + if(OldText.Compare(NewText, ESearchCase::CaseSensitive) != 0) + { + FString DescriptorFileName = Mod->GetDescriptorFileName(); + + // First attempt to check out the file if SCC is enabled + ISourceControlModule& SourceControlModule = ISourceControlModule::Get(); + if(SourceControlModule.IsEnabled()) + { + ISourceControlProvider& SourceControlProvider = SourceControlModule.GetProvider(); + TSharedPtr SourceControlState = SourceControlProvider.GetState(DescriptorFileName, EStateCacheUsage::ForceUpdate); + if(SourceControlState.IsValid() && SourceControlState->CanCheckout()) + { + SourceControlProvider.Execute(ISourceControlOperation::Create(), DescriptorFileName); + } + } + + // Write to the file and update the in-memory metadata + FText FailReason; + if(!Mod->UpdateDescriptor(NewDescriptor, FailReason)) + { + FMessageDialog::Open(EAppMsgType::Ok, FailReason); + } + } + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntry.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntry.cpp index ebd23b404e..49e70f76a1 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntry.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntry.cpp @@ -1,8 +1,10 @@ #include "AlpakitModEntry.h" #include "Alpakit.h" #include "AlpakitInstance.h" +#include "AlpakitEditModDialog.h" #include "AlpakitSettings.h" #include "AlpakitStyle.h" +#include "ModTargetsConfig.h" #include "ISourceControlModule.h" #include "ISourceControlProvider.h" #include "ISourceControlOperation.h" @@ -26,9 +28,12 @@ void SAlpakitModEntry::Construct(const FArguments& Args, TSharedRef InM const FString PluginName = Mod->GetName(); Checkbox = SNew(SCheckBox) + .ToolTipText(LOCTEXT("AlpakitModEntryEnabled_Tooltip", "If enabled, this mod will be packaged when the 'Alpakit Dev' or 'Alpakit Release' buttons are pressed")) .OnCheckStateChanged(this, &SAlpakitModEntry::OnEnableCheckboxChanged) - .IsChecked(Settings->ModSelection.FindOrAdd(PluginName, false)); + .IsChecked(Settings->ModSelection.FindOrAdd(PluginName, false) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); + ModTargetsConfig = FModTargetsConfig(InMod); + ChildSlot[ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 5, 0).VAlign(VAlign_Center)[ @@ -42,19 +47,21 @@ void SAlpakitModEntry::Construct(const FArguments& Args, TSharedRef InM return FReply::Handled(); }) .ToolTipText_Lambda([this](){ - return FText::FromString(FString::Printf(TEXT("Alpakit %s"), *this->Mod->GetName())); + // TODO localize correctly + return FText::FromString(FString::Printf(TEXT("Alpakit Dev just %s\n\nPackage this mod for each of the targets you have Enabled in your Dev Packaging Settings (NOT Release Targets!), then run your after packaging tasks for each target.\nCan build Shipping C++ as required."), *this->Mod->GetName())); }) .IsEnabled(this, &SAlpakitModEntry::IsPackageButtonEnabled) ] + SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 5, 0).VAlign(VAlign_Center)[ - SNew(SButton) + SAssignNew(EditButton, SButton) .Text(LOCTEXT("EditModAlpakit", "Edit")) .OnClicked_Lambda([this](){ - this->OnEditMod(); + const TSharedRef EditModDialog = SNew(SAlpakitEditModDialog, Mod.ToSharedRef()); + FSlateApplication::Get().AddModalWindow(EditModDialog, Owner); return FReply::Handled(); }) .ToolTipText_Lambda([this](){ - return FText::FromString(FString::Printf(TEXT("Edit %s"), *this->Mod->GetName())); + return FText::FromString(FString::Printf(TEXT("Edit %s via the wizard"), *this->Mod->GetName())); }) ] + SHorizontalBox::Slot().FillWidth(1).VAlign(VAlign_Center)[ @@ -67,112 +74,35 @@ void SAlpakitModEntry::Construct(const FArguments& Args, TSharedRef InM return FText::FromString(InOwner->GetLastFilter()); }) ] + + SHorizontalBox::Slot().AutoWidth().Padding(20, 0)[ + SNew(SCheckBox) + .IsChecked(ModTargetsConfig.bWindows ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([this](ECheckBoxState State) { + ModTargetsConfig.bWindows = State == ECheckBoxState::Checked; + ModTargetsConfig.Save(); + }) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(38, 0)[ + SNew(SCheckBox) + .IsChecked(ModTargetsConfig.bWindowsServer ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([this](ECheckBoxState State) { + ModTargetsConfig.bWindowsServer = State == ECheckBoxState::Checked; + ModTargetsConfig.Save(); + }) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(28, 0)[ + SNew(SCheckBox) + .IsChecked(ModTargetsConfig.bLinuxServer ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([this](ECheckBoxState State) { + ModTargetsConfig.bLinuxServer = State == ECheckBoxState::Checked; + ModTargetsConfig.Save(); + }) + ] ]; } -void SAlpakitModEntry::OnEditMod() -{ - // Construct the plugin metadata object using the descriptor for this plugin - UModMetadataObject* MetadataObject = NewObject(); - MetadataObject->TargetIconPath = Mod->GetBaseDir() / TEXT("Resources/Icon128.png"); - MetadataObject->PopulateFromDescriptor(Mod->GetDescriptor()); - MetadataObject->AddToRoot(); - - // Create a property view - FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); - EditModule.RegisterCustomClassLayout(UModMetadataObject::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FModMetadataCustomization::MakeInstance)); - TSharedRef PropertyView = EditModule.CreateDetailView(FDetailsViewArgs(false, false, false, FDetailsViewArgs::ActorsUseNameArea, true)); - PropertyView->SetObject(MetadataObject, true); - - // Create the window - PropertiesWindow = SNew(SWindow) - .ClientSize(FVector2D(800.0f, 700.0f)) - .Title(LOCTEXT("ModMetadata", "Mod Properties")) - .Content() - [ - SNew(SBorder) - .Padding(FMargin(8.0f, 8.0f)) - [ - SNew(SVerticalBox) - - +SVerticalBox::Slot() - .AutoHeight() - .Padding(FMargin(5.0f, 10.0f, 5.0f, 5.0f)) - [ - SNew(STextBlock) - .Text(FText::FromString(Mod->GetName())) - ] - - + SVerticalBox::Slot() - .Padding(5) - [ - PropertyView - ] - - + SVerticalBox::Slot() - .AutoHeight() - .Padding(5) - .HAlign(HAlign_Right) - [ - SNew(SButton) - .ContentPadding(FMargin(20.0f, 2.0f)) - .Text(LOCTEXT("OkButtonLabel", "Ok")) - .OnClicked(this, &SAlpakitModEntry::OnEditModFinished, MetadataObject) - ] - ] - ]; - - FSlateApplication::Get().AddModalWindow(PropertiesWindow.ToSharedRef(), Owner); -} - -FReply SAlpakitModEntry::OnEditModFinished(UModMetadataObject* MetadataObject) -{ - FPluginDescriptor OldDescriptor = Mod->GetDescriptor(); - - // Update the descriptor with the new metadata - FPluginDescriptor NewDescriptor = OldDescriptor; - MetadataObject->CopyIntoDescriptor(NewDescriptor); - MetadataObject->RemoveFromRoot(); - - // Close the properties window - PropertiesWindow->RequestDestroyWindow(); - - FString DescriptorFileName = Mod->GetDescriptorFileName(); - - // First attempt to check out the file if SCC is enabled - ISourceControlModule& SourceControlModule = ISourceControlModule::Get(); - if(SourceControlModule.IsEnabled()) - { - ISourceControlProvider& SourceControlProvider = SourceControlModule.GetProvider(); - TSharedPtr SourceControlState = SourceControlProvider.GetState(DescriptorFileName, EStateCacheUsage::ForceUpdate); - if(SourceControlState.IsValid() && SourceControlState->CanCheckout()) - { - SourceControlProvider.Execute(ISourceControlOperation::Create(), DescriptorFileName); - } - } - - // Write to the file and update the in-memory metadata - FText FailReason; - if(!Mod->UpdateDescriptor(NewDescriptor, FailReason)) - { - FMessageDialog::Open(EAppMsgType::Ok, FailReason); - } - return FReply::Handled(); -} - -FText GetCurrentPlatformName() { -#if PLATFORM_WINDOWS - return LOCTEXT("PlatformName_Windows", "Windows"); -#elif PLATFORM_MAC - return LOCTEXT("PlatformName_Mac", "Mac"); -#elif PLATFORM_LINUX - return LOCTEXT("PlatformName_Linux", "Linux"); -#else - return LOCTEXT("PlatformName_Other", "Other OS"); -#endif -} void SAlpakitModEntry::PackageMod() { - FAlpakitModule::Get().PackageMods({Mod.ToSharedRef()}); + FAlpakitModule::Get().PackageModsDevelopment({Mod.ToSharedRef()}); } void SAlpakitModEntry::OnEnableCheckboxChanged(ECheckBoxState NewState) { @@ -183,6 +113,8 @@ void SAlpakitModEntry::OnEnableCheckboxChanged(ECheckBoxState NewState) { Settings->ModSelection.Add(PluginName, NewState == ECheckBoxState::Checked); Settings->SaveSettings(); + + Owner->UpdateAllCheckbox(); } bool SAlpakitModEntry::IsPackageButtonEnabled() const { diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntryList.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntryList.cpp index 0ae19e5ba6..ba372e98bc 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntryList.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitModEntryList.cpp @@ -1,6 +1,7 @@ #include "AlpakitModEntryList.h" #include "Alpakit.h" #include "AlpakitModEntry.h" +#include "AlpakitSettings.h" #include "Interfaces/IPluginManager.h" #include "Slate.h" @@ -8,30 +9,93 @@ void SAlpakitModEntryList::Construct(const FArguments& Args) { ChildSlot[ - SNew(SVerticalBox) - + SVerticalBox::Slot().AutoHeight()[ + SNew(SVerticalBox) + +SVerticalBox::Slot().AutoHeight().Padding(0, 5)[ SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth()[ - SNew(SButton) - .Text(LOCTEXT("PackageModAlpakitAll", "Alpakit Selected!")) - .OnClicked(this,& SAlpakitModEntryList::PackageAllMods) - .IsEnabled(this, &SAlpakitModEntryList::IsPackageButtonEnabled) + +SHorizontalBox::Slot().FillWidth(1).VAlign(VAlign_Center)[ + SNew(SVerticalBox) + +SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 5)[ + Args._BarSlot.Widget + ] + +SVerticalBox::Slot()[ + SNew(SHorizontalBox) + +SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 5, 0)[ + SAssignNew(AllModsCheckbox, SCheckBox) + .ToolTipText(LOCTEXT("AllModsCheckboxAlpakit_Tooltip", "Select/deselect all displayed mods. Search can be used to narrow down which are affected")) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InState) { + SetAllMods(InState == ECheckBoxState::Checked); + }) + ] + +SHorizontalBox::Slot().FillWidth(1).VAlign(VAlign_Center)[ + SNew(SEditableTextBox) + .ToolTipText(LOCTEXT("SearchHint_Tooltip", "Filter the list based on the text entered here. Friendly name and Mod Reference supported")) + .HintText(LOCTEXT("SearchHint", "Search Plugin...")) + .OnTextChanged_Lambda([this](const FText& InText) { + this->Filter(InText.ToString()); + }) + ] + +SHorizontalBox::Slot().AutoWidth()[ + SNew(SSpacer) + .Size(FVector2D(20.0f, 10.0f)) + ] + +SHorizontalBox::Slot().AutoWidth()[ + SNew(SCheckBox) + .Content()[ + SNew(STextBlock) + .ToolTipText(LOCTEXT("ShowEnginePlugins_Tooltip", "Display all Unreal Engine plugins loaded at the engine level (notably not mods, you usually don't need this enabled)")) + .Text(LOCTEXT("ShowEnginePlugins", "Show Engine Plugins")) + ] + .OnCheckStateChanged_Lambda([this](ECheckBoxState InState) { + this->SetShowEngine(InState == ECheckBoxState::Checked); + }) + ] + +SHorizontalBox::Slot().AutoWidth()[ + SNew(SSpacer) + .Size(FVector2D(10.0f, 10.0f)) + ] + +SHorizontalBox::Slot().AutoWidth()[ + SNew(SCheckBox) + .Content()[ + SNew(STextBlock) + .ToolTipText(LOCTEXT("ShowProjectPlugins_Tooltip", "Display all Unreal Engine plugins loaded at the project level (such as Plugins folder mods, you usually don't need this enabled)")) + .Text(LOCTEXT("ShowProjectPlugins", "Show Project Plugins")) + ] + .OnCheckStateChanged_Lambda([this](ECheckBoxState InState) { + this->SetShowProject(InState == ECheckBoxState::Checked); + }) + ] + ] ] - + SHorizontalBox::Slot().AutoWidth()[ - SNew(SButton) - .Text(LOCTEXT("PackageModCheckAll", "Check All")) - .OnClicked(this,& SAlpakitModEntryList::CheckAllMods) - ] - + SHorizontalBox::Slot().AutoWidth()[ - SNew(SButton) - .Text(LOCTEXT("PackageModUncheckAll", "Check None")) - .OnClicked(this,& SAlpakitModEntryList::UncheckAllMods) + + +SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Fill).Padding(5,0)[ + SNew(SSeparator) + .Orientation(Orient_Vertical) ] - + SHorizontalBox::Slot().FillWidth(1.0f) - + SHorizontalBox::Slot().AutoWidth()[ - SNew(SButton) - .Text(LOCTEXT("CreateMod", "Create Mod")) - .OnClicked(this,& SAlpakitModEntryList::CreateMod) + + +SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5, 0, 15, 0)[ + SNew(SVerticalBox) + +SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0, 0, 0, 5)[ + SNew(STextBlock) + .ToolTipText(LOCTEXT("ReleaseTargets_Tooltip", "Select what target platforms this mod supports, which controls what will be built by the 'Alpakit Release' button. The 'Alpakit Dev' and Alpakit! for one mod buttons ignore this and use the Dev Packaging Settings instead.")) + .Text(LOCTEXT("ReleaseTargets", "Release Targets")) + ] + +SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0, 5, 0, 0)[ + SNew(SHorizontalBox) + +SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5,0)[ + SNew(STextBlock) + .Text(LOCTEXT("ReleaseWindows", "Windows")) + ] + + +SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5,0)[ + SNew(STextBlock) + .Text(LOCTEXT("ReleaseWindowsServer", "Windows Server")) + ] + + +SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5,0)[ + SNew(STextBlock) + .Text(LOCTEXT("ReleaseLinuxServer", "Linux Server")) + ] + ] ] ] + SVerticalBox::Slot().FillHeight(1.0f)[ @@ -53,6 +117,7 @@ void SAlpakitModEntryList::Construct(const FArguments& Args) { ]; LoadMods(); + UpdateAllCheckbox(); IPluginManager::Get().OnNewPluginCreated().AddSP(this, &SAlpakitModEntryList::OnNewPluginCreated); } @@ -138,6 +203,7 @@ void SAlpakitModEntryList::Filter(const FString& InFilter) { } ModList->RequestListRefresh(); + UpdateAllCheckbox(); } FString SAlpakitModEntryList::GetLastFilter() const { @@ -159,14 +225,9 @@ void SAlpakitModEntryList::OnNewPluginCreated(IPlugin& Plugin) LoadMods(); } -FReply SAlpakitModEntryList::PackageAllMods() { - TArray> ModsToPackage; - - UE_LOG(LogAlpakit, Display, TEXT("Alpakit Selected!")); - +void SAlpakitModEntryList::SetAllMods(bool Checked) { + UAlpakitSettings* Settings = UAlpakitSettings::Get(); for (TSharedRef Mod : FilteredMods) { - UE_LOG(LogAlpakit, Display, TEXT("Collecting Plugin %s!"), *Mod->GetName()); - TSharedPtr TableRow = ModList->WidgetFromItem(Mod); if (!TableRow.IsValid()) { UE_LOG(LogAlpakit, Display, TEXT("TableRow not found!")); @@ -178,69 +239,32 @@ FReply SAlpakitModEntryList::PackageAllMods() { UE_LOG(LogAlpakit, Display, TEXT("TableRow content is not valid!")); continue; } + ModEntry->SetSelected(Checked); - if(!ModEntry->IsSelected()) { - UE_LOG(LogAlpakit, Display, TEXT("Plugin is not selected: %s"), * Mod->GetName()); - continue; - } - - ModsToPackage.Add(Mod); - } - - if (ModsToPackage.Num() == 0) { - return FReply::Handled(); + Settings->ModSelection.Add(Mod->GetName(), Checked); } - FAlpakitModule::Get().PackageMods(ModsToPackage); - - return FReply::Handled(); -} - -FReply SAlpakitModEntryList::CheckAllMods() { - for (TSharedRef Mod : FilteredMods) { - TSharedPtr TableRow = ModList->WidgetFromItem(Mod); - if (!TableRow.IsValid()) { - UE_LOG(LogAlpakit, Display, TEXT("TableRow not found!")); - continue; - } - - TSharedPtr ModEntry = StaticCastSharedPtr(TableRow->GetContent()); - if (!ModEntry.IsValid()) { - UE_LOG(LogAlpakit, Display, TEXT("TableRow content is not valid!")); - continue; - } - ModEntry->SetSelected(true); - } - return FReply::Handled(); + Settings->SaveSettings(); } +void SAlpakitModEntryList::UpdateAllCheckbox() { + UAlpakitSettings* Settings = UAlpakitSettings::Get(); -FReply SAlpakitModEntryList::UncheckAllMods() { + bool allFalse = true; + bool allTrue = true; + for (TSharedRef Mod : FilteredMods) { - TSharedPtr TableRow = ModList->WidgetFromItem(Mod); - if (!TableRow.IsValid()) { - UE_LOG(LogAlpakit, Display, TEXT("TableRow not found!")); - continue; - } - - TSharedPtr ModEntry = StaticCastSharedPtr(TableRow->GetContent()); - if (!ModEntry.IsValid()) { - UE_LOG(LogAlpakit, Display, TEXT("TableRow content is not valid!")); - continue; - } - ModEntry->SetSelected(false); + if (Settings->ModSelection.FindOrAdd(Mod->GetName(), false)) + allFalse = false; + else + allTrue = false; } - return FReply::Handled(); -} - -FReply SAlpakitModEntryList::CreateMod() -{ - FGlobalTabmanager::Get()->TryInvokeTab(FAlpakitModule::ModCreatorTabName); - return FReply::Handled(); -} - -bool SAlpakitModEntryList::IsPackageButtonEnabled() const { - return !FAlpakitModule::Get().IsPackaging(); + if (!allTrue && !allFalse) + AllModsCheckbox->SetIsChecked(ECheckBoxState::Undetermined); + else if (allTrue) + AllModsCheckbox->SetIsChecked(ECheckBoxState::Checked); + else if (allFalse) + AllModsCheckbox->SetIsChecked(ECheckBoxState::Unchecked); } #undef LOCTEXT_NAMESPACE diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitProfile.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitProfile.cpp index e2625b13e3..c0c88f0742 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitProfile.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitProfile.cpp @@ -1,6 +1,8 @@ #include "AlpakitProfile.h" #include "PlatformInfo.h" +// UE_DISABLE_OPTIMIZATION + FString FAlpakitProfile::MakeUATPlatformArgs() { // Code below replicates the minimum configuration required from FLauncherWorker::CreateUATCommand FString CommandLine; @@ -53,6 +55,7 @@ FString FAlpakitProfile::MakeUATPlatformArgs() { if (Platforms.Len() > 0) { PlatformCommand = TEXT(" -platform=") + Platforms.RightChop(1); + OptionalParams = OptionalParams.Replace(TEXT("-noclient"), TEXT("")); } CommandLine += PlatformCommand; @@ -83,11 +86,13 @@ FString FAlpakitProfile::MakeUATCommandLine() { CommandLine += GIsEditor || FApp::IsEngineInstalled() ? TEXT(" -nocompileeditor") : TEXT(""); CommandLine += FApp::IsEngineInstalled() ? TEXT(" -installed") : TEXT(""); + CommandLine += bMergeArchive ? TEXT(" -merge") : TEXT(""); + for (auto [Platform, GameInfo] : PlatformGameInfo) { if (GameInfo.bCopyToGame) { - CommandLine += FString::Printf(TEXT(" -CopyToGameDirectory_%s=\"%s\""), *Platform, *GameInfo.GamePath); + CommandLine += FString::Printf(TEXT(" -CopyToGameDirectory_%s=\"%s\""), *Platform, *GameInfo.GamePath.Path); } - if (GameInfo.StartGameType != EAlpakitStartGameType::NONE) { + if (GameInfo.bStartGame) { CommandLine += FString::Printf(TEXT(" -LaunchGame_%s=%s"), *Platform, LexToString(GameInfo.StartGameType)); } } diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitSettings.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitSettings.cpp index 25fd274536..78cff7876c 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitSettings.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitSettings.cpp @@ -4,10 +4,17 @@ const TCHAR* LexToString(EAlpakitStartGameType StartGameType) { switch (StartGameType) { case EAlpakitStartGameType::STEAM: return TEXT("Steam"); + case EAlpakitStartGameType::STEAM_SERVER: + return TEXT("SteamDS"); case EAlpakitStartGameType::EPIC_EARLY_ACCESS: return TEXT("EpicEA"); case EAlpakitStartGameType::EPIC_EXPERIMENTAL: return TEXT("EpicExp"); + case EAlpakitStartGameType::EPIC_SERVER: + return TEXT("EpicDS"); + case EAlpakitStartGameType::EPIC_SERVER_EXPERIMENTAL: + return TEXT("EpicDSExp"); + case EAlpakitStartGameType::CUSTOM: default: return TEXT(""); } @@ -41,3 +48,11 @@ EBuildConfiguration UAlpakitSettings::GetBuildConfiguration() const // LexTryParseString( ResultBuildConfiguration, *BuildConfiguration ); return ResultBuildConfiguration; } + +const TMap UAlpakitSettings::GetPlatformTargetSettings() const { + return { + {TEXT("Windows"), WindowsGameTargetSettings}, + {TEXT("WindowsServer"), WindowsServerTargetSettings}, + {TEXT("LinuxServer"), LinuxServerTargetSettings}, + }; +} diff --git a/Mods/Alpakit/Source/Alpakit/Private/AlpakitWidget.cpp b/Mods/Alpakit/Source/Alpakit/Private/AlpakitWidget.cpp index f4a14e3dae..e7e85a880f 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/AlpakitWidget.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/AlpakitWidget.cpp @@ -6,7 +6,7 @@ #define LOCTEXT_NAMESPACE "AlpakitWidget" -void SAlpakitWidget::Construct(const FArguments& InArgs) { +void SAlpakitWidget::Construct(const FArguments& InArgs) { FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = true; DetailsViewArgs.bHideSelectionTip = true; @@ -34,48 +34,71 @@ void SAlpakitWidget::Construct(const FArguments& InArgs) { +SVerticalBox::Slot().AutoHeight()[ DetailsView.ToSharedRef() ] - +SVerticalBox::Slot().AutoHeight()[ - SNew(SHorizontalBox) - +SHorizontalBox::Slot().FillWidth(1)[ - SNew(SEditableTextBox) - .HintText(LOCTEXT("SearchHint", "Search Plugin...")) - .OnTextChanged_Lambda([this](const FText& InText) { - this->ModList->Filter(InText.ToString()); - }) - ] - +SHorizontalBox::Slot().AutoWidth()[ - SNew(SSpacer) - .Size(FVector2D(20.0f, 10.0f)) - ] - +SHorizontalBox::Slot().AutoWidth()[ - SNew(SCheckBox) - .Content()[ - SNew(STextBlock) - .Text(LOCTEXT("ShowEnginePlugins", "Show Engine Plugins")) - ] - .OnCheckStateChanged_Lambda([this](ECheckBoxState InState) { - this->ModList->SetShowEngine(InState == ECheckBoxState::Checked); - }) - ] - +SHorizontalBox::Slot().AutoWidth()[ - SNew(SSpacer) - .Size(FVector2D(10.0f, 10.0f)) - ] - +SHorizontalBox::Slot().AutoWidth()[ - SNew(SCheckBox) - .Content()[ - SNew(STextBlock) - .Text(LOCTEXT("ShowProjectPlugins", "Show Project Plugins")) - ] - .OnCheckStateChanged_Lambda([this](ECheckBoxState InState) { - this->ModList->SetShowProject(InState == ECheckBoxState::Checked); - }) - ] - ] +SVerticalBox::Slot().FillHeight(1).Padding(3)[ SAssignNew(ModList, SAlpakitModEntryList) + .BarSlot()[ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth()[ + SAssignNew(AlpakitAllDevButton, SButton) + .Text(LOCTEXT("PackageModAlpakitAllDev", "Alpakit Dev")) + .ToolTipText(LOCTEXT("PackageModAlpakitAllDev_Tooltip", "For each selected mod, package the mod for each of the targets you have Enabled in your Dev Packaging Settings (NOT Release Targets!).\nAfter all tasks complete, run your after packaging tasks for each target.\nCan build Shipping C++ as required.")) + .OnClicked_Lambda([this] { + PackageAllMods(false); + return FReply::Handled(); + }) + .IsEnabled(this, &SAlpakitWidget::IsPackageButtonEnabled) + ] + + SHorizontalBox::Slot().AutoWidth()[ + SAssignNew(AlpakitAllReleaseButton, SButton) + .Text(LOCTEXT("PackageModAlpakitAllRelease", "Alpakit Release")) + .ToolTipText(LOCTEXT("PackageModAlpakitAllRelease_Tooltip", "For each selected mod, prepare the multi-target zip for upload to the Mod Repository. The targets included in the zip are controlled by what you have marked in Release Targets.\nDoes not execute the after packing tasks (those are only for dev).\nCan build Shipping C++ as required.")) + .OnClicked_Lambda([this] { + PackageAllMods(true); + return FReply::Handled(); + }) + .IsEnabled(this, &SAlpakitWidget::IsPackageButtonEnabled) + ] + + SHorizontalBox::Slot().FillWidth(1.0f) + + SHorizontalBox::Slot().AutoWidth()[ + SNew(SButton) + .Text(LOCTEXT("CreateMod", "Create Mod")) + .ToolTipText(LOCTEXT("CreateMod_Tooltip", "Open the wizard to create a new mod from a template")) + .OnClicked(this, &SAlpakitWidget::CreateMod) + ] + ] ] ]; } +FReply SAlpakitWidget::PackageAllMods(bool ReleaseBuild) { + FAlpakitModule& AlpakitModule = FModuleManager::GetModuleChecked(TEXT("Alpakit")); + + TArray> ModsToPackage; + + UAlpakitSettings* Settings = UAlpakitSettings::Get(); + for (TSharedRef Mod : ModList->GetFilteredMods()) { + if (Settings->ModSelection.FindOrAdd(Mod->GetName(), false)) { + ModsToPackage.Add(Mod); + } + } + + if (ReleaseBuild) { + AlpakitModule.PackageModsRelease(ModsToPackage); + } else { + AlpakitModule.PackageModsDevelopment(ModsToPackage); + } + + return FReply::Handled(); +} + +FReply SAlpakitWidget::CreateMod() +{ + FGlobalTabmanager::Get()->TryInvokeTab(FAlpakitModule::ModCreatorTabName); + return FReply::Handled(); +} + +bool SAlpakitWidget::IsPackageButtonEnabled() const { + return !FAlpakitModule::Get().IsPackaging(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Mods/Alpakit/Source/Alpakit/Private/ModTargetsConfig.cpp b/Mods/Alpakit/Source/Alpakit/Private/ModTargetsConfig.cpp new file mode 100644 index 0000000000..99deaef891 --- /dev/null +++ b/Mods/Alpakit/Source/Alpakit/Private/ModTargetsConfig.cpp @@ -0,0 +1,35 @@ +#include "ModTargetsConfig.h" + +#include "Interfaces/IPluginManager.h" + +FModTargetsConfig::FModTargetsConfig(TSharedRef InMod) { + Mod = InMod; + + ConfigFile.Read(GetConfigPath()); + + TArray Targets; + ConfigFile.GetArray(TEXT("ModTargets"), TEXT("Targets"), Targets); + bWindows = Targets.Contains(TEXT("Windows")) || Targets.Num() == 0; + bWindowsServer = Targets.Contains(TEXT("WindowsServer")); + bLinuxServer = Targets.Contains(TEXT("LinuxServer")); +} + +void FModTargetsConfig::Save() { + ConfigFile.SetArray(TEXT("ModTargets"), TEXT("Targets"), GetCookedPlatforms()); + ConfigFile.Write(GetConfigPath()); +} + +TArray FModTargetsConfig::GetCookedPlatforms() { + TArray Targets; + if (bWindows) + Targets.Add(TEXT("Windows")); + if (bWindowsServer) + Targets.Add(TEXT("WindowsServer")); + if (bLinuxServer) + Targets.Add(TEXT("LinuxServer")); + return Targets; +} + +FString FModTargetsConfig::GetConfigPath() const { + return FPaths::Combine(Mod->GetBaseDir(), TEXT("Config"), TEXT("Alpakit.ini")); +} diff --git a/Mods/Alpakit/Source/Alpakit/Private/ModWizardDefinition.cpp b/Mods/Alpakit/Source/Alpakit/Private/ModWizardDefinition.cpp index dfffc1aad4..8fd50bf6fd 100644 --- a/Mods/Alpakit/Source/Alpakit/Private/ModWizardDefinition.cpp +++ b/Mods/Alpakit/Source/Alpakit/Private/ModWizardDefinition.cpp @@ -71,7 +71,7 @@ bool FModWizardDefinition::IsMod() const FText FModWizardDefinition::GetInstructions() const { - return LOCTEXT("ChooseModTemplate", "Choose a template and then specify a name to create a new mod."); + return LOCTEXT("ChooseModTemplate", "Choose a template and then specify a name to create a new mod. Remember, your plugin name (mod reference) can no longer be changed after you release the mod! See the docs for more info."); } bool FModWizardDefinition::GetPluginIconPath(FString& OutIconPath) const diff --git a/Mods/Alpakit/Source/Alpakit/Public/Alpakit.h b/Mods/Alpakit/Source/Alpakit/Public/Alpakit.h index dc98ca2fef..d93ca6f195 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/Alpakit.h +++ b/Mods/Alpakit/Source/Alpakit/Public/Alpakit.h @@ -16,7 +16,8 @@ class FAlpakitModule : public IModuleInterface { TArray> GetModTemplates() const { return ModTemplates; } - void PackageMods(TArray> Mods); + void PackageModsDevelopment(TArray> Mods); + void PackageModsRelease(TArray> Mods); void PackageMods(TArray> ProfilesToPackage); bool IsPackaging() const { return bIsPackaging; } diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h new file mode 100644 index 0000000000..182213895d --- /dev/null +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitEditModDialog.h @@ -0,0 +1,20 @@ +#pragma once + +#include "ModMetadataObject.h" +#include "Slate.h" +#include "Interfaces/IPluginManager.h" + +class SAlpakitEditModDialog : public SWindow +{ +public: + SLATE_BEGIN_ARGS(SAlpakitEditModDialog) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, TSharedRef InMod); + +private: + TSharedPtr Mod; + UModMetadataObject* MetadataObject = nullptr; + + FReply OnOkClicked(); +}; diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntry.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntry.h index b03c365dfc..0686966baa 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntry.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntry.h @@ -1,6 +1,7 @@ #pragma once #include "AlpakitModEntryList.h" #include "AlpakitProfile.h" +#include "ModTargetsConfig.h" #include "Interfaces/IPluginManager.h" /** @@ -15,9 +16,6 @@ class SAlpakitModEntry : public SCompoundWidget { void OnEnableCheckboxChanged(ECheckBoxState NewState); bool IsPackageButtonEnabled() const; - void OnEditMod(); - FReply OnEditModFinished(class UModMetadataObject* Metadata); - FORCEINLINE bool IsSelected() { return Checkbox && Checkbox->IsChecked(); } @@ -32,8 +30,15 @@ class SAlpakitModEntry : public SCompoundWidget { TSharedPtr Mod; TSharedPtr Owner; - TSharedPtr Checkbox; + TSharedPtr Checkbox; + TSharedPtr EditButton; + TSharedPtr ConfigureReleaseButton; /** Dialog for editing mod properties */ TSharedPtr PropertiesWindow; + + FModTargetsConfig ModTargetsConfig; + + void QueueStarted(); + void QueueComplete(); }; diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntryList.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntryList.h index 04c4096421..7fca1d51e7 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntryList.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitModEntryList.h @@ -6,6 +6,7 @@ */ class SAlpakitModEntryList : public SCompoundWidget { SLATE_BEGIN_ARGS(SAlpakitModEntryList) {} + SLATE_NAMED_SLOT(FArguments, BarSlot) SLATE_END_ARGS() void Construct(const FArguments& Args); @@ -31,19 +32,18 @@ class SAlpakitModEntryList : public SCompoundWidget { void SetShowEngine(bool bInShowEngine); void SetShowProject(bool bInShowProject); + TArray> GetFilteredMods() const { return FilteredMods; } + + void UpdateAllCheckbox(); + void OnNewPluginCreated(IPlugin& Plugin); + void SetAllMods(bool Checked); private: TSharedPtr>> ModList; + TSharedPtr AllModsCheckbox; TArray> Mods; TArray> FilteredMods; FString LastFilter; bool bShowEngine = false; bool bShowProject = false; - - FReply PackageAllMods(); - FReply CheckAllMods(); - FReply UncheckAllMods(); - FReply CreateMod(); - - bool IsPackageButtonEnabled() const; }; diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitProfile.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitProfile.h index 5d1f179fab..527948976b 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitProfile.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitProfile.h @@ -2,13 +2,14 @@ #include "AlpakitSettings.h" struct FAlpakitProfileGameInfo { - FAlpakitProfileGameInfo(): bCopyToGame(false), StartGameType(EAlpakitStartGameType::NONE) {} - FAlpakitProfileGameInfo(bool bInCopyToGame, FString InGamePath, EAlpakitStartGameType InStartGameType): - bCopyToGame(bInCopyToGame), GamePath(InGamePath), StartGameType(InStartGameType) {} + FAlpakitProfileGameInfo() {} + FAlpakitProfileGameInfo(bool bInCopyToGame, FDirectoryPath InGamePath, bool bInStartGame, EAlpakitStartGameType InStartGameType): + bCopyToGame(bInCopyToGame), GamePath(InGamePath), bStartGame(bInStartGame), StartGameType(InStartGameType) {} - bool bCopyToGame; - FString GamePath; - EAlpakitStartGameType StartGameType; + bool bCopyToGame{false}; + FDirectoryPath GamePath; + bool bStartGame{false}; + EAlpakitStartGameType StartGameType{}; }; struct FAlpakitProfile { @@ -19,6 +20,7 @@ struct FAlpakitProfile { TArray CookedPlatforms; FString PluginName; TMap PlatformGameInfo; + bool bMergeArchive{false}; FString MakeUATCommandLine(); private: diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitSettings.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitSettings.h index ff97321961..e39fdf27e3 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitSettings.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitSettings.h @@ -6,10 +6,38 @@ UENUM() enum class EAlpakitStartGameType : uint8 { - NONE UMETA(DisplayName = "Do not launch"), STEAM UMETA(DisplayName = "Steam"), + STEAM_SERVER UMETA(DisplayName = "Steam: Dedicated Server"), EPIC_EARLY_ACCESS UMETA(DisplayName = "Epic: Early Access"), - EPIC_EXPERIMENTAL UMETA(DisplayName = "Epic: Experimental") + EPIC_EXPERIMENTAL UMETA(DisplayName = "Epic: Experimental"), + EPIC_SERVER UMETA(DisplayName = "Epic: Dedicated Server"), + EPIC_SERVER_EXPERIMENTAL UMETA(DisplayName = "Epic: Dedicated Server Experimental"), + CUSTOM UMETA(DisplayName = "Custom") +}; + +USTRUCT() +struct ALPAKIT_API FAlpakitTargetSettings +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, config, Category = Config, meta = (ToolTip = "Controls if this target will be built when pressing the 'Alpakit Dev' button (or Alpakit! for one mod) and what action will be taken for this target after all tasks complete.")) + bool bEnabled = true; + + UPROPERTY(EditAnywhere, config, Category = Config, meta = (InlineEditConditionToggle)) + bool bCopyModsToGame = false; + + UPROPERTY(EditAnywhere, config, Category = Config, DisplayName = "Copy to Game Path", meta = (EditCondition = bCopyModsToGame, ToolTip = "If enabled, mods will be copied to this installation location after a successful pack.\n\nExample value:\nC:\\EpicGamesGames\\SatisfactoryExperimental")) + FDirectoryPath SatisfactoryGamePath; + + UPROPERTY(EditAnywhere, config, Category = Config, meta = (InlineEditConditionToggle)) + bool bLaunchGame = false; + + UPROPERTY(EditAnywhere, config, Category = Config, meta = ( EditCondition = bLaunchGame, ToolTip = "If enabled, this approach will be used to launch this target after a successful pack")) + EAlpakitStartGameType LaunchGameType; + + UPROPERTY(EditAnywhere, config, Category = Config, meta = ( EditCondition = "LaunchGameType == EAlpakitStartGameType::CUSTOM", EditConditionHides, ToolTip = "When 'Custom' is selected, the Unreal Automation tool will execute this after a successful pack.\nCheck the 'Testing' page on the modding docs for example scripts to call with this feature.\n\nExample value:\nC:\\Git\\SF_ModProject\\RobWorkingDir\\Auto_Exp_LoadLatest.bat")) + FString CustomLaunchPath; }; ALPAKIT_API const TCHAR* LexToString(EAlpakitStartGameType StartGameType); @@ -23,27 +51,23 @@ class ALPAKIT_API UAlpakitSettings : public UObject { /** Saves alpakit settings to configuration file */ void SaveSettings(); - - // Hide these fields for now, as only Windows Shipping is allowed + + // Hide this field for now, as the game is only built for Shipping /* // Name of the build configuration in which the mod should be built - UPROPERTY(EditAnywhere, config, Category = Config, meta = ( GetOptions = GetAllowedBuildConfigurations )) + UPROPERTY(EditAnywhere, config, Category = "Dev Packaging Settings", meta = ( GetOptions = GetAllowedBuildConfigurations )) FString BuildConfiguration; - - // The configurations to cook the mods in (Windows, WindowsServer, and so on) - UPROPERTY(EditAnywhere, config, Category = Config) - TArray CookPlatforms; */ - UPROPERTY(EditAnywhere, config, Category = Config) - FDirectoryPath SatisfactoryGamePath; - - UPROPERTY(EditAnywhere, config, Category = Config) - EAlpakitStartGameType LaunchGameAfterPacking; + UPROPERTY(EditAnywhere, config, Category = "Dev Packaging Settings", DisplayName = "Windows", meta = (ToolTip = "Packaging settings for 'Alpakit Dev' for the Windows target")) + FAlpakitTargetSettings WindowsGameTargetSettings; - UPROPERTY(EditAnywhere, config, Category = Config) - bool bCopyModsToGame = false; + UPROPERTY(EditAnywhere, config, Category = "Dev Packaging Settings", DisplayName="Windows Server", meta = (ToolTip = "Packaging settings for 'Alpakit Dev' for the Windows Server target")) + FAlpakitTargetSettings WindowsServerTargetSettings; + UPROPERTY(EditAnywhere, config, Category = "Dev Packaging Settings", DisplayName="Linux Server", meta = (ToolTip = "Packaging settings for 'Alpakit Dev' for the Linux Server target")) + FAlpakitTargetSettings LinuxServerTargetSettings; + UPROPERTY(BlueprintReadOnly, config, Category = Config) TMap ModSelection; @@ -51,4 +75,6 @@ class ALPAKIT_API UAlpakitSettings : public UObject { TArray GetAllowedBuildConfigurations() const; EBuildConfiguration GetBuildConfiguration() const; + + const TMap GetPlatformTargetSettings() const; }; diff --git a/Mods/Alpakit/Source/Alpakit/Public/AlpakitWidget.h b/Mods/Alpakit/Source/Alpakit/Public/AlpakitWidget.h index 32cb50e5bc..9b32bef311 100644 --- a/Mods/Alpakit/Source/Alpakit/Public/AlpakitWidget.h +++ b/Mods/Alpakit/Source/Alpakit/Public/AlpakitWidget.h @@ -13,4 +13,12 @@ class SAlpakitWidget : public SCompoundWidget { private: TSharedPtr DetailsView; TSharedPtr ModList; + TSharedPtr QueueText; + TSharedPtr AlpakitAllDevButton; + TSharedPtr AlpakitAllReleaseButton; + + FReply PackageAllMods(bool ReleaseBuild); + FReply CreateMod(); + + bool IsPackageButtonEnabled() const; }; diff --git a/Mods/Alpakit/Source/Alpakit/Public/ModTargetsConfig.h b/Mods/Alpakit/Source/Alpakit/Public/ModTargetsConfig.h new file mode 100644 index 0000000000..b05b65884b --- /dev/null +++ b/Mods/Alpakit/Source/Alpakit/Public/ModTargetsConfig.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Interfaces/IPluginManager.h" + +struct FModTargetsConfig +{ +public: + FModTargetsConfig() {} + FModTargetsConfig(TSharedRef InMod); + + void Save(); + TArray GetCookedPlatforms(); + + bool bWindows = false; + bool bWindowsServer = false; + bool bLinuxServer = false; +private: + TSharedPtr Mod; + + FConfigFile ConfigFile; + + FString GetConfigPath() const; +}; diff --git a/Mods/Alpakit/Templates/CPPAndBlueprintBlank/Source/PLUGIN_NAME/PLUGIN_NAME.Build.cs b/Mods/Alpakit/Templates/CPPAndBlueprintBlank/Source/PLUGIN_NAME/PLUGIN_NAME.Build.cs index b82520d434..5d80389f9e 100644 --- a/Mods/Alpakit/Templates/CPPAndBlueprintBlank/Source/PLUGIN_NAME/PLUGIN_NAME.Build.cs +++ b/Mods/Alpakit/Templates/CPPAndBlueprintBlank/Source/PLUGIN_NAME/PLUGIN_NAME.Build.cs @@ -1,53 +1,84 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - using UnrealBuildTool; +using System.IO; +using System; public class PLUGIN_NAME : ModuleRules { public PLUGIN_NAME(ReadOnlyTargetRules Target) : base(Target) { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - // ... add public include paths required here ... - } - ); - + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + // FactoryGame transitive dependencies + // Not all of these are required, but including the extra ones saves you from having to add them later. + // Some entries are commented out to avoid compile-time warnings about depending on a module that you don't explicitly depend on. + // You can uncomment these as necessary when your code actually needs to use them. + PublicDependencyModuleNames.AddRange(new string[] { + "Core", "CoreUObject", + "Engine", + "DeveloperSettings", + "PhysicsCore", + "InputCore", + //"OnlineSubsystem", "OnlineSubsystemUtils", "OnlineSubsystemNull", + //"SignificanceManager", + "GeometryCollectionEngine", + //"ChaosVehiclesCore", "ChaosVehicles", "ChaosSolverEngine", + "AnimGraphRuntime", + //"AkAudio", + "AssetRegistry", + "NavigationSystem", + //"ReplicationGraph", + "AIModule", + "GameplayTasks", + "SlateCore", "Slate", "UMG", + //"InstancedSplines", + "RenderCore", + "CinematicCamera", + "Foliage", + //"Niagara", + //"EnhancedInput", + //"GameplayCameras", + //"TemplateSequence", + "NetCore", + "GameplayTags", + "Json", "JsonUtilities", + "AssetRegistry" + }); + + // FactoryGame plugins + PublicDependencyModuleNames.AddRange(new string[] { + //"AbstractInstance", + //"InstancedSplinesComponent", + //"SignificanceISPC" + }); + + // Header stubs + PublicDependencyModuleNames.AddRange(new string[] { + "DummyHeaders", + }); + + if (Target.Type == TargetRules.TargetType.Editor) { + PublicDependencyModuleNames.AddRange(new string[] {/*"OnlineBlueprintSupport",*/ "AnimGraph"}); + } + PublicDependencyModuleNames.AddRange(new string[] {"FactoryGame", "SML"}); - PrivateIncludePaths.AddRange( - new string[] { - // ... add other private include paths required here ... - } - ); - + PublicIncludePaths.AddRange(new string[] { + // ... add public include paths required here ... + }); - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - // ... add other public dependencies that you statically link with here ... - } - ); - + PrivateIncludePaths.AddRange(new string[] { + // ... add private include paths required here ... + }); - PrivateDependencyModuleNames.AddRange( - new string[] - { - "CoreUObject", - "Engine", - "Slate", - "SlateCore", - // ... add private dependencies that you statically link with here ... - } - ); + PublicDependencyModuleNames.AddRange(new string[] { + // ... add public dependencies that you statically link with here ... + }); + PrivateDependencyModuleNames.AddRange(new string[] { + // ... add private dependencies that you statically link with here ... + }); - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - // ... add any modules that your module loads dynamically here ... - } - ); + DynamicallyLoadedModuleNames.AddRange(new string[] { + // ... add any modules that your module loads dynamically here ... + }); } } diff --git a/Mods/SML/.gitignore b/Mods/SML/.gitignore new file mode 100644 index 0000000000..385ae7102c --- /dev/null +++ b/Mods/SML/.gitignore @@ -0,0 +1 @@ +!ThirdParty/** \ No newline at end of file diff --git a/Mods/SML/Config/Alpakit.ini b/Mods/SML/Config/Alpakit.ini new file mode 100644 index 0000000000..ce6588b812 --- /dev/null +++ b/Mods/SML/Config/Alpakit.ini @@ -0,0 +1,4 @@ +[ModTargets] +Targets=Windows +Targets=WindowsServer +Targets=LinuxServer diff --git a/Mods/SML/Source/SML/Private/Configuration/ModConfiguration.cpp b/Mods/SML/Source/SML/Private/Configuration/ModConfiguration.cpp index 530cd3ac53..a6c03d7777 100644 --- a/Mods/SML/Source/SML/Private/Configuration/ModConfiguration.cpp +++ b/Mods/SML/Source/SML/Private/Configuration/ModConfiguration.cpp @@ -20,3 +20,5 @@ EDataValidationResult UModConfiguration::IsDataValid(TArray& ValidationEr return ValidationResult; } #endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyArray.cpp b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyArray.cpp index 6185c841fc..1058fdda81 100644 --- a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyArray.cpp +++ b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyArray.cpp @@ -3,6 +3,7 @@ #include "Configuration/CodeGeneration/ConfigVariableLibrary.h" #include "Configuration/RawFileFormat/RawFormatValueArray.h" #include "Reflection/BlueprintReflectedObject.h" + #define LOCTEXT_NAMESPACE "SML" UConfigProperty* UConfigPropertyArray::AddNewElement() { @@ -103,3 +104,5 @@ FConfigVariableDescriptor UConfigPropertyArray::CreatePropertyDescriptor_Impleme UConfigGenerationContext* Context, const FString& OuterPath) const { return UConfigVariableLibrary::MakeConfigVariableArray(DefaultValue->CreatePropertyDescriptor(Context, OuterPath)); } + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyClass.cpp b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyClass.cpp index b36e4d6168..e77da7a3ab 100644 --- a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyClass.cpp +++ b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertyClass.cpp @@ -3,6 +3,7 @@ #include "Configuration/CodeGeneration/ConfigVariableLibrary.h" #include "Configuration/RawFileFormat/RawFormatValueString.h" #include "Reflection/BlueprintReflectedObject.h" + #define LOCTEXT_NAMESPACE "SML" UConfigPropertyClass::UConfigPropertyClass() { @@ -69,3 +70,5 @@ FConfigVariableDescriptor UConfigPropertyClass::CreatePropertyDescriptor_Impleme UConfigGenerationContext* Context, const FString& OuterPath) const { return UConfigVariableLibrary::MakeConfigVariableClass(BaseClass); } + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertySection.cpp b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertySection.cpp index c54d2f7387..18fc35acd6 100644 --- a/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertySection.cpp +++ b/Mods/SML/Source/SML/Private/Configuration/Properties/ConfigPropertySection.cpp @@ -4,6 +4,7 @@ #include "Configuration/CodeGeneration/ConfigVariableLibrary.h" #include "Configuration/RawFileFormat/RawFormatValueObject.h" #include "Reflection/BlueprintReflectedObject.h" + #define LOCTEXT_NAMESPACE "SML" FString UConfigPropertySection::DescribeValue_Implementation() const { @@ -108,3 +109,5 @@ FConfigVariableDescriptor UConfigPropertySection::CreatePropertyDescriptor_Imple } return UConfigVariableLibrary::MakeConfigVariableGeneratedStruct(GeneratedStruct); } + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp b/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp index 29ef6651a0..af44c9c621 100644 --- a/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp +++ b/Mods/SML/Source/SML/Private/Network/NetworkHandler.cpp @@ -91,10 +91,9 @@ void UModNetworkHandler::InitializePatches() { } }; - void* WorldNetworkNotifyInstance = static_cast(WorldObjectInstance); - SUBSCRIBE_METHOD_VIRTUAL(UWorld::NotifyControlMessage, WorldNetworkNotifyInstance, MessageHandler); + SUBSCRIBE_METHOD_VIRTUAL(UWorld::NotifyControlMessage, WorldObjectInstance, MessageHandler); - UPendingNetGame* PendingNetGame = (UPendingNetGame*) FindObjectChecked(NULL, TEXT("/Script/Engine.PendingNetGame"))->GetDefaultObject(); - void* PendingGameNetworkNotifyInstance = static_cast(PendingNetGame); - SUBSCRIBE_METHOD_VIRTUAL(UPendingNetGame::NotifyControlMessage, PendingGameNetworkNotifyInstance, MessageHandler); + // Same for UPendingNetGame + UPendingNetGame* PendingNetGame = static_cast(FindObjectChecked(NULL, TEXT("/Script/Engine.PendingNetGame"))->GetDefaultObject()); + SUBSCRIBE_METHOD_VIRTUAL(UPendingNetGame::NotifyControlMessage, PendingNetGame, MessageHandler); } diff --git a/Mods/SML/Source/SML/Private/Patching/NativeHookManager.cpp b/Mods/SML/Source/SML/Private/Patching/NativeHookManager.cpp index 7608942249..85a53ec506 100644 --- a/Mods/SML/Source/SML/Private/Patching/NativeHookManager.cpp +++ b/Mods/SML/Source/SML/Private/Patching/NativeHookManager.cpp @@ -40,10 +40,13 @@ void LogDebugAssemblyAnalyzer(const ANSICHAR* Message) { UE_LOG(LogNativeHookManager, Display, TEXT("AssemblyAnalyzer Debug: %hs"), Message); } +// Installs a hook a the original function. Returns true if a new hook is installed or false on error or +// a hook already exists and is reused. bool HookStandardFunction(const FString& DebugSymbolName, void* OriginalFunctionPointer, void* HookFunctionPointer, void** OutTrampolineFunction) { if (InstalledHookMap.Contains(OriginalFunctionPointer)) { //Hook already installed, set trampoline function and return *OutTrampolineFunction = InstalledHookMap.FindChecked(OriginalFunctionPointer); + UE_LOG(LogNativeHookManager, Display, TEXT("Hook already installed")); return false; } funchook* funchook = funchook_create(); @@ -52,6 +55,9 @@ bool HookStandardFunction(const FString& DebugSymbolName, void* OriginalFunction return false; } *OutTrampolineFunction = OriginalFunctionPointer; + + UE_LOG(LogNativeHookManager, Display, TEXT("Overriding %s at %p to %p"), *DebugSymbolName, OriginalFunctionPointer, HookFunctionPointer); + CHECK_FUNCHOOK_ERR(funchook_prepare(funchook, OutTrampolineFunction, HookFunctionPointer)); CHECK_FUNCHOOK_ERR(funchook_install(funchook, 0)); InstalledHookMap.Add(OriginalFunctionPointer, *OutTrampolineFunction); @@ -59,33 +65,62 @@ bool HookStandardFunction(const FString& DebugSymbolName, void* OriginalFunction return true; } +// This method is provided for backwards-compatibility SML_API void* FNativeHookManagerInternal::RegisterHookFunction(const FString& DebugSymbolName, void* OriginalFunctionPointer, const void* SampleObjectInstance, int ThisAdjustment, void* HookFunctionPointer, void** OutTrampolineFunction) { + // Previous SML versions only supported Windows mods, which have no Vtable adjustment information + // in the member function pointer, so we set that value to zero. + FMemberFunctionPointer MemberFunctionPointer = {OriginalFunctionPointer, static_cast(ThisAdjustment), 0}; + return FNativeHookManagerInternal::RegisterHookFunction(DebugSymbolName, MemberFunctionPointer, SampleObjectInstance, HookFunctionPointer, OutTrampolineFunction); +} + +SML_API void* FNativeHookManagerInternal::RegisterHookFunction(const FString& DebugSymbolName, FMemberFunctionPointer MemberFunctionPointer, const void* SampleObjectInstance, void* HookFunctionPointer, void** OutTrampolineFunction) { SetDebugLoggingHook(&LogDebugAssemblyAnalyzer); - FunctionInfo FunctionInfo = DiscoverFunction((uint8*) OriginalFunctionPointer); - checkf(FunctionInfo.bIsValid, TEXT("Attempt to hook invalid function %s: Provided code pointer %p is not valid"), *DebugSymbolName, OriginalFunctionPointer); - if (FunctionInfo.bIsVirtualFunction) { +#ifdef _WIN64 + // On Windows, the OriginalFunctionPointer is a valid function pointer. We can simply check its info here. + UE_LOG(LogNativeHookManager, Display, TEXT("Attempting to discover %s at %p"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress); + FunctionInfo FunctionInfo = DiscoverFunction((uint8 *)MemberFunctionPointer.FunctionAddress); + checkf(FunctionInfo.bIsValid, TEXT("Attempt to hook invalid function %s: Provided code pointer %p is not valid"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress); + + // We assign the vtable offset from the FunctionInfo struct whether we found a vtable offset or not. If the + // method isn't virtual, this value isn't used. + MemberFunctionPointer.VtableDisplacement = FunctionInfo.VirtualTableFunctionOffset; + bool isVirtual = FunctionInfo.bIsVirtualFunction; +#else + // On Linux, MemberFunctionPointer.FunctionAddress will not be a valid pointer if the method is virtual. See ConvertFunctionPointer + // for more info. + FunctionInfo FunctionInfo; + + bool isVirtual = (MemberFunctionPointer.FunctionAddress == nullptr); + + if (!isVirtual) { + FunctionInfo = DiscoverFunction((uint8*) MemberFunctionPointer.FunctionAddress); + } +#endif + + if (isVirtual) { + // The patched call is virtual. Calculate the actual address of the function being called. checkf(SampleObjectInstance, TEXT("Attempt to hook virtual function override without providing object instance for implementation resolution")); - UE_LOG(LogNativeHookManager, Display, TEXT("Attempting to resolve virtual function %s. This adjustment: %d, virtual function table offset: %d"), *DebugSymbolName, ThisAdjustment, FunctionInfo.VirtualTableFunctionOffset); + UE_LOG(LogNativeHookManager, Display, TEXT("Attempting to resolve virtual function %s. This adjustment: 0x%x, virtual function table offset: 0x%x"), *DebugSymbolName, MemberFunctionPointer.ThisAdjustment, MemberFunctionPointer.VtableDisplacement); //Target Function Address = (this + ThisAdjustment)->vftable[VirtualFunctionOffset] - void* AdjustedThisPointer = ((uint8*) SampleObjectInstance) + ThisAdjustment; + void* AdjustedThisPointer = ((uint8*) SampleObjectInstance) + MemberFunctionPointer.ThisAdjustment; uint8** VirtualFunctionTableBase = *((uint8***) AdjustedThisPointer); //Offset is in bytes from the start of the virtual table, we need to convert it to pointer array index - uint8* FunctionImplementationPointer = VirtualFunctionTableBase[FunctionInfo.VirtualTableFunctionOffset / 8]; + uint8* FunctionImplementationPointer = VirtualFunctionTableBase[MemberFunctionPointer.VtableDisplacement / 8]; FunctionInfo = DiscoverFunction(FunctionImplementationPointer); //Perform basic checking to make sure calculation was correct, or at least seems to be so - checkf(FunctionInfo.bIsValid, TEXT("Failed to resolve virtual function for thunk %s at %p, reuslting address contains no executable code"), *DebugSymbolName, OriginalFunctionPointer); - checkf(!FunctionInfo.bIsVirtualFunction, TEXT("Failed to resolve virtual function for thunk %s at %p, resulting function still points to a thunk"), *DebugSymbolName, OriginalFunctionPointer); + checkf(FunctionInfo.bIsValid, TEXT("Failed to resolve virtual function for thunk %s at %p, resulting address contains no executable code"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress); + checkf(!FunctionInfo.bIsVirtualFunction, TEXT("Failed to resolve virtual function for thunk %s at %p, resulting function still points to a thunk"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress); - UE_LOG(LogNativeHookManager, Display, TEXT("Successfully resolved virtual function thunk %s at %p to function implementation at %p"), *DebugSymbolName, OriginalFunctionPointer, FunctionInfo.RealFunctionAddress); + UE_LOG(LogNativeHookManager, Display, TEXT("Successfully resolved virtual function thunk %s at %p to function implementation at %p"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress, FunctionInfo.RealFunctionAddress); } //Log debugging information just in case void* ResolvedHookingFunctionPointer = FunctionInfo.RealFunctionAddress; - UE_LOG(LogNativeHookManager, Display, TEXT("Hooking function %s: Provided address: %p, resolved address: %p"), *DebugSymbolName, OriginalFunctionPointer, ResolvedHookingFunctionPointer); + UE_LOG(LogNativeHookManager, Display, TEXT("Hooking function %s: Provided address: %p, resolved address: %p"), *DebugSymbolName, MemberFunctionPointer.FunctionAddress, ResolvedHookingFunctionPointer); HookStandardFunction(DebugSymbolName, ResolvedHookingFunctionPointer, HookFunctionPointer, OutTrampolineFunction); UE_LOG(LogNativeHookManager, Display, TEXT("Successfully hooked function %s at %p"), *DebugSymbolName, ResolvedHookingFunctionPointer); diff --git a/Mods/SML/Source/SML/Private/SatisfactoryModLoader.cpp b/Mods/SML/Source/SML/Private/SatisfactoryModLoader.cpp index e3a72622e7..d795acf606 100644 --- a/Mods/SML/Source/SML/Private/SatisfactoryModLoader.cpp +++ b/Mods/SML/Source/SML/Private/SatisfactoryModLoader.cpp @@ -11,6 +11,8 @@ #include "Player/PlayerCheatManagerHandler.h" #include "Util/DebuggerHelper.h" #include "funchook.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" #ifndef FACTORYGAME_VERSION #define FACTORYGAME_VERSION 0 diff --git a/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsManager.cpp b/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsManager.cpp index 69b0e8f61b..16b9e86108 100644 --- a/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsManager.cpp +++ b/Mods/SML/Source/SML/Private/SessionSettings/SessionSettingsManager.cpp @@ -268,7 +268,7 @@ void USessionSettingsManager::SetFloatOptionValue(const FString& cvar, float new } bool USessionSettingsManager::HasAnyUnsavedOptionValueChanges() const { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { if (Options.Value->HasPendingChanges()) return true; } @@ -292,7 +292,7 @@ bool USessionSettingsManager::HasAnyPendingRestartOptionValue(const FString& cva } bool USessionSettingsManager::GetRequireSessionRestart() const { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { if (Options.Value->HasSessionRestartRequiredChanges()) return true; } @@ -300,7 +300,7 @@ bool USessionSettingsManager::GetRequireSessionRestart() const { } bool USessionSettingsManager::GetRequireGameRestart() const { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { if (Options.Value->HasGameRestartRequiredChanges()) return true; } @@ -320,7 +320,7 @@ void USessionSettingsManager::UnsubscribeToDynamicOptionUpdate(const FString& cv } void USessionSettingsManager::UnsubscribeToAllDynamicOptionUpdate(UObject* boundObject) { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { Options.Value->RemoveObjectAsSubscriber(boundObject); } } @@ -348,13 +348,13 @@ IFGOptionInterface* USessionSettingsManager::GetActiveOptionInterface() const } void USessionSettingsManager::SubscribeToAllOptionUpdates(const FOnOptionUpdated& onOptionUpdatedDelegate) { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { Options.Value->AddSubscriber(onOptionUpdatedDelegate); } } void USessionSettingsManager::UnsubscribeToAllOptionUpdates(const FOnOptionUpdated& onOptionUpdatedDelegate) { - for (const TTuple Options : SessionSettings) { + for (const TTuple& Options : SessionSettings) { Options.Value->RemoveSubscriber(onOptionUpdatedDelegate); } } diff --git a/Mods/SML/Source/SML/Private/Tooltip/ItemTooltipSubsystem.cpp b/Mods/SML/Source/SML/Private/Tooltip/ItemTooltipSubsystem.cpp index 94298476b8..9d58e112f9 100644 --- a/Mods/SML/Source/SML/Private/Tooltip/ItemTooltipSubsystem.cpp +++ b/Mods/SML/Source/SML/Private/Tooltip/ItemTooltipSubsystem.cpp @@ -9,7 +9,7 @@ //Overwrites delegates bound to title & description widgets to use FTooltipHookHelper, add custom item widget void UItemTooltipSubsystem::ApplyItemOverridesToTooltip(UWidget* TooltipWidget, APlayerController* OwningPlayer, const FInventoryStack& InventoryStack) { - //Gather UProperty exposed by tooltip widget + //Gather FProperty exposed by tooltip widget UClass* TooltipWidgetClass = TooltipWidget->GetClass(); FObjectProperty* TitleWidgetProperty = CastField(TooltipWidgetClass->FindPropertyByName(TEXT("mTitle"))); FObjectProperty* DescriptionWidgetProperty = CastField(TooltipWidgetClass->FindPropertyByName(TEXT("mDescription"))); diff --git a/Mods/SML/Source/SML/Private/Util/SemVersion.cpp b/Mods/SML/Source/SML/Private/Util/SemVersion.cpp index 4e8f774918..2e0368bcdc 100644 --- a/Mods/SML/Source/SML/Private/Util/SemVersion.cpp +++ b/Mods/SML/Source/SML/Private/Util/SemVersion.cpp @@ -1,26 +1,27 @@ -#pragma once #include "Util/SemVersion.h" #include +#include "Containers/StringConv.h" +#include "SatisfactoryModLoader.h" //Use stdlib regex here because UE Regex(which is ICU regex wrapper) requires initialized localization system, and our versioning can be called much earlier than that -std::wregex VersionRegex(TEXT("^(~|v|=|<=|<|>|>=|\\^)?(X|x|\\*|0|[1-9]\\d*)(?:\\.(X|x|\\*|0|[1-9]\\d*)(?:\\.(X|x|\\*|0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?)?$"), std::regex::ECMAScript | std::regex::optimize); +std::wregex VersionRegex(L"^(~|v|=|<=|<|>|>=|\\^)?(X|x|\\*|0|[1-9]\\d*)(?:\\.(X|x|\\*|0|[1-9]\\d*)(?:\\.(X|x|\\*|0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?)?$"); FString ParseVersionTemplate(const FString& string, FVersion& version, EVersionComparisonOp& Comparison); -EVersionComparisonOp ParseComparisonOp(const FString& type) { - if (type == TEXT("<=")) +EVersionComparisonOp ParseComparisonOp(const std::wstring& type) { + if (type.compare(L"<=") == 0) return EVersionComparisonOp::LESS_EQUALS; - if (type == TEXT("<")) + if (type.compare(L"<") == 0) return EVersionComparisonOp::LESS; - if (type == TEXT(">=")) + if (type.compare(L">=") == 0) return EVersionComparisonOp::GREATER_EQUALS; - if (type == TEXT(">")) + if (type.compare(L">") == 0) return EVersionComparisonOp::GREATER; - if (type == TEXT("^")) + if (type.compare(L"^") == 0) return EVersionComparisonOp::CARET; - if (type == TEXT("~")) + if (type.compare(L"~") == 0) return EVersionComparisonOp::TILDE; - if (type == TEXT("=") || type == TEXT("v") || type == TEXT("")) + if (type.compare(L"=") == 0|| type.compare(L"v") == 0|| type.compare(L"") == 0) return EVersionComparisonOp::EQUALS; return EVersionComparisonOp::INVALID; } @@ -490,15 +491,17 @@ bool FVersionRange::Matches(const FVersion& Version) const { } bool IsWildcardVersionNumber(const std::wstring& Number) { - return Number == TEXT("X") || Number == TEXT("x") || Number == TEXT("*"); + return Number.compare(L"X") == 0 || Number.compare(L"x") == 0 || Number.compare(L"*") == 0; } //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 == TEXT("")) { + 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); @@ -506,12 +509,12 @@ int64 ParseVersionNumber(const std::wstring& Number) { FString ParseVersionTemplate(const FString& string, FVersion& version, EVersionComparisonOp& Comparison) { std::wsmatch Match; - const std::wstring WideString(*string); + const std::wstring WideString(TCHAR_TO_WCHAR(*string)); if (!std::regex_match(WideString, Match, VersionRegex)) { return TEXT("Version doesn't match SemVer pattern"); } const std::wstring& FirstMatch = Match[1]; - Comparison = ParseComparisonOp(FirstMatch.c_str()); + Comparison = ParseComparisonOp(FirstMatch); if (Comparison == EVersionComparisonOp::INVALID) { return TEXT("Invalid version comparator"); @@ -533,8 +536,8 @@ FString ParseVersionTemplate(const FString& string, FVersion& version, EVersionC return TEXT("Wildcard cannot be followed by version number"); } } - version.PreRelease = Match[5].str().c_str(); - version.BuildInfo = Match[6].str().c_str(); + version.PreRelease = WCHAR_TO_TCHAR(Match[5].str().c_str()); + version.BuildInfo = WCHAR_TO_TCHAR(Match[6].str().c_str()); return TEXT(""); } diff --git a/Mods/SML/Source/SML/Public/Command/ChatCommandLibrary.h b/Mods/SML/Source/SML/Public/Command/ChatCommandLibrary.h index 9138f2fc8b..e7d3b735fe 100644 --- a/Mods/SML/Source/SML/Public/Command/ChatCommandLibrary.h +++ b/Mods/SML/Source/SML/Public/Command/ChatCommandLibrary.h @@ -1,6 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "command/ChatCommandInstance.h" +#include "Command/ChatCommandInstance.h" #include "Subsystem/ModSubsystem.h" #include "ChatCommandLibrary.generated.h" diff --git a/Mods/SML/Source/SML/Public/Configuration/ConfigProperty.h b/Mods/SML/Source/SML/Public/Configuration/ConfigProperty.h index 3012f07e7f..d1cc52bce6 100644 --- a/Mods/SML/Source/SML/Public/Configuration/ConfigProperty.h +++ b/Mods/SML/Source/SML/Public/Configuration/ConfigProperty.h @@ -66,4 +66,8 @@ class SML_API UConfigProperty : public UObject { /** Fills variable of provided object with the value carried by this property */ UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void FillConfigStruct(const FReflectedObject& ReflectedObject, const FString& VariableName) const; + +private: + /** The Serialize() definition above shadows the native UObject::Serialize. Declare that we want to keep the UBOject implementation. */ + using UObject::Serialize; }; diff --git a/Mods/SML/Source/SML/Public/Kismet/RuntimeBlueprintFunctionLibrary.h b/Mods/SML/Source/SML/Public/Kismet/RuntimeBlueprintFunctionLibrary.h index 7482351d69..bfe5f4a490 100644 --- a/Mods/SML/Source/SML/Public/Kismet/RuntimeBlueprintFunctionLibrary.h +++ b/Mods/SML/Source/SML/Public/Kismet/RuntimeBlueprintFunctionLibrary.h @@ -130,4 +130,3 @@ class SML_API URuntimeBlueprintFunctionLibrary : public UBlueprintFunctionLibrar UFUNCTION(BlueprintCallable, Category="SML | Online") static void CreateSessionAndTravelToMapWithStartingLocation(APlayerController* player, const FString& mapName, const FString& startingLocation, const FString& sessionName, ESessionVisibility sessionVisibility, bool skipOnboarding); }; - diff --git a/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h b/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h index d502d8af4b..41a705b56e 100644 --- a/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h +++ b/Mods/SML/Source/SML/Public/Patching/NativeHookManager.h @@ -2,7 +2,7 @@ #include "CoreMinimal.h" #include -DECLARE_LOG_CATEGORY_EXTERN(LogNativeHookManager, Log, Log); +SML_API DECLARE_LOG_CATEGORY_EXTERN(LogNativeHookManager, Log, Log); //NOTE: This struct does not actually fully represent member function pointer even on MSVC, //because it does not contain additional information for handling unknown inheritance @@ -16,33 +16,76 @@ struct FMemberFunctionPointer { uint32 VtableDisplacement; }; -template -struct TMemberFunctionPointer { - T MemberFunctionPointer; +struct FLinuxMemberFunctionPointer { + union { + void *FunctionAddress; // For non-virtual members + ptrdiff_t VtableDisplacementPlusOne; // For virtual members + intptr_t IntPtr; // For determining whether this is virtual or not. + }; + ptrdiff_t ThisAdjustment; }; template -FORCEINLINE FMemberFunctionPointer ConvertFunctionPointer(const TMemberFunctionPointer* SourcePointer) { +FORCEINLINE FMemberFunctionPointer ConvertFunctionPointer(const T& SourcePointer) { const SIZE_T FunctionPointerSize = sizeof(SourcePointer); +#ifdef _WIN64 //We only support non-virtual inheritance, so assert on virtual inheritance and unknown inheritance cases //Note that it might also mean that we are dealing with "proper" compiler with static function pointer size //(e.g anything different from Intel C++ and MSVC) checkf(FunctionPointerSize == 8 || FunctionPointerSize == 16, TEXT("Unsupported function pointer size received: \ Hooking can only support non-virtual multiple inheritence. \ - This might be also caused by unsupported compiler. Currently, only MSVC and Intel C++ are supported\ + This might be also caused by unsupported compiler. Currently, only MSVC and Intel C++ are supported on Windows. \ Function pointer size: %d bytes."), FunctionPointerSize); - const FMemberFunctionPointer* RawFunctionPointer = (const FMemberFunctionPointer*) SourcePointer; + const FMemberFunctionPointer* RawFunctionPointer = (const FMemberFunctionPointer*)&SourcePointer; +#else + checkf(FunctionPointerSize == sizeof(FLinuxMemberFunctionPointer), TEXT("Unsupported function pointer size received: %d bytes, expected %d bytes."), FunctionPointerSize, sizeof(FLinuxMemberFunctionPointer)); + const FLinuxMemberFunctionPointer* RawFunctionPointer = (const FLinuxMemberFunctionPointer*)&SourcePointer; +#endif + + UE_LOG(LogNativeHookManager, Display, TEXT("Size of member pointer is %d"), FunctionPointerSize); //TODO we cannot really make sure there is no virtual inheritance involved, so we just assume it for now FMemberFunctionPointer ResultPointer{}; +#ifdef _WIN64 ResultPointer.FunctionAddress = RawFunctionPointer->FunctionAddress; if (FunctionPointerSize >= 16) { ResultPointer.ThisAdjustment = RawFunctionPointer->ThisAdjustment; + // The vtable displacement here is only valid if the method is NOT virtual. If the method IS virtual, + // the vtable offset will be found in the thunk. See FNativeHookManagerInternal::RegisterHookFunction() ResultPointer.VtableDisplacement = RawFunctionPointer->VtableDisplacement; + } else { + // The function pointer contains no information about a `this` adjustment or the vtable displacement. + // We can conclude that there is no `this` adjustment, but the vtable index MUST be calculated from + // the thunk at FunctionAddress. We set an intentionally bad value here to make that easier to identify. + ResultPointer.ThisAdjustment = 0; + ResultPointer.VtableDisplacement = 0xDEADBEEF; } - +#else + // On Linux, the first word is either the function pointer itself or the vtable offset in bytes depending on + // whether the function is virtual or not. I don't think we can depend on the usage of this function to + // determine whether the method is virtual or not. Instead, I assert that the vtable offset will be word-aligned, + // as will any function pointer. However, the first word on Linux is the vtable offset PLUS ONE, meaning the + // bottom 3 bits must be 0b001 for a virtual function and 0b000 for a direct pointer. + if ((RawFunctionPointer->IntPtr & 0x7) == 1) { + //UE_LOG(LogNativeHookManager, Display, TEXT("Member pointer looks like a Vtable offset: 0x%08x."), RawFunctionPointer->IntPtr); + + // We mark this method as virtual by leaving the original function pointer null. This works in concert with + // RegisterHookFunction(). + ResultPointer.VtableDisplacement = RawFunctionPointer->VtableDisplacementPlusOne - 1; + } else if ((RawFunctionPointer->IntPtr & 0x7) == 0) { + //UE_LOG(LogNativeHookManager, Display, TEXT("Member pointer looks like a function pointer: 0x%08x"), RawFunctionPointer->IntPtr); + + ResultPointer.FunctionAddress = RawFunctionPointer->FunctionAddress; + } else { + UE_LOG(LogNativeHookManager, Fatal, TEXT("Unwilling to convert unaligned method pointer 0x%08x"), RawFunctionPointer->IntPtr); + } + + // The offset to 'this' always has the same definition, whether this is a virtual method or not. + ResultPointer.ThisAdjustment = RawFunctionPointer->ThisAdjustment; +#endif + return ResultPointer; } @@ -50,8 +93,13 @@ class SML_API FNativeHookManagerInternal { public: static void* GetHandlerListInternal( const void* RealFunctionAddress); static void SetHandlerListInstanceInternal(void* RealFunctionAddress, void* HandlerList); - static void* RegisterHookFunction(const FString& DebugSymbolName, void* OriginalFunctionPointer, const void* SampleObjectInstance, int ThisAdjustment, void* HookFunctionPointer, void** OutTrampolineFunction); + static void* RegisterHookFunction(const FString& DebugSymbolName, FMemberFunctionPointer MemberFunctionPointer, const void* SampleObjectInstance, void* + HookFunctionPointer, void** OutTrampolineFunction); static void UnregisterHookFunction( const FString& DebugSymbolName, const void* RealFunctionAddress ); + + // A call to this function signature is inlined in mods + // Keep it for backwards compatibility + static void* RegisterHookFunction(const FString& DebugSymbolName, void* OriginalFunctionPointer, const void* SampleObjectInstance, int ThisAdjustment, void* HookFunctionPointer, void** OutTrampolineFunction); }; template @@ -261,7 +309,7 @@ struct HookInvokerExecutorGlobalFunction { { bHookInitialized = true; void* HookFunctionPointer = static_cast( GetApplyCall() ); - RealFunctionAddress = FNativeHookManagerInternal::RegisterHookFunction( DebugSymbolName, Callable, NULL, 0, HookFunctionPointer, (void**) &FunctionPtr ); + RealFunctionAddress = FNativeHookManagerInternal::RegisterHookFunction( DebugSymbolName, {Callable, 0, 0}, NULL, HookFunctionPointer, (void**) &FunctionPtr ); THandlerLists* HandlerLists = CreateHandlerLists( RealFunctionAddress ); HandlersBefore = &HandlerLists->HandlersBefore; @@ -429,14 +477,11 @@ struct HookInvokerExecutorMemberFunction { { bHookInitialized = true; void* HookFunctionPointer = GetApplyCall(); - TMemberFunctionPointer RawFunctionPointer{}; - RawFunctionPointer.MemberFunctionPointer = Callable; - const FMemberFunctionPointer MemberFunctionPointer = ConvertFunctionPointer( &RawFunctionPointer ); + const FMemberFunctionPointer MemberFunctionPointer = ConvertFunctionPointer( Callable ); RealFunctionAddress = FNativeHookManagerInternal::RegisterHookFunction( DebugSymbolName, - MemberFunctionPointer.FunctionAddress, + MemberFunctionPointer, SampleObjectInstance, - MemberFunctionPointer.ThisAdjustment, HookFunctionPointer, (void**) &FunctionPtr ); THandlerLists* HandlerLists = CreateHandlerLists( RealFunctionAddress ); diff --git a/Mods/SML/Source/SML/SML.Build.cs b/Mods/SML/Source/SML/SML.Build.cs index 26d17450b7..0e3087e5b6 100644 --- a/Mods/SML/Source/SML/SML.Build.cs +++ b/Mods/SML/Source/SML/SML.Build.cs @@ -48,13 +48,30 @@ public SML(ReadOnlyTargetRules Target) : base(Target) var PlatformName = Target.Platform.ToString(); var LibraryFolder = Path.Combine(ThirdPartyFolder, PlatformName); - // https://github.com/kubo/funchook/tree/7cb8819594f0d586454011ab691fab4edb625068 - PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "funchook.lib")); + 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 + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "funchook.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")); + // 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 + // Built on windows using the Unreal Engine cross-compile clang toolchain + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libfunchook.a")); + // funchook will additionally build distorm + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libdistorm.a")); + + // https://github.com/satisfactorymodding/AssemblyAnalyzer/tree/e08ec4402b6e016a9b7aa59ab8c82dd0840e8f98 + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libAssemblyAnalyzer.a")); + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libZydis.a")); + PublicAdditionalLibraries.Add(Path.Combine(LibraryFolder, "libZycore.a")); + } AddFactoryGameInfo(); AddSMLInfo(); diff --git a/Mods/SML/ThirdParty/Linux/libAssemblyAnalyzer.a b/Mods/SML/ThirdParty/Linux/libAssemblyAnalyzer.a new file mode 100644 index 0000000000..6eb0d1f8be Binary files /dev/null and b/Mods/SML/ThirdParty/Linux/libAssemblyAnalyzer.a differ diff --git a/Mods/SML/ThirdParty/Linux/libZycore.a b/Mods/SML/ThirdParty/Linux/libZycore.a new file mode 100644 index 0000000000..955239b6df Binary files /dev/null and b/Mods/SML/ThirdParty/Linux/libZycore.a differ diff --git a/Mods/SML/ThirdParty/Linux/libZydis.a b/Mods/SML/ThirdParty/Linux/libZydis.a new file mode 100644 index 0000000000..2e10cc2a0f Binary files /dev/null and b/Mods/SML/ThirdParty/Linux/libZydis.a differ diff --git a/Mods/SML/ThirdParty/Linux/libdistorm.a b/Mods/SML/ThirdParty/Linux/libdistorm.a new file mode 100644 index 0000000000..a3ec2fa7f9 Binary files /dev/null and b/Mods/SML/ThirdParty/Linux/libdistorm.a differ diff --git a/Mods/SML/ThirdParty/Linux/libfunchook.a b/Mods/SML/ThirdParty/Linux/libfunchook.a new file mode 100644 index 0000000000..66fe0dba79 Binary files /dev/null and b/Mods/SML/ThirdParty/Linux/libfunchook.a differ diff --git a/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/NativeCodeGenerator.cpp b/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/NativeCodeGenerator.cpp index e5e40cc346..783076ddff 100644 --- a/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/NativeCodeGenerator.cpp +++ b/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/NativeCodeGenerator.cpp @@ -408,3 +408,5 @@ bool FNativeCodeGenerator::IsValidCppIdentifierString(const FString& Identifier) } return true; } + +#undef LOCTEXT_NAMESPACE diff --git a/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/UserDefinedStructCodeGenerator.cpp b/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/UserDefinedStructCodeGenerator.cpp index 1574eb7f3a..41660e0fec 100644 --- a/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/UserDefinedStructCodeGenerator.cpp +++ b/Mods/SMLEditor/Source/SMLEditor/Private/CodeGeneration/UserDefinedStructCodeGenerator.cpp @@ -257,3 +257,5 @@ FEdGraphPinType FUserDefinedStructCodeGenerator::CreatePinTypeForVariable(const } return GraphPinType; } + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/SMLEditor/Source/SMLEditor/Private/Node/K2Node_GetModConfiguration.cpp b/Mods/SMLEditor/Source/SMLEditor/Private/Node/K2Node_GetModConfiguration.cpp index 9d9263e348..b380a9c02c 100644 --- a/Mods/SMLEditor/Source/SMLEditor/Private/Node/K2Node_GetModConfiguration.cpp +++ b/Mods/SMLEditor/Source/SMLEditor/Private/Node/K2Node_GetModConfiguration.cpp @@ -280,3 +280,5 @@ void UK2Node_GetModConfiguration::RetrieveAllConfigurationClasses(TArray> UCommonSessionSubs TFuture> UCommonSessionSubsystem::JoinSession(UE::Online::FJoinSession::Params&& Params, EOnlineIntegrationUnmappedContext Context){ return TFuture>(); } void UCommonSessionSubsystem::LeaveSession(ULocalUserInfo* UserInfo, FName SessionName){ } TFuture> UCommonSessionSubsystem::LeaveSession(UE::Online::FLeaveSession::Params&& Params, EOnlineIntegrationUnmappedContext Context){ return TFuture>(); } -UE::Online::TOnlineResult UCommonSessionSubsystem::GetSessionByName(UE::Online::FGetSessionByName::Params&& Params, EOnlineIntegrationUnmappedContext Context){ return UE::Online::TOnlineResult({MakeShareable(nullptr)}); } +UE::Online::TOnlineResult UCommonSessionSubsystem::GetSessionByName(UE::Online::FGetSessionByName::Params&& Params, EOnlineIntegrationUnmappedContext Context){ return UE::Online::TOnlineResult({TSharedPtr().ToSharedRef()}); } TFuture> UCommonSessionSubsystem::UpdateSessionSettings(UE::Online::FUpdateSessionSettings::Params&& Params, EOnlineIntegrationUnmappedContext Context){ return TFuture>(); } UE::Online::FOnlineSessionId UCommonSessionSubsystem::MakeOnlineSessionId(const FString& SessionIdString, EOnlineIntegrationUnmappedContext Context){ return UE::Online::FOnlineSessionId(); } FString UCommonSessionSubsystem::OnlineSessionIdToString(UE::Online::FOnlineSessionId SessionId){ return FString(); } diff --git a/Source/FactoryGame/Public/FGDroneVehicle.h b/Source/FactoryGame/Public/FGDroneVehicle.h index 2e2ea5a11e..15872e10dc 100644 --- a/Source/FactoryGame/Public/FGDroneVehicle.h +++ b/Source/FactoryGame/Public/FGDroneVehicle.h @@ -2,6 +2,11 @@ #pragma once +#if defined(__clang__) +#include "Clang/ClangPlatformCompilerPreSetup.h" +PRAGMA_DISABLE_OVERLOADED_VIRTUAL_WARNINGS // TEMPORARY EDIT +#endif + #include "FactoryGame.h" #include "CoreMinimal.h" @@ -775,4 +780,8 @@ struct FACTORYGAME_API FDroneAction_TravelStartSequence : public FDroneAction_Ti FVector mDestination; }; -#undef ACTION_STATIC_STRUCT_IMPL \ No newline at end of file +#undef ACTION_STATIC_STRUCT_IMPL + +#if defined(__clang__) +PRAGMA_ENABLE_OVERLOADED_VIRTUAL_WARNINGS // TEMPORARY EDIT +#endif diff --git a/Source/FactoryServer.Target.cs b/Source/FactoryServer.Target.cs new file mode 100644 index 0000000000..da0b609c96 --- /dev/null +++ b/Source/FactoryServer.Target.cs @@ -0,0 +1,26 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using System; +using UnrealBuildTool; + +public class FactoryServerTarget : TargetRules +{ + public FactoryServerTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Server; + BuildEnvironment = TargetBuildEnvironment.Shared; + LinkType = TargetLinkType.Modular; + CppStandard = CppStandardVersion.Cpp20; + DefaultBuildSettings = BuildSettingsVersion.V2; + + ExtraModuleNames.AddRange(new[] {"FactoryGame"}); + + // bOverrideAppNameForSharedBuild = true; + + bUseChecksInShipping = true; + bUseLoggingInShipping = true; + + WindowsPlatform.bEnableRayTracing = false; + LinuxPlatform.bEnableRayTracing = false; + } +}