From 7188f88ba1ef66a8379b39784cb6e29772127fc4 Mon Sep 17 00:00:00 2001 From: IceReaper Date: Sun, 6 Nov 2022 19:23:49 +0100 Subject: [PATCH] Replaced hardcoded source actions by user defined ISourceAction. --- OpenRA.Mods.Common/Installer/ISourceAction.cs | 21 ++ .../Installer/InstallerUtils.cs | 78 +++++ .../SourceActions/CopySourceAction.cs | 56 +++ .../SourceActions/DeleteSourceAction.cs | 32 ++ .../SourceActions/ExtractBlastSourceAction.cs | 77 ++++ .../SourceActions/ExtractIscabSourceAction.cs | 79 +++++ .../SourceActions/ExtractMscabSourceAction.cs | 54 +++ .../SourceActions/ExtractRawSourceAction.cs | 76 ++++ OpenRA.Mods.Common/ModContent.cs | 4 +- .../Installation/InstallFromDiscLogic.cs | 330 +----------------- .../Logic/Installation/ModContentLogic.cs | 2 +- mods/cnc/installer/covertops.yaml | 2 +- mods/cnc/installer/firstdecade.yaml | 6 +- mods/cnc/installer/gdi95.yaml | 6 +- mods/cnc/installer/nod95.yaml | 8 +- mods/cnc/installer/origin.yaml | 4 +- mods/d2k/installer/d2k-a.yaml | 6 +- mods/d2k/installer/d2k-b.yaml | 6 +- mods/d2k/installer/d2k-c.yaml | 6 +- mods/d2k/installer/d2k-d.yaml | 6 +- mods/d2k/installer/d2k-e.yaml | 6 +- mods/d2k/installer/gruntmods.yaml | 8 +- mods/ra/installer/aftermath.yaml | 4 +- mods/ra/installer/allies95.yaml | 4 +- mods/ra/installer/cnc95.yaml | 2 +- mods/ra/installer/counterstrike.yaml | 2 +- mods/ra/installer/firstdecade.yaml | 10 +- mods/ra/installer/origin.yaml | 8 +- mods/ra/installer/soviet95.yaml | 4 +- mods/ts/installer/firestorm.yaml | 4 +- mods/ts/installer/firstdecade.yaml | 10 +- mods/ts/installer/origin.yaml | 6 +- mods/ts/installer/tibsun.yaml | 4 +- 33 files changed, 546 insertions(+), 385 deletions(-) create mode 100644 OpenRA.Mods.Common/Installer/ISourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/InstallerUtils.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/CopySourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/DeleteSourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/ExtractBlastSourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/ExtractMscabSourceAction.cs create mode 100644 OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs diff --git a/OpenRA.Mods.Common/Installer/ISourceAction.cs b/OpenRA.Mods.Common/Installer/ISourceAction.cs new file mode 100644 index 0000000000..9bc08eaabb --- /dev/null +++ b/OpenRA.Mods.Common/Installer/ISourceAction.cs @@ -0,0 +1,21 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; + +namespace OpenRA.Mods.Common.Installer +{ + public interface ISourceAction + { + void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage); + } +} diff --git a/OpenRA.Mods.Common/Installer/InstallerUtils.cs b/OpenRA.Mods.Common/Installer/InstallerUtils.cs new file mode 100644 index 0000000000..b746770dd4 --- /dev/null +++ b/OpenRA.Mods.Common/Installer/InstallerUtils.cs @@ -0,0 +1,78 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Linq; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class InstallerUtils + { + public static bool IsValidSourcePath(string path, ModContent.ModSource source) + { + try + { + foreach (var kv in source.IDFiles.Nodes) + { + var filePath = FS.ResolveCaseInsensitivePath(Path.Combine(path, kv.Key)); + if (!File.Exists(filePath)) + return false; + + using (var fileStream = File.OpenRead(filePath)) + { + var offsetNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); + var lengthNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + if (offsetNode != null || lengthNode != null) + { + var offset = 0L; + if (offsetNode != null) + offset = FieldLoader.GetValue("Offset", offsetNode.Value.Value); + + var length = fileStream.Length - offset; + if (lengthNode != null) + length = FieldLoader.GetValue("Length", lengthNode.Value.Value); + + fileStream.Position = offset; + var data = fileStream.ReadBytes((int)length); + if (CryptoUtil.SHA1Hash(data) != kv.Value.Value) + return false; + } + else if (CryptoUtil.SHA1Hash(fileStream) != kv.Value.Value) + return false; + } + } + } + catch (Exception) + { + return false; + } + + return true; + } + + public static void CopyStream(Stream input, Stream output, long length, Action onProgress = null) + { + var buffer = new byte[4096]; + var copied = 0L; + while (copied < length) + { + var read = (int)Math.Min(buffer.Length, length - copied); + var write = input.Read(buffer, 0, read); + output.Write(buffer, 0, write); + copied += write; + + onProgress?.Invoke(copied); + } + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/CopySourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/CopySourceAction.cs new file mode 100644 index 0000000000..9150fb5328 --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/CopySourceAction.cs @@ -0,0 +1,56 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using OpenRA.Mods.Common.Widgets.Logic; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class CopySourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + var sourceDir = Path.Combine(path, actionYaml.Value); + foreach (var node in actionYaml.Nodes) + { + var sourcePath = FS.ResolveCaseInsensitivePath(Path.Combine(sourceDir, node.Value.Value)); + var targetPath = Platform.ResolvePath(node.Key); + if (File.Exists(targetPath)) + { + Log.Write("install", "Ignoring installed file " + targetPath); + continue; + } + + Log.Write("install", $"Copying {sourcePath} -> {targetPath}"); + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + + using (var source = File.OpenRead(sourcePath)) + using (var target = File.OpenWrite(targetPath)) + { + var displayFilename = Path.GetFileName(targetPath); + var length = source.Length; + + Action onProgress = null; + if (length < InstallFromDiscLogic.ShowPercentageThreshold) + updateMessage(modData.Translation.GetString(InstallFromDiscLogic.CopyingFilename, Translation.Arguments("filename", displayFilename))); + else + onProgress = b => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.CopyingFilenameProgress, Translation.Arguments("filename", displayFilename, "progress", 100 * b / length))); + + InstallerUtils.CopyStream(source, target, length, onProgress); + } + } + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/DeleteSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/DeleteSourceAction.cs new file mode 100644 index 0000000000..5755a822b7 --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/DeleteSourceAction.cs @@ -0,0 +1,32 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; + +namespace OpenRA.Mods.Common.Installer +{ + public class DeleteSourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + // Yaml path must be specified relative to a named directory (e.g. ^SupportDir) + if (!actionYaml.Value.StartsWith("^")) + return; + + var sourcePath = Platform.ResolvePath(actionYaml.Value); + + Log.Write("debug", $"Deleting {sourcePath}"); + File.Delete(sourcePath); + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractBlastSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractBlastSourceAction.cs new file mode 100644 index 0000000000..49504a8ed7 --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractBlastSourceAction.cs @@ -0,0 +1,77 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Widgets.Logic; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class ExtractBlastSourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path + var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); + + using (var source = File.OpenRead(sourcePath)) + { + foreach (var node in actionYaml.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); + if (offsetNode == null) + { + Log.Write("install", "Skipping entry with missing Offset definition " + targetPath); + continue; + } + + var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + if (lengthNode == null) + { + Log.Write("install", "Skipping entry with missing Length definition " + targetPath); + continue; + } + + var length = FieldLoader.GetValue("Length", lengthNode.Value.Value); + source.Position = FieldLoader.GetValue("Offset", offsetNode.Value.Value); + + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + + Action onProgress = null; + if (length < InstallFromDiscLogic.ShowPercentageThreshold) + updateMessage(modData.Translation.GetString(InstallFromDiscLogic.Extracing, Translation.Arguments("filename", displayFilename))); + else + onProgress = b => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", 100 * b / length))); + + using (var target = File.OpenWrite(targetPath)) + { + Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); + Blast.Decompress(source, target, (read, _) => onProgress?.Invoke(read)); + } + } + } + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs new file mode 100644 index 0000000000..c1dc28906f --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs @@ -0,0 +1,79 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Widgets.Logic; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class ExtractIscabSourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path + var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); + + var volumeNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Volumes"); + if (volumeNode == null) + throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node"); + + var extractNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Extract"); + if (extractNode == null) + throw new InvalidDataException("extract-iscab entry doesn't define an Extract node"); + + var volumes = new Dictionary(); + try + { + foreach (var node in volumeNode.Value.Nodes) + { + var volume = FieldLoader.GetValue("(key)", node.Key); + var stream = File.OpenRead(FS.ResolveCaseInsensitivePath(Path.Combine(path, node.Value.Value))); + volumes.Add(volume, stream); + } + + using (var source = File.OpenRead(sourcePath)) + { + var reader = new InstallShieldCABCompression(source, volumes); + foreach (var node in extractNode.Value.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + using (var target = File.OpenWrite(targetPath)) + { + Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + Action onProgress = percent => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", percent))); + reader.ExtractFile(node.Value.Value, target, onProgress); + } + } + } + } + finally + { + foreach (var kv in volumes) + kv.Value.Dispose(); + } + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractMscabSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractMscabSourceAction.cs new file mode 100644 index 0000000000..9a8fb1feed --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractMscabSourceAction.cs @@ -0,0 +1,54 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Widgets.Logic; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class ExtractMscabSourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path + var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); + + using (var source = File.OpenRead(sourcePath)) + { + var reader = new MSCabCompression(source); + foreach (var node in actionYaml.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + using (var target = File.OpenWrite(targetPath)) + { + Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + Action onProgress = percent => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", percent))); + reader.ExtractFile(node.Value.Value, target, onProgress); + } + } + } + } + } +} diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs new file mode 100644 index 0000000000..3b1dc6f66e --- /dev/null +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs @@ -0,0 +1,76 @@ +#region Copyright & License Information +/* + * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Mods.Common.Widgets.Logic; +using FS = OpenRA.FileSystem.FileSystem; + +namespace OpenRA.Mods.Common.Installer +{ + public class ExtractRawSourceAction : ISourceAction + { + public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List extracted, Action updateMessage) + { + // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path + var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); + + using (var source = File.OpenRead(sourcePath)) + { + foreach (var node in actionYaml.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); + if (offsetNode == null) + { + Log.Write("install", "Skipping entry with missing Offset definition " + targetPath); + continue; + } + + var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + if (lengthNode == null) + { + Log.Write("install", "Skipping entry with missing Length definition " + targetPath); + continue; + } + + var length = FieldLoader.GetValue("Length", lengthNode.Value.Value); + source.Position = FieldLoader.GetValue("Offset", offsetNode.Value.Value); + + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + + Action onProgress = null; + if (length < InstallFromDiscLogic.ShowPercentageThreshold) + updateMessage(modData.Translation.GetString(InstallFromDiscLogic.Extracing, Translation.Arguments("filename", displayFilename))); + else + onProgress = b => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", 100 * b / length))); + + using (var target = File.OpenWrite(targetPath)) + { + Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); + InstallerUtils.CopyStream(source, target, length, onProgress); + } + } + } + } + } +} diff --git a/OpenRA.Mods.Common/ModContent.cs b/OpenRA.Mods.Common/ModContent.cs index 553409ef72..12d441045f 100644 --- a/OpenRA.Mods.Common/ModContent.cs +++ b/OpenRA.Mods.Common/ModContent.cs @@ -41,6 +41,7 @@ namespace OpenRA public class ModSource { + public readonly ObjectCreator ObjectCreator; public readonly SourceType Type = SourceType.Disc; // Used to find installation locations for SourceType.Install @@ -56,8 +57,9 @@ namespace OpenRA [FieldLoader.Ignore] public readonly List Install; - public ModSource(MiniYaml yaml) + public ModSource(MiniYaml yaml, ObjectCreator objectCreator) { + ObjectCreator = objectCreator; Title = yaml.Value; var idFiles = yaml.Nodes.FirstOrDefault(n => n.Key == "IDFiles"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs index 80076f152c..7e025620b8 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs @@ -13,18 +13,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; -using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Installer; using OpenRA.Widgets; -using FS = OpenRA.FileSystem.FileSystem; namespace OpenRA.Mods.Common.Widgets.Logic { public class InstallFromDiscLogic : ChromeLogic { // Hide percentage indicators for files smaller than 25 MB - const int ShowPercentageThreshold = 26214400; + public const int ShowPercentageThreshold = 26214400; enum Mode { Progress, Message, List } @@ -83,10 +81,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic static readonly string InstallingContent = "installing-content"; [TranslationReference("filename")] - static readonly string CopyingFilename = "copying-filename"; + public static readonly string CopyingFilename = "copying-filename"; [TranslationReference("filename", "progress")] - static readonly string CopyingFilenameProgress = "copying-filename-progress"; + public static readonly string CopyingFilenameProgress = "copying-filename-progress"; [TranslationReference] static readonly string InstallationFailed = "installation-failed"; @@ -95,10 +93,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic static readonly string CheckInstallLog = "check-install-log"; [TranslationReference("filename")] - static readonly string Extracing = "extracting-filename"; + public static readonly string Extracing = "extracting-filename"; [TranslationReference("filename", "progress")] - static readonly string ExtracingProgress = "extracting-filename-progress"; + public static readonly string ExtracingProgress = "extracting-filename-progress"; [TranslationReference] static readonly string Continue = "continue"; @@ -264,82 +262,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { foreach (var i in modSource.Install) { - switch (i.Key) - { - case "copy": - { - var sourceDir = Path.Combine(path, i.Value.Value); - foreach (var node in i.Value.Nodes) - { - var sourcePath = FS.ResolveCaseInsensitivePath(Path.Combine(sourceDir, node.Value.Value)); - var targetPath = Platform.ResolvePath(node.Key); - if (File.Exists(targetPath)) - { - Log.Write("install", "Ignoring installed file " + targetPath); - continue; - } - - Log.Write("install", $"Copying {sourcePath} -> {targetPath}"); - extracted.Add(targetPath); - Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); - - using (var source = File.OpenRead(sourcePath)) - using (var target = File.OpenWrite(targetPath)) - { - var displayFilename = Path.GetFileName(targetPath); - var length = source.Length; - - Action onProgress = null; - if (length < ShowPercentageThreshold) - message = modData.Translation.GetString(CopyingFilename, Translation.Arguments("filename", displayFilename)); - else - onProgress = b => message = modData.Translation.GetString(CopyingFilenameProgress, Translation.Arguments("filename", displayFilename, "progress", 100 * b / length)); - - CopyStream(source, target, length, onProgress); - } - } - - break; - } - - case "extract-raw": - { - ExtractFromPackage(modData, ExtractionType.Raw, path, i.Value, extracted, m => message = m); - break; - } - - case "extract-blast": - { - ExtractFromPackage(modData, ExtractionType.Blast, path, i.Value, extracted, m => message = m); - break; - } - - case "extract-mscab": - { - ExtractFromMSCab(modData, path, i.Value, extracted, m => message = m); - break; - } - - case "extract-iscab": - { - ExtractFromISCab(modData, path, i.Value, extracted, m => message = m); - break; - } - - case "delete": - { - // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected disc path - var sourcePath = i.Value.Value.StartsWith("^") ? Platform.ResolvePath(i.Value.Value) : Path.Combine(path, i.Value.Value); - - Log.Write("debug", $"Deleting {sourcePath}"); - File.Delete(sourcePath); - break; - } - - default: - Log.Write("debug", $"Unknown installation command {i.Key} - ignoring"); - break; - } + var sourceAction = modSource.ObjectCreator.CreateObject($"{i.Key}SourceAction"); + sourceAction.RunActionOnSource(i.Value, path, modData, extracted, m => message = m); } Game.RunAfterTick(Ui.CloseWindow); @@ -363,244 +287,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic }).Start(); } - static void CopyStream(Stream input, Stream output, long length, Action onProgress = null) - { - var buffer = new byte[4096]; - var copied = 0L; - while (copied < length) - { - var read = (int)Math.Min(buffer.Length, length - copied); - var write = input.Read(buffer, 0, read); - output.Write(buffer, 0, write); - copied += write; - - onProgress?.Invoke(copied); - } - } - - enum ExtractionType { Raw, Blast } - - static void ExtractFromPackage(ModData modData, ExtractionType type, string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) - { - // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected disc path - var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); - - using (var source = File.OpenRead(sourcePath)) - { - foreach (var node in actionYaml.Nodes) - { - var targetPath = Platform.ResolvePath(node.Key); - - if (File.Exists(targetPath)) - { - Log.Write("install", "Skipping installed file " + targetPath); - continue; - } - - var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); - if (offsetNode == null) - { - Log.Write("install", "Skipping entry with missing Offset definition " + targetPath); - continue; - } - - var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); - if (lengthNode == null) - { - Log.Write("install", "Skipping entry with missing Length definition " + targetPath); - continue; - } - - var length = FieldLoader.GetValue("Length", lengthNode.Value.Value); - source.Position = FieldLoader.GetValue("Offset", offsetNode.Value.Value); - - extractedFiles.Add(targetPath); - Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); - var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); - - Action onProgress = null; - if (length < ShowPercentageThreshold) - updateMessage(modData.Translation.GetString(Extracing, Translation.Arguments("filename", displayFilename))); - else - onProgress = b => updateMessage(modData.Translation.GetString(ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", 100 * b / length))); - - using (var target = File.OpenWrite(targetPath)) - { - Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); - if (type == ExtractionType.Blast) - Blast.Decompress(source, target, (read, _) => onProgress?.Invoke(read)); - else - CopyStream(source, target, length, onProgress); - } - } - } - } - - static void ExtractFromMSCab(ModData modData, string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) - { - // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected disc path - var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); - - using (var source = File.OpenRead(sourcePath)) - { - var reader = new MSCabCompression(source); - foreach (var node in actionYaml.Nodes) - { - var targetPath = Platform.ResolvePath(node.Key); - - if (File.Exists(targetPath)) - { - Log.Write("install", "Skipping installed file " + targetPath); - continue; - } - - extractedFiles.Add(targetPath); - Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); - using (var target = File.OpenWrite(targetPath)) - { - Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); - var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); - Action onProgress = percent => updateMessage(modData.Translation.GetString(ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", percent))); - reader.ExtractFile(node.Value.Value, target, onProgress); - } - } - } - } - - static void ExtractFromISCab(ModData modData, string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) - { - // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected disc path - var sourcePath = actionYaml.Value.StartsWith("^") ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); - - var volumeNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Volumes"); - if (volumeNode == null) - throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node"); - - var extractNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Extract"); - if (extractNode == null) - throw new InvalidDataException("extract-iscab entry doesn't define an Extract node"); - - var volumes = new Dictionary(); - try - { - foreach (var node in volumeNode.Value.Nodes) - { - var volume = FieldLoader.GetValue("(key)", node.Key); - var stream = File.OpenRead(FS.ResolveCaseInsensitivePath(Path.Combine(path, node.Value.Value))); - volumes.Add(volume, stream); - } - - using (var source = File.OpenRead(sourcePath)) - { - var reader = new InstallShieldCABCompression(source, volumes); - foreach (var node in extractNode.Value.Nodes) - { - var targetPath = Platform.ResolvePath(node.Key); - - if (File.Exists(targetPath)) - { - Log.Write("install", "Skipping installed file " + targetPath); - continue; - } - - extractedFiles.Add(targetPath); - Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); - using (var target = File.OpenWrite(targetPath)) - { - Log.Write("install", $"Extracting {sourcePath} -> {targetPath}"); - var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); - Action onProgress = percent => updateMessage(modData.Translation.GetString(ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", percent))); - reader.ExtractFile(node.Value.Value, target, onProgress); - } - } - } - } - finally - { - foreach (var kv in volumes) - kv.Value.Dispose(); - } - } - - string FindSourcePath(ModContent.ModSource source, IEnumerable volumes) - { - if (source.Type == ModContent.SourceType.RegistryDirectory || source.Type == ModContent.SourceType.RegistryDirectoryFromFile) - { - if (source.RegistryKey == null) - return null; - - if (Platform.CurrentPlatform != PlatformType.Windows) - return null; - - // We need an extra check for the platform here to silence a warning when the registry is accessed - // TODO: Remove this once our platform checks use the same method - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return null; - - foreach (var prefix in source.RegistryPrefixes) - { - if (!(Microsoft.Win32.Registry.GetValue(prefix + source.RegistryKey, source.RegistryValue, null) is string path)) - continue; - - if (source.Type == ModContent.SourceType.RegistryDirectoryFromFile) - path = Path.GetDirectoryName(path); - - return IsValidSourcePath(path, source) ? path : null; - } - - return null; - } - - if (source.Type == ModContent.SourceType.Disc) - foreach (var volume in volumes) - if (IsValidSourcePath(volume, source)) - return volume; - - return null; - } - - static bool IsValidSourcePath(string path, ModContent.ModSource source) - { - try - { - foreach (var kv in source.IDFiles.Nodes) - { - var filePath = FS.ResolveCaseInsensitivePath(Path.Combine(path, kv.Key)); - if (!File.Exists(filePath)) - return false; - - using (var fileStream = File.OpenRead(filePath)) - { - var offsetNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); - var lengthNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); - if (offsetNode != null || lengthNode != null) - { - var offset = 0L; - if (offsetNode != null) - offset = FieldLoader.GetValue("Offset", offsetNode.Value.Value); - - var length = fileStream.Length - offset; - if (lengthNode != null) - length = FieldLoader.GetValue("Length", lengthNode.Value.Value); - - fileStream.Position = offset; - var data = fileStream.ReadBytes((int)length); - if (CryptoUtil.SHA1Hash(data) != kv.Value.Value) - return false; - } - else if (CryptoUtil.SHA1Hash(fileStream) != kv.Value.Value) - return false; - } - } - } - catch (Exception) - { - return false; - } - - return true; - } - void ShowMessage(string title, string message) { visible = Mode.Message; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs index 4370e7dcae..ec9025a94d 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs @@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var sourceYaml = MiniYaml.Load(modFileSystem, content.Sources, null); foreach (var s in sourceYaml) - sources.Add(s.Key, new ModContent.ModSource(s.Value)); + sources.Add(s.Key, new ModContent.ModSource(s.Value, modObjectCreator)); var downloadYaml = MiniYaml.Load(modFileSystem, content.Downloads, null); foreach (var d in downloadYaml) diff --git a/mods/cnc/installer/covertops.yaml b/mods/cnc/installer/covertops.yaml index ad94adfdbf..d1d2a53f60 100644 --- a/mods/cnc/installer/covertops.yaml +++ b/mods/cnc/installer/covertops.yaml @@ -3,5 +3,5 @@ covertops: Covert Operations Expansion (English) GAME/GAME.DAT: be5a6c4c0a581da09e8f34a3bbf7bd17e525085c CONQUER.MIX: 713b53fa4c188ca9619c6bbeadbfc86513704266 Install: - copy: . + Copy: . ^SupportDir|Content/cnc/scores-covertops.mix: SCORES.MIX diff --git a/mods/cnc/installer/firstdecade.yaml b/mods/cnc/installer/firstdecade.yaml index 4e320e4fb5..3c640fcb4d 100644 --- a/mods/cnc/installer/firstdecade.yaml +++ b/mods/cnc/installer/firstdecade.yaml @@ -3,7 +3,7 @@ tfd: C&C The First Decade (English) data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 Install: - extract-iscab: data1.hdr + ExtractIscab: data1.hdr Volumes: 2: data2.cab 3: data3.cab @@ -20,7 +20,7 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/cnc/transit.mix: CnC\\TRANSIT.MIX ^SupportDir|Content/cnc/scores-covertops.mix: CnC\covert\SCORES.MIX ^SupportDir|Content/cnc/movies.mix: CnC\\MOVIES.MIX - extract-raw: ^SupportDir|Content/cnc/movies.mix + ExtractRaw: ^SupportDir|Content/cnc/movies.mix ^SupportDir|Content/cnc/movies/airstrk.vqa: Offset: 1266 Length: 4444442 @@ -333,4 +333,4 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/cnc/movies/banner.vqa: Offset: 751962554 Length: 2229408 - delete: ^SupportDir|Content/cnc/movies.mix \ No newline at end of file + Delete: ^SupportDir|Content/cnc/movies.mix diff --git a/mods/cnc/installer/gdi95.yaml b/mods/cnc/installer/gdi95.yaml index f837237d34..6c92c88332 100644 --- a/mods/cnc/installer/gdi95.yaml +++ b/mods/cnc/installer/gdi95.yaml @@ -3,7 +3,7 @@ gdi95: C&C Gold (GDI Disc, English) DISK.WAV: 8bef9a6687c0fe0afd57c6561df58fa6e64f145d CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30 Install: - copy: . + Copy: . ^SupportDir|Content/cnc/conquer.mix: CONQUER.MIX ^SupportDir|Content/cnc/desert.mix: DESERT.MIX ^SupportDir|Content/cnc/general.mix: GENERAL.MIX @@ -11,7 +11,7 @@ gdi95: C&C Gold (GDI Disc, English) ^SupportDir|Content/cnc/sounds.mix: SOUNDS.MIX ^SupportDir|Content/cnc/temperat.mix: TEMPERAT.MIX ^SupportDir|Content/cnc/winter.mix: WINTER.MIX - extract-blast: INSTALL/SETUP.Z + ExtractBlast: INSTALL/SETUP.Z ^SupportDir|Content/cnc/speech.mix: Offset: 10203213 Length: 603293 @@ -21,7 +21,7 @@ gdi95: C&C Gold (GDI Disc, English) ^SupportDir|Content/cnc/transit.mix: Offset: 11078017 Length: 3724462 - extract-raw: MOVIES.MIX + ExtractRaw: MOVIES.MIX ^SupportDir|Content/cnc/movies/visor.vqa: Offset: 786 Length: 4407162 diff --git a/mods/cnc/installer/nod95.yaml b/mods/cnc/installer/nod95.yaml index 7e2227ff96..aa6937477f 100644 --- a/mods/cnc/installer/nod95.yaml +++ b/mods/cnc/installer/nod95.yaml @@ -3,7 +3,7 @@ nod95: C&C Gold (Nod Disc, English) DISK.WAV: 83a0235525afa5cd6096f2967e3eae032996e38c CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30 Install: - copy: . + Copy: . ^SupportDir|Content/cnc/conquer.mix: CONQUER.MIX ^SupportDir|Content/cnc/desert.mix: DESERT.MIX ^SupportDir|Content/cnc/general.mix: GENERAL.MIX @@ -11,7 +11,7 @@ nod95: C&C Gold (Nod Disc, English) ^SupportDir|Content/cnc/sounds.mix: SOUNDS.MIX ^SupportDir|Content/cnc/temperat.mix: TEMPERAT.MIX ^SupportDir|Content/cnc/winter.mix: WINTER.MIX - extract-blast: INSTALL/SETUP.Z + ExtractBlast: INSTALL/SETUP.Z ^SupportDir|Content/cnc/speech.mix: Offset: 10203213 Length: 603293 @@ -21,7 +21,7 @@ nod95: C&C Gold (Nod Disc, English) ^SupportDir|Content/cnc/transit.mix: Offset: 11078017 Length: 3724462 - extract-raw: MOVIES.MIX + ExtractRaw: MOVIES.MIX ^SupportDir|Content/cnc/movies/visor.vqa: Offset: 738 Length: 4407162 @@ -204,4 +204,4 @@ nod95: C&C Gold (Nod Disc, English) Length: 7803444 ^SupportDir|Content/cnc/movies/airstrk.vqa: Offset: 428091252 - Length: 4444442 \ No newline at end of file + Length: 4444442 diff --git a/mods/cnc/installer/origin.yaml b/mods/cnc/installer/origin.yaml index 33e3fc7421..4d27d057b3 100644 --- a/mods/cnc/installer/origin.yaml +++ b/mods/cnc/installer/origin.yaml @@ -6,7 +6,7 @@ origin: C&C The Ultimate Collection (Origin version, English) IDFiles: CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30 Install: - copy: . + Copy: . ^SupportDir|Content/cnc/conquer.mix: CONQUER.MIX ^SupportDir|Content/cnc/desert.mix: DESERT.MIX ^SupportDir|Content/cnc/general.mix: GENERAL.MIX @@ -18,7 +18,7 @@ origin: C&C The Ultimate Collection (Origin version, English) ^SupportDir|Content/cnc/tempicnh.mix: TEMPICNH.MIX ^SupportDir|Content/cnc/transit.mix: TRANSIT.MIX ^SupportDir|Content/cnc/scores-covertops.mix: covert/SCORES.MIX - extract-raw: movies.mix + ExtractRaw: movies.mix ^SupportDir|Content/cnc/movies/akira.vqa: Offset: 4445708 Length: 7803444 diff --git a/mods/d2k/installer/d2k-a.yaml b/mods/d2k/installer/d2k-a.yaml index 3773437fcc..fac3e22118 100644 --- a/mods/d2k/installer/d2k-a.yaml +++ b/mods/d2k/installer/d2k-a.yaml @@ -3,7 +3,7 @@ d2k-a: Dune 2000 (English) MUSIC/AMBUSH.AUD: bd5926a56a83bc0e49f96037e1f909014ac0772a SETUP/SETUP.Z: 937f5ceb1236bf3f3d4e43929305ffe5004078e7 Install: - copy: MOVIES + Copy: MOVIES ^SupportDir|Content/d2k/v2/Movies/A_BR01_E.VQA: A_BR01_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR02_E.VQA: A_BR02_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR03_E.VQA: A_BR03_E.VQA @@ -46,7 +46,7 @@ d2k-a: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Movies/G_PLN2_E.VQA: G_PLN2_E.VQA ^SupportDir|Content/d2k/v2/Movies/G_PLNT_E.VQA: G_PLNT_E.VQA ^SupportDir|Content/d2k/v2/Movies/T_TITL_E.VQA: T_TITL_E.VQA - copy: MUSIC + Copy: MUSIC ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -64,7 +64,7 @@ d2k-a: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - extract-blast: SETUP/SETUP.Z + ExtractBlast: SETUP/SETUP.Z ^SupportDir|Content/d2k/v2/BLOXBAT.R8: Offset: 1156652 Length: 512750 diff --git a/mods/d2k/installer/d2k-b.yaml b/mods/d2k/installer/d2k-b.yaml index f0628124d0..08c80af18f 100644 --- a/mods/d2k/installer/d2k-b.yaml +++ b/mods/d2k/installer/d2k-b.yaml @@ -3,7 +3,7 @@ d2k-b: Dune 2000 (English) MUSIC/AMBUSH.AUD: bd5926a56a83bc0e49f96037e1f909014ac0772a SETUP/SETUP.Z: 029722e70fb7636f8120028f5c9b6ce81627ff90 Install: - copy: MOVIES + Copy: MOVIES ^SupportDir|Content/d2k/v2/Movies/A_BR01_E.VQA: A_BR01_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR02_E.VQA: A_BR02_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR03_E.VQA: A_BR03_E.VQA @@ -46,7 +46,7 @@ d2k-b: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Movies/G_PLN2_E.VQA: G_PLN2_E.VQA ^SupportDir|Content/d2k/v2/Movies/G_PLNT_E.VQA: G_PLNT_E.VQA ^SupportDir|Content/d2k/v2/Movies/T_TITL_E.VQA: T_TITL_E.VQA - copy: MUSIC + Copy: MUSIC ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -64,7 +64,7 @@ d2k-b: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - extract-blast: SETUP/SETUP.Z + ExtractBlast: SETUP/SETUP.Z ^SupportDir|Content/d2k/v2/BLOXBAT.R8: Offset: 1156877 Length: 512750 diff --git a/mods/d2k/installer/d2k-c.yaml b/mods/d2k/installer/d2k-c.yaml index 5b5a718727..2977ac256b 100644 --- a/mods/d2k/installer/d2k-c.yaml +++ b/mods/d2k/installer/d2k-c.yaml @@ -3,7 +3,7 @@ d2k-c: Dune 2000 (English) MUSIC/AMBUSH.AUD: bd5926a56a83bc0e49f96037e1f909014ac0772a SETUP/SETUP.Z: d939b39bdbc952b259ce2b45c0bbedefa534b7f2 Install: - copy: MOVIES + Copy: MOVIES ^SupportDir|Content/d2k/v2/Movies/A_BR01_E.VQA: A_BR01_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR02_E.VQA: A_BR02_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR03_E.VQA: A_BR03_E.VQA @@ -46,7 +46,7 @@ d2k-c: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Movies/G_PLN2_E.VQA: G_PLN2_E.VQA ^SupportDir|Content/d2k/v2/Movies/G_PLNT_E.VQA: G_PLNT_E.VQA ^SupportDir|Content/d2k/v2/Movies/T_TITL_E.VQA: T_TITL_E.VQA - copy: MUSIC + Copy: MUSIC ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -64,7 +64,7 @@ d2k-c: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - extract-blast: SETUP/SETUP.Z + ExtractBlast: SETUP/SETUP.Z ^SupportDir|Content/d2k/v2/BLOXBAT.R8: Offset: 704502 Length: 512750 diff --git a/mods/d2k/installer/d2k-d.yaml b/mods/d2k/installer/d2k-d.yaml index 6a41268c0a..b576b41e04 100644 --- a/mods/d2k/installer/d2k-d.yaml +++ b/mods/d2k/installer/d2k-d.yaml @@ -3,7 +3,7 @@ d2k-d: Dune 2000 (English) MUSIC/AMBUSH.AUD: bd5926a56a83bc0e49f96037e1f909014ac0772a SETUP/SETUP.Z: 2411cc5df36954ebd534ceafa3007c8aa9232909 Install: - copy: MOVIES + Copy: MOVIES ^SupportDir|Content/d2k/v2/Movies/A_BR01_E.VQA: A_BR01_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR02_E.VQA: A_BR02_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR03_E.VQA: A_BR03_E.VQA @@ -46,7 +46,7 @@ d2k-d: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Movies/G_PLN2_E.VQA: G_PLN2_E.VQA ^SupportDir|Content/d2k/v2/Movies/G_PLNT_E.VQA: G_PLNT_E.VQA ^SupportDir|Content/d2k/v2/Movies/T_TITL_E.VQA: T_TITL_E.VQA - copy: MUSIC + Copy: MUSIC ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -64,7 +64,7 @@ d2k-d: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - extract-blast: SETUP/SETUP.Z + ExtractBlast: SETUP/SETUP.Z ^SupportDir|Content/d2k/v2/BLOXBAT.R8: Offset: 702406 Length: 512750 diff --git a/mods/d2k/installer/d2k-e.yaml b/mods/d2k/installer/d2k-e.yaml index 61a110ffd0..6560f5f9ce 100644 --- a/mods/d2k/installer/d2k-e.yaml +++ b/mods/d2k/installer/d2k-e.yaml @@ -3,7 +3,7 @@ d2k-e: Dune 2000 (English) MUSIC/AMBUSH.AUD: bd5926a56a83bc0e49f96037e1f909014ac0772a SETUP/SETUP.Z: b476661e82eeb05949e97fa1f75fed2343174be5 Install: - copy: MOVIES + Copy: MOVIES ^SupportDir|Content/d2k/v2/Movies/A_BR01_E.VQA: A_BR01_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR02_E.VQA: A_BR02_E.VQA ^SupportDir|Content/d2k/v2/Movies/A_BR03_E.VQA: A_BR03_E.VQA @@ -46,7 +46,7 @@ d2k-e: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Movies/G_PLN2_E.VQA: G_PLN2_E.VQA ^SupportDir|Content/d2k/v2/Movies/G_PLNT_E.VQA: G_PLNT_E.VQA ^SupportDir|Content/d2k/v2/Movies/T_TITL_E.VQA: T_TITL_E.VQA - copy: MUSIC + Copy: MUSIC ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -64,7 +64,7 @@ d2k-e: Dune 2000 (English) ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - extract-blast: SETUP/SETUP.Z + ExtractBlast: SETUP/SETUP.Z ^SupportDir|Content/d2k/v2/BLOXBAT.R8: Offset: 702405 Length: 512750 diff --git a/mods/d2k/installer/gruntmods.yaml b/mods/d2k/installer/gruntmods.yaml index 6a3abf4c57..a40671f491 100644 --- a/mods/d2k/installer/gruntmods.yaml +++ b/mods/d2k/installer/gruntmods.yaml @@ -6,7 +6,7 @@ gruntmods: Dune 2000: GruntMods Edition Dune 2000/data/BLOXXMAS.R8: afc818feda44f5873e3af07bd2191573ba9965db Dune 2000/data/DATA.R8: 2b229cf4be47104a6214237039a55329f6c45bc9 Install: - copy: Dune 2000/data/Music + Copy: Dune 2000/data/Music ^SupportDir|Content/d2k/v2/Music/AMBUSH.AUD: AMBUSH.AUD ^SupportDir|Content/d2k/v2/Music/ARAKATAK.AUD: ARAKATAK.AUD ^SupportDir|Content/d2k/v2/Music/ATREGAIN.AUD: ATREGAIN.AUD @@ -24,7 +24,7 @@ gruntmods: Dune 2000: GruntMods Edition ^SupportDir|Content/d2k/v2/Music/SPICESCT.AUD: SPICESCT.AUD ^SupportDir|Content/d2k/v2/Music/UNDERCON.AUD: UNDERCON.AUD ^SupportDir|Content/d2k/v2/Music/WAITGAME.AUD: WAITGAME.AUD - copy: Dune 2000/data + Copy: Dune 2000/data ^SupportDir|Content/d2k/v2/BLOXBAT.R8: BLOXBAT.R8 ^SupportDir|Content/d2k/v2/BLOXBASE.R8: BLOXBASE.R8 ^SupportDir|Content/d2k/v2/BLOXBGBS.R8: BLOXBGBS.R8 @@ -36,10 +36,10 @@ gruntmods: Dune 2000: GruntMods Edition ^SupportDir|Content/d2k/v2/MOUSE.R8: MOUSE.R8 ^SupportDir|Content/d2k/v2/FONTCOL.FNT: FONTCOL.FNT ^SupportDir|Content/d2k/v2/FONTCOL.FPL: FONTCOL.FPL - copy: Dune 2000/data/bin + Copy: Dune 2000/data/bin ^SupportDir|Content/d2k/v2/PALETTE.BIN: PALETTE.BIN ^SupportDir|Content/d2k/v2/FONT.BIN: FONT.BIN - copy: Dune 2000/data/GAMESFX + Copy: Dune 2000/data/GAMESFX ^SupportDir|Content/d2k/v2/GAMESFX/A_ECONF2.AUD: A_ECONF2.AUD ^SupportDir|Content/d2k/v2/GAMESFX/A_ECONF1.AUD: A_ECONF1.AUD ^SupportDir|Content/d2k/v2/GAMESFX/A_ECONF3.AUD: A_ECONF3.AUD diff --git a/mods/ra/installer/aftermath.yaml b/mods/ra/installer/aftermath.yaml index e8851ddae0..e67d5e7f34 100644 --- a/mods/ra/installer/aftermath.yaml +++ b/mods/ra/installer/aftermath.yaml @@ -3,7 +3,7 @@ aftermath: Aftermath Expansion Disc (English) README.TXT: 9902fb74c019df1b76ff5634e68f0371d790b5e0 SETUP/INSTALL/PATCH.RTP: 5bce93f834f9322ddaa7233242e5b6c7fea0bf17 Install: - extract-raw: SETUP/INSTALL/PATCH.RTP + ExtractRaw: SETUP/INSTALL/PATCH.RTP ^SupportDir|Content/ra/v2/expand/expand2.mix: Offset: 4712984 Length: 469922 @@ -13,7 +13,7 @@ aftermath: Aftermath Expansion Disc (English) ^SupportDir|Content/ra/v2/expand/lores1.mix: Offset: 5273320 Length: 57076 - extract-raw: MAIN.MIX + ExtractRaw: MAIN.MIX ^SupportDir|Content/ra/v2/expand/await.aud: Offset: 158698809 Length: 2972788 diff --git a/mods/ra/installer/allies95.yaml b/mods/ra/installer/allies95.yaml index 6772e4f45c..fdf3762035 100644 --- a/mods/ra/installer/allies95.yaml +++ b/mods/ra/installer/allies95.yaml @@ -4,7 +4,7 @@ allied: Red Alert 95 (Allied Disc, English) Length: 4096 INSTALL/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef Install: - extract-raw: INSTALL/REDALERT.MIX + ExtractRaw: INSTALL/REDALERT.MIX ^SupportDir|Content/ra/v2/hires.mix: Offset: 650612 Length: 5817417 @@ -17,7 +17,7 @@ allied: Red Alert 95 (Allied Disc, English) ^SupportDir|Content/ra/v2/speech.mix: Offset: 23042864 Length: 2003464 - extract-raw: MAIN.MIX + ExtractRaw: MAIN.MIX ^SupportDir|Content/ra/v2/conquer.mix: Offset: 236 Length: 2177047 diff --git a/mods/ra/installer/cnc95.yaml b/mods/ra/installer/cnc95.yaml index aeb125b5b6..7348a02b6c 100644 --- a/mods/ra/installer/cnc95.yaml +++ b/mods/ra/installer/cnc95.yaml @@ -2,5 +2,5 @@ cnc95: C&C Gold (GDI or Nod Disc, English) IDFiles: CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30 Install: - copy: . + Copy: . ^SupportDir|Content/ra/v2/cnc/desert.mix: DESERT.MIX diff --git a/mods/ra/installer/counterstrike.yaml b/mods/ra/installer/counterstrike.yaml index 474c098670..e7aa48efc1 100644 --- a/mods/ra/installer/counterstrike.yaml +++ b/mods/ra/installer/counterstrike.yaml @@ -3,7 +3,7 @@ counterstrike: Counterstrike Expansion Disc (English) README.TXT: 0efe8087383f0b159a9633f891fb5f53c6097cd4 SETUP/INSTALL/CSTRIKE.RTP: fae8ba82db71574f6ecd8fb4ff4026fcb65d2adc Install: - extract-raw: MAIN.MIX + ExtractRaw: MAIN.MIX ^SupportDir|Content/ra/v2/expand/2nd_hand.aud: Offset: 209070947 Length: 3070092 diff --git a/mods/ra/installer/firstdecade.yaml b/mods/ra/installer/firstdecade.yaml index c677718c23..dcffa25229 100644 --- a/mods/ra/installer/firstdecade.yaml +++ b/mods/ra/installer/firstdecade.yaml @@ -3,7 +3,7 @@ tfd: C&C The First Decade (English) data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 Install: - extract-iscab: data1.hdr + ExtractIscab: data1.hdr Volumes: 2: data2.cab 3: data3.cab @@ -16,7 +16,7 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ra/v2/expand/lores1.mix: Red Alert\\LORES1.MIX ^SupportDir|Content/ra/v2/expand/expand2.mix: Red Alert\\EXPAND2.MIX ^SupportDir|Content/ra/v2/cnc/desert.mix: CnC\\DESERT.MIX - extract-raw: ^SupportDir|Content/ra/v2/redalert.mix + ExtractRaw: ^SupportDir|Content/ra/v2/redalert.mix ^SupportDir|Content/ra/v2/hires.mix: Offset: 650612 Length: 5817417 @@ -29,8 +29,8 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ra/v2/speech.mix: Offset: 23042864 Length: 2003464 - delete: ^SupportDir|Content/ra/v2/redalert.mix - extract-raw: ^SupportDir|Content/ra/v2/main.mix + Delete: ^SupportDir|Content/ra/v2/redalert.mix + ExtractRaw: ^SupportDir|Content/ra/v2/main.mix ^SupportDir|Content/ra/v2/movies/aagun.vqa: Offset: 668669829 Length: 3295512 @@ -445,4 +445,4 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ra/v2/expand/myes1.aud: Offset: 844509702 Length: 9073 - delete: ^SupportDir|Content/ra/v2/main.mix \ No newline at end of file + Delete: ^SupportDir|Content/ra/v2/main.mix diff --git a/mods/ra/installer/origin.yaml b/mods/ra/installer/origin.yaml index e097b240f7..2b993cfc34 100644 --- a/mods/ra/installer/origin.yaml +++ b/mods/ra/installer/origin.yaml @@ -6,7 +6,7 @@ ra-origin: C&C The Ultimate Collection (Origin version, English) IDFiles: REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef Install: - copy: . + Copy: . ^SupportDir|Content/ra/v2/expand/2nd_hand.aud: 2nd_hand.aud ^SupportDir|Content/ra/v2/expand/araziod.aud: araziod.aud ^SupportDir|Content/ra/v2/expand/await.aud: await.aud @@ -27,7 +27,7 @@ ra-origin: C&C The Ultimate Collection (Origin version, English) ^SupportDir|Content/ra/v2/expand/expand2.mix: EXPAND2.MIX ^SupportDir|Content/ra/v2/expand/hires1.mix: HIRES1.MIX ^SupportDir|Content/ra/v2/expand/lores1.mix: LORES1.MIX - extract-raw: REDALERT.MIX + ExtractRaw: REDALERT.MIX ^SupportDir|Content/ra/v2/hires.mix: Offset: 650612 Length: 5817417 @@ -40,7 +40,7 @@ ra-origin: C&C The Ultimate Collection (Origin version, English) ^SupportDir|Content/ra/v2/speech.mix: Offset: 23042864 Length: 2003464 - extract-raw: MAIN.MIX + ExtractRaw: MAIN.MIX ^SupportDir|Content/ra/v2/movies/aagun.vqa: Offset: 668669829 Length: 3295512 @@ -464,5 +464,5 @@ cnc-origin: Command & Conquer (Origin version, English) IDFiles: CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30 Install: - copy: . + Copy: . ^SupportDir|Content/ra/v2/cnc/desert.mix: DESERT.MIX diff --git a/mods/ra/installer/soviet95.yaml b/mods/ra/installer/soviet95.yaml index e6b227d4fb..09557c3e4e 100644 --- a/mods/ra/installer/soviet95.yaml +++ b/mods/ra/installer/soviet95.yaml @@ -4,7 +4,7 @@ soviet: Red Alert 95 (Soviet Disc, English) Length: 4096 INSTALL/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef Install: - extract-raw: INSTALL/REDALERT.MIX + ExtractRaw: INSTALL/REDALERT.MIX ^SupportDir|Content/ra/v2/hires.mix: Offset: 650612 Length: 5817417 @@ -17,7 +17,7 @@ soviet: Red Alert 95 (Soviet Disc, English) ^SupportDir|Content/ra/v2/speech.mix: Offset: 23042864 Length: 2003464 - extract-raw: MAIN.MIX + ExtractRaw: MAIN.MIX ^SupportDir|Content/ra/v2/conquer.mix: Offset: 236 Length: 2177047 diff --git a/mods/ts/installer/firestorm.yaml b/mods/ts/installer/firestorm.yaml index 31363e577f..5859dcb2ee 100644 --- a/mods/ts/installer/firestorm.yaml +++ b/mods/ts/installer/firestorm.yaml @@ -3,9 +3,9 @@ fstorm: Firestorm Expansion Disc (English) Install/README.TXT: f2810b540fce8f3880250213ee08c57780d81c20 Install/Language.dll: 4df87c1a2289da57dd14d0a7299546f37357fcca Install: - copy: . + Copy: . ^SupportDir|Content/ts/firestorm/scores01.mix: scores01.mix - extract-raw: Install/expand01.mix + ExtractRaw: Install/expand01.mix ^SupportDir|Content/ts/firestorm/m_emp.vxl: Offset: 2187652 Length: 24652 diff --git a/mods/ts/installer/firstdecade.yaml b/mods/ts/installer/firstdecade.yaml index aba678894b..22ec5538ab 100644 --- a/mods/ts/installer/firstdecade.yaml +++ b/mods/ts/installer/firstdecade.yaml @@ -3,7 +3,7 @@ tfd: C&C The First Decade (English) data1.hdr: bef3a08c3fc1b1caf28ca0dbb97c1f900005930e data1.cab: 12ad6113a6890a1b4d5651a75378c963eaf513b9 Install: - extract-iscab: data1.hdr + ExtractIscab: data1.hdr Volumes: 6: data6.cab 7: data7.cab @@ -12,7 +12,7 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ts/tibsun.mix: Tiberian Sun\SUN\TIBSUN.MIX ^SupportDir|Content/ts/expand01.mix: Tiberian Sun\SUN\expand01.mix ^SupportDir|Content/ts/firestorm/scores01.mix: Tiberian Sun\SUN\scores01.mix - extract-raw: ^SupportDir|Content/ts/tibsun.mix + ExtractRaw: ^SupportDir|Content/ts/tibsun.mix ^SupportDir|Content/ts/cache.mix: Offset: 300 Length: 169176 @@ -55,8 +55,8 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ts/temperat.mix: Offset: 73056940 Length: 2037606 - delete: ^SupportDir|Content/ts/tibsun.mix - extract-raw: ^SupportDir|Content/ts/expand01.mix + Delete: ^SupportDir|Content/ts/tibsun.mix + ExtractRaw: ^SupportDir|Content/ts/expand01.mix ^SupportDir|Content/ts/firestorm/m_emp.vxl: Offset: 2187652 Length: 24652 @@ -591,4 +591,4 @@ tfd: C&C The First Decade (English) ^SupportDir|Content/ts/firestorm/sounds01.mix: Offset: 28229956 Length: 990096 - delete: ^SupportDir|Content/ts/expand01.mix + Delete: ^SupportDir|Content/ts/expand01.mix diff --git a/mods/ts/installer/origin.yaml b/mods/ts/installer/origin.yaml index 2f1d867564..7207c22601 100644 --- a/mods/ts/installer/origin.yaml +++ b/mods/ts/installer/origin.yaml @@ -6,10 +6,10 @@ origin: C&C The Ultimate Collection (Origin version, English) IDFiles: GDFBinary_en_US.dll: 4bb56a449bd0003e7ae67625d90a11ae169319d6 Install: - copy: . + Copy: . ^SupportDir|Content/ts/scores.mix: SCORES.MIX ^SupportDir|Content/ts/firestorm/scores01.mix: scores01.mix - extract-raw: TIBSUN.MIX + ExtractRaw: TIBSUN.MIX ^SupportDir|Content/ts/cache.mix: Offset: 300 Length: 169176 @@ -52,7 +52,7 @@ origin: C&C The Ultimate Collection (Origin version, English) ^SupportDir|Content/ts/temperat.mix: Offset: 73056940 Length: 2037606 - extract-raw: expand01.mix + ExtractRaw: expand01.mix ^SupportDir|Content/ts/firestorm/m_emp.vxl: Offset: 2187652 Length: 24652 diff --git a/mods/ts/installer/tibsun.yaml b/mods/ts/installer/tibsun.yaml index d71547cfd3..27dbb758cb 100644 --- a/mods/ts/installer/tibsun.yaml +++ b/mods/ts/installer/tibsun.yaml @@ -3,9 +3,9 @@ tibsun: Tiberian Sun (GDI or Nod Disc, English) README.TXT: 45745c4a0c888317ec900208a426472779c42bf7 AUTOPLAY.WAV: 2dfce5d00f98b641849c29942b651f4e98d30e30 Install: - copy: . + Copy: . ^SupportDir|Content/ts/scores.mix: SCORES.MIX - extract-raw: INSTALL/TIBSUN.MIX + ExtractRaw: INSTALL/TIBSUN.MIX ^SupportDir|Content/ts/cache.mix: Offset: 300 Length: 169176