Inside Unreal: Adding Mod Support with the Simple UGC Plugin

I think I got it now.
Here is my PackageSimpleUGCPlugin.cs that creates .pak files for both Linux and Windows.

// Copyright Epic Games, Inc. All Rights Reserved.

using AutomationTool;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnrealBuildTool;
using Tools.DotNETCommon;

using Microsoft.Win32;
using System.Diagnostics;


namespace SimpleUGC.Automation

{
	public class PackageUGC : BuildCommand
	{
		static public ProjectParams GetParams(BuildCommand Cmd, string ProjectFileName, out FileReference PluginFile)
		{
			string VersionString = Cmd.ParseParamValue("Version", "NOVERSION");

			// Get the plugin filename
			string PluginPath = Cmd.ParseParamValue("PluginPath");
			if (PluginPath == null)
			{
				throw new AutomationException("Missing -PluginPath=... argument");
			}

			// Check it exists
			PluginFile = new FileReference(PluginPath);
			if (!FileReference.Exists(PluginFile))
			{
				throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName);
			}

			string ReleaseVersion = Cmd.ParseParamValue("BasedOnReleaseVersion", "UGCExampleGame_v1");

			FileReference ProjectFile = new FileReference(ProjectFileName);

			ProjectParams Params = new ProjectParams(
				RawProjectPath: ProjectFile,
				Command: Cmd,
				ClientTargetPlatforms: new List<TargetPlatformDescriptor>() { new TargetPlatformDescriptor(UnrealTargetPlatform.Win64) },
				Build: false,
				Cook: true,
				Stage: true,
				Pak: true,
				Manifests: true,
				DLCIncludeEngineContent: true, // Need this to allow engine content that wasn't cooked in the base game to be included in the PAK file 
				BasedOnReleaseVersion: ReleaseVersion,
				DLCName: PluginFile.GetFileNameWithoutAnyExtensions(),

				RunAssetNativization: true
				);



			Params.ValidateAndLog();
			return Params;
		}
		
		static public ProjectParams GetBuild(BuildCommand Cmd, string ProjectFileName, out FileReference PluginFile)
		{
			string VersionString = Cmd.ParseParamValue("Version", "NOVERSION");

			// Get the plugin filename
			string PluginPath = Cmd.ParseParamValue("PluginPath");
			if (PluginPath == null)
			{
				throw new AutomationException("Missing -PluginPath=... argument");
			}

			// Check it exists
			PluginFile = new FileReference(PluginPath);
			if (!FileReference.Exists(PluginFile))
			{
				throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName);
			}

			string ReleaseVersion = Cmd.ParseParamValue("BasedOnReleaseVersion", "UGCExampleGame_v1");

			FileReference ProjectFile = new FileReference(ProjectFileName);

			ProjectParams LinuxParams = new ProjectParams(
				RawProjectPath: ProjectFile,
				Command: Cmd,
				ClientTargetPlatforms: new List<TargetPlatformDescriptor>() { new TargetPlatformDescriptor(UnrealTargetPlatform.Linux) },
				Build: false,
				Cook: true,
				Stage: true,
				Pak: true,
				Manifests: true,
				DLCIncludeEngineContent: true, // Need this to allow engine content that wasn't cooked in the base game to be included in the PAK file 
				BasedOnReleaseVersion: ReleaseVersion,
				DLCName: PluginFile.GetFileNameWithoutAnyExtensions(),

				RunAssetNativization: true
				);



			LinuxParams.ValidateAndLog();
			return LinuxParams;
		}
	
		public override void ExecuteBuild()
		{
			int WorkingCL = -1;
			FileReference PluginFile = null;
			string ProjectFileName = ParseParamValue("Project");
			if (ProjectFileName == null)
			{
				ProjectFileName = CombinePaths(CmdEnv.LocalRoot, "SimpleGame", "SimpleGame.uproject");
			}
			LogInformation(ProjectFileName);


			ProjectParams Params = GetParams(this, ProjectFileName, out PluginFile);
			ProjectParams LinuxParams = GetBuild(this, ProjectFileName, out PluginFile);



			// Check whether folder already exists so we know if we can delete it later
			string PlatformStageDir = Path.Combine(Params.StageDirectoryParam, "WindowsNoEditor");
			bool bPreExistingStageDir = Directory.Exists(PlatformStageDir);

			string LinuxPlatformStageDir = Path.Combine(LinuxParams.StageDirectoryParam, "LinuxNoEditor");
			bool bPreExistingLinuxStageDir = Directory.Exists(LinuxPlatformStageDir);

			PluginDescriptor Plugin = PluginDescriptor.FromFile(PluginFile);

			FileReference ProjectFile = new FileReference(ProjectFileName);

			// Add Plugin to folders excluded for nativization in config file
			FileReference UserEditorIni = new FileReference(Path.Combine(Path.GetDirectoryName(ProjectFileName), "Config", "UserEditor.ini"));
			bool bPreExistingUserEditorIni = FileReference.Exists(UserEditorIni);
			if (!bPreExistingUserEditorIni)
			{
				// Expect this most of the time so we will create and clean up afterwards
				DirectoryReference.CreateDirectory(UserEditorIni.Directory);
				CommandUtils.WriteAllText(UserEditorIni.FullName, "");
			}

			const string ConfigSection = "BlueprintNativizationSettings";
			const string ConfigKey = "ExcludedFolderPaths";
			string ConfigValue = "/" + PluginFile.GetFileNameWithoutAnyExtensions() + "/";

			ConfigFile UserEditorConfig = new ConfigFile(UserEditorIni);
			ConfigFileSection BPNSection = UserEditorConfig.FindOrAddSection(ConfigSection);
			bool bUpdateConfigFile = !BPNSection.Lines.Exists(x => String.Equals(x.Key, ConfigKey, StringComparison.OrdinalIgnoreCase) && String.Equals(x.Value, ConfigValue, StringComparison.OrdinalIgnoreCase));
			if (bUpdateConfigFile)
			{
				BPNSection.Lines.Add(new ConfigLine(ConfigLineAction.Add, ConfigKey, ConfigValue));
				UserEditorConfig.Write(UserEditorIni);
			}

			Project.Cook(Params);
			if (!bPreExistingUserEditorIni)
			{
				FileReference.Delete(UserEditorIni);
			}

			Project.Cook(LinuxParams);
			if (!bPreExistingUserEditorIni)
			{
				FileReference.Delete(UserEditorIni);
			}

			Project.CopyBuildToStagingDirectory(Params);
			Project.Package(LinuxParams);
			Project.Archive(Params);
			Project.Deploy(Params);

			Project.CopyBuildToStagingDirectory(LinuxParams);
			Project.Package(LinuxParams, WorkingCL);
			Project.Archive(LinuxParams);
			Project.Deploy(LinuxParams);




			// Get path to where the plugin was staged
			string StagedPluginDir = Path.Combine(PluginFile.GetFileNameWithoutAnyExtensions(), PlatformStageDir, Path.GetFileNameWithoutExtension(ProjectFileName));
			string StagedLinuxPluginDir = Path.Combine(PluginFile.GetFileNameWithoutAnyExtensions(), LinuxPlatformStageDir, Path.GetFileNameWithoutExtension(ProjectFileName));
			File.Delete(PlatformStageDir + "/" + Path.GetFileNameWithoutExtension(ProjectFileName) + "/" + "Mods/" + PluginFile.GetFileNameWithoutAnyExtensions() + "/" + PluginFile.GetFileNameWithoutAnyExtensions() + ".uplugin");
			string ZipFile = Path.Combine(Params.StageDirectoryParam, "Win" + PluginFile.GetFileNameWithoutAnyExtensions());
			string LinuxZipFile = Path.Combine(Params.StageDirectoryParam, "Linux" + PluginFile.GetFileNameWithoutAnyExtensions());
			string ZipReleaseFile = Path.Combine(Params.StageDirectoryParam, PluginFile.GetFileNameWithoutAnyExtensions());
			CommandUtils.DeleteFile(ZipFile);
			CommandUtils.DeleteFile(LinuxZipFile);

			System.IO.Compression.ZipFile.CreateFromDirectory(StagedPluginDir, ZipFile + ".zip");
			System.IO.Compression.ZipFile.CreateFromDirectory(StagedLinuxPluginDir, LinuxZipFile + ".zip");


			//Exracting the .Zip files to the same path so we can create one file for both Win and Linux.
			System.IO.Compression.ZipFile.ExtractToDirectory(Params.StageDirectoryParam + "/Linux" + PluginFile.GetFileNameWithoutAnyExtensions() + ".zip", Params.StageDirectoryParam + "/");
			System.IO.Compression.ZipFile.ExtractToDirectory(Params.StageDirectoryParam + "/Win" + PluginFile.GetFileNameWithoutAnyExtensions() + ".zip", Params.StageDirectoryParam + "/");

			//Compresses the all the needed files for Win and Linux to one .Zip file
			System.IO.Compression.ZipFile.CreateFromDirectory(Params.StageDirectoryParam + "/Mods", ZipReleaseFile + ".zip");
			CommandUtils.DeleteDirectory(Params.StageDirectoryParam + "/Mods");



			if (!bPreExistingStageDir)
			{
				CommandUtils.DeleteDirectory(PlatformStageDir);

			}
			if (!bPreExistingLinuxStageDir)
			{
				CommandUtils.DeleteDirectory(LinuxPlatformStageDir);


			}
		}
	}
}


This plugin is great!

1 Like