Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
20
OpenRA.Mods.Common/Installer/Availability.cs
Normal file
20
OpenRA.Mods.Common/Installer/Availability.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public enum Availability
|
||||
{
|
||||
Unavailable,
|
||||
GameSource,
|
||||
DigitalInstall
|
||||
}
|
||||
}
|
||||
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 (c) The OpenRA Developers and Contributors
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
19
OpenRA.Mods.Common/Installer/ISourceResolver.cs
Normal file
19
OpenRA.Mods.Common/Installer/ISourceResolver.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public interface ISourceResolver
|
||||
{
|
||||
string FindSourcePath(ModContent.ModSource modSource);
|
||||
Availability GetAvailability();
|
||||
}
|
||||
}
|
||||
80
OpenRA.Mods.Common/Installer/InstallerUtils.cs
Normal file
80
OpenRA.Mods.Common/Installer/InstallerUtils.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public static class InstallerUtils
|
||||
{
|
||||
public static bool IsValidSourcePath(string path, ModContent.ModSource source)
|
||||
{
|
||||
if (source.IDFiles == null)
|
||||
return true;
|
||||
|
||||
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.NodeWithKeyOrDefault("Offset");
|
||||
var lengthNode = kv.Value.NodeWithKeyOrDefault("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,59 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 < InstallFromSourceLogic.ShowPercentageThreshold)
|
||||
updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.CopyingFilename,
|
||||
"filename", displayFilename));
|
||||
else
|
||||
onProgress = b => updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.CopyingFilenameProgress,
|
||||
"filename", displayFilename,
|
||||
"progress", 100 * b / length));
|
||||
|
||||
InstallerUtils.CopyStream(source, target, length, onProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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,87 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 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))
|
||||
{
|
||||
source.Position = 12;
|
||||
var numFiles = source.ReadUInt16();
|
||||
source.Position = 51;
|
||||
source.Position = source.ReadUInt32();
|
||||
|
||||
var entries = new Dictionary<string, (uint Length, uint Offset)>();
|
||||
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
{
|
||||
source.Position += 7;
|
||||
var entry = (source.ReadUInt32(), source.ReadUInt32());
|
||||
source.Position += 14;
|
||||
var key = source.ReadASCII(source.ReadByte());
|
||||
source.Position += 13;
|
||||
|
||||
// This does not apply on game relevant data.
|
||||
entries.TryAdd(key, entry);
|
||||
}
|
||||
|
||||
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 (length, offset) = entries[node.Value.Value];
|
||||
|
||||
source.Position = offset;
|
||||
|
||||
extracted.Add(targetPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
|
||||
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
|
||||
|
||||
Action<long> onProgress = null;
|
||||
if (length < InstallFromSourceLogic.ShowPercentageThreshold)
|
||||
updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.Extracting,
|
||||
"filename", displayFilename));
|
||||
else
|
||||
onProgress = b => updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.ExtractingProgress,
|
||||
"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,82 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 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.NodeWithKeyOrDefault("Volumes");
|
||||
if (volumeNode == null)
|
||||
throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node");
|
||||
|
||||
var extractNode = actionYaml.NodeWithKeyOrDefault("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));
|
||||
void OnProgress(int percent) => updateMessage(FluentProvider.GetMessage(
|
||||
InstallFromSourceLogic.ExtractingProgress,
|
||||
"filename", displayFilename, "progress", percent));
|
||||
reader.ExtractFile(node.Value.Value, target, OnProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var kv in volumes)
|
||||
kv.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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));
|
||||
void OnProgress(int percent) => updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.ExtractingProgress,
|
||||
"filename", displayFilename,
|
||||
"progress", percent));
|
||||
reader.ExtractFile(node.Value.Value, target, OnProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 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.NodeWithKeyOrDefault("Offset");
|
||||
if (offsetNode == null)
|
||||
{
|
||||
Log.Write("install", "Skipping entry with missing Offset definition " + targetPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var lengthNode = node.Value.NodeWithKeyOrDefault("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 < InstallFromSourceLogic.ShowPercentageThreshold)
|
||||
updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.Extracting,
|
||||
"filename", displayFilename));
|
||||
else
|
||||
onProgress = b => updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.ExtractingProgress,
|
||||
"filename", displayFilename,
|
||||
"progress", 100 * b / length));
|
||||
|
||||
using (var target = File.OpenWrite(targetPath))
|
||||
{
|
||||
Log.Write("install", $"Extracting {sourcePath} -> {targetPath}");
|
||||
InstallerUtils.CopyStream(source, target, length, onProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 ICSharpCode.SharpZipLib.Zip;
|
||||
using OpenRA.Mods.Common.Widgets.Logic;
|
||||
using FS = OpenRA.FileSystem.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class ExtractZipSourceAction : ISourceAction
|
||||
{
|
||||
public void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List<string> extracted,
|
||||
Action<string> updateMessage)
|
||||
{
|
||||
var zipPath = actionYaml.Value.StartsWith('^')
|
||||
? Platform.ResolvePath(actionYaml.Value)
|
||||
: FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value));
|
||||
|
||||
using (var zipFile = new ZipFile(File.OpenRead(zipPath)))
|
||||
{
|
||||
foreach (var node in actionYaml.Nodes)
|
||||
{
|
||||
var targetPath = Platform.ResolvePath(node.Key);
|
||||
var sourcePath = node.Value.Value;
|
||||
var displayFilename = Path.GetFileName(targetPath);
|
||||
|
||||
if (File.Exists(targetPath))
|
||||
{
|
||||
Log.Write("install", "Skipping installed file " + targetPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Write("install", $"Extracting {sourcePath} -> {targetPath}");
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
|
||||
|
||||
var sourceStream = zipFile.GetInputStream(zipFile.GetEntry(sourcePath));
|
||||
using (var targetStream = File.OpenWrite(targetPath))
|
||||
sourceStream.CopyTo(targetStream);
|
||||
|
||||
updateMessage(FluentProvider.GetMessage(InstallFromSourceLogic.ExtractingProgress,
|
||||
"filename", displayFilename,
|
||||
"progress", 100));
|
||||
|
||||
extracted.Add(targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class DiscSourceResolver : ISourceResolver
|
||||
{
|
||||
public string FindSourcePath(ModContent.ModSource source)
|
||||
{
|
||||
var volumes = DriveInfo.GetDrives()
|
||||
.Where(d =>
|
||||
{
|
||||
if (d.DriveType == DriveType.CDRom && d.IsReady)
|
||||
return true;
|
||||
|
||||
// HACK: the "TFD" DVD is detected as a fixed udf-formatted drive on OSX
|
||||
if (d.DriveType == DriveType.Fixed && d.DriveFormat == "udf")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
})
|
||||
.Select(v => v.RootDirectory.FullName);
|
||||
|
||||
if (Platform.CurrentPlatform == PlatformType.Linux)
|
||||
{
|
||||
// Outside of Gnome, most mounting tools on Linux don't set DriveType.CDRom
|
||||
// so provide a fallback by allowing users to manually mount images on known paths
|
||||
volumes = volumes.Concat(
|
||||
[
|
||||
"/media/openra",
|
||||
"/media/" + Environment.UserName + "/openra",
|
||||
"/mnt/openra"
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (var volume in volumes)
|
||||
if (InstallerUtils.IsValidSourcePath(volume, source))
|
||||
return volume;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Availability GetAvailability()
|
||||
{
|
||||
return Availability.GameSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class GogSourceResolver : ISourceResolver
|
||||
{
|
||||
public string FindSourcePath(ModContent.ModSource modSource)
|
||||
{
|
||||
var appId = modSource.Type.NodeWithKeyOrDefault("AppId");
|
||||
|
||||
if (appId == 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;
|
||||
|
||||
var prefixes = new[] { "HKEY_LOCAL_MACHINE\\Software\\", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\" };
|
||||
|
||||
foreach (var prefix in prefixes)
|
||||
{
|
||||
if (Registry.GetValue($"{prefix}GOG.com\\Games\\{appId.Value.Value}", "path", null) is not string installDir)
|
||||
continue;
|
||||
|
||||
if (InstallerUtils.IsValidSourcePath(installDir, modSource))
|
||||
return installDir;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Availability GetAvailability()
|
||||
{
|
||||
return Platform.CurrentPlatform != PlatformType.Windows ? Availability.DigitalInstall : Availability.Unavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class RegistryDirectoryFromFileSourceResolver : ISourceResolver
|
||||
{
|
||||
public string FindSourcePath(ModContent.ModSource source)
|
||||
{
|
||||
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 not string path)
|
||||
continue;
|
||||
|
||||
path = Path.GetDirectoryName(path);
|
||||
|
||||
return InstallerUtils.IsValidSourcePath(path, source) ? path : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Availability GetAvailability()
|
||||
{
|
||||
return Platform.CurrentPlatform != PlatformType.Windows ? Availability.DigitalInstall : Availability.Unavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class RegistryDirectorySourceResolver : ISourceResolver
|
||||
{
|
||||
public string FindSourcePath(ModContent.ModSource source)
|
||||
{
|
||||
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 not string path)
|
||||
continue;
|
||||
|
||||
// Resolve 8.3 format (DOS-style) paths to the full path.
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
return InstallerUtils.IsValidSourcePath(path, source) ? path : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Availability GetAvailability()
|
||||
{
|
||||
return Platform.CurrentPlatform != PlatformType.Windows ? Availability.DigitalInstall : Availability.Unavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
{
|
||||
public class SteamSourceResolver : ISourceResolver
|
||||
{
|
||||
public string FindSourcePath(ModContent.ModSource modSource)
|
||||
{
|
||||
var appId = modSource.Type.NodeWithKeyOrDefault("AppId");
|
||||
|
||||
if (appId == null)
|
||||
return null;
|
||||
|
||||
foreach (var steamDirectory in SteamDirectory())
|
||||
{
|
||||
var manifestPath = Path.Combine(steamDirectory, "steamapps", $"appmanifest_{appId.Value.Value}.acf");
|
||||
|
||||
if (!File.Exists(manifestPath))
|
||||
continue;
|
||||
|
||||
var data = ParseGameManifest(manifestPath);
|
||||
|
||||
if (!data.TryGetValue("StateFlags", out var stateFlags) || stateFlags != "4")
|
||||
continue;
|
||||
|
||||
if (!data.TryGetValue("installdir", out var installDir))
|
||||
continue;
|
||||
|
||||
if (installDir == null)
|
||||
continue;
|
||||
|
||||
var path = Path.Combine(steamDirectory, "steamapps", "common", installDir);
|
||||
|
||||
if (InstallerUtils.IsValidSourcePath(path, modSource))
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Availability GetAvailability()
|
||||
{
|
||||
return Availability.DigitalInstall;
|
||||
}
|
||||
|
||||
static IEnumerable<string> SteamDirectory()
|
||||
{
|
||||
var candidatePaths = new List<string>();
|
||||
|
||||
switch (Platform.CurrentPlatform)
|
||||
{
|
||||
case PlatformType.Windows:
|
||||
{
|
||||
// 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))
|
||||
break;
|
||||
|
||||
var prefixes = new[] { "HKEY_LOCAL_MACHINE\\Software\\", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\" };
|
||||
|
||||
foreach (var prefix in prefixes)
|
||||
{
|
||||
if (Registry.GetValue($"{prefix}Valve\\Steam", "InstallPath", null) is string path)
|
||||
candidatePaths.Add(path);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformType.OSX:
|
||||
candidatePaths.Add(Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"Library",
|
||||
"Application Support",
|
||||
"Steam"));
|
||||
|
||||
break;
|
||||
|
||||
case PlatformType.Linux:
|
||||
// Direct distro install
|
||||
candidatePaths.Add(Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".steam",
|
||||
"root"));
|
||||
|
||||
// Flatpak installed via Flathub
|
||||
candidatePaths.Add(Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".var",
|
||||
"app",
|
||||
"com.valvesoftware.Steam",
|
||||
".steam",
|
||||
"root"));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var libraryPath in candidatePaths.Where(Directory.Exists))
|
||||
{
|
||||
yield return libraryPath;
|
||||
|
||||
var libraryFoldersPath = Path.Combine(libraryPath, "steamapps", "libraryfolders.vdf");
|
||||
|
||||
if (!File.Exists(libraryFoldersPath))
|
||||
continue;
|
||||
|
||||
foreach (var e in ParseLibraryManifest(libraryFoldersPath).Where(e => e.Item1 == "path"))
|
||||
yield return Unescape(e.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
static string Unescape(string path)
|
||||
{
|
||||
return path.Replace(@"\\", @"\");
|
||||
}
|
||||
|
||||
static Dictionary<string, string> ParseGameManifest(string path)
|
||||
{
|
||||
var regex = new Regex("^\\s*\"(?<key>[^\"]*)\"\\s*\"(?<value>[^\"]*)\"\\s*$");
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
using (var s = new FileStream(path, FileMode.Open))
|
||||
{
|
||||
foreach (var line in s.ReadAllLines())
|
||||
{
|
||||
var match = regex.Match(line);
|
||||
|
||||
if (match.Success)
|
||||
result[match.Groups["key"].Value] = match.Groups["value"].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<Tuple<string, string>> ParseLibraryManifest(string path)
|
||||
{
|
||||
var regex = new Regex("^\\s*\"(?<key>[^\"]*)\"\\s*\"(?<value>[^\"]*)\"\\s*$");
|
||||
var result = new List<Tuple<string, string>>();
|
||||
|
||||
using (var s = new FileStream(path, FileMode.Open))
|
||||
{
|
||||
foreach (var line in s.ReadAllLines())
|
||||
{
|
||||
var match = regex.Match(line);
|
||||
|
||||
if (match.Success)
|
||||
result.Add(new Tuple<string, string>(match.Groups["key"].Value, match.Groups["value"].Value));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user