Merge pull request #11375 from pchote/asset-installer-rework

New mod content installer
This commit is contained in:
Oliver Brakmann
2016-06-15 22:53:42 +02:00
committed by GitHub
43 changed files with 4514 additions and 1440 deletions

View File

@@ -1,41 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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.Collections.Generic;
namespace OpenRA
{
// Referenced from ModMetadata, so needs to be in OpenRA.Game :(
public class ContentInstaller : IGlobalModData
{
public enum FilenameCase { Input, ForceLower, ForceUpper }
public readonly string[] TestFiles = { };
public readonly string[] DiskTestFiles = { };
public readonly string PackageToExtractFromCD = null;
public readonly bool OverwriteFiles = true;
public readonly FilenameCase OutputFilenameCase = FilenameCase.ForceLower;
public readonly Dictionary<string, string[]> CopyFilesFromCD = new Dictionary<string, string[]>();
public readonly Dictionary<string, string[]> ExtractFilesFromCD = new Dictionary<string, string[]>();
public readonly string PackageMirrorList = null;
public readonly string MusicPackageMirrorList = null;
public readonly int ShippedSoundtracks = 0;
/// <summary> InstallShield .CAB file IDs, used to extract Mod-specific files. </summary>
public readonly HashSet<int> InstallShieldCABFileIds = new HashSet<int>();
/// <summary> InstallShield .CAB file IDs, used to extract Mod-specific archives and extract contents of ExtractFilesFromCD. </summary>
public readonly HashSet<string> InstallShieldCABFilePackageIds = new HashSet<string>();
}
}

View File

@@ -18,7 +18,7 @@ namespace OpenRA.FileSystem
{
public sealed class InstallShieldPackage : IReadOnlyPackage
{
struct Entry
public struct Entry
{
public readonly uint Offset;
public readonly uint Length;
@@ -124,6 +124,8 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename);
}
public IReadOnlyDictionary<string, Entry> Index { get { return new ReadOnlyDictionary<string, Entry>(index); } }
public void Dispose()
{
s.Dispose();

View File

@@ -205,6 +205,15 @@ namespace OpenRA.FileSystem
return GetContent(e);
}
public IReadOnlyDictionary<string, PackageEntry> Index
{
get
{
var absoluteIndex = index.ToDictionary(e => e.Key, e => new PackageEntry(e.Value.Hash, (uint)(e.Value.Offset + dataStart), e.Value.Length));
return new ReadOnlyDictionary<string, PackageEntry>(absoluteIndex);
}
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);

View File

@@ -344,8 +344,10 @@ namespace OpenRA
using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps();
var installData = ModData.Manifest.Get<ContentInstaller>();
var isModContentInstalled = installData.TestFiles.All(f => File.Exists(Platform.ResolvePath(f)));
var content = ModData.Manifest.Get<ModContent>();
var isModContentInstalled = content.Packages
.Where(p => p.Value.Required)
.All(p => p.Value.TestFiles.All(f => File.Exists(Platform.ResolvePath(f))));
// Mod assets are missing!
if (!isModContentInstalled)

118
OpenRA.Game/ModContent.cs Normal file
View File

@@ -0,0 +1,118 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA
{
public class ModContent : IGlobalModData
{
public class ModPackage
{
public readonly string Title;
public readonly string[] TestFiles = { };
public readonly string[] Discs = { };
public readonly bool Required;
public readonly string Download;
public ModPackage(MiniYaml yaml)
{
Title = yaml.Value;
FieldLoader.Load(this, yaml);
}
public bool IsInstalled()
{
return TestFiles.All(file => File.Exists(Path.GetFullPath(Platform.ResolvePath(file))));
}
}
public class ModDisc
{
public readonly string Title;
public readonly Dictionary<string, string> IDFiles;
[FieldLoader.Ignore] public readonly List<MiniYamlNode> Install;
public ModDisc(MiniYaml yaml)
{
Title = yaml.Value;
var installNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Install");
if (installNode != null)
Install = installNode.Value.Nodes;
FieldLoader.Load(this, yaml);
}
}
public class ModDownload
{
public readonly string Title;
public readonly string URL;
public readonly string MirrorList;
public readonly Dictionary<string, string> Extract;
public ModDownload(MiniYaml yaml)
{
Title = yaml.Value;
FieldLoader.Load(this, yaml);
}
}
public readonly string InstallPromptMessage;
public readonly string QuickDownload;
public readonly string HeaderMessage;
[FieldLoader.LoadUsing("LoadPackages")]
public readonly Dictionary<string, ModPackage> Packages = new Dictionary<string, ModPackage>();
static object LoadPackages(MiniYaml yaml)
{
var packages = new Dictionary<string, ModPackage>();
var packageNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Packages");
if (packageNode != null)
foreach (var node in packageNode.Value.Nodes)
packages.Add(node.Key, new ModPackage(node.Value));
return packages;
}
[FieldLoader.LoadUsing("LoadDownloads")]
public readonly Dictionary<string, ModDownload> Downloads;
static object LoadDownloads(MiniYaml yaml)
{
var downloads = new Dictionary<string, ModDownload>();
var downloadNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Downloads");
if (downloadNode != null)
foreach (var node in downloadNode.Value.Nodes)
downloads.Add(node.Key, new ModDownload(node.Value));
return downloads;
}
[FieldLoader.LoadUsing("LoadDiscs")]
public readonly Dictionary<string, ModDisc> Discs;
static object LoadDiscs(MiniYaml yaml)
{
var discs = new Dictionary<string, ModDisc>();
var discNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Discs");
if (discNode != null)
foreach (var node in discNode.Value.Nodes)
discs.Add(node.Key, new ModDisc(node.Value));
return discs;
}
}
}

View File

@@ -30,7 +30,7 @@ namespace OpenRA
public bool Hidden;
public Dictionary<string, string> RequiresMods;
public ContentInstaller Content;
public ModContent ModContent;
public IReadOnlyPackage Package;
static Dictionary<string, ModMetadata> ValidateMods()
@@ -78,8 +78,8 @@ namespace OpenRA
else
metadata.RequiresMods = new Dictionary<string, string>();
if (nd.ContainsKey("ContentInstaller"))
metadata.Content = FieldLoader.Load<ContentInstaller>(nd["ContentInstaller"]);
if (nd.ContainsKey("ModContent"))
metadata.ModContent = FieldLoader.Load<ModContent>(nd["ModContent"]);
// Mods in the support directory and oramod packages (which are listed later
// in the CandidateMods list) override mods in the main install.

View File

@@ -103,7 +103,6 @@
<Compile Include="FileSystem\BagFile.cs" />
<Compile Include="Map\MapGrid.cs" />
<Compile Include="Map\MapPlayers.cs" />
<Compile Include="ContentInstaller.cs" />
<Compile Include="MPos.cs" />
<Compile Include="Download.cs" />
<Compile Include="Effects\AsyncAction.cs" />
@@ -249,6 +248,7 @@
<Compile Include="Traits\ActivityUtils.cs" />
<Compile Include="FileSystem\ZipFolder.cs" />
<Compile Include="Primitives\float3.cs" />
<Compile Include="ModContent.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" />

View File

@@ -33,9 +33,23 @@ namespace OpenRA.Widgets
public static void CloseWindow()
{
if (WindowList.Count > 0)
Root.RemoveChild(WindowList.Pop());
{
var hidden = WindowList.Pop();
Root.RemoveChild(hidden);
if (hidden.LogicObjects != null)
foreach (var l in hidden.LogicObjects)
l.BecameHidden();
}
if (WindowList.Count > 0)
Root.AddChild(WindowList.Peek());
{
var restore = WindowList.Peek();
Root.AddChild(restore);
if (restore.LogicObjects != null)
foreach (var l in restore.LogicObjects)
l.BecameVisible();
}
}
public static Widget OpenWindow(string id)
@@ -148,6 +162,8 @@ namespace OpenRA.Widgets
{
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
public virtual void Tick() { }
public virtual void BecameHidden() { }
public virtual void BecameVisible() { }
protected virtual void Dispose(bool disposing) { }
}

View File

@@ -0,0 +1,131 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
namespace OpenRA.Mods.Common.FileFormats
{
public sealed class MSCabCompression
{
class CabFolder
{
public readonly uint BlockOffset;
public readonly ushort BlockCount;
public readonly ushort CompressionType;
public CabFolder(Stream stream)
{
BlockOffset = stream.ReadUInt32();
BlockCount = stream.ReadUInt16();
CompressionType = stream.ReadUInt16();
}
}
class CabFile
{
public readonly string FileName;
public readonly uint DecompressedLength;
public readonly uint DecompressedOffset;
public readonly ushort FolderIndex;
public CabFile(Stream stream)
{
DecompressedLength = stream.ReadUInt32();
DecompressedOffset = stream.ReadUInt32();
FolderIndex = stream.ReadUInt16();
stream.Position += 6;
FileName = stream.ReadASCIIZ();
}
}
readonly CabFolder[] folders;
readonly CabFile[] files;
readonly Stream stream;
public MSCabCompression(Stream stream)
{
this.stream = stream;
var signature = stream.ReadASCII(4);
if (signature != "MSCF")
throw new InvalidDataException("Not a Microsoft CAB package!");
stream.Position += 12;
var filesOffset = stream.ReadUInt32();
stream.Position += 6;
var folderCount = stream.ReadUInt16();
var fileCount = stream.ReadUInt16();
if (stream.ReadUInt16() != 0)
throw new InvalidDataException("Only plain packages (without reserved header space or prev/next archives) are supported!");
stream.Position += 4;
folders = new CabFolder[folderCount];
for (var i = 0; i < folderCount; i++)
{
folders[i] = new CabFolder(stream);
if (folders[i].CompressionType != 1)
throw new InvalidDataException("Compression type is not supported");
}
files = new CabFile[fileCount];
stream.Seek(filesOffset, SeekOrigin.Begin);
for (var i = 0; i < fileCount; i++)
files[i] = new CabFile(stream);
}
public byte[] ExtractFile(string filename, Action<int> onProgress = null)
{
var file = files.FirstOrDefault(f => f.FileName == filename);
if (file == null)
return null;
var folder = folders[file.FolderIndex];
stream.Seek(folder.BlockOffset, SeekOrigin.Begin);
using (var outputStream = new MemoryStream())
{
var inflater = new Inflater(true);
var buffer = new byte[4096];
for (var i = 0; i < folder.BlockCount; i++)
{
if (onProgress != null)
onProgress((int)(100 * outputStream.Position / file.DecompressedLength));
// Ignore checksums
stream.Position += 4;
var blockLength = stream.ReadUInt16();
stream.Position += 4;
using (var batch = new MemoryStream(stream.ReadBytes(blockLength - 2)))
using (var inflaterStream = new InflaterInputStream(batch, inflater))
{
int n;
while ((n = inflaterStream.Read(buffer, 0, buffer.Length)) > 0)
outputStream.Write(buffer, 0, n);
}
inflater.Reset();
}
outputStream.Seek(file.DecompressedOffset, SeekOrigin.Begin);
return outputStream.ReadBytes((int)file.DecompressedLength);
}
}
public IEnumerable<string> Contents { get { return files.Select(f => f.FileName); } }
}
}

View File

@@ -1,189 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip;
using OpenRA.FileSystem;
namespace OpenRA.Mods.Common
{
public static class InstallUtils
{
static IEnumerable<ZipEntry> GetEntries(this ZipInputStream z)
{
for (;;)
{
var e = z.GetNextEntry();
if (e != null) yield return e; else break;
}
}
public static string GetMountedDisk(Func<string, bool> isValidDisk)
{
var volumes = DriveInfo.GetDrives()
.Where(v => v.DriveType == DriveType.CDRom && v.IsReady)
.Select(v => v.RootDirectory.FullName);
return volumes.FirstOrDefault(isValidDisk);
}
static string GetFileName(string path, ContentInstaller.FilenameCase caseModifier)
{
// Gets the file path, splitting on both / and \
var index = path.LastIndexOfAny(new[] { '\\', '/' });
var output = path.Substring(index + 1);
switch (caseModifier)
{
case ContentInstaller.FilenameCase.ForceLower:
return output.ToLowerInvariant();
case ContentInstaller.FilenameCase.ForceUpper:
return output.ToUpperInvariant();
default:
return output;
}
}
// TODO: The package should be mounted into its own context to avoid name collisions with installed files
public static bool ExtractFromPackage(FileSystem.FileSystem fileSystem, string srcPath, string package, Dictionary<string, string[]> filesByDirectory,
string destPath, bool overwrite, ContentInstaller.FilenameCase caseModifier, Action<string> onProgress, Action<string> onError)
{
Directory.CreateDirectory(destPath);
Log.Write("debug", "Mounting {0}".F(srcPath));
fileSystem.Mount(srcPath);
Log.Write("debug", "Mounting {0}".F(package));
fileSystem.Mount(package);
foreach (var directory in filesByDirectory)
{
var targetDir = directory.Key;
foreach (var file in directory.Value)
{
var containingDir = Path.Combine(destPath, targetDir);
var dest = Path.Combine(containingDir, GetFileName(file, caseModifier));
if (File.Exists(dest))
{
if (overwrite)
File.Delete(dest);
else
{
Log.Write("debug", "Skipping {0}".F(dest));
continue;
}
}
Directory.CreateDirectory(containingDir);
using (var sourceStream = fileSystem.Open(file))
using (var destStream = File.Create(dest))
{
Log.Write("debug", "Extracting {0} to {1}".F(file, dest));
onProgress("Extracting " + file);
destStream.Write(sourceStream.ReadAllBytes());
}
}
}
return true;
}
public static bool CopyFiles(string srcPath, Dictionary<string, string[]> files, string destPath,
bool overwrite, ContentInstaller.FilenameCase caseModifier, Action<string> onProgress, Action<string> onError)
{
Directory.CreateDirectory(destPath);
foreach (var folder in files)
{
var targetDir = folder.Key;
foreach (var file in folder.Value)
{
var sourcePath = Path.Combine(srcPath, file);
if (!File.Exists(sourcePath))
{
onError("Cannot find " + file);
return false;
}
var destFile = GetFileName(file, caseModifier);
var containingDir = Path.Combine(destPath, targetDir);
var dest = Path.Combine(containingDir, destFile);
if (File.Exists(dest) && !overwrite)
{
Log.Write("debug", "Skipping {0}".F(dest));
continue;
}
Directory.CreateDirectory(containingDir);
onProgress("Copying " + destFile);
Log.Write("debug", "Copy {0} to {1}".F(sourcePath, dest));
File.Copy(sourcePath, dest, true);
}
}
return true;
}
public static bool ExtractZip(string zipFile, string dest, Action<string> onProgress, Action<string> onError)
{
if (!File.Exists(zipFile))
{
onError("Invalid path: " + zipFile);
return false;
}
var extracted = new List<string>();
try
{
using (var stream = File.OpenRead(zipFile))
using (var z = new ZipInputStream(stream))
z.ExtractZip(dest, extracted, s => onProgress("Extracting " + s));
}
catch (SharpZipBaseException)
{
foreach (var f in extracted)
File.Delete(f);
onError("Invalid archive");
return false;
}
return true;
}
// TODO: this belongs in FileSystem/ZipFile
static void ExtractZip(this ZipInputStream z, string destPath, List<string> extracted, Action<string> onProgress)
{
foreach (var entry in z.GetEntries())
{
if (!entry.IsFile) continue;
onProgress(entry.Name);
Directory.CreateDirectory(Path.Combine(destPath, Path.GetDirectoryName(entry.Name)));
var path = Path.Combine(destPath, entry.Name);
extracted.Add(path);
using (var f = File.Create(path))
z.CopyTo(f);
}
z.Close();
}
}
}

View File

@@ -50,14 +50,7 @@ namespace OpenRA.Mods.Common.LoadScreens
var widgetArgs = new WidgetArgs();
Ui.LoadWidget("MODCHOOSER_BACKGROUND", Ui.Root, widgetArgs);
if (args != null && args.Contains("installMusic"))
{
widgetArgs.Add("modId", args.GetValue("installMusic", ""));
Ui.OpenWindow("INSTALL_MUSIC_PANEL", widgetArgs);
}
else
Ui.OpenWindow("MODCHOOSER_DIALOG", widgetArgs);
Ui.OpenWindow("MODCHOOSER_DIALOG", widgetArgs);
}
public void Dispose()

View File

@@ -610,11 +610,7 @@
<Compile Include="Widgets\Logic\Ingame\SupportPowerBinLogic.cs" />
<Compile Include="Widgets\Logic\Ingame\SupportPowerTooltipLogic.cs" />
<Compile Include="Widgets\Logic\Ingame\WorldTooltipLogic.cs" />
<Compile Include="Widgets\Logic\Installation\InstallFromCDLogic.cs" />
<Compile Include="Widgets\Logic\Installation\DownloadPackagesLogic.cs" />
<Compile Include="Widgets\Logic\Installation\InstallModLogic.cs" />
<Compile Include="Widgets\Logic\Installation\InstallLogic.cs" />
<Compile Include="Widgets\Logic\Installation\InstallMusicLogic.cs" />
<Compile Include="Widgets\Logic\Lobby\ClientTooltipLogic.cs" />
<Compile Include="Widgets\Logic\Lobby\KickClientLogic.cs" />
<Compile Include="Widgets\Logic\Lobby\KickSpectatorsLogic.cs" />
@@ -662,7 +658,6 @@
<Compile Include="LoadScreens\LogoStripeLoadScreen.cs" />
<Compile Include="LoadScreens\BlankLoadScreen.cs" />
<Compile Include="Widgets\Logic\ReplayUtils.cs" />
<Compile Include="InstallUtils.cs" />
<Compile Include="Graphics\DefaultSpriteSequence.cs" />
<Compile Include="Widgets\BackgroundWidget.cs" />
<Compile Include="Widgets\ButtonWidget.cs" />
@@ -751,6 +746,15 @@
<Compile Include="Traits\Radar\RadarColorFromTerrain.cs" />
<Compile Include="Traits\Radar\JamsRadar.cs" />
<Compile Include="AI\DummyAI.cs" />
<Compile Include="UtilityCommands\ListInstallShieldContentsCommand.cs" />
<Compile Include="Widgets\Logic\Installation\ModContentLogic.cs" />
<Compile Include="Widgets\Logic\Installation\DownloadPackageLogic.cs" />
<Compile Include="Widgets\Logic\Installation\ModContentDiscTooltipLogic.cs" />
<Compile Include="Widgets\Logic\Installation\InstallFromDiscLogic.cs" />
<Compile Include="Widgets\Logic\Installation\ModContentPromptLogic.cs" />
<Compile Include="UtilityCommands\ListMixContentsCommand.cs" />
<Compile Include="UtilityCommands\ListMSCabContentsCommand.cs" />
<Compile Include="FileFormats\MSCabCompression.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@@ -74,10 +74,8 @@ namespace OpenRA.Mods.Common.Traits
}
else
{
// Start playback with a random song, but only if the player has installed more music
var installData = Game.ModData.Manifest.Get<ContentInstaller>();
if (playlist.Length > installData.ShippedSoundtracks)
currentSong = random.FirstOrDefault();
// Start playback with a random song
currentSong = random.FirstOrDefault();
}
if (SongExists(info.StartingMusic))

View File

@@ -0,0 +1,46 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ListInstallShieldContents : IUtilityCommand
{
public string Name { get { return "--list-installshield"; } }
public bool ValidateArguments(string[] args)
{
return args.Length == 2;
}
[Desc("ARCHIVE.Z", "Lists the content ranges for a InstallShield V3 file")]
public void Run(ModData modData, string[] args)
{
var filename = Path.GetFileName(args[1]);
var path = Path.GetDirectoryName(args[1]);
var fs = new OpenRA.FileSystem.FileSystem();
fs.Mount(path, "parent");
var package = new InstallShieldPackage(fs, "parent|" + filename);
foreach (var kv in package.Index)
{
Console.WriteLine("{0}:", kv.Key);
Console.WriteLine("\tOffset: {0}", 255 + kv.Value.Offset);
Console.WriteLine("\tLength: {0}", kv.Value.Length);
}
}
}
}

View File

@@ -0,0 +1,35 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Mods.Common.FileFormats;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ListMSCabContentsCommand : IUtilityCommand
{
public string Name { get { return "--list-mscab"; } }
public bool ValidateArguments(string[] args)
{
return args.Length == 2;
}
[Desc("ARCHIVE.CAB", "Lists the filenames contained within a MSCAB file")]
public void Run(ModData modData, string[] args)
{
var package = new MSCabCompression(File.OpenRead(args[1]));
foreach (var file in package.Contents)
Console.WriteLine("{0}", file);
}
}
}

View File

@@ -0,0 +1,50 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ListMixContents : IUtilityCommand
{
public string Name { get { return "--list-mix"; } }
public bool ValidateArguments(string[] args)
{
return args.Length == 2;
}
[Desc("ARCHIVE.Z", "Lists the content ranges for a mix file")]
public void Run(ModData modData, string[] args)
{
var filename = Path.GetFileName(args[1]);
var path = Path.GetDirectoryName(args[1]);
var fs = new OpenRA.FileSystem.FileSystem();
// Needed to access the global mix database
fs.LoadFromManifest(modData.Manifest);
fs.Mount(path, "parent");
var package = new MixFile(fs, "parent|" + filename);
foreach (var kv in package.Index.OrderBy(kv => kv.Value.Offset))
{
Console.WriteLine("{0}:", kv.Key);
Console.WriteLine("\tOffset: {0}", kv.Value.Offset);
Console.WriteLine("\tLength: {0}", kv.Value.Length);
}
}
}
}

View File

@@ -17,16 +17,27 @@ namespace OpenRA.Mods.Common.Widgets
{
public class ImageWidget : Widget
{
public readonly string TooltipTemplate;
public readonly string TooltipContainer;
public string ImageCollection = "";
public string ImageName = "";
public bool ClickThrough = true;
public Func<string> GetImageName;
public Func<string> GetImageCollection;
[Translate] public string TooltipText;
Lazy<TooltipContainerWidget> tooltipContainer;
public Func<string> GetTooltipText;
public ImageWidget()
{
GetImageName = () => ImageName;
GetImageCollection = () => ImageCollection;
GetTooltipText = () => TooltipText;
tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
}
protected ImageWidget(ImageWidget other)
@@ -37,6 +48,12 @@ namespace OpenRA.Mods.Common.Widgets
GetImageName = other.GetImageName;
ImageCollection = other.ImageCollection;
GetImageCollection = other.GetImageCollection;
TooltipTemplate = other.TooltipTemplate;
TooltipContainer = other.TooltipContainer;
GetTooltipText = other.GetTooltipText;
tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
}
public override Widget Clone() { return new ImageWidget(this); }
@@ -57,5 +74,21 @@ namespace OpenRA.Mods.Common.Widgets
{
return !ClickThrough && RenderBounds.Contains(mi.Location);
}
public override void MouseEntered()
{
if (TooltipContainer == null || GetTooltipText == null)
return;
tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() { { "getText", GetTooltipText } });
}
public override void MouseExited()
{
if (TooltipContainer == null)
return;
tooltipContainer.Value.RemoveTooltip();
}
}
}

View File

@@ -0,0 +1,234 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip;
using OpenRA.Support;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class DownloadPackageLogic : ChromeLogic
{
static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
readonly ModContent.ModDownload download;
readonly Action onSuccess;
readonly Widget panel;
readonly ProgressBarWidget progressBar;
Func<string> getStatusText = () => "";
string downloadHost;
[ObjectCreator.UseCtor]
public DownloadPackageLogic(Widget widget, ModContent.ModDownload download, Action onSuccess)
{
this.download = download;
this.onSuccess = onSuccess;
Log.AddChannel("install", "install.log");
panel = widget.Get("PACKAGE_DOWNLOAD_PANEL");
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
var statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
var statusFont = Game.Renderer.Fonts[statusLabel.Font];
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, statusLabel.Bounds.Width, statusFont));
statusLabel.GetText = () => status.Update(getStatusText());
var text = "Downloading {0}".F(download.Title);
panel.Get<LabelWidget>("TITLE").Text = text;
ShowDownloadDialog();
}
void ShowDownloadDialog()
{
getStatusText = () => "Fetching list of mirrors...";
progressBar.Indeterminate = true;
var retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
retryButton.IsVisible = () => false;
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Action deleteTempFile = () =>
{
Log.Write("install", "Deleting temporary file " + file);
File.Delete(file);
};
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
{
var dataReceived = 0.0f;
var dataTotal = 0.0f;
var mag = 0;
var dataSuffix = "";
if (i.TotalBytesToReceive < 0)
{
dataTotal = float.NaN;
dataReceived = i.BytesReceived;
dataSuffix = SizeSuffixes[0];
}
else
{
mag = (int)Math.Log(i.TotalBytesToReceive, 1024);
dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10));
dataReceived = i.BytesReceived / (float)(1L << (mag * 10));
dataSuffix = SizeSuffixes[mag];
}
progressBar.Indeterminate = false;
progressBar.Percentage = i.ProgressPercentage;
getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage,
dataReceived, dataTotal, dataSuffix,
downloadHost ?? "unknown host");
};
Action<string> onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s);
Action<string> onError = s => Game.RunAfterTick(() =>
{
Log.Write("install", "Download failed: " + s);
progressBar.Indeterminate = false;
progressBar.Percentage = 100;
getStatusText = () => "Error: " + s;
retryButton.IsVisible = () => true;
});
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) =>
{
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
// Automatically extract
getStatusText = () => "Extracting...";
progressBar.Indeterminate = true;
var extracted = new List<string>();
try
{
using (var stream = File.OpenRead(file))
using (var z = new ZipFile(stream))
{
foreach (var kv in download.Extract)
{
var entry = z.GetEntry(kv.Value);
if (!entry.IsFile)
continue;
onExtractProgress("Extracting " + entry.Name);
Log.Write("install", "Extracting " + entry.Name);
var targetPath = Platform.ResolvePath(kv.Key);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
extracted.Add(targetPath);
using (var zz = z.GetInputStream(entry))
using (var f = File.Create(targetPath))
zz.CopyTo(f);
}
z.Close();
}
Game.RunAfterTick(() => { Ui.CloseWindow(); onSuccess(); });
}
catch (Exception)
{
Log.Write("install", "Extraction failed");
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
onError("Invalid archive");
}
finally
{
deleteTempFile();
}
};
Action<string> downloadUrl = url =>
{
Log.Write("install", "Downloading " + url);
downloadHost = new Uri(url).Host;
var dl = new Download(url, file, onDownloadProgress, onDownloadComplete);
cancelButton.OnClick = () => { dl.Cancel(); deleteTempFile(); Ui.CloseWindow(); };
retryButton.OnClick = () => { dl.Cancel(); ShowDownloadDialog(); };
};
if (download.MirrorList != null)
{
Log.Write("install", "Fetching mirrors from " + download.MirrorList);
Action<DownloadDataCompletedEventArgs, bool> onFetchMirrorsComplete = (i, cancelled) =>
{
progressBar.Indeterminate = true;
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
try
{
var data = Encoding.UTF8.GetString(i.Result);
var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
downloadUrl(mirrorList.Random(new MersenneTwister()));
}
catch (Exception e)
{
Log.Write("install", "Mirror selection failed with error:");
Log.Write("install", e.ToString());
onError("Online mirror is not available. Please install from an original disc.");
}
};
var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete);
cancelButton.OnClick = () => { updateMirrors.Cancel(); Ui.CloseWindow(); };
retryButton.OnClick = () => { updateMirrors.Cancel(); ShowDownloadDialog(); };
}
else
downloadUrl(download.URL);
}
}
}

View File

@@ -1,159 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using OpenRA.Support;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class DownloadPackagesLogic : ChromeLogic
{
static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
readonly Widget panel;
readonly string modId;
readonly string mirrorListUrl;
readonly ProgressBarWidget progressBar;
readonly LabelWidget statusLabel;
readonly Action afterInstall;
string mirror;
[ObjectCreator.UseCtor]
public DownloadPackagesLogic(Widget widget, Action afterInstall, string mirrorListUrl, string modId)
{
this.mirrorListUrl = mirrorListUrl;
this.afterInstall = afterInstall;
this.modId = modId;
panel = widget.Get("INSTALL_DOWNLOAD_PANEL");
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
var text = "Downloading {0} assets...".F(ModMetadata.AllMods[modId].Title);
panel.Get<LabelWidget>("TITLE").Text = text;
ShowDownloadDialog();
}
void ShowDownloadDialog()
{
statusLabel.GetText = () => "Fetching list of mirrors...";
progressBar.Indeterminate = true;
var retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
retryButton.IsVisible = () => false;
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dest = Platform.ResolvePath("^", "Content", modId);
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
{
var dataReceived = 0.0f;
var dataTotal = 0.0f;
var mag = 0;
var dataSuffix = "";
if (i.TotalBytesToReceive < 0)
{
dataTotal = float.NaN;
dataReceived = i.BytesReceived;
dataSuffix = SizeSuffixes[0];
}
else
{
mag = (int)Math.Log(i.TotalBytesToReceive, 1024);
dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10));
dataReceived = i.BytesReceived / (float)(1L << (mag * 10));
dataSuffix = SizeSuffixes[mag];
}
progressBar.Indeterminate = false;
progressBar.Percentage = i.ProgressPercentage;
statusLabel.GetText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage,
dataReceived, dataTotal, dataSuffix,
mirror != null ? new Uri(mirror).Host : "unknown host");
};
Action<string> onExtractProgress = s => Game.RunAfterTick(() => statusLabel.GetText = () => s);
Action<string> onError = s => Game.RunAfterTick(() =>
{
statusLabel.GetText = () => "Error: " + s;
retryButton.IsVisible = () => true;
});
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) =>
{
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
// Automatically extract
statusLabel.GetText = () => "Extracting...";
progressBar.Indeterminate = true;
if (InstallUtils.ExtractZip(file, dest, onExtractProgress, onError))
{
Game.RunAfterTick(() =>
{
Ui.CloseWindow();
afterInstall();
});
}
};
Action<DownloadDataCompletedEventArgs, bool> onFetchMirrorsComplete = (i, cancelled) =>
{
progressBar.Indeterminate = true;
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
var data = Encoding.UTF8.GetString(i.Result);
var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
mirror = mirrorList.Random(new MersenneTwister());
// Save the package to a temp file
var dl = new Download(mirror, file, onDownloadProgress, onDownloadComplete);
cancelButton.OnClick = () => { dl.Cancel(); Ui.CloseWindow(); };
retryButton.OnClick = () => { dl.Cancel(); ShowDownloadDialog(); };
};
// Get the list of mirrors
var updateMirrors = new Download(mirrorListUrl, onDownloadProgress, onFetchMirrorsComplete);
cancelButton.OnClick = () => { updateMirrors.Cancel(); Ui.CloseWindow(); };
retryButton.OnClick = () => { updateMirrors.Cancel(); ShowDownloadDialog(); };
}
}
}

View File

@@ -1,218 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 System.Threading;
using OpenRA.FileSystem;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallFromCDLogic : ChromeLogic
{
readonly ModData modData;
readonly string modId;
readonly Widget panel;
readonly ProgressBarWidget progressBar;
readonly LabelWidget statusLabel;
readonly Action afterInstall;
readonly ButtonWidget retryButton, backButton;
readonly Widget installingContainer, insertDiskContainer;
readonly ContentInstaller installData;
[ObjectCreator.UseCtor]
public InstallFromCDLogic(Widget widget, ModData modData, Action afterInstall, string modId)
{
this.modData = modData;
this.modId = modId;
installData = ModMetadata.AllMods[modId].Content;
this.afterInstall = afterInstall;
panel = widget.Get("INSTALL_FROMCD_PANEL");
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.OnClick = Ui.CloseWindow;
retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
retryButton.OnClick = CheckForDisk;
installingContainer = panel.Get("INSTALLING");
insertDiskContainer = panel.Get("INSERT_DISK");
CheckForDisk();
}
bool IsValidDisk(string diskRoot)
{
return installData.DiskTestFiles.All(f => File.Exists(Path.Combine(diskRoot, f)));
}
bool IsTFD(string diskpath)
{
var test = File.Exists(Path.Combine(diskpath, "data1.hdr"));
var i = 0;
while (test && i < 14)
test &= File.Exists(Path.Combine(diskpath, "data{0}.cab".F(++i)));
return test;
}
void CheckForDisk()
{
var path = InstallUtils.GetMountedDisk(IsValidDisk);
if (path != null)
Install(path);
else if ((installData.InstallShieldCABFileIds.Count != 0 || installData.InstallShieldCABFilePackageIds.Count != 0)
&& (path = InstallUtils.GetMountedDisk(IsTFD)) != null)
InstallTFD(Platform.ResolvePath(path, "data1.hdr"));
else
{
var text = "Please insert a {0} install CD and click Retry.".F(ModMetadata.AllMods[modId].Title);
insertDiskContainer.Get<LabelWidget>("INFO2").Text = text;
insertDiskContainer.IsVisible = () => true;
installingContainer.IsVisible = () => false;
}
}
void InstallTFD(string source)
{
backButton.IsDisabled = () => true;
retryButton.IsDisabled = () => true;
insertDiskContainer.IsVisible = () => false;
installingContainer.IsVisible = () => true;
progressBar.Percentage = 0;
new Thread(() =>
{
using (var cabExtractor = new InstallShieldCABExtractor(modData.ModFiles, source))
{
var denom = installData.InstallShieldCABFileIds.Count;
var extractFiles = installData.ExtractFilesFromCD;
if (installData.InstallShieldCABFilePackageIds.Count > 0)
denom += extractFiles.SelectMany(x => x.Value).Count();
var installPercent = 100 / denom;
foreach (uint index in installData.InstallShieldCABFileIds)
{
var filename = cabExtractor.FileName(index);
statusLabel.GetText = () => "Extracting {0}".F(filename);
var dest = Platform.ResolvePath("^", "Content", modId, filename.ToLowerInvariant());
cabExtractor.ExtractFile(index, dest);
progressBar.Percentage += installPercent;
}
var ArchivesToExtract = installData.InstallShieldCABFilePackageIds.Select(x => x.Split(':'));
var destDir = Platform.ResolvePath("^", "Content", modId);
var onError = (Action<string>)(s => { });
var overwrite = installData.OverwriteFiles;
var onProgress = (Action<string>)(s => Game.RunAfterTick(() =>
{
progressBar.Percentage += installPercent;
statusLabel.GetText = () => s;
}));
foreach (var archive in ArchivesToExtract)
{
var filename = cabExtractor.FileName(uint.Parse(archive[0]));
statusLabel.GetText = () => "Extracting {0}".F(filename);
var destFile = Platform.ResolvePath("^", "Content", modId, filename.ToLowerInvariant());
cabExtractor.ExtractFile(uint.Parse(archive[0]), destFile);
InstallUtils.ExtractFromPackage(modData.ModFiles, source, destFile, extractFiles, destDir, overwrite, installData.OutputFilenameCase, onProgress, onError);
progressBar.Percentage += installPercent;
}
}
Game.RunAfterTick(() =>
{
Ui.CloseWindow();
afterInstall();
});
}) { IsBackground = true }.Start();
}
void Install(string source)
{
backButton.IsDisabled = () => true;
retryButton.IsDisabled = () => true;
insertDiskContainer.IsVisible = () => false;
installingContainer.IsVisible = () => true;
var dest = Platform.ResolvePath("^", "Content", modId);
var copyFiles = installData.CopyFilesFromCD;
var packageToExtract = installData.PackageToExtractFromCD.Split(':');
var extractPackage = packageToExtract.First();
var extractFiles = installData.ExtractFilesFromCD;
var overwrite = installData.OverwriteFiles;
var installCounter = 0;
var installTotal = copyFiles.SelectMany(x => x.Value).Count() + extractFiles.SelectMany(x => x.Value).Count();
var onProgress = (Action<string>)(s => Game.RunAfterTick(() =>
{
progressBar.Percentage = installCounter * 100 / installTotal;
installCounter++;
statusLabel.GetText = () => s;
}));
var onError = (Action<string>)(s => Game.RunAfterTick(() =>
{
statusLabel.GetText = () => "Error: " + s;
backButton.IsDisabled = () => false;
retryButton.IsDisabled = () => false;
}));
new Thread(() =>
{
try
{
if (!InstallUtils.CopyFiles(source, copyFiles, dest, overwrite, installData.OutputFilenameCase, onProgress, onError))
{
onError("Copying files from CD failed.");
return;
}
if (!string.IsNullOrEmpty(extractPackage))
{
if (!InstallUtils.ExtractFromPackage(modData.ModFiles, source, extractPackage, extractFiles, dest,
overwrite, installData.OutputFilenameCase, onProgress, onError))
{
onError("Extracting files from CD failed.");
return;
}
}
Game.RunAfterTick(() =>
{
statusLabel.GetText = () => "Game assets have been extracted.";
Ui.CloseWindow();
afterInstall();
});
}
catch (Exception e)
{
onError("Installation failed.\n{0}".F(e.Message));
Log.Write("debug", e.ToString());
return;
}
}) { IsBackground = true }.Start();
}
}
}

View File

@@ -0,0 +1,422 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 System.Security.Cryptography;
using System.Threading.Tasks;
using OpenRA.FileFormats;
using OpenRA.Mods.Common.FileFormats;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallFromDiscLogic : ChromeLogic
{
enum Mode { Progress, Message, List }
readonly ModContent content;
readonly Widget panel;
readonly LabelWidget titleLabel;
readonly ButtonWidget primaryButton;
readonly ButtonWidget secondaryButton;
// Progress panel
readonly Widget progressContainer;
readonly ProgressBarWidget progressBar;
readonly LabelWidget progressLabel;
// Message panel
readonly Widget messageContainer;
readonly LabelWidget messageLabel;
// List Panel
readonly Widget listContainer;
readonly ScrollPanelWidget listPanel;
readonly LabelWidget listTemplate;
readonly LabelWidget listLabel;
Mode visible = Mode.Progress;
[ObjectCreator.UseCtor]
public InstallFromDiscLogic(Widget widget, ModContent content, Action afterInstall)
{
this.content = content;
Log.AddChannel("install", "install.log");
// this.afterInstall = afterInstall;
panel = widget.Get("DISC_INSTALL_PANEL");
titleLabel = panel.Get<LabelWidget>("TITLE");
primaryButton = panel.Get<ButtonWidget>("PRIMARY_BUTTON");
secondaryButton = panel.Get<ButtonWidget>("SECONDARY_BUTTON");
// Progress view
progressContainer = panel.Get("PROGRESS");
progressContainer.IsVisible = () => visible == Mode.Progress;
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
progressLabel = panel.Get<LabelWidget>("PROGRESS_MESSAGE");
progressLabel.IsVisible = () => visible == Mode.Progress;
// Message view
messageContainer = panel.Get("MESSAGE");
messageContainer.IsVisible = () => visible == Mode.Message;
messageLabel = messageContainer.Get<LabelWidget>("MESSAGE_MESSAGE");
// List view
listContainer = panel.Get("LIST");
listContainer.IsVisible = () => visible == Mode.List;
listPanel = listContainer.Get<ScrollPanelWidget>("LIST_PANEL");
listTemplate = listPanel.Get<LabelWidget>("LIST_TEMPLATE");
listPanel.RemoveChildren();
listLabel = listContainer.Get<LabelWidget>("LIST_MESSAGE");
DetectContentDisks();
}
void DetectContentDisks()
{
var message = "Detecting drives";
ShowProgressbar("Checking Discs", () => message);
ShowBackRetry(DetectContentDisks);
new Task(() =>
{
var volumes = DriveInfo.GetDrives()
.Where(v => v.DriveType == DriveType.CDRom && v.IsReady)
.Select(v => v.RootDirectory.FullName);
foreach (var kv in content.Discs)
{
message = "Searching for " + kv.Value.Title;
foreach (var volume in volumes)
{
if (PathIsDiscMount(volume, kv.Value))
{
var packages = content.Packages.Values
.Where(p => p.Discs.Contains(kv.Key) && !p.IsInstalled())
.Select(p => p.Title);
// Ignore disc if content is already installed
if (packages.Any())
{
Game.RunAfterTick(() =>
{
ShowList(kv.Value.Title, "The following content packages will be installed:", packages);
ShowContinueCancel(() => InstallFromDisc(volume, kv.Value));
});
return;
}
}
}
}
var discTitles = content.Packages.Values
.Where(p => !p.IsInstalled())
.SelectMany(p => p.Discs)
.Select(d => content.Discs[d].Title)
.Distinct();
Game.RunAfterTick(() =>
{
ShowList("Disc Content Not Found", "Please insert or mount one of the following discs and try again", discTitles);
ShowBackRetry(DetectContentDisks);
});
}).Start();
}
void InstallFromDisc(string path, ModContent.ModDisc disc)
{
var message = "";
ShowProgressbar("Installing Content", () => message);
ShowDisabledCancel();
new Task(() =>
{
var extracted = new List<string>();
try
{
foreach (var i in disc.Install)
{
switch (i.Key)
{
case "copy":
{
var sourceDir = Path.Combine(path, i.Value.Value);
foreach (var node in i.Value.Nodes)
{
var sourcePath = 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 {0} -> {1}".F(sourcePath, targetPath));
message = "Copying " + Path.GetFileName(sourcePath);
extracted.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
File.Copy(sourcePath, targetPath);
}
break;
}
case "extract-raw":
{
ExtractFromPackage(ExtractionType.Raw, path, i.Value, extracted, ref message);
break;
}
case "extract-blast":
{
ExtractFromPackage(ExtractionType.Blast, path, i.Value, extracted, ref message);
break;
}
case "extract-mscab":
{
ExtractFromMSCab(path, i.Value, extracted, m => message = m);
break;
}
default:
Game.Debug("debug", "Unknown installation command {0} - ignoring", i.Key);
break;
}
}
Game.RunAfterTick(Ui.CloseWindow);
}
catch (Exception e)
{
Log.Write("install", e.ToString());
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
Game.RunAfterTick(() =>
{
ShowMessage("Installation Failed", "Refer to install.log in the logs directory for details.");
ShowBackRetry(() => InstallFromDisc(path, disc));
});
}
}).Start();
}
enum ExtractionType { Raw, Blast }
static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List<string> extractedFiles, ref string progressMessage)
{
var sourcePath = 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));
using (var target = File.OpenWrite(targetPath))
{
// This is a bit dumb memory-wise, but we load the whole thing when running the game anyway
Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath));
progressMessage = "Extracting " + Path.GetFileName(Path.GetFileName(targetPath));
var data = source.ReadBytes(length);
if (type == ExtractionType.Blast)
data = Blast.Decompress(data);
target.Write(data);
}
}
}
}
static void ExtractFromMSCab(string path, MiniYaml actionYaml, List<string> extractedFiles, Action<string> updateMessage)
{
var sourcePath = 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))
{
// This is a bit dumb memory-wise, but we load the whole thing when running the game anyway
Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath));
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action<int> onProgress = percent => updateMessage("Extracting {0} ({1}%)".F(displayFilename, percent));
target.Write(reader.ExtractFile(node.Value.Value, onProgress));
}
}
}
}
bool PathIsDiscMount(string path, ModContent.ModDisc disc)
{
try
{
foreach (var kv in disc.IDFiles)
{
var filePath = Path.Combine(path, kv.Key);
if (!File.Exists(filePath))
return false;
using (var fileStream = File.OpenRead(filePath))
using (var csp = SHA1.Create())
{
var hash = new string(csp.ComputeHash(fileStream).SelectMany(a => a.ToString("x2")).ToArray());
if (hash != kv.Value)
return false;
}
}
}
catch (Exception)
{
return false;
}
return true;
}
void ShowMessage(string title, string message)
{
visible = Mode.Message;
titleLabel.Text = title;
messageLabel.Text = message;
primaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (messageContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = messageContainer.Bounds.Height;
}
void ShowProgressbar(string title, Func<string> getMessage)
{
visible = Mode.Progress;
titleLabel.Text = title;
progressBar.IsIndeterminate = () => true;
var font = Game.Renderer.Fonts[progressLabel.Font];
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, progressLabel.Bounds.Width, font));
progressLabel.GetText = () => status.Update(getMessage());
primaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (progressContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = progressContainer.Bounds.Height;
}
void ShowList(string title, string message, IEnumerable<string> items)
{
visible = Mode.List;
titleLabel.Text = title;
listLabel.Text = message;
listPanel.RemoveChildren();
foreach (var i in items)
{
var item = i;
var labelWidget = (LabelWidget)listTemplate.Clone();
labelWidget.GetText = () => item;
listPanel.AddChild(labelWidget);
}
primaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (listContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = listContainer.Bounds.Height;
}
void ShowContinueCancel(Action continueAction)
{
primaryButton.OnClick = continueAction;
primaryButton.Text = "Continue";
primaryButton.Visible = true;
secondaryButton.OnClick = Ui.CloseWindow;
secondaryButton.Text = "Cancel";
secondaryButton.Visible = true;
secondaryButton.Disabled = false;
Game.RunAfterTick(Ui.ResetTooltips);
}
void ShowBackRetry(Action retryAction)
{
primaryButton.OnClick = retryAction;
primaryButton.Text = "Retry";
primaryButton.Visible = true;
secondaryButton.OnClick = Ui.CloseWindow;
secondaryButton.Text = "Back";
secondaryButton.Visible = true;
secondaryButton.Disabled = false;
Game.RunAfterTick(Ui.ResetTooltips);
}
void ShowDisabledCancel()
{
primaryButton.Visible = false;
secondaryButton.Disabled = true;
Game.RunAfterTick(Ui.ResetTooltips);
}
}
}

View File

@@ -1,43 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public InstallLogic(Widget widget, string mirrorListUrl, string modId)
{
var panel = widget.Get("INSTALL_PANEL");
var widgetArgs = new WidgetArgs
{
{ "afterInstall", () => { Game.InitializeMod(modId, new Arguments()); } },
{ "mirrorListUrl", mirrorListUrl },
{ "modId", modId }
};
var mod = ModMetadata.AllMods[modId];
var text = "OpenRA requires the original {0} game content.".F(mod.Title);
panel.Get<LabelWidget>("DESC1").Text = text;
var downloadButton = panel.Get<ButtonWidget>("DOWNLOAD_BUTTON");
downloadButton.OnClick = () => Ui.OpenWindow("INSTALL_DOWNLOAD_PANEL", widgetArgs);
downloadButton.IsDisabled = () => string.IsNullOrEmpty(mod.Content.PackageMirrorList);
panel.Get<ButtonWidget>("INSTALL_BUTTON").OnClick = () =>
Ui.OpenWindow("INSTALL_FROMCD_PANEL", widgetArgs);
panel.Get<ButtonWidget>("BACK_BUTTON").OnClick = Ui.CloseWindow;
}
}
}

View File

@@ -1,60 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallMusicLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public InstallMusicLogic(Widget widget, string modId)
{
var installMusicContainer = widget.Get("INSTALL_MUSIC_PANEL");
Action loadDefaultMod = () => Game.RunAfterTick(() => Game.InitializeMod(modId, null));
var cancelButton = installMusicContainer.GetOrNull<ButtonWidget>("BACK_BUTTON");
if (cancelButton != null)
cancelButton.OnClick = loadDefaultMod;
var copyFromDiscButton = installMusicContainer.GetOrNull<ButtonWidget>("INSTALL_MUSIC_BUTTON");
if (copyFromDiscButton != null)
{
copyFromDiscButton.OnClick = () =>
{
Ui.OpenWindow("INSTALL_FROMCD_PANEL", new WidgetArgs
{
{ "afterInstall", loadDefaultMod },
{ "modId", modId }
});
};
}
var downloadButton = installMusicContainer.GetOrNull<ButtonWidget>("DOWNLOAD_MUSIC_BUTTON");
if (downloadButton != null)
{
var installData = ModMetadata.AllMods[modId].Content;
downloadButton.IsDisabled = () => string.IsNullOrEmpty(installData.MusicPackageMirrorList);
downloadButton.OnClick = () =>
{
Ui.OpenWindow("INSTALL_DOWNLOAD_PANEL", new WidgetArgs
{
{ "afterInstall", loadDefaultMod },
{ "mirrorListUrl", installData.MusicPackageMirrorList },
{ "modId", modId }
});
};
}
}
}
}

View File

@@ -0,0 +1,53 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentDiscTooltipLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public ModContentDiscTooltipLogic(Widget widget, Func<string> getText)
{
var discs = widget.Get<ContainerWidget>("DISCS");
var template = discs.Get<LabelWidget>("DISC_TEMPLATE");
discs.RemoveChildren();
var desc = widget.Get<LabelWidget>("DESCRIPTION");
var font = Game.Renderer.Fonts[template.Font];
var discTitles = getText().Split('\n');
var maxWidth = 0;
var sideMargin = desc.Bounds.X;
var bottomMargin = discs.Bounds.Height;
foreach (var disc in discTitles)
{
var label = (LabelWidget)template.Clone();
var title = disc;
label.GetText = () => title;
label.Bounds.Y = discs.Bounds.Height;
label.Bounds.Width = font.Measure(disc).X;
maxWidth = Math.Max(maxWidth, label.Bounds.Width + label.Bounds.X);
discs.AddChild(label);
discs.Bounds.Height += label.Bounds.Height;
}
widget.Bounds.Width = 2 * sideMargin + maxWidth;
widget.Bounds.Height = discs.Bounds.Y + bottomMargin + discs.Bounds.Height;
}
}
}

View File

@@ -0,0 +1,127 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentLogic : ChromeLogic
{
readonly ModContent content;
readonly ScrollPanelWidget scrollPanel;
readonly Widget template;
bool discAvailable;
[ObjectCreator.UseCtor]
public ModContentLogic(Widget widget, string modId, Action onCancel)
{
var panel = widget.Get("CONTENT_PANEL");
content = ModMetadata.AllMods[modId].ModContent;
scrollPanel = panel.Get<ScrollPanelWidget>("PACKAGES");
template = scrollPanel.Get<ContainerWidget>("PACKAGE_TEMPLATE");
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines = !string.IsNullOrEmpty(content.HeaderMessage) ? content.HeaderMessage.Replace("\\n", "\n").Split('\n') : new string[0];
var headerHeight = 0;
foreach (var l in headerLines)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
headerHeight += headerTemplate.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
panel.Bounds.Y -= headerHeight / 2;
scrollPanel.Bounds.Y += headerHeight;
var discButton = panel.Get<ButtonWidget>("CHECK_DISC_BUTTON");
discButton.Bounds.Y += headerHeight;
discButton.IsVisible = () => discAvailable;
discButton.OnClick = () => Ui.OpenWindow("DISC_INSTALL_PANEL", new WidgetArgs
{
{ "afterInstall", () => { } },
{ "content", content }
});
var backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.Bounds.Y += headerHeight;
backButton.OnClick = () => { Ui.CloseWindow(); onCancel(); };
PopulateContentList();
Game.RunAfterTick(Ui.ResetTooltips);
}
public override void BecameVisible()
{
PopulateContentList();
}
void PopulateContentList()
{
scrollPanel.RemoveChildren();
foreach (var p in content.Packages)
{
var container = template.Clone();
var titleWidget = container.Get<LabelWidget>("TITLE");
var title = p.Value.Title;
titleWidget.GetText = () => title;
var requiredWidget = container.Get<LabelWidget>("REQUIRED");
requiredWidget.IsVisible = () => p.Value.Required;
var discWidget = container.Get<ImageWidget>("DISC");
var discs = p.Value.Discs.Select(s => content.Discs[s].Title).Distinct();
var discList = discs.JoinWith("\n");
var discAvailable = discs.Any();
discWidget.GetTooltipText = () => discList;
discWidget.IsVisible = () => discAvailable;
var installed = p.Value.IsInstalled();
var downloadButton = container.Get<ButtonWidget>("DOWNLOAD");
var downloadEnabled = !installed && p.Value.Download != null;
downloadButton.IsVisible = () => downloadEnabled;
if (downloadEnabled)
{
var download = content.Downloads[p.Value.Download];
var widgetArgs = new WidgetArgs
{
{ "download", download },
{ "onSuccess", () => { } }
};
downloadButton.OnClick = () => Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", widgetArgs);
}
var installedWidget = container.Get<LabelWidget>("INSTALLED");
installedWidget.IsVisible = () => installed;
var requiresDiscWidget = container.Get<LabelWidget>("REQUIRES_DISC");
requiresDiscWidget.IsVisible = () => !installed && !downloadEnabled;
if (!discAvailable)
requiresDiscWidget.GetText = () => "Manual Install";
scrollPanel.AddChild(container);
}
discAvailable = content.Packages.Values.Any(p => p.Discs.Any() && !p.IsInstalled());
}
}
}

View File

@@ -0,0 +1,72 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 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 OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentPromptLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public ModContentPromptLogic(Widget widget, string modId, Action continueLoading)
{
var panel = widget.Get("CONTENT_PROMPT_PANEL");
var mod = ModMetadata.AllMods[modId];
var content = ModMetadata.AllMods[modId].ModContent;
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines = !string.IsNullOrEmpty(content.InstallPromptMessage) ? content.InstallPromptMessage.Replace("\\n", "\n").Split('\n') : new string[0];
var headerHeight = 0;
foreach (var l in headerLines)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
headerHeight += headerTemplate.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
panel.Bounds.Y -= headerHeight / 2;
var advancedButton = panel.Get<ButtonWidget>("ADVANCED_BUTTON");
advancedButton.Bounds.Y += headerHeight;
advancedButton.OnClick = () =>
{
Ui.OpenWindow("CONTENT_PANEL", new WidgetArgs
{
{ "modId", modId },
{ "onCancel", Ui.CloseWindow }
});
};
var quickButton = panel.Get<ButtonWidget>("QUICK_BUTTON");
quickButton.IsVisible = () => !string.IsNullOrEmpty(content.QuickDownload);
quickButton.Bounds.Y += headerHeight;
quickButton.OnClick = () =>
{
Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", new WidgetArgs
{
{ "download", content.Downloads[content.QuickDownload] },
{ "onSuccess", continueLoading }
});
};
var backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.Bounds.Y += headerHeight;
backButton.OnClick = Ui.CloseWindow;
Game.RunAfterTick(Ui.ResetTooltips);
}
}
}

View File

@@ -27,8 +27,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly ModMetadata[] allMods;
readonly Dictionary<string, Sprite> previews = new Dictionary<string, Sprite>();
readonly Dictionary<string, Sprite> logos = new Dictionary<string, Sprite>();
readonly Cache<ModMetadata, bool> modInstallStatus;
readonly Cache<string, bool> modPrerequisitesFulfilled;
readonly Widget modChooserPanel;
readonly ButtonWidget loadButton;
readonly SheetBuilder sheetBuilder;
@@ -40,14 +38,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[ObjectCreator.UseCtor]
public ModBrowserLogic(Widget widget, ModData modData)
{
modInstallStatus = new Cache<ModMetadata, bool>(IsModInstalled);
modPrerequisitesFulfilled = new Cache<string, bool>(Game.IsModInstalled);
modChooserPanel = widget;
loadButton = modChooserPanel.Get<ButtonWidget>("LOAD_BUTTON");
loadButton.OnClick = () => LoadMod(selectedMod);
loadButton.IsDisabled = () => selectedMod.Id == modData.Manifest.Mod.Id;
var contentButton = modChooserPanel.Get<ButtonWidget>("CONFIGURE_BUTTON");
contentButton.IsDisabled = () => selectedMod.ModContent == null;
contentButton.OnClick = () =>
{
var widgetArgs = new WidgetArgs
{
{ "modId", selectedMod.Id },
{ "onCancel", () => { } }
};
Ui.OpenWindow("CONTENT_PANEL", widgetArgs);
};
modChooserPanel.Get<ButtonWidget>("QUIT_BUTTON").OnClick = Game.Exit;
modList = modChooserPanel.Get("MOD_LIST");
@@ -160,14 +168,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var selectedIndex = Array.IndexOf(allMods, mod);
if (selectedIndex - modOffset > 4)
modOffset = selectedIndex - 4;
loadButton.Text = !modPrerequisitesFulfilled[mod.Id] ? "Install mod" :
modInstallStatus[mod] ? "Load Mod" : "Install Assets";
}
void LoadMod(ModMetadata mod)
{
if (!modPrerequisitesFulfilled[mod.Id])
if (!Game.IsModInstalled(mod.Id))
{
var widgetArgs = new WidgetArgs
{
@@ -178,17 +183,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return;
}
if (!modInstallStatus[mod])
if (!IsModInstalled(mod))
{
var widgetArgs = new WidgetArgs
{
{ "continueLoading", () =>
Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) },
{ "mirrorListUrl", mod.Content.PackageMirrorList },
Game.RunAfterTick(() => Game.InitializeMod(mod.Id, new Arguments())) },
{ "modId", mod.Id }
};
Ui.OpenWindow("INSTALL_PANEL", widgetArgs);
Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs);
return;
}
@@ -203,7 +207,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
static bool IsModInstalled(ModMetadata mod)
{
return mod.Content.TestFiles.All(file => File.Exists(Path.GetFullPath(Platform.ResolvePath(file))));
return mod.ModContent.Packages
.Where(p => p.Value.Required)
.All(p => p.Value.TestFiles.All(f => File.Exists(Platform.ResolvePath(f))));
}
}
}

View File

@@ -103,18 +103,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
musicSlider.OnChange += x => Game.Sound.MusicVolume = x;
musicSlider.Value = Game.Sound.MusicVolume;
var installButton = widget.GetOrNull<ButtonWidget>("INSTALL_BUTTON");
if (installButton != null)
{
installButton.IsDisabled = () => world.Type != WorldType.Shellmap;
var args = new[] { "installMusic={0}".F(modData.Manifest.Mod.Id) };
installButton.OnClick = () =>
Game.RunAfterTick(() => Game.InitializeMod("modchooser", new Arguments(args)));
var installData = modData.Manifest.Get<ContentInstaller>();
installButton.IsVisible = () => modData.DefaultRules.InstalledMusic.ToArray().Length <= installData.ShippedSoundtracks;
}
var songWatcher = widget.GetOrNull<LogicTickerWidget>("SONG_WATCHER");
if (songWatcher != null)
{

View File

@@ -34,6 +34,7 @@ namespace OpenRA.Mods.Common.Widgets
{
readonly Ruleset modRules;
public int ScrollbarWidth = 24;
public int BorderWidth = 1;
public int TopBottomSpacing = 2;
public int ItemSpacing = 0;
public int ButtonDepth = ChromeMetrics.Get<int>("ButtonDepth");
@@ -165,7 +166,7 @@ namespace OpenRA.Mods.Common.Widgets
WidgetUtils.DrawRGBA(ChromeProvider.GetImage("scrollbar", downPressed || downDisabled ? "down_pressed" : "down_arrow"),
new float2(downButtonRect.Left + downOffset, downButtonRect.Top + downOffset));
var drawBounds = backgroundRect.InflateBy(-1, -1, -1, -1);
var drawBounds = backgroundRect.InflateBy(-BorderWidth, -BorderWidth, -BorderWidth, -BorderWidth);
Game.Renderer.EnableScissor(drawBounds);
drawBounds.Offset((-ChildOrigin).ToPoint());

View File

@@ -170,21 +170,27 @@ Container@LOBBY_MUSIC_BIN:
Visible: false
Children:
Label@TITLE:
Y: 75
Y: 65
Width: PARENT_RIGHT-24
Height: 25
Font: Bold
Align: Center
Text: No Music Available
Text: Music Not Installed
Label@DESCA:
Y: 95
Y: 85
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: Music can be installed from the
Label@DESCA:
Y: 115
Text: The game music can be installed
Label@DESCB:
Y: 105
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: game Extras menu.
Text: from the "Manage Content" menu
Label@DESCC:
Y: 125
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: in the mod chooser.

View File

@@ -154,15 +154,37 @@ Container@MUSIC_PANEL:
Height: 20
Font: Regular
Text: Loop
Label@NO_MUSIC_LABEL:
Container@NO_MUSIC_LABEL:
X: 15
Y: 147
Y: 120
Width: 330
Height: 25
Font: Bold
Align: Center
Height: 80
Visible: false
Text: No Music Installed
Children:
Label@TITLE:
Width: PARENT_RIGHT-24
Height: 25
Font: Bold
Align: Center
Text: Music Not Installed
Label@DESCA:
Y: 20
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: The game music can be installed
Label@DESCB:
Y: 40
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: from the "Manage Content" menu
Label@DESCC:
Y: 60
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: in the mod chooser.
Button@BACK_BUTTON:
Key: escape
X: 0
@@ -170,73 +192,9 @@ Container@MUSIC_PANEL:
Width: 140
Height: 35
Text: Back
Button@INSTALL_BUTTON:
X: 220
Y: 399
Width: 140
Height: 35
Text: Install Music
Label@MUTE_LABEL:
X: 165
Y: 399
Width: 300
Height: 20
Font: Small
Container@INSTALL_MUSIC_PANEL:
Logic: InstallMusicLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - 150)/2
Width: 640
Height: 150
Children:
Label@TITLE:
Width: PARENT_RIGHT
Y: 0-25
Font: BigBold
Contrast: true
Align: Center
Text: Install Music
Background@bg:
Width: 640
Height: 150
Background: panel-black
Children:
Image@INSTALL:
X: 11
Y: 11
ImageCollection: logos
ImageName: install
Label@INFO:
X: 170
Y: 35
Width: PARENT_RIGHT-30
Height: 25
Text: The soundtrack is currently unavailable.
Font: Bold
Label@INFO2:
X: 170
Y: 70
Width: PARENT_RIGHT-185
Height: 25
WordWrap: true
Text: OpenRA can download the music files from the internet, or you can install from the original C&C CDs.
Font: Bold
Button@CANCEL_BUTTON:
Key: escape
Y: 149
Width: 140
Height: 35
Text: Back
Button@DOWNLOAD_BUTTON:
X: 350
Y: 149
Width: 140
Height: 35
Text: Download
Button@COPY_FROM_CD_BUTTON:
X: 500
Y: 149
Width: 140
Height: 35
Text: Use CD
Font: Small

View File

@@ -24,6 +24,7 @@ Packages:
~movies.mix
~scores.mix
~scores2.mix
~scores-covertops.mix
~transit.mix
cnc|bits/snow.mix
cnc|bits
@@ -143,19 +144,6 @@ LoadScreen: CncLoadScreen
Image: cnc|uibits/chrome.png
Text: Loading
ContentInstaller:
TestFiles: ^Content/cnc/conquer.mix, ^Content/cnc/desert.mix, ^Content/cnc/sounds.mix, ^Content/cnc/speech.mix, ^Content/cnc/temperat.mix, ^Content/cnc/tempicnh.mix, ^Content/cnc/winter.mix
PackageMirrorList: http://www.openra.net/packages/cnc-mirrors.txt
DiskTestFiles: conquer.mix, desert.mix, install/setup.z
PackageToExtractFromCD: install/setup.z
ExtractFilesFromCD:
.: C&C95\SPEECH.MIX, C&C95\TEMPICNH.MIX, C&C95\TRANSIT.MIX
CopyFilesFromCD:
.: conquer.mix, desert.mix, general.mix, scores.mix, sounds.mix, temperat.mix, winter.mix
ShippedSoundtracks: 4
MusicPackageMirrorList: http://www.openra.net/packages/cnc-music-mirrors.txt
InstallShieldCABFileIds: 66, 45, 42, 65, 68, 67, 71, 47, 49, 60, 75, 73, 53
ServerTraits:
LobbyCommands
PlayerPinger
@@ -232,3 +220,155 @@ GameSpeeds:
OrderLatency: 6
ColorValidator:
ModContent:
InstallPromptMessage: Tiberian Dawn requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2007 C&C Gold freeware release.\n\nAdvanced Install includes options for downloading the music and for\ncopying the videos and other content from an original game disc.
QuickDownload: basefiles
HeaderMessage: The original game content may be copied from an original game disc,\nor downloaded from an online mirror of the 2007 freeware release.
Packages:
base: Base Game Files
TestFiles: ^Content/cnc/conquer.mix, ^Content/cnc/desert.mix, ^Content/cnc/sounds.mix, ^Content/cnc/speech.mix, ^Content/cnc/temperat.mix, ^Content/cnc/tempicnh.mix, ^Content/cnc/winter.mix
Discs: gdi95, gdi95-linux, nod95, nod95-linux
Required: true
Download: basefiles
music: Base Game Music
TestFiles: ^Content/cnc/scores.mix
Discs: gdi95, gdi95-linux, nod95, nod95-linux
Download: music
movies-gdi: GDI Campaign Briefings
TestFiles: ^Content/cnc/movies-gdi.mix
Discs: gdi95, gdi95-linux
movies-nod: Nod Campaign Briefings
TestFiles: ^Content/cnc/movies-nod.mix
Discs: nod95, nod95-linux
music-covertops: Covert Operations Music
TestFiles: ^Content/cnc/scores-covertops.mix
Discs: covertops, covertops-linux
Downloads:
basefiles: Base Freeware Content
MirrorList: http://www.openra.net/packages/cnc-mirrors.txt
Extract:
^Content/cnc/conquer.mix: conquer.mix
^Content/cnc/desert.mix: desert.mix
^Content/cnc/general.mix: general.mix
^Content/cnc/sounds.mix: sounds.mix
^Content/cnc/speech.mix: speech.mix
^Content/cnc/temperat.mix: temperat.mix
^Content/cnc/tempicnh.mix: tempicnh.mix
^Content/cnc/transit.mix: transit.mix
^Content/cnc/winter.mix: winter.mix
music: Freeware Music
MirrorList: http://www.openra.net/packages/cnc-music-mirrors.txt
Extract:
^Content/cnc/scores.mix: scores.mix
Discs:
gdi95: C&C Gold (GDI Disc, English)
IDFiles:
DISK.WAV: 8bef9a6687c0fe0afd57c6561df58fa6e64f145d
CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30
Install:
copy: .
^Content/cnc/conquer.mix: CONQUER.MIX
^Content/cnc/desert.mix: DESERT.MIX
^Content/cnc/general.mix: GENERAL.MIX
^Content/cnc/scores.mix: SCORES.MIX
^Content/cnc/sounds.mix: SOUNDS.MIX
^Content/cnc/temperat.mix: TEMPERAT.MIX
^Content/cnc/winter.mix: WINTER.MIX
^Content/cnc/movies-gdi.mix: MOVIES.MIX
extract-blast: INSTALL/SETUP.Z
^Content/cnc/speech.mix:
Offset: 10203213
Length: 603293
^Content/cnc/tempicnh.mix:
Offset: 10925941
Length: 119096
^Content/cnc/transit.mix:
Offset: 11078017
Length: 3724462
gdi95-linux: C&C Gold (GDI Disc, English)
IDFiles:
disk.wav: 8bef9a6687c0fe0afd57c6561df58fa6e64f145d
conquer.mix: 833e02a09aae694659eb312d3838367f681d1b30
Install:
copy: .
^Content/cnc/conquer.mix: conquer.mix
^Content/cnc/desert.mix: desert.mix
^Content/cnc/general.mix: general.mix
^Content/cnc/scores.mix: scores.mix
^Content/cnc/sounds.mix: sounds.mix
^Content/cnc/temperat.mix: temperat.mix
^Content/cnc/winter.mix: winter.mix
^Content/cnc/movies-gdi.mix: movies.mix
extract-blast: install/setup.z
^Content/cnc/speech.mix:
Offset: 10203213
Length: 603293
^Content/cnc/tempicnh.mix:
Offset: 10925941
Length: 119096
^Content/cnc/transit.mix:
Offset: 11078017
Length: 3724462
nod95: C&C Gold (Nod Disc, English)
IDFiles:
DISK.WAV: 83a0235525afa5cd6096f2967e3eae032996e38c
CONQUER.MIX: 833e02a09aae694659eb312d3838367f681d1b30
Install:
copy: .
^Content/cnc/conquer.mix: CONQUER.MIX
^Content/cnc/desert.mix: DESERT.MIX
^Content/cnc/general.mix: GENERAL.MIX
^Content/cnc/scores.mix: SCORES.MIX
^Content/cnc/sounds.mix: SOUNDS.MIX
^Content/cnc/temperat.mix: TEMPERAT.MIX
^Content/cnc/winter.mix: WINTER.MIX
^Content/cnc/movies-nod.mix: MOVIES.MIX
extract-blast: INSTALL/SETUP.Z
^Content/cnc/speech.mix:
Offset: 10203213
Length: 603293
^Content/cnc/tempicnh.mix:
Offset: 10925941
Length: 119096
^Content/cnc/transit.mix:
Offset: 11078017
Length: 3724462
nod95-linux: C&C Gold (Nod Disc, English)
IDFiles:
disk.wav: 83a0235525afa5cd6096f2967e3eae032996e38c
conquer.mix: 833e02a09aae694659eb312d3838367f681d1b30
Install:
copy: .
^Content/cnc/conquer.mix: conquer.mix
^Content/cnc/desert.mix: desert.mix
^Content/cnc/general.mix: general.mix
^Content/cnc/scores.mix: scores.mix
^Content/cnc/sounds.mix: sounds.mix
^Content/cnc/temperat.mix: temperat.mix
^Content/cnc/winter.mix: winter.mix
^Content/cnc/movies-nod.mix: movies.mix
extract-blast: install/setup.z
^Content/cnc/speech.mix:
Offset: 10203213
Length: 603293
^Content/cnc/tempicnh.mix:
Offset: 10925941
Length: 119096
^Content/cnc/transit.mix:
Offset: 11078017
Length: 3724462
covertops: Covert Operations Expansion (English)
IDFiles:
GAME/GAME.DAT: be5a6c4c0a581da09e8f34a3bbf7bd17e525085c
CONQUER.MIX: 713b53fa4c188ca9619c6bbeadbfc86513704266
Install:
copy: .
^Content/cnc/scores-covertops.mix: SCORES.MIX
covertops-linux: Covert Operations Expansion (English)
IDFiles:
game/game.dat: be5a6c4c0a581da09e8f34a3bbf7bd17e525085c
conquer.mix: 713b53fa4c188ca9619c6bbeadbfc86513704266
Install:
copy: .
^Content/cnc/scores-covertops.mix: scores.mix

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -31,6 +31,17 @@ panel-thinborder: chrome.png
corner-bl: 1,573,2,2
corner-br: 61,573,2,2
panel-thinborder-light: chrome.png
background: 643,515,58,58
border-r: 701,515,2,58
border-l: 641,515,2,58
border-b: 643,573,58,2
border-t: 643,513,58,2
corner-tl: 641,513,2,2
corner-tr: 701,513,2,2
corner-bl: 641,573,2,2
corner-br: 701,573,2,2
button: chrome.png
background: 138,522,44,44
border-r: 182,522,10,44
@@ -53,19 +64,9 @@ button-hover: chrome.png
corner-bl: 192,566,10,10
corner-br: 246,566,10,10
# Copy of button
button-disabled: chrome.png
background: 138,522,44,44
border-r: 182,522,10,44
border-l: 128,522,10,44
border-b: 138,566,44,10
border-t: 138,512,44,10
corner-tl: 128,512,10,10
corner-tr: 182,512,10,10
corner-bl: 128,566,10,10
corner-br: 182,566,10,10
Inherits: button
# Copy of button-highlighted-hover
button-pressed: chrome.png
background: 330,522,44,44
border-r: 374,522,10,44
@@ -89,40 +90,42 @@ button-highlighted: chrome.png
corner-br: 310,566,10,10
button-highlighted-hover: chrome.png
background: 330,522,44,44
border-r: 374,522,10,44
border-l: 320,522,10,44
border-b: 330,566,44,10
border-t: 330,512,44,10
corner-tl: 320,512,10,10
corner-tr: 374,512,10,10
corner-bl: 320,566,10,10
corner-br: 374,566,10,10
Inherits: button-pressed
# Copy of button-mod-highlighted-hover
button-highlighted-pressed: chrome.png
background: 330,522,44,44
border-r: 374,522,10,44
border-l: 320,522,10,44
border-b: 330,566,44,10
border-t: 330,512,44,10
corner-tl: 320,512,10,10
corner-tr: 374,512,10,10
corner-bl: 320,566,10,10
corner-br: 374,566,10,10
Inherits: button-pressed
# Copy of button-mod-highlighted
button-highlighted-disabled: chrome.png
background: 266,522,44,44
border-r: 310,522,10,44
border-l: 256,522,10,44
border-b: 266,566,44,10
border-t: 266,512,44,10
corner-tl: 256,512,10,10
corner-tr: 310,512,10,10
corner-bl: 256,566,10,10
corner-br: 310,566,10,10
Inherits: button-highlighted
button-highlighted-thin: chrome.png
background: 522,522,44,44
border-r: 566,522,10,44
border-l: 512,522,10,44
border-b: 522,566,44,10
border-t: 522,512,44,10
corner-tl: 512,512,10,10
corner-tr: 566,512,10,10
corner-bl: 512,566,10,10
corner-br: 566,566,10,10
button-highlighted-thin-hover: chrome.png
Inherits: button-highlighted-thin-pressed
button-highlighted-thin-pressed: chrome.png
background: 586,522,44,44
border-r: 630,522,10,44
border-l: 576,522,10,44
border-b: 586,566,44,10
border-t: 586,512,44,10
corner-tl: 576,512,10,10
corner-tr: 630,512,10,10
corner-bl: 576,566,10,10
corner-br: 630,566,10,10
button-highlighted-thin-disabled: chrome.png
Inherits: button-highlighted-thin
progressbar-bg: chrome.png
background: 453,565,56,6
@@ -155,4 +158,26 @@ background: chrome.png
modchooser: chrome.png
logo: 0,576,280,128
leftarrow:384,512,20,64
rightarrow:404,512,20,64
rightarrow:404,512,20,64
cdicon: 448,512,20,20
scrollpanel-bg: chrome.png
Inherits: panel-thinborder
scrollpanel-button: chrome.png
Inherits: panel-thinborder
scrollpanel-button-hover: chrome.png
Inherits: panel-thinborder-light
scrollpanel-button-disabled: chrome.png
Inherits: panel-thinborder
scrollpanel-button-pressed: chrome.png
Inherits: panel-thinborder-light
scrollbar: chrome.png
down_arrow: 480,512,16,16
down_pressed: 480,512,16,16
up_arrow: 480,528,16,16
up_pressed: 480,528,16,16

View File

@@ -0,0 +1,370 @@
Background@CONTENT_PANEL:
Logic: ModContentLogic
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - HEIGHT) / 2
Width: 500
Height: 268
Background: panel-bg
Children:
Background@RULE:
X: 30
Y: 50
Width: 440
Height: 150
Background: panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Manage Content
Align: Center
Font: MediumBold
Label@HEADER_TEMPLATE:
Y: 65
Width: PARENT_RIGHT
Height: 16
Align: Center
ScrollPanel@PACKAGES:
X: 30
Y: 84
Width: PARENT_RIGHT - 60
Height: 115
TopBottomSpacing: 4
ItemSpacing: 2
BorderWidth: 2
Children:
Container@PACKAGE_TEMPLATE:
X: 6
Width: PARENT_RIGHT - 16
Height: 23
Children:
Label@TITLE:
Width: 275
Height: 23
Label@REQUIRED:
X: 185
Y: 0-2
Width: 90
Height: 23
Align: Center
Font: Bold
TextColor: CC0000
Text: Required
Image@DISC:
X: 275
Y: 1
Width: 20
Height: 20
ImageCollection: modchooser
ImageName: cdicon
TooltipContainer: TOOLTIP_CONTAINER
TooltipTemplate: DISC_TOOLTIP
Button@DOWNLOAD:
X: 304
Y: 0
Width: 100
Height: 23
Background: button-highlighted-thin
Text: Download
Label@INSTALLED:
X: 304
Y: 0-2
Width: 100
Height: 23
Align: Center
Font: Bold
TextColor: 00CC00
Text: Installed
Label@REQUIRES_DISC:
X: 304
Y: 0-2
Width: 100
Height: 23
Align: Center
Font: Bold
TextColor: DDDDDD
Text: Requires Disc
Button@CHECK_DISC_BUTTON:
X: 30
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Detect Disc
Font: Bold
Button@BACK_BUTTON:
X: PARENT_RIGHT - 140
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape
TooltipContainer@TOOLTIP_CONTAINER:
Background@DISC_TOOLTIP:
Logic: ModContentDiscTooltipLogic
Background: panel-thinborder
Height: 25
Children:
Label@DESCRIPTION:
X: 5
Height: 23
Font: Bold
Text: Content available from:
Container@DISCS:
Y: 15
Width: PARENT_RIGHT - 10
Height: 7 # used as bottom margin
Children:
Label@DISC_TEMPLATE:
X: 20
Height: 14
Font: TinyBold
Container@PACKAGE_DOWNLOAD_PANEL:
Logic: DownloadPackageLogic
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - HEIGHT) / 2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height: 150
Background: panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Align: Center
Font: MediumBold
ProgressBar@PROGRESS_BAR:
X: 30
Y: 64
Width: PARENT_RIGHT - 60
Height: 16
BarMargin: 0, 0
Label@STATUS_LABEL:
X: 30
Y: 85
Width: PARENT_RIGHT - 60
Height: 25
Align: Left
Button@RETRY_BUTTON:
X: 30
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 120
Height: 32
Visible: false
Text: Retry
Font: Bold
Key: return
Button@CANCEL_BUTTON:
X: PARENT_RIGHT - 30 - WIDTH
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Cancel
Font: Bold
Key: escape
Background@DISC_INSTALL_PANEL:
Logic: InstallFromDiscLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Background: panel-bg
Children:
Background@RULE:
X: 30
Y: 50
Width: 440
Height:150
Background: panel-rule
Label@TITLE:
Y: 12
Width: PARENT_RIGHT
Height: 25
Align: Center
Font: MediumBold
Container@PROGRESS:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Visible: false
Children:
ProgressBar@PROGRESS_BAR:
X: 30
Y: 60
Width: PARENT_RIGHT - 60
Height: 16
BarMargin: 0, 0
Label@PROGRESS_MESSAGE:
X: 30
Y: 80
Width: PARENT_RIGHT - 60
Height: 25
Align: Left
Container@MESSAGE:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Height: 157
Visible: false
Children:
Label@MESSAGE_MESSAGE:
Y: 65
Width: PARENT_RIGHT
Height: 25
Align: Center
Container@LIST:
Width: PARENT_RIGHT
Height: 268
Visible: false
Children:
Label@LIST_MESSAGE:
Y: 65
Width: PARENT_RIGHT
Height: 16
Align: Center
ScrollPanel@LIST_PANEL:
X: 30
Y: 99
Width: PARENT_RIGHT - 60
Height: 100
TopBottomSpacing: 4
ItemSpacing: 2
BorderWidth: 2
Children:
Label@LIST_TEMPLATE:
X: 6
Width: PARENT_RIGHT - 16
Height: 23
Button@PRIMARY_BUTTON:
X: 30
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Font: Bold
Key: return
Button@SECONDARY_BUTTON:
X: PARENT_RIGHT - 140
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Font: Bold
Key: escape
Background@CONTENT_PROMPT_PANEL:
Logic: ModContentPromptLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 140
Background: panel-bg
Children:
Background@RULE:
X: 30
Y: 50
Width: 440
Height: 150
Background: panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Install Content
Align: Center
Font: MediumBold
Label@HEADER_TEMPLATE:
Y: 65
Width: PARENT_RIGHT
Height: 16
Align: Center
Button@ADVANCED_BUTTON:
X: 30
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 140
Height: 32
Text: Advanced Install
Font: Bold
Button@QUICK_BUTTON:
X: 185
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Quick Install
Font: Bold
Button@BACK_BUTTON:
X: PARENT_RIGHT - WIDTH - 30
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape
Container@INSTALL_MOD_PANEL:
Logic: InstallModLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height: 150
Background: panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Missing dependencies
Align: Center
Font: MediumBold
Label@DESC:
X: 0
Y: 65
Width: PARENT_RIGHT
Height: 25
Align: Center
Text: Please fully install the following mods then try again:
Label@MOD_LIST:
X: 0
Y: 85
Width: PARENT_RIGHT
Height: 25
Align: Center
Button@BACK_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape

View File

@@ -1,309 +0,0 @@
Container@INSTALL_PANEL:
Logic: InstallLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height:150
Background:panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Install Assets
Align: Center
Font: MediumBold
Label@DESC1:
X: 0
Y: 65
Width: PARENT_RIGHT
Height: 25
Align: Center
Label@DESC2:
X: 0
Y: 85
Width: PARENT_RIGHT
Height: 25
Text: Content can be downloaded (if available), or copied from the install CD.
Align: Center
Button@DOWNLOAD_BUTTON:
X: 20
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Download
Font: Bold
Button@INSTALL_BUTTON:
X: 140
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Use CD
Font: Bold
Button@BACK_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape
Container@INSTALL_DOWNLOAD_PANEL:
Logic: DownloadPackagesLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height:150
Background:panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Align: Center
Font: MediumBold
ProgressBar@PROGRESS_BAR:
X: 50
Y: 64
Width: PARENT_RIGHT - 100
Height: 16
BarMargin: 0, 0
Label@STATUS_LABEL:
X: 36
Y: 85
Width: PARENT_RIGHT - 100
Height: 25
Align: Left
Button@RETRY_BUTTON:
X: PARENT_RIGHT - 280
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 120
Height: 32
Visible: false
Text: Retry
Font: Bold
Key: return
Button@CANCEL_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Cancel
Font: Bold
Key: escape
Container@INSTALL_FROMCD_PANEL:
Logic: InstallFromCDLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height:150
Background:panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Fetching assets from CD...
Align: Center
Font: MediumBold
Container@INSTALLING:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Visible: false
Children:
ProgressBar@PROGRESS_BAR:
X: 50
Y: 60
Width: PARENT_RIGHT - 100
Height: 16
BarMargin: 0, 0
Label@STATUS_LABEL:
X: 36
Y: 80
Width: PARENT_RIGHT - 100
Height: 25
Align: Left
Container@INSERT_DISK:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Visible: false
Children:
Label@INFO1:
Y: 65
Width: PARENT_RIGHT
Height: 25
Text: Disk not found.
Align: Center
Label@INFO2:
Y: 85
Width: PARENT_RIGHT
Height: 25
Align: Center
Button@RETRY_BUTTON:
X: 20
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Retry
Font: Bold
Key: return
Button@BACK_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape
Container@INSTALL_MUSIC_PANEL:
Logic: InstallMusicLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height:150
Background:panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Install Music
Align: Center
Font: MediumBold
Label@DESC1:
X: 0
Y: 65
Width: PARENT_RIGHT
Height: 25
Text: OpenRA can download the music files from the internet (if available),
Align: Center
Label@DESC2:
X: 0
Y: 85
Width: PARENT_RIGHT
Height: 25
Text: or you can install them from an original CD.
Align: Center
Button@DOWNLOAD_MUSIC_BUTTON:
X: 20
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Download
Font: Bold
Button@INSTALL_MUSIC_BUTTON:
X: 140
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Use CD
Font: Bold
Button@BACK_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background:button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape
Container@INSTALL_MOD_PANEL:
Logic: InstallModLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 177
Children:
Background:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Background: panel-bg
Background@RULE:
X: 30
Y: 50
Width: 440
Height: 150
Background: panel-rule
Label@TITLE:
X: 0
Y: 12
Width: PARENT_RIGHT
Height: 25
Text: Missing dependencies
Align: Center
Font: MediumBold
Label@DESC:
X: 0
Y: 65
Width: PARENT_RIGHT
Height: 25
Align: Center
Text: Please fully install the following mods then try again:
Label@MOD_LIST:
X: 0
Y: 85
Width: PARENT_RIGHT
Height: 25
Align: Center
Button@BACK_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 52
Background: button-highlighted
Width: 110
Height: 32
Text: Back
Font: Bold
Key: escape

View File

@@ -22,7 +22,7 @@ Assemblies:
ChromeLayout:
modchooser|modchooser.yaml
modchooser|install.yaml
modchooser|content.yaml
Notifications:
modchooser|notifications.yaml

View File

@@ -1,162 +1,169 @@
Background@MODCHOOSER_BACKGROUND:
Background: background
Width:WINDOW_RIGHT
Height:WINDOW_BOTTOM
Width: WINDOW_RIGHT
Height: WINDOW_BOTTOM
Background@MODCHOOSER_DIALOG:
Logic:ModBrowserLogic
Logic: ModBrowserLogic
Children:
Container:
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - 500)/2
Width:750
Height:550-4-32
X: (WINDOW_RIGHT - WIDTH) / 2
Y: (WINDOW_BOTTOM - 500) / 2
Width: 750
Height: 514
Children:
Background@DIALOG_BACKGROUND:
Y:69
Width:PARENT_RIGHT
Height:PARENT_BOTTOM - 69
Background:panel-bg
Y: 69
Width: PARENT_RIGHT
Height: PARENT_BOTTOM - 69
Background: panel-bg
Children:
Label:
X:53
Y:30
Align:Left
Font:MediumBold
Text:Choose your Battlefield:
X: 53
Y: 30
Align: Left
Font: MediumBold
Text: Choose your Battlefield:
Container@MOD_LIST:
X:53
Y:60
Width:PARENT_RIGHT-106
Height:150
X: 53
Y: 60
Width: PARENT_RIGHT-106
Height: 150
Children:
Button@MOD_TEMPLATE:
X:16
Width:114
Height:114
TooltipContainer:TOOLTIP_CONTAINER
IgnoreChildMouseOver:true
X: 16
Width: 114
Height: 114
TooltipContainer: TOOLTIP_CONTAINER
IgnoreChildMouseOver: true
Children:
Container@MOD_NO_LOGO:
Width:PARENT_RIGHT
Height:PARENT_BOTTOM
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
Label@A:
Width:PARENT_RIGHT
Height:PARENT_BOTTOM-20
Text:Missing or
Align:center
Width: PARENT_RIGHT
Height: PARENT_BOTTOM-20
Text: Missing or
Align: Center
Label@B:
Y:20
Width:PARENT_RIGHT
Height:PARENT_BOTTOM-20
Text:invalid logo
Align:center
Y: 20
Width: PARENT_RIGHT
Height: PARENT_BOTTOM-20
Text: invalid logo
Align: Center
RGBASprite@MOD_LOGO:
X:9
Y:9
X: 9
Y: 9
Button@PREV_MOD:
X:15
Y:60+41-16
Width:25
Height:64
IgnoreChildMouseOver:true
X: 15
Y: 85
Width: 25
Height: 64
IgnoreChildMouseOver: true
Children:
Image:
X:2
ImageCollection:modchooser
ImageName:leftarrow
X: 2
ImageCollection: modchooser
ImageName: leftarrow
Button@NEXT_MOD:
X:PARENT_RIGHT - WIDTH - 20
Y:60+41-16
Width:25
Height:64
IgnoreChildMouseOver:true
X: PARENT_RIGHT - WIDTH - 20
Y: 85
Width: 25
Height: 64
IgnoreChildMouseOver: true
Children:
Image:
X:3
ImageCollection:modchooser
ImageName:rightarrow
X: 3
ImageCollection: modchooser
ImageName: rightarrow
Background@RULE:
X:53
Y:PARENT_BOTTOM - 249
Width:PARENT_RIGHT-106
Height:150
Background:panel-rule
X: 30
Y: PARENT_BOTTOM - 249
Width: PARENT_RIGHT - 60
Height: 150
Background: panel-rule
Label@MOD_TITLE:
X:PARENT_RIGHT - 53 - 140 - 170
Y:PARENT_BOTTOM-220
Align:Left
Font:Bold
X: PARENT_RIGHT - 400
Y: PARENT_BOTTOM-220
Align: Left
Font: Bold
Label@MOD_AUTHOR:
X:PARENT_RIGHT - 53 - 140 - 170
Y:PARENT_BOTTOM-205
Align:Left
Font:TinyBold
X: PARENT_RIGHT - 400
Y: PARENT_BOTTOM-205
Align: Left
Font: TinyBold
Label@MOD_VERSION:
X:PARENT_RIGHT - 53 - 140 - 170
Y:PARENT_BOTTOM-192
Align:Left
Font:Tiny
X: PARENT_RIGHT - 400
Y: PARENT_BOTTOM-192
Align: Left
Font: Tiny
Label@MOD_DESC:
X:PARENT_RIGHT - 53 - 140 - 170
Y:PARENT_BOTTOM-175
Align:Left
VAlign:Top
Font:Tiny
X: PARENT_RIGHT - 400
Y: PARENT_BOTTOM-175
Align: Left
VAlign: Top
Font: Tiny
Background@PREVIEW:
X:53
Y:PARENT_BOTTOM - 25 - HEIGHT
Width:300
Height:200
Background:panel-thinborder
X: 30
Y: PARENT_BOTTOM - 25 - HEIGHT
Width: 300
Height: 200
Background: panel-thinborder
Children:
Label:
Width:PARENT_RIGHT
Height:PARENT_BOTTOM
Text:Missing or invalid preview
Align:Center
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Text: Missing or invalid preview
Align: Center
RGBASprite@MOD_PREVIEW:
X:2
Y:2
X: 2
Y: 2
Button@LOAD_BUTTON:
Background:button-highlighted
Key:return
X:PARENT_RIGHT - 53 - WIDTH - 170
Y:PARENT_BOTTOM - 25 - HEIGHT
Width:140
Height:32
Text:Load Mod
X: PARENT_RIGHT - 300 - WIDTH
Y: PARENT_BOTTOM - 25 - HEIGHT
Width: 100
Height: 32
Text: Play
Background: button-highlighted
Key: return
Button@CONFIGURE_BUTTON:
X: PARENT_RIGHT - 145 - WIDTH
Y: PARENT_BOTTOM - 25 - HEIGHT
Width: 140
Height: 32
Text: Manage Content
Background: button-highlighted
Button@QUIT_BUTTON:
Background:button-highlighted
X:PARENT_RIGHT - 53 - WIDTH
Y:PARENT_BOTTOM - 25 - HEIGHT
Width:140
Height:32
Text:Quit
X: PARENT_RIGHT - 30 - WIDTH
Y: PARENT_BOTTOM - 25 - HEIGHT
Width: 100
Height: 32
Text: Quit
Background: button-highlighted
Background@DIALOG_HEADER:
Width:PARENT_RIGHT
Height:72
Background:panel-header
Width: PARENT_RIGHT
Height: 72
Background: panel-header
Children:
Image:
X:(PARENT_RIGHT - WIDTH)/2
Y:0-28
Width:280
ImageCollection:modchooser
ImageName:logo
X: (PARENT_RIGHT - WIDTH) / 2
Y: 0-28
Width: 280
ImageCollection: modchooser
ImageName: logo
TooltipContainer@TOOLTIP_CONTAINER:
Background@BUTTON_TOOLTIP:
Logic:ButtonTooltipLogic
Background:panel-thinborder
Height:25
Logic: ButtonTooltipLogic
Background: panel-thinborder
Height: 25
Children:
Label@LABEL:
X:5
Height:23
Font:Bold
X: 5
Height: 23
Font: Bold
Label@HOTKEY:
TextColor:255,255,0
Height:23
Font:Bold
TextColor: FFFF00
Height: 23
Font: Bold

View File

@@ -160,21 +160,27 @@ Container@LOBBY_MUSIC_BIN:
Visible: false
Children:
Label@TITLE:
Y: 75
Y: 65
Width: PARENT_RIGHT-24
Height: 25
Font: Bold
Align: Center
Text: No Music Available
Text: Music Not Installed
Label@DESCA:
Y: 95
Y: 85
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: Music can be installed from the
Label@DESCA:
Y: 115
Text: The game music can be installed
Label@DESCB:
Y: 105
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: game Extras menu.
Text: from the "Manage Content" menu
Label@DESCC:
Y: 125
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: in the mod chooser.

View File

@@ -141,22 +141,37 @@ Background@MUSIC_PANEL:
Width: 70
Height: 20
Text: Loop
Label@NO_MUSIC_LABEL:
Container@NO_MUSIC_LABEL:
X: 15
Y: 147
Y: 135
Width: 330
Height: 25
Font: Bold
Align: Center
Height: 80
Visible: false
Text: No Music Installed
Button@INSTALL_BUTTON:
X: 20
Y: PARENT_BOTTOM - 45
Width: 120
Height: 25
Text: Install Music
Font: Bold
Children:
Label@TITLE:
Width: PARENT_RIGHT-24
Height: 25
Font: Bold
Align: Center
Text: Music Not Installed
Label@DESCA:
Y: 20
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: The game music can be installed
Label@DESCB:
Y: 40
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: from the "Manage Content" menu
Label@DESCC:
Y: 60
Width: PARENT_RIGHT-24
Height: 25
Align: Center
Text: in the mod chooser.
Button@BACK_BUTTON:
X: PARENT_RIGHT - 140
Y: PARENT_BOTTOM - 45
@@ -171,54 +186,3 @@ Background@MUSIC_PANEL:
Width: 300
Height: 20
Font: Small
Background@INSTALL_MUSIC_PANEL:
Logic: InstallMusicLogic
X: (WINDOW_RIGHT - WIDTH)/2
Y: (WINDOW_BOTTOM - HEIGHT)/2
Width: 500
Height: 160
Children:
Label@TITLE:
X: 0
Y: 20
Width: PARENT_RIGHT
Height: 25
Text: Install Music
Align: Center
Font: Bold
Label@DESC1:
X: 0
Y: 50
Width: PARENT_RIGHT
Height: 25
Text: The soundtrack is currently unavailable.
Align: Center
Label@DESC2:
X: 0
Y: 70
Width: PARENT_RIGHT
Height: 25
Text: Please choose an installation method.
Align: Center
Button@COPY_FROM_CD_BUTTON:
X: 20
Y: PARENT_BOTTOM - 45
Width: 110
Height: 25
Text: Use CD
Font: Bold
Button@DOWNLOAD_BUTTON:
X: 140
Y: PARENT_BOTTOM - 45
Width: 110
Height: 25
Text: Download
Font: Bold
Button@CANCEL_BUTTON:
X: PARENT_RIGHT - 130
Y: PARENT_BOTTOM - 45
Width: 110
Height: 25
Text: Back
Font: Bold

View File

@@ -145,20 +145,6 @@ LoadScreen: LogoStripeLoadScreen
Image: ra|uibits/loadscreen.png
Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms...
ContentInstaller:
TestFiles: ^Content/ra/allies.mix, ^Content/ra/conquer.mix, ^Content/ra/interior.mix, ^Content/ra/redalert.mix, ^Content/ra/russian.mix, ^Content/ra/snow.mix, ^Content/ra/sounds.mix, ^Content/ra/temperat.mix
PackageMirrorList: http://www.openra.net/packages/ra-mirrors.txt
DiskTestFiles: MAIN.MIX, INSTALL/REDALERT.MIX
PackageToExtractFromCD: MAIN.MIX
ExtractFilesFromCD:
.: conquer.mix, russian.mix, allies.mix, sounds.mix, scores.mix, snow.mix, interior.mix, temperat.mix, general.mix
CopyFilesFromCD:
.: INSTALL/REDALERT.MIX
ShippedSoundtracks: 2
MusicPackageMirrorList: http://www.openra.net/packages/ra-music-mirrors.txt
InstallShieldCABFilePackageIds: 105
InstallShieldCABFileIds: 116
ServerTraits:
LobbyCommands
PlayerPinger
@@ -234,3 +220,203 @@ GameSpeeds:
OrderLatency: 6
ColorValidator:
ModContent:
InstallPromptMessage: Red Alert requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2008 Red Alert freeware release.\n\nAdvanced Install includes options for downloading the music and for\ncopying the videos and other content from an original game disc.
QuickDownload: basefiles
HeaderMessage: The original game content may be copied from an original game disc,\nor downloaded from an online mirror of the 2008 freeware release.
Packages:
base: Base Game Files
TestFiles: ^Content/ra/allies.mix, ^Content/ra/conquer.mix, ^Content/ra/interior.mix, ^Content/ra/redalert.mix, ^Content/ra/russian.mix, ^Content/ra/snow.mix, ^Content/ra/sounds.mix, ^Content/ra/temperat.mix
Discs: allied, allied-linux, soviet, soviet-linux
Required: true
Download: basefiles
music: Base Game Music
TestFiles: ^Content/ra/scores.mix
Discs: allied, allied-linux, soviet, soviet-linux
Download: music
movies-allied: Allied Campaign Briefings
TestFiles: ^Content/ra/movies1.mix
Discs: allied, allied-linux
movies-soviet: Soviet Campaign Briefings
TestFiles: ^Content/ra/movies2.mix
Discs: soviet, soviet-linux
music-covertops: Counterstrike Music
TestFiles: ^Content/ra/scores-counterstrike.mix
Discs: counterstrike, counterstrike-linux
Downloads:
basefiles: Base Freeware Content
MirrorList: http://www.openra.net/packages/ra-mirrors.txt
Extract:
^Content/ra/allies.mix: allies.mix
^Content/ra/conquer.mix: conquer.mix
^Content/ra/general.mix: general.mix
^Content/ra/interior.mix: interior.mix
^Content/ra/redalert.mix: redalert.mix
^Content/ra/russian.mix: russian.mix
^Content/ra/snow.mix: snow.mix
^Content/ra/sounds.mix: sounds.mix
^Content/ra/temperat.mix: temperat.mix
music: Freeware Music
MirrorList: http://www.openra.net/packages/ra-music-mirrors.txt
Extract:
^Content/ra/scores.mix: scores.mix
Discs:
allied: Red Alert 95 (Allied Disc, English)
IDFiles:
eahelp.GID: 13a8a4a1e7d9d6d893c38df5a39262c4689aeba5
INSTALL/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
Install:
copy: INSTALL
^Content/ra/redalert.mix: REDALERT.MIX
extract-raw: MAIN.MIX
^Content/ra/conquer.mix:
Offset: 236
Length: 2177047
^Content/ra/interior.mix:
Offset: 17172192
Length: 247425
^Content/ra/movies1.mix:
Offset: 17419617
Length: 369362336
^Content/ra/scores.mix:
Offset: 386781953
Length: 64171360
^Content/ra/snow.mix:
Offset: 450953313
Length: 1030861
^Content/ra/sounds.mix:
Offset: 451984174
Length: 1006778
^Content/ra/russian.mix:
Offset: 452990952
Length: 266077
^Content/ra/allies.mix:
Offset: 453257029
Length: 309406
^Content/ra/temperat.mix:
Offset: 453566435
Length: 1038859
allied-linux: Red Alert 95 (Allied Disc, English)
IDFiles:
eahelp.gid: 13a8a4a1e7d9d6d893c38df5a39262c4689aeba5
install/redalert.mix: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
Install:
copy: install
^Content/ra/redalert.mix: redalert.mix
extract-raw: main.mix
^Content/ra/conquer.mix:
Offset: 236
Length: 2177047
^Content/ra/interior.mix:
Offset: 17172192
Length: 247425
^Content/ra/movies1.mix:
Offset: 17419617
Length: 369362336
^Content/ra/scores.mix:
Offset: 386781953
Length: 64171360
^Content/ra/snow.mix:
Offset: 450953313
Length: 1030861
^Content/ra/sounds.mix:
Offset: 451984174
Length: 1006778
^Content/ra/russian.mix:
Offset: 452990952
Length: 266077
^Content/ra/allies.mix:
Offset: 453257029
Length: 309406
^Content/ra/temperat.mix:
Offset: 453566435
Length: 1038859
soviet: Red Alert 95 (Soviet Disc, English)
IDFiles:
automenu.apm: bb61132a492bfb37069a0139f95671da3655d916
INSTALL/REDALERT.MIX: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
Install:
copy: INSTALL
^Content/cnc/redalert.mix: REDALERT.MIX
extract-raw: MAIN.MIX
^Content/ra/conquer.mix:
Offset: 236
Length: 2177047
^Content/ra/interior.mix:
Offset: 17172192
Length: 247425
^Content/ra/movies2.mix:
Offset: 17419617
Length: 415334456
^Content/ra/scores.mix:
Offset: 432754073
Length: 64171360
^Content/ra/snow.mix:
Offset: 496925433
Length: 1030861
^Content/ra/russian.mix:
Offset: 497956294
Length: 266077
^Content/ra/allies.mix:
Offset: 498222371
Length: 309406
^Content/ra/sounds.mix:
Offset: 498531777
Length: 1006778
^Content/ra/temperat.mix:
Offset: 499538555
Length: 1038859
soviet-linux: Red Alert 95 (Soviet Disc, English)
IDFiles:
automenu.apm: bb61132a492bfb37069a0139f95671da3655d916
install/redalert.mix: 0e58f4b54f44f6cd29fecf8cf379d33cf2d4caef
Install:
copy: install
^Content/cnc/redalert.mix: redalert.mix
extract-raw: main.mix
^Content/ra/conquer.mix:
Offset: 236
Length: 2177047
^Content/ra/interior.mix:
Offset: 17172192
Length: 247425
^Content/ra/movies2.mix:
Offset: 17419617
Length: 415334456
^Content/ra/scores.mix:
Offset: 432754073
Length: 64171360
^Content/ra/snow.mix:
Offset: 496925433
Length: 1030861
^Content/ra/russian.mix:
Offset: 497956294
Length: 266077
^Content/ra/allies.mix:
Offset: 498222371
Length: 309406
^Content/ra/sounds.mix:
Offset: 498531777
Length: 1006778
^Content/ra/temperat.mix:
Offset: 499538555
Length: 1038859
counterstrike: Counterstrike Expansion (English)
IDFiles:
README.TXT: 0efe8087383f0b159a9633f891fb5f53c6097cd4
SETUP/INSTALL/CSTRIKE.RTP: fae8ba82db71574f6ecd8fb4ff4026fcb65d2adc
Install:
extract-raw: MAIN.MIX
^Content/ra/scores-counterstrike.mix:
Offset: 144899491
Length: 87483135
counterstrike-linux: Counterstrike Expansion (English)
IDFiles:
readme.txt: 0efe8087383f0b159a9633f891fb5f53c6097cd4
setup/install/cstrike.rtp: fae8ba82db71574f6ecd8fb4ff4026fcb65d2adc
Install:
extract-raw: main.mix
^Content/ra/scores-counterstrike.mix:
Offset: 144899491
Length: 87483135

View File

@@ -1,4 +1,4 @@
Metadata:
Metadata:
Title: Tiberian Sun
Description: Developer stub, not yet ready for release!
Version: {DEV_VERSION}
@@ -189,20 +189,6 @@ LoadScreen: LogoStripeLoadScreen
Image: ts|uibits/loadscreen.png
Text: Updating EVA installation..., Changing perspective...
ContentInstaller:
TestFiles: ^Content/ts/cache.mix, ^Content/ts/conquer.mix, ^Content/ts/isosnow.mix, ^Content/ts/isotemp.mix, ^Content/ts/local.mix, ^Content/ts/sidec01.mix, ^Content/ts/sidec02.mix, ^Content/ts/sno.mix, ^Content/ts/snow.mix, ^Content/ts/sounds.mix, ^Content/ts/speech01.mix, ^Content/ts/tem.mix, ^Content/ts/temperat.mix
PackageMirrorList: http://www.openra.net/packages/ts-mirrors.txt
DiskTestFiles: MULTI.MIX, INSTALL/TIBSUN.MIX
CopyFilesFromCD:
.: INSTALL/TIBSUN.MIX, SCORES.MIX, MULTI.MIX
PackageToExtractFromCD: INSTALL/TIBSUN.MIX
ExtractFilesFromCD:
.: cache.mix, conquer.mix, isosnow.mix, isotemp.mix, local.mix, sidec01.mix, sidec02.mix, sno.mix, snow.mix, sounds.mix, speech01.mix, tem.mix, temperat.mix
ShippedSoundtracks: 2
MusicPackageMirrorList: http://www.openra.net/packages/ts-music-mirrors.txt
InstallShieldCABFilePackageIds: 332
InstallShieldCABFileIds: 323, 364
ServerTraits:
LobbyCommands
PlayerPinger
@@ -272,3 +258,141 @@ GameSpeeds:
OrderLatency: 6
ColorValidator:
ModContent:
InstallPromptMessage: Tiberian Sun requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2012 Tiberian Sun freeware release.\n\nAdvanced Install includes options for downloading the music and for\ncopying the videos and other content from an original game disc.
QuickDownload: basefiles
HeaderMessage: The original game content may be copied from an original game disc,\nor downloaded from an online mirror of the 2012 freeware release.
Packages:
base: Base Game Files
TestFiles: ^Content/ts/cache.mix, ^Content/ts/conquer.mix, ^Content/ts/isosnow.mix, ^Content/ts/isotemp.mix, ^Content/ts/local.mix, ^Content/ts/sidec01.mix, ^Content/ts/sidec02.mix, ^Content/ts/sno.mix, ^Content/ts/snow.mix, ^Content/ts/sounds.mix, ^Content/ts/speech01.mix, ^Content/ts/tem.mix, ^Content/ts/temperat.mix
Discs: tibsun, tibsun-linux
Required: true
Download: basefiles
music: Base Game Music
TestFiles: ^Content/ts/scores.mix
Discs: tibsun, tibsun-linux
Download: music
Downloads:
basefiles: Base Freeware Content
MirrorList: http://www.openra.net/packages/ts-mirrors.txt
Extract:
^Content/ts/cache.mix: cache.mix
^Content/ts/conquer.mix: conquer.mix
^Content/ts/isosnow.mix: isosnow.mix
^Content/ts/isotemp.mix: isotemp.mix
^Content/ts/local.mix: local.mix
^Content/ts/sidec01.mix: sidec01.mix
^Content/ts/sidec02.mix: sidec02.mix
^Content/ts/sno.mix: sno.mix
^Content/ts/snow.mix: snow.mix
^Content/ts/sounds.mix: sounds.mix
^Content/ts/speech01.mix: speech01.mix
^Content/ts/speech02.mix: speech02.mix
^Content/ts/tem.mix: tem.mix
^Content/ts/temperat.mix: temperat.mix
music: Freeware Music
MirrorList: http://www.openra.net/packages/ts-music-mirrors.txt
Extract:
^Content/ts/scores.mix: scores.mix
Discs:
tibsun: Tiberan Sun (GDI or Nod Disc, English)
IDFiles:
README.TXT: 45745c4a0c888317ec900208a426472779c42bf7
AUTOPLAY.WAV: 2dfce5d00f98b641849c29942b651f4e98d30e30
Install:
copy: .
^Content/ts/scores.mix: SCORES.MIX
extract-raw: INSTALL/TIBSUN.MIX
^Content/ts/cache.mix:
Offset: 300
Length: 169176
^Content/ts/conquer.mix:
Offset: 169484
Length: 5700088
^Content/ts/isosnow.mix:
Offset: 5869580
Length: 7624750
^Content/ts/isotemp.mix:
Offset: 13494332
Length: 8617646
^Content/ts/local.mix:
Offset: 22111980
Length: 18044736
^Content/ts/sidec01.mix:
Offset: 40156716
Length: 998476
^Content/ts/sidec02.mix:
Offset: 41155196
Length: 1039996
^Content/ts/snow.mix:
Offset: 56104508
Length: 2087806
^Content/ts/sno.mix:
Offset: 58192316
Length: 7826
^Content/ts/sounds.mix:
Offset: 58200156
Length: 3224136
^Content/ts/speech01.mix:
Offset: 61424300
Length: 6028236
^Content/ts/speech02.mix:
Offset: 67452540
Length: 5596628
^Content/ts/tem.mix:
Offset: 73049180
Length: 7746
^Content/ts/temperat.mix:
Offset: 73056940
Length: 2037606
tibsun-linux: Tiberan Sun (GDI or Nod Disc, English)
IDFiles:
readme.txt: 45745c4a0c888317ec900208a426472779c42bf7
autoplay.wav: 2dfce5d00f98b641849c29942b651f4e98d30e30
Install:
copy: .
^Content/ts/scores.mix: scores.mix
extract-raw: install/tibsun.mix
^Content/ts/cache.mix:
Offset: 300
Length: 169176
^Content/ts/conquer.mix:
Offset: 169484
Length: 5700088
^Content/ts/isosnow.mix:
Offset: 5869580
Length: 7624750
^Content/ts/isotemp.mix:
Offset: 13494332
Length: 8617646
^Content/ts/local.mix:
Offset: 22111980
Length: 18044736
^Content/ts/sidec01.mix:
Offset: 40156716
Length: 998476
^Content/ts/sidec02.mix:
Offset: 41155196
Length: 1039996
^Content/ts/snow.mix:
Offset: 56104508
Length: 2087806
^Content/ts/sno.mix:
Offset: 58192316
Length: 7826
^Content/ts/sounds.mix:
Offset: 58200156
Length: 3224136
^Content/ts/speech01.mix:
Offset: 61424300
Length: 6028236
^Content/ts/speech02.mix:
Offset: 67452540
Length: 5596628
^Content/ts/tem.mix:
Offset: 73049180
Length: 7746
^Content/ts/temperat.mix:
Offset: 73056940
Length: 2037606