Add new content installer framework.
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
|
||||
118
OpenRA.Game/ModContent.cs
Normal file
118
OpenRA.Game/ModContent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace OpenRA
|
||||
|
||||
public Dictionary<string, string> RequiresMods;
|
||||
public ContentInstaller Content;
|
||||
public ModContent ModContent;
|
||||
public IReadOnlyPackage Package;
|
||||
|
||||
static Dictionary<string, ModMetadata> ValidateMods()
|
||||
@@ -81,6 +82,9 @@ namespace OpenRA
|
||||
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.
|
||||
ret[pair.First] = metadata;
|
||||
|
||||
@@ -249,6 +249,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" />
|
||||
|
||||
@@ -752,6 +752,11 @@
|
||||
<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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs
Normal file
127
OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,31 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modInstallStatus[mod])
|
||||
if (!IsModInstalled(mod))
|
||||
{
|
||||
var widgetArgs = new WidgetArgs
|
||||
if (mod.ModContent != null)
|
||||
{
|
||||
{ "continueLoading", () =>
|
||||
Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) },
|
||||
{ "mirrorListUrl", mod.Content.PackageMirrorList },
|
||||
{ "modId", mod.Id }
|
||||
};
|
||||
var widgetArgs = new WidgetArgs
|
||||
{
|
||||
{ "continueLoading", () =>
|
||||
Game.RunAfterTick(() => Game.InitializeMod(mod.Id, new Arguments())) },
|
||||
{ "modId", mod.Id }
|
||||
};
|
||||
|
||||
Ui.OpenWindow("INSTALL_PANEL", widgetArgs);
|
||||
Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
var widgetArgs = new WidgetArgs
|
||||
{
|
||||
{ "continueLoading", () =>
|
||||
Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) },
|
||||
{ "mirrorListUrl", mod.Content.PackageMirrorList },
|
||||
{ "modId", mod.Id }
|
||||
};
|
||||
|
||||
Ui.OpenWindow("INSTALL_PANEL", widgetArgs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 80 KiB |
@@ -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
|
||||
|
||||
370
mods/modchooser/content.yaml
Normal file
370
mods/modchooser/content.yaml
Normal 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
|
||||
@@ -260,50 +260,3 @@ Container@INSTALL_MUSIC_PANEL:
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@ Assemblies:
|
||||
ChromeLayout:
|
||||
modchooser|modchooser.yaml
|
||||
modchooser|install.yaml
|
||||
modchooser|content.yaml
|
||||
|
||||
Notifications:
|
||||
modchooser|notifications.yaml
|
||||
|
||||
@@ -78,34 +78,34 @@ Background@MODCHOOSER_DIALOG:
|
||||
ImageCollection:modchooser
|
||||
ImageName:rightarrow
|
||||
Background@RULE:
|
||||
X:53
|
||||
X:30
|
||||
Y:PARENT_BOTTOM - 249
|
||||
Width:PARENT_RIGHT-106
|
||||
Width:PARENT_RIGHT-30
|
||||
Height:150
|
||||
Background:panel-rule
|
||||
Label@MOD_TITLE:
|
||||
X:PARENT_RIGHT - 53 - 140 - 170
|
||||
X:PARENT_RIGHT - 400
|
||||
Y:PARENT_BOTTOM-220
|
||||
Align:Left
|
||||
Font:Bold
|
||||
Label@MOD_AUTHOR:
|
||||
X:PARENT_RIGHT - 53 - 140 - 170
|
||||
X:PARENT_RIGHT - 400
|
||||
Y:PARENT_BOTTOM-205
|
||||
Align:Left
|
||||
Font:TinyBold
|
||||
Label@MOD_VERSION:
|
||||
X:PARENT_RIGHT - 53 - 140 - 170
|
||||
X:PARENT_RIGHT - 400
|
||||
Y:PARENT_BOTTOM-192
|
||||
Align:Left
|
||||
Font:Tiny
|
||||
Label@MOD_DESC:
|
||||
X:PARENT_RIGHT - 53 - 140 - 170
|
||||
X:PARENT_RIGHT - 400
|
||||
Y:PARENT_BOTTOM-175
|
||||
Align:Left
|
||||
VAlign:Top
|
||||
Font:Tiny
|
||||
Background@PREVIEW:
|
||||
X:53
|
||||
X:30
|
||||
Y:PARENT_BOTTOM - 25 - HEIGHT
|
||||
Width:300
|
||||
Height:200
|
||||
@@ -120,20 +120,27 @@ Background@MODCHOOSER_DIALOG:
|
||||
X:2
|
||||
Y:2
|
||||
Button@LOAD_BUTTON:
|
||||
X:PARENT_RIGHT - 300 - WIDTH
|
||||
Y:PARENT_BOTTOM - 25 - HEIGHT
|
||||
Width:100
|
||||
Height:32
|
||||
Text:Play
|
||||
Background:button-highlighted
|
||||
Key:return
|
||||
X:PARENT_RIGHT - 53 - WIDTH - 170
|
||||
Button@CONFIGURE_BUTTON:
|
||||
X:PARENT_RIGHT - 145 - WIDTH
|
||||
Y:PARENT_BOTTOM - 25 - HEIGHT
|
||||
Width:140
|
||||
Height:32
|
||||
Text:Load Mod
|
||||
Button@QUIT_BUTTON:
|
||||
Text:Manage Content
|
||||
Background:button-highlighted
|
||||
X:PARENT_RIGHT - 53 - WIDTH
|
||||
Button@QUIT_BUTTON:
|
||||
X:PARENT_RIGHT - 30 - WIDTH
|
||||
Y:PARENT_BOTTOM - 25 - HEIGHT
|
||||
Width:140
|
||||
Width:100
|
||||
Height:32
|
||||
Text:Quit
|
||||
Background:button-highlighted
|
||||
Background@DIALOG_HEADER:
|
||||
Width:PARENT_RIGHT
|
||||
Height:72
|
||||
@@ -157,6 +164,6 @@ Background@BUTTON_TOOLTIP:
|
||||
Height:23
|
||||
Font:Bold
|
||||
Label@HOTKEY:
|
||||
TextColor:255,255,0
|
||||
TextColor:FFFF00
|
||||
Height:23
|
||||
Font:Bold
|
||||
Reference in New Issue
Block a user