Initial commit: OpenRA game engine
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled

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:
let5sne.win10
2026-01-10 21:46:54 +08:00
commit 9cf6ebb986
4065 changed files with 635973 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
#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.Collections.Generic;
using System.Collections.Immutable;
namespace OpenRA.Mods.Common.FileSystem
{
[Desc("A file system that loads game assets installed by the user into their support directory.")]
public class ContentInstallerFileSystemLoader : IFileSystemLoader, IFileSystemExternalContent
{
[FieldLoader.Require]
[Desc("Mod to use for content installation.")]
public readonly string ContentInstallerMod = null;
[Desc("A list of mod-provided packages. Anything required to display the initial load screen must be listed here.")]
[FieldLoader.LoadUsing(nameof(LoadSystemPackages))]
public readonly ImmutableArray<KeyValuePair<string, string>> SystemPackages = default;
[Desc("A list of user-installed packages. If missing (and not marked as optional), these will trigger the content installer.")]
[FieldLoader.LoadUsing(nameof(LoadContentPackages))]
public readonly ImmutableArray<KeyValuePair<string, string>> ContentPackages = default;
[Desc("Files that aren't mounted as packages, but still need to trigger the content installer if missing.")]
[FieldLoader.LoadUsing(nameof(LoadRequiredContentFiles))]
public readonly ImmutableArray<KeyValuePair<string, string>> RequiredContentFiles = default;
bool isContentAvailable = true;
static object LoadSystemPackages(MiniYaml yaml)
{
return LoadPackages(yaml, nameof(SystemPackages), true);
}
static object LoadContentPackages(MiniYaml yaml)
{
return LoadPackages(yaml, nameof(ContentPackages), false);
}
static object LoadRequiredContentFiles(MiniYaml yaml)
{
return LoadPackages(yaml, nameof(RequiredContentFiles), false);
}
static object LoadPackages(MiniYaml yaml, string key, bool required)
{
var packageNode = yaml.NodeWithKeyOrDefault(key);
if (packageNode == null)
{
if (required)
throw new FieldLoader.MissingFieldsException([key]);
return default(ImmutableArray<KeyValuePair<string, string>>);
}
var packages = new List<KeyValuePair<string, string>>(packageNode.Value.Nodes.Length);
foreach (var node in packageNode.Value.Nodes)
packages.Add(KeyValuePair.Create(node.Key, node.Value.Value));
return packages.ToImmutableArray();
}
public void Mount(Manifest manifest, OpenRA.FileSystem.FileSystem fileSystem, ObjectCreator objectCreator)
{
foreach (var kv in SystemPackages)
fileSystem.Mount(kv.Key, kv.Value);
if (ContentPackages != null)
{
foreach (var kv in ContentPackages)
{
try
{
fileSystem.Mount(kv.Key, kv.Value);
}
catch
{
isContentAvailable = false;
}
}
}
if (RequiredContentFiles != null)
foreach (var kv in RequiredContentFiles)
if (!fileSystem.Exists(kv.Key))
isContentAvailable = false;
}
bool IFileSystemExternalContent.InstallContentIfRequired(ModData modData)
{
if (!isContentAvailable && Game.Mods.TryGetValue(ContentInstallerMod, out var mod))
Game.InitializeMod(mod, new Arguments());
return !isContentAvailable;
}
void IFileSystemExternalContent.ManageContent(ModData modData)
{
// Switching mods changes the world state (by disposing it),
// so we can't do this inside the input handler.
if (Game.Mods.TryGetValue(ContentInstallerMod, out var mod))
Game.RunAfterTick(() => Game.InitializeMod(mod, new Arguments()));
}
}
}

View File

@@ -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.Collections.Generic;
using System.Collections.Immutable;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.FileSystem
{
[RequireExplicitImplementation]
public interface IFileSystemExternalContent
{
bool InstallContentIfRequired(ModData modData);
void ManageContent(ModData modData);
}
public class DefaultFileSystemLoader : IFileSystemLoader
{
[FieldLoader.LoadUsing(nameof(LoadPackages))]
public readonly ImmutableArray<KeyValuePair<string, string>> Packages = default;
static object LoadPackages(MiniYaml yaml)
{
var packageNode = yaml.NodeWithKeyOrDefault(nameof(Packages));
if (packageNode == null)
return default(ImmutableArray<KeyValuePair<string, string>>);
var packages = new List<KeyValuePair<string, string>>(packageNode.Value.Nodes.Length);
foreach (var node in packageNode.Value.Nodes)
packages.Add(KeyValuePair.Create(node.Key, node.Value.Value));
return packages.ToImmutableArray();
}
public void Mount(Manifest manifest, OpenRA.FileSystem.FileSystem fileSystem, ObjectCreator objectCreator)
{
if (Packages != null)
foreach (var kv in Packages)
fileSystem.Mount(kv.Key, kv.Value);
}
}
}

View File

@@ -0,0 +1,221 @@
#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.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using OpenRA.FileSystem;
using OpenRA.Primitives;
using FS = OpenRA.FileSystem.FileSystem;
namespace OpenRA.Mods.Common.FileSystem
{
public class ISO9660Loader : IPackageLoader
{
public sealed class ISO9660Package : IReadOnlyPackage
{
public readonly record struct Entry(uint Offset, uint Length);
public string Name { get; }
public string VolumeName { get; }
public IEnumerable<string> Contents => index.Keys;
readonly Dictionary<string, Entry> index = [];
readonly Stream s;
readonly record struct DirectoryRecord(string Name, uint Offset, uint Length, bool IsDirectory);
static bool TryReadDirectoryRecord(Stream s, out DirectoryRecord record, bool isJoliet = false)
{
var start = s.Position;
var recordLength = s.ReadUInt8();
if (recordLength == 0)
{
s.Position = start;
record = default;
return false;
}
s.Position++;
var location = s.ReadUInt32();
s.Position += 4;
var length = s.ReadUInt32();
s.Position += 11;
var flags = s.ReadUInt8();
s.Position += 6;
var identifierLength = s.ReadUInt8();
var buffer = identifierLength < 128 ? stackalloc byte[identifierLength] : new byte[identifierLength];
s.ReadBytes(buffer);
var identifier = (isJoliet ? Encoding.BigEndianUnicode : Encoding.ASCII).GetString(buffer);
s.Position = start + recordLength;
record = new DirectoryRecord(identifier.Split(';')[0], 2048 * location, length, (flags & 2) != 0);
return true;
}
void EnumerateDirectories(DirectoryRecord parent, string path = null, bool isJoliet = false)
{
var pos = s.Position;
s.Position = parent.Offset;
// Skip . and .. records
TryReadDirectoryRecord(s, out _);
TryReadDirectoryRecord(s, out _);
while (s.Position < parent.Offset + parent.Length)
{
if (!TryReadDirectoryRecord(s, out var child, isJoliet))
break;
var childPath = path != null ? $"{path}/{child.Name}" : child.Name;
if (child.IsDirectory)
EnumerateDirectories(child, childPath, isJoliet);
else
index[childPath] = new Entry(child.Offset, child.Length);
}
s.Position = pos;
}
public ISO9660Package(Stream s, string filename)
{
Name = filename;
this.s = s;
try
{
var complete = false;
// Skip system area
s.Position = 32768;
// Parse volume descriptors
while (!complete && s.Position < s.Length)
{
var start = s.Position;
var vdType = s.ReadUInt8();
var vdIdentifier = s.ReadASCII(5);
if (vdIdentifier != "CD001")
throw new InvalidDataException("Invalid volume descriptor");
switch (vdType)
{
// Terminator
case 0xFF:
complete = true;
break;
// Primary volume descriptor
case 0x01:
{
s.Position = start + 40;
VolumeName = s.ReadASCII(32).Trim();
s.Position = start + 156;
TryReadDirectoryRecord(s, out var root);
EnumerateDirectories(root);
break;
}
// Supplementary volume descriptor
case 0x02:
{
s.Position = start + 7;
var volumeFlags = s.ReadUInt8();
s.Position = start + 88;
var escape = s.ReadASCII(32).Trim('\x00');
// Joliet extension
if (volumeFlags == 0 && escape is "%/@" or "%/C" or "%/E")
{
s.Position = start + 156;
TryReadDirectoryRecord(s, out var root, isJoliet: true);
EnumerateDirectories(root, isJoliet: true);
}
break;
}
}
s.Position = start + 2048;
}
index.TrimExcess();
}
catch
{
Dispose();
throw;
}
}
public Stream GetStream(string filename)
{
if (!index.TryGetValue(filename, out var entry))
return null;
return SegmentStream.CreateWithoutOwningStream(s, entry.Offset, (int)entry.Length);
}
public IReadOnlyPackage OpenPackage(string filename, FS context)
{
var childStream = GetStream(filename);
if (childStream == null)
return null;
if (context.TryParsePackage(childStream, filename, out var package))
return package;
childStream.Dispose();
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
}
public IReadOnlyDictionary<string, Entry> Index => new ReadOnlyDictionary<string, Entry>(index);
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
{
if (s.Length < 34816)
{
package = null;
return false;
}
// Check the volume descriptor
var pos = s.Position;
s.Position = 32769;
var identifier = s.ReadASCII(5);
s.Position = pos;
if (identifier != "CD001")
{
package = null;
return false;
}
package = new ISO9660Package(s, filename);
return true;
}
}
}

View File

@@ -0,0 +1,162 @@
#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.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using OpenRA.FileSystem;
using OpenRA.Mods.Common.FileFormats;
using FS = OpenRA.FileSystem.FileSystem;
namespace OpenRA.Mods.Common.FileSystem
{
public class InstallShieldLoader : IPackageLoader
{
public sealed class InstallShieldPackage : IReadOnlyPackage
{
public readonly record struct Entry(uint Offset, uint Length);
public string Name { get; }
public IEnumerable<string> Contents => index.Keys;
readonly Dictionary<string, Entry> index;
readonly Stream s;
readonly long dataStart = 255;
public InstallShieldPackage(Stream s, string filename)
{
Name = filename;
this.s = s;
try
{
// Parse package header
s.ReadUInt32(); // signature
s.Position += 8;
s.ReadUInt16(); // FileCount
s.Position += 4;
s.ReadUInt32(); // ArchiveSize
s.Position += 19;
var tocAddress = s.ReadInt32();
s.Position += 4;
var dirCount = s.ReadUInt16();
// Parse the directory list
s.Position = tocAddress;
// Parse directories
var directories = new Dictionary<string, uint>(dirCount);
var totalFileCount = 0;
for (var i = 0; i < dirCount; i++)
{
// Parse directory header
var fileCount = s.ReadUInt16();
var chunkSize = s.ReadUInt16();
var nameLength = s.ReadUInt16();
var dirName = s.ReadASCII(nameLength);
// Skip to the end of the chunk
s.Position += chunkSize - nameLength - 6;
directories.Add(dirName, fileCount);
totalFileCount += fileCount;
}
// Parse files
index = new Dictionary<string, Entry>(totalFileCount);
foreach (var dir in directories)
for (var i = 0; i < dir.Value; i++)
ParseFile(dir.Key);
index.TrimExcess();
}
catch
{
Dispose();
throw;
}
}
uint accumulatedData = 0;
void ParseFile(string dirName)
{
s.Position += 7;
var compressedSize = s.ReadUInt32();
s.Position += 12;
var chunkSize = s.ReadUInt16();
s.Position += 4;
var nameLength = s.ReadUInt8();
var fileName = dirName + "\\" + s.ReadASCII(nameLength);
// Use index syntax to overwrite any duplicate entries with the last value
index[fileName] = new Entry(accumulatedData, compressedSize);
accumulatedData += compressedSize;
// Skip to the end of the chunk
s.Position += chunkSize - nameLength - 30;
}
public Stream GetStream(string filename)
{
if (!index.TryGetValue(filename, out var e))
return null;
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
var ret = new MemoryStream();
Blast.Decompress(s, ret);
ret.Seek(0, SeekOrigin.Begin);
return ret;
}
public IReadOnlyPackage OpenPackage(string filename, FS context)
{
var childStream = GetStream(filename);
if (childStream == null)
return null;
if (context.TryParsePackage(childStream, filename, out var package))
return package;
childStream.Dispose();
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
}
public IReadOnlyDictionary<string, Entry> Index => new ReadOnlyDictionary<string, Entry>(index);
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package)
{
// Take a peek at the file signature
var signature = s.ReadUInt32();
s.Position -= 4;
if (signature != 0x8C655D13)
{
package = null;
return false;
}
package = new InstallShieldPackage(s, filename);
return true;
}
}
}