Replaced hardcoded source actions by user defined ISourceAction.
This commit is contained in:
committed by
Matthias Mailänder
parent
fcc8f53b59
commit
7188f88ba1
21
OpenRA.Mods.Common/Installer/ISourceAction.cs
Normal file
21
OpenRA.Mods.Common/Installer/ISourceAction.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
78
OpenRA.Mods.Common/Installer/InstallerUtils.cs
Normal file
78
OpenRA.Mods.Common/Installer/InstallerUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user