Replaced hardcoded source actions by user defined ISourceAction.

This commit is contained in:
IceReaper
2022-11-06 19:23:49 +01:00
committed by Matthias Mailänder
parent fcc8f53b59
commit 7188f88ba1
33 changed files with 546 additions and 385 deletions

View File

@@ -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<string> extracted, Action<string> updateMessage);
}
}

View File

@@ -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<long>("Offset", offsetNode.Value.Value);
var length = fileStream.Length - offset;
if (lengthNode != null)
length = FieldLoader.GetValue<long>("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<long> 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);
}
}
}
}

View File

@@ -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<string> extracted, Action<string> 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<long> 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);
}
}
}
}
}

View File

@@ -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<string> extracted, Action<string> 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);
}
}
}

View File

@@ -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<string> extracted, Action<string> 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<int>("Length", lengthNode.Value.Value);
source.Position = FieldLoader.GetValue<int>("Offset", offsetNode.Value.Value);
extracted.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action<long> 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));
}
}
}
}
}
}

View File

@@ -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<string> extracted, Action<string> 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<int, Stream>();
try
{
foreach (var node in volumeNode.Value.Nodes)
{
var volume = FieldLoader.GetValue<int>("(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<int> 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();
}
}
}
}

View File

@@ -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<string> extracted, Action<string> 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<int> onProgress = percent => updateMessage(modData.Translation.GetString(InstallFromDiscLogic.ExtracingProgress, Translation.Arguments("filename", displayFilename, "progress", percent)));
reader.ExtractFile(node.Value.Value, target, onProgress);
}
}
}
}
}
}

View File

@@ -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<string> extracted, Action<string> 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<int>("Length", lengthNode.Value.Value);
source.Position = FieldLoader.GetValue<int>("Offset", offsetNode.Value.Value);
extracted.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action<long> 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);
}
}
}
}
}
}

View File

@@ -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<MiniYamlNode> 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");

View File

@@ -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<long> 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<ISourceAction>($"{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<long> 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<string> extractedFiles, Action<string> 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<int>("Length", lengthNode.Value.Value);
source.Position = FieldLoader.GetValue<int>("Offset", offsetNode.Value.Value);
extractedFiles.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action<long> 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<string> extractedFiles, Action<string> 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<int> 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<string> extractedFiles, Action<string> 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<int, Stream>();
try
{
foreach (var node in volumeNode.Value.Nodes)
{
var volume = FieldLoader.GetValue<int>("(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<int> 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<string> 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<long>("Offset", offsetNode.Value.Value);
var length = fileStream.Length - offset;
if (lengthNode != null)
length = FieldLoader.GetValue<long>("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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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
Delete: ^SupportDir|Content/cnc/movies.mix

View File

@@ -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

View File

@@ -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
Length: 4444442

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
Delete: ^SupportDir|Content/ra/v2/main.mix

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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