Merge pull request #3305 from pchote/mixdatabase

Parse XCC mix databases
This commit is contained in:
Chris Forbes
2013-05-24 20:25:38 -07:00
21 changed files with 396 additions and 554 deletions

View File

@@ -0,0 +1,44 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.IO;
namespace OpenRA.FileFormats
{
public class XccGlobalDatabase
{
public readonly string[] Entries;
public XccGlobalDatabase(Stream s)
{
var entries = new List<string>();
var reader = new BinaryReader(s);
while (reader.PeekChar() > -1)
{
var count = reader.ReadInt32();
for (var i = 0; i < count; i++)
{
var chars = new List<char>();
char c;
// Read filename
while ((c = reader.ReadChar()) != 0)
chars.Add(c);
entries.Add(new string(chars.ToArray()));
// Skip comment
while ((c = reader.ReadChar()) != 0);
}
}
Entries = entries.ToArray();
}
}
}

View File

@@ -0,0 +1,67 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace OpenRA.FileFormats
{
public class XccLocalDatabase
{
public readonly string[] Entries;
public XccLocalDatabase(Stream s)
{
// Skip unnecessary header data
s.Seek(48, SeekOrigin.Begin);
var reader = new BinaryReader(s);
var count = reader.ReadInt32();
Entries = new string[count];
for (var i = 0; i < count; i++)
{
var chars = new List<char>();
char c;
while ((c = reader.ReadChar()) != 0)
chars.Add(c);
Entries[i] = new string(chars.ToArray());
}
}
public XccLocalDatabase(IEnumerable<string> filenames)
{
Entries = filenames.ToArray();
}
public byte[] Data()
{
var data = new MemoryStream();
using (var writer = new BinaryWriter(data))
{
writer.Write(Encoding.ASCII.GetBytes("XCC by Olaf van der Spek"));
writer.Write(new byte[] {0x1A,0x04,0x17,0x27,0x10,0x19,0x80,0x00});
writer.Write((int)(Entries.Aggregate(Entries.Length, (a,b) => a + b.Length) + 52)); // Size
writer.Write((int)0); // Type
writer.Write((int)0); // Version
writer.Write((int)0); // Game/Format (0 == TD)
writer.Write((int)Entries.Length); // Entries
foreach (var e in Entries)
{
writer.Write(Encoding.ASCII.GetBytes(e));
writer.Write((byte)0);
}
}
return data.ToArray();
}
}
}

View File

@@ -18,9 +18,9 @@ namespace OpenRA.FileFormats
{
public static class FileSystem
{
static List<IFolder> MountedFolders = new List<IFolder>();
static Cache<uint, List<IFolder>> allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
public static List<IFolder> MountedFolders = new List<IFolder>();
static Cache<uint, List<IFolder>> classicHashIndex = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
static Cache<uint, List<IFolder>> crcHashIndex = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
public static List<string> FolderPaths = new List<string>();
@@ -28,9 +28,16 @@ namespace OpenRA.FileFormats
{
MountedFolders.Add(folder);
foreach (var hash in folder.AllFileHashes())
foreach (var hash in folder.ClassicHashes())
{
var l = allFiles[hash];
var l = classicHashIndex[hash];
if (!l.Contains(folder))
l.Add(folder);
}
foreach (var hash in folder.CrcHashes())
{
var l = crcHashIndex[hash];
if (!l.Contains(folder))
l.Add(folder);
}
@@ -38,11 +45,6 @@ namespace OpenRA.FileFormats
static int order = 0;
static IFolder OpenPackage(string filename)
{
return OpenPackage(filename, order++);
}
public static IFolder CreatePackage(string filename, int order, Dictionary<string, byte[]> content)
{
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
@@ -57,10 +59,14 @@ namespace OpenRA.FileFormats
return new Folder(filename, order, content);
}
public static IFolder OpenPackage(string filename, int order)
public static IFolder OpenPackage(string filename, string annotation, int order)
{
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
return new MixFile(filename, order);
{
var type = string.IsNullOrEmpty(annotation) ? PackageHashType.Classic :
FieldLoader.GetValue<PackageHashType>("(value)", annotation);
return new MixFile(filename, type, order);
}
else if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(filename, order);
else if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
@@ -72,6 +78,11 @@ namespace OpenRA.FileFormats
}
public static void Mount(string name)
{
Mount(name, null);
}
public static void Mount(string name, string annotation)
{
var optional = name.StartsWith("~");
if (optional) name = name.Substring(1);
@@ -80,10 +91,8 @@ namespace OpenRA.FileFormats
if (name.StartsWith("^"))
name = Platform.SupportDir+name.Substring(1);
if (Directory.Exists(name))
FolderPaths.Add(name);
var a = (Action)(() => FileSystem.MountInner(OpenPackage(name)));
FolderPaths.Add(name);
Action a = () => FileSystem.MountInner(OpenPackage(name, annotation, order++));
if (optional)
try { a(); }
@@ -96,7 +105,8 @@ namespace OpenRA.FileFormats
{
MountedFolders.Clear();
FolderPaths.Clear();
allFiles = new Cache<uint, List<IFolder>>( _ => new List<IFolder>() );
classicHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
crcHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
}
public static bool Unmount(IFolder mount)
@@ -112,13 +122,17 @@ namespace OpenRA.FileFormats
public static void LoadFromManifest(Manifest manifest)
{
UnmountAll();
foreach (var dir in manifest.Folders) Mount(dir);
foreach (var pkg in manifest.Packages) Mount(pkg);
foreach (var dir in manifest.Folders)
Mount(dir);
foreach (var pkg in manifest.Packages)
Mount(pkg.Key, pkg.Value);
}
static Stream GetFromCache(Cache<uint, List<IFolder>> index, string filename)
static Stream GetFromCache(PackageHashType type, string filename)
{
var folder = index[PackageEntry.HashFilename(filename)]
var index = type == PackageHashType.CRC32 ? crcHashIndex : classicHashIndex;
var folder = index[PackageEntry.HashFilename(filename, type)]
.Where(x => x.Exists(filename))
.OrderBy(x => x.Priority)
.FirstOrDefault();
@@ -133,11 +147,15 @@ namespace OpenRA.FileFormats
public static Stream OpenWithExts(string filename, params string[] exts)
{
if( filename.IndexOfAny( new char[] { '/', '\\' } ) == -1 )
if (filename.IndexOfAny(new char[] { '/', '\\' } ) == -1)
{
foreach( var ext in exts )
foreach (var ext in exts)
{
var s = GetFromCache(allFiles, filename + ext);
var s = GetFromCache(PackageHashType.Classic, filename + ext);
if (s != null)
return s;
s = GetFromCache(PackageHashType.CRC32, filename + ext);
if (s != null)
return s;
}

View File

@@ -17,8 +17,7 @@ namespace OpenRA.FileFormats
public class Folder : IFolder
{
readonly string path;
int priority;
readonly int priority;
// Create a new folder package
public Folder(string path, int priority, Dictionary<string, byte[]> contents)
@@ -45,13 +44,21 @@ namespace OpenRA.FileFormats
catch { return null; }
}
public IEnumerable<uint> AllFileHashes()
public IEnumerable<uint> ClassicHashes()
{
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
{
yield return PackageEntry.HashFilename(Path.GetFileName(filename)); // RA1 and TD
yield return PackageEntry.CrcHashFilename(Path.GetFileName(filename)); // TS
}
yield return PackageEntry.HashFilename(Path.GetFileName(filename), PackageHashType.Classic);
}
public IEnumerable<uint> CrcHashes()
{
yield break;
}
public IEnumerable<string> AllFileNames()
{
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
yield return Path.GetFileName(filename);
}
public bool Exists(string filename)
@@ -59,11 +66,8 @@ namespace OpenRA.FileFormats
return File.Exists(Path.Combine(path, filename));
}
public int Priority
{
get { return priority; }
}
public int Priority { get { return priority; } }
public string Name { get { return path; } }
public void Write(Dictionary<string, byte[]> contents)
{

View File

@@ -18,13 +18,17 @@ namespace OpenRA.FileFormats
public class InstallShieldPackage : IFolder
{
readonly Dictionary<uint, PackageEntry> index = new Dictionary<uint, PackageEntry>();
readonly List<string> filenames;
readonly Stream s;
readonly long dataStart = 255;
int priority;
readonly int priority;
readonly string filename;
public InstallShieldPackage(string filename, int priority)
{
this.filename = filename;
this.priority = priority;
filenames = new List<string>();
s = FileSystem.Open(filename);
// Parse package header
@@ -76,8 +80,9 @@ namespace OpenRA.FileFormats
var NameLength = reader.ReadByte();
var FileName = new String(reader.ReadChars(NameLength));
var hash = PackageEntry.HashFilename(FileName);
index.Add(hash, new PackageEntry(hash,AccumulatedData, CompressedSize));
var hash = PackageEntry.HashFilename(FileName, PackageHashType.Classic);
index.Add(hash, new PackageEntry(hash, AccumulatedData, CompressedSize));
filenames.Add(FileName);
AccumulatedData += CompressedSize;
// Skip to the end of the chunk
@@ -99,24 +104,31 @@ namespace OpenRA.FileFormats
public Stream GetContent(string filename)
{
return GetContent(PackageEntry.HashFilename(filename));
return GetContent(PackageEntry.HashFilename(filename, PackageHashType.Classic));
}
public IEnumerable<uint> AllFileHashes()
public IEnumerable<uint> ClassicHashes()
{
return index.Keys;
}
public IEnumerable<uint> CrcHashes()
{
yield break;
}
public IEnumerable<string> AllFileNames()
{
return filenames;
}
public bool Exists(string filename)
{
return index.ContainsKey(PackageEntry.HashFilename(filename));
return index.ContainsKey(PackageEntry.HashFilename(filename, PackageHashType.Classic));
}
public int Priority
{
get { return 2000 + priority; }
}
public int Priority { get { return 2000 + priority; }}
public string Name { get { return filename; } }
public void Write(Dictionary<string, byte[]> contents)
{

View File

@@ -10,6 +10,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -19,9 +20,12 @@ namespace OpenRA.FileFormats
{
Stream GetContent(string filename);
bool Exists(string filename);
IEnumerable<uint> AllFileHashes();
IEnumerable<uint> ClassicHashes();
IEnumerable<uint> CrcHashes();
IEnumerable<string> AllFileNames();
void Write(Dictionary<string, byte[]> contents);
int Priority { get; }
string Name { get; }
}
public class MixFile : IFolder
@@ -29,24 +33,31 @@ namespace OpenRA.FileFormats
readonly Dictionary<uint, PackageEntry> index;
readonly long dataStart;
readonly Stream s;
int priority;
readonly int priority;
readonly string filename;
readonly PackageHashType type;
// Save a mix to disk with the given contents
public MixFile(string filename, int priority, Dictionary<string, byte[]> contents)
{
this.filename = filename;
this.priority = priority;
this.type = PackageHashType.Classic;
if (File.Exists(filename))
File.Delete(filename);
s = File.Create(filename);
// TODO: Add a local mix database.dat for compatibility with XCC Mixer
index = new Dictionary<uint, PackageEntry>();
contents.Add("local mix database.dat", new XccLocalDatabase(contents.Keys.Append("local mix database.dat")).Data());
Write(contents);
}
public MixFile(string filename, int priority)
public MixFile(string filename, PackageHashType type, int priority)
{
this.filename = filename;
this.priority = priority;
this.type = type;
s = FileSystem.Open(filename);
// Detect format type
@@ -137,6 +148,23 @@ namespace OpenRA.FileFormats
return ret;
}
uint? FindMatchingHash(string filename)
{
var hash = PackageEntry.HashFilename(filename, type);
if (index.ContainsKey(hash))
return hash;
// Maybe we were given a raw hash?
uint raw;
if (!uint.TryParse(filename, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out raw))
return null;
if ("{0:X}".F(raw) == filename && index.ContainsKey(raw))
return raw;
return null;
}
public Stream GetContent(uint hash)
{
PackageEntry e;
@@ -151,29 +179,63 @@ namespace OpenRA.FileFormats
public Stream GetContent(string filename)
{
var content = GetContent(PackageEntry.HashFilename(filename)); // RA1 and TD
if (content != null)
return content;
else
return GetContent(PackageEntry.CrcHashFilename(filename)); // TS
var hash = FindMatchingHash(filename);
return hash.HasValue ? GetContent(hash.Value) : null;
}
public IEnumerable<uint> AllFileHashes()
static readonly uint[] Nothing = {};
public IEnumerable<uint> ClassicHashes()
{
return index.Keys;
if (type == PackageHashType.Classic)
return index.Keys;
return Nothing;
}
public IEnumerable<uint> CrcHashes()
{
if (type == PackageHashType.CRC32)
return index.Keys;
return Nothing;
}
public IEnumerable<string> AllFileNames()
{
var lookup = new Dictionary<uint, string>();
if (Exists("local mix database.dat"))
{
var db = new XccLocalDatabase(GetContent("local mix database.dat"));
foreach (var e in db.Entries)
{
var hash = PackageEntry.HashFilename(e, type);
if (!lookup.ContainsKey(hash))
lookup.Add(hash, e);
}
}
if (FileSystem.Exists("global mix database.dat"))
{
var db = new XccGlobalDatabase(FileSystem.Open("global mix database.dat"));
foreach (var e in db.Entries)
{
var hash = PackageEntry.HashFilename(e, type);
if (!lookup.ContainsKey(hash))
lookup.Add(hash, e);
}
}
return index.Keys.Select(k => lookup.ContainsKey(k) ? lookup[k] : "{0:X}".F(k));
}
public bool Exists(string filename)
{
return (index.ContainsKey(PackageEntry.HashFilename(filename)) || index.ContainsKey(PackageEntry.CrcHashFilename(filename)));
}
public int Priority
{
get { return 1000 + priority; }
return FindMatchingHash(filename).HasValue;
}
public int Priority { get { return 1000 + priority; } }
public string Name { get { return filename; } }
public void Write(Dictionary<string, byte[]> contents)
{
// Cannot modify existing mixfile - rename existing file and
@@ -190,8 +252,8 @@ namespace OpenRA.FileFormats
foreach (var kv in contents)
{
var length = (uint)kv.Value.Length;
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key));
items.Add(new PackageEntry(hash, dataSize, length)); // TODO: Tiberian Sun uses CRC hashes
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key), type);
items.Add(new PackageEntry(hash, dataSize, length));
dataSize += length;
}

View File

@@ -51,7 +51,6 @@ namespace OpenRA.FileFormats
public Stream GetContent(string filename)
{
using (var z = pkg.GetInputStream(pkg.GetEntry(filename)))
{
var ms = new MemoryStream();
@@ -65,10 +64,21 @@ namespace OpenRA.FileFormats
}
}
public IEnumerable<uint> AllFileHashes()
public IEnumerable<uint> ClassicHashes()
{
foreach(ZipEntry entry in pkg)
yield return PackageEntry.HashFilename(entry.Name);
yield return PackageEntry.HashFilename(entry.Name, PackageHashType.Classic);
}
public IEnumerable<uint> CrcHashes()
{
yield break;
}
public IEnumerable<string> AllFileNames()
{
foreach(ZipEntry entry in pkg)
yield return entry.Name;
}
public bool Exists(string filename)
@@ -76,10 +86,8 @@ namespace OpenRA.FileFormats
return pkg.GetEntry(filename) != null;
}
public int Priority
{
get { return 500 + priority; }
}
public int Priority { get { return 500 + priority; } }
public string Name { get { return filename; } }
public void Write(Dictionary<string, byte[]> contents)
{

View File

@@ -19,10 +19,12 @@ namespace OpenRA.FileFormats
public class Manifest
{
public readonly string[]
Mods, Folders, Packages, Rules, ServerTraits,
Mods, Folders, Rules, ServerTraits,
Sequences, Cursors, Chrome, Assemblies, ChromeLayout,
Weapons, Voices, Notifications, Music, Movies, TileSets,
ChromeMetrics, PackageContents;
public readonly Dictionary<string, string> Packages;
public readonly MiniYaml LoadScreen;
public readonly Dictionary<string, Pair<string,int>> Fonts;
public readonly int TileSize = 24;
@@ -36,7 +38,7 @@ namespace OpenRA.FileFormats
// TODO: Use fieldloader
Folders = YamlList(yaml, "Folders");
Packages = YamlList(yaml, "Packages");
Packages = yaml["Packages"].NodesDict.ToDictionary(x => x.Key, x => x.Value.Value);
Rules = YamlList(yaml, "Rules");
ServerTraits = YamlList(yaml, "ServerTraits");
Sequences = YamlList(yaml, "Sequences");

View File

@@ -139,6 +139,8 @@
<Compile Include="WRange.cs" />
<Compile Include="HSLColor.cs" />
<Compile Include="Graphics\ShpTSReader.cs" />
<Compile Include="FileFormats\XccLocalDatabase.cs" />
<Compile Include="FileFormats\XccGlobalDatabase.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">

View File

@@ -8,19 +8,21 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace OpenRA.FileFormats
{
public enum PackageHashType { Classic, CRC32 }
public class PackageEntry
{
public readonly uint Hash;
public readonly uint Offset;
public readonly uint Length;
public PackageEntry(uint hash, uint offset, uint length)
{
Hash = hash;
@@ -51,49 +53,54 @@ namespace OpenRA.FileFormats
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
}
public static uint HashFilename(string name) // Red Alert 1 and Tiberian Dawn
public static uint HashFilename(string name, PackageHashType type)
{
if (name.Length > 12)
name = name.Substring(0, 12);
name = name.ToUpperInvariant();
if (name.Length % 4 != 0)
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(name));
BinaryReader reader = new BinaryReader(ms);
int len = name.Length >> 2;
uint result = 0;
while (len-- != 0)
result = ((result << 1) | (result >> 31)) + reader.ReadUInt32();
return result;
}
public static uint CrcHashFilename(string name) // Tiberian Sun
{
name = name.ToUpperInvariant();
var l = name.Length;
int a = l >> 2;
if ((l & 3) != 0)
switch(type)
{
name += (char)(l - (a << 2));
int i = 3 - (l & 3);
while (i-- != 0)
name += name[a << 2];
case PackageHashType.Classic:
{
name = name.ToUpperInvariant();
if (name.Length % 4 != 0)
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(name));
BinaryReader reader = new BinaryReader(ms);
int len = name.Length >> 2;
uint result = 0;
while (len-- != 0)
result = ((result << 1) | (result >> 31)) + reader.ReadUInt32();
return result;
}
case PackageHashType.CRC32:
{
name = name.ToUpperInvariant();
var l = name.Length;
int a = l >> 2;
if ((l & 3) != 0)
{
name += (char)(l - (a << 2));
int i = 3 - (l & 3);
while (i-- != 0)
name += name[a << 2];
}
return CRC32.Calculate(Encoding.ASCII.GetBytes(name));
}
default: throw new NotImplementedException("Unknown hash type `{0}`".F(type));
}
return CRC32.Calculate(Encoding.ASCII.GetBytes(name));
}
static Dictionary<uint, string> Names = new Dictionary<uint,string>();
public static void AddStandardName(string s)
{
uint hash = HashFilename(s); // RA1 and TD
uint hash = HashFilename(s, PackageHashType.Classic); // RA1 and TD
Names.Add(hash, s);
uint crcHash = CrcHashFilename(s); // TS
uint crcHash = HashFilename(s, PackageHashType.CRC32); // TS
Names.Add(crcHash, s);
}

View File

@@ -311,9 +311,9 @@ namespace OpenRA
Sound.StopVideo();
Sound.Initialize();
modData = new ModData( mm );
modData = new ModData(mm);
Renderer.InitializeFonts(modData.Manifest);
modData.LoadInitialAssets(true);
modData.InitializeLoaders();
PerfHistory.items["render"].hasNormalTick = false;

View File

@@ -25,7 +25,6 @@ namespace OpenRA
public static Dictionary<string, MusicInfo> Music;
public static Dictionary<string, string> Movies;
public static Dictionary<string, TileSet> TileSets;
public static Dictionary<string, string> PackageContents;
public static void LoadRules(Manifest m, Map map)
{
@@ -36,7 +35,6 @@ namespace OpenRA
Notifications = LoadYamlRules(m.Notifications, map.Notifications, (k, _) => new SoundInfo(k.Value));
Music = LoadYamlRules(m.Music, new List<MiniYamlNode>(), (k, _) => new MusicInfo(k.Key, k.Value));
Movies = LoadYamlRules(m.Movies, new List<MiniYamlNode>(), (k, v) => k.Value.Value);
PackageContents = LoadYamlRules(m.PackageContents, new List<MiniYamlNode>(), (k, v) => k.Value.Value);
TileSets = new Dictionary<string, TileSet>();
foreach (var file in m.TileSets)

View File

@@ -104,7 +104,7 @@ namespace OpenRA
public Map(string path)
{
Path = path;
Container = FileSystem.OpenPackage(path, int.MaxValue);
Container = FileSystem.OpenPackage(path, null, int.MaxValue);
AssertExists("map.yaml");
AssertExists("map.bin");

View File

@@ -37,20 +37,20 @@ namespace OpenRA
LoadScreen.Init(Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value));
LoadScreen.Display();
WidgetLoader = new WidgetLoader(this);
}
public void LoadInitialAssets(bool enumMaps)
{
// all this manipulation of static crap here is nasty and breaks
// horribly when you use ModData in unexpected ways.
AvailableMaps = FindMaps(Manifest.Mods);
// HACK: Mount only local folders so we have a half-working environment for the asset installer
FileSystem.UnmountAll();
foreach (var dir in Manifest.Folders)
FileSystem.Mount(dir);
if (enumMaps)
AvailableMaps = FindMaps(Manifest.Mods);
}
public void InitializeLoaders()
{
// all this manipulation of static crap here is nasty and breaks
// horribly when you use ModData in unexpected ways.
ChromeMetrics.Initialize(Manifest.ChromeMetrics);
ChromeProvider.Initialize(Manifest.Chrome);
SheetBuilder = new SheetBuilder(SheetType.Indexed);
@@ -66,12 +66,11 @@ namespace OpenRA
var map = new Map(AvailableMaps[uid].Path);
// Reinit all our assets
LoadInitialAssets(false);
foreach (var pkg in Manifest.Packages)
FileSystem.Mount(pkg);
InitializeLoaders();
FileSystem.LoadFromManifest(Manifest);
// Mount map package so custom assets can be used. TODO: check priority.
FileSystem.Mount(FileSystem.OpenPackage(map.Path, int.MaxValue));
FileSystem.Mount(FileSystem.OpenPackage(map.Path, null, int.MaxValue));
Rules.LoadRules(Manifest, map);
SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder);

View File

@@ -23,17 +23,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{
Widget panel;
static ShpImageWidget spriteImage;
static TextFieldWidget filenameInput;
static SliderWidget frameSlider;
static ButtonWidget playButton, pauseButton;
static ScrollPanelWidget assetList;
static ScrollItemWidget template;
ShpImageWidget spriteImage;
TextFieldWidget filenameInput;
SliderWidget frameSlider;
ButtonWidget playButton, pauseButton;
ScrollPanelWidget assetList;
ScrollItemWidget template;
public enum SourceType { Folders, Packages }
public static SourceType AssetSource = SourceType.Folders;
public static List<string> AvailableShps = new List<string>();
IFolder AssetSource = null;
List<string> AvailableShps = new List<string>();
[ObjectCreator.UseCtor]
public AssetBrowserLogic(Widget widget, Action onExit, World world)
@@ -42,9 +40,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var sourceDropdown = panel.Get<DropDownButtonWidget>("SOURCE_SELECTOR");
sourceDropdown.OnMouseDown = _ => ShowSourceDropdown(sourceDropdown);
sourceDropdown.GetText = () => AssetSource == SourceType.Folders ? "Folders"
: AssetSource == SourceType.Packages ? "Packages" : "None";
sourceDropdown.Disabled = !Rules.PackageContents.Keys.Any();
sourceDropdown.GetText = () =>
{
var name = AssetSource != null ? AssetSource.Name : "All Packages";
if (name.Length > 15)
name = "..."+name.Substring(name.Length - 15);
return name;
};
AssetSource = FileSystem.MountedFolders.First();
spriteImage = panel.Get<ShpImageWidget>("SPRITE");
@@ -177,30 +182,22 @@ namespace OpenRA.Mods.RA.Widgets.Logic
panel.Get<ButtonWidget>("CLOSE_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
}
static void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template)
void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template)
{
var sprite = Path.GetFileNameWithoutExtension(filepath);
var filename = Path.GetFileName(filepath);
var item = ScrollItemWidget.Setup(template,
() => spriteImage != null && spriteImage.Image == sprite,
() => spriteImage.Image == sprite,
() => LoadAsset(sprite));
item.Get<LabelWidget>("TITLE").GetText = () => filename;
item.Get<LabelWidget>("TITLE").GetText = () => filepath;
list.AddChild(item);
}
static bool LoadAsset(string sprite)
bool LoadAsset(string sprite)
{
if (sprite == null)
return false;
if (!sprite.ToLower().Contains("r8"))
{
filenameInput.Text = sprite+".shp";
sprite = Path.GetFileNameWithoutExtension(sprite);
}
spriteImage.Frame = 0;
spriteImage.Image = sprite;
frameSlider.MaximumValue = (float)spriteImage.FrameCount;
@@ -208,54 +205,46 @@ namespace OpenRA.Mods.RA.Widgets.Logic
return true;
}
public static bool ShowSourceDropdown(DropDownButtonWidget dropdown)
bool ShowSourceDropdown(DropDownButtonWidget dropdown)
{
var options = new Dictionary<string, SourceType>()
{
{ "Folders", SourceType.Folders },
{ "Packages", SourceType.Packages },
};
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) =>
Func<IFolder, ScrollItemWidget, ScrollItemWidget> setupItem = (source, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => AssetSource == options[o],
() => { AssetSource = options[o]; PopulateAssetList(); });
item.Get<LabelWidget>("LABEL").GetText = () => o;
() => AssetSource == source,
() => { AssetSource = source; PopulateAssetList(); });
item.Get<LabelWidget>("LABEL").GetText = () => source != null ? source.Name : "All Packages";
return item;
};
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, options.Keys, setupItem);
// TODO: Re-enable "All Packages" once list generation is done in a background thread
//var sources = new[] { (IFolder)null }.Concat(FileSystem.MountedFolders);
var sources = FileSystem.MountedFolders;
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 250, sources, setupItem);
return true;
}
public static void PopulateAssetList()
void PopulateAssetList()
{
assetList.RemoveChildren();
AvailableShps.Clear();
if (AssetSource == SourceType.Folders)
// TODO: This is too slow to run in the main thread
//var files = AssetSource != null ? AssetSource.AllFileNames() :
// FileSystem.MountedFolders.SelectMany(f => f.AllFileNames());
if (AssetSource == null)
return;
var files = AssetSource.AllFileNames();
foreach (var file in files)
{
foreach (var folder in FileSystem.FolderPaths)
if (file.EndsWith(".shp"))
{
if (Directory.Exists(folder))
{
var shps = Directory.GetFiles(folder, "*.shp");
foreach (var shp in shps)
{
AddAsset(assetList, shp, template);
AvailableShps.Add(Path.GetFileName(shp));
}
}
AddAsset(assetList, file, template);
AvailableShps.Add(file);
}
}
if (AssetSource == SourceType.Packages)
foreach (var hiddenFile in Rules.PackageContents.Keys)
{
AddAsset(assetList, hiddenFile, template);
AvailableShps.Add(hiddenFile);
}
}
}
}

BIN
global mix database.dat Normal file

Binary file not shown.

View File

@@ -29,8 +29,6 @@ Packages:
~scores2.mix
~transit.mix
PackageContents:
Rules:
mods/cnc/rules/defaults.yaml
mods/cnc/rules/system.yaml

View File

@@ -21,8 +21,6 @@ Packages:
~main.mix
conquer.mix
PackageContents:
Rules:
mods/d2k/rules/system.yaml
mods/d2k/rules/defaults.yaml

View File

@@ -1,227 +0,0 @@
# conquer.mix filename list for the game asset browser
#appear1.aud:
#beepy6.aud:
#briefing.aud:
#clock1.aud:
#country1.aud:
#country4.aud:
#keystrok.aud:
#mapwipe2.aud:
#mapwipe5.aud:
#scold1.aud:
#sfx4.aud:
#toney10.aud:
#toney4.aud:
#toney7.aud:
#type.fnt:
#alibackh.pcx:
#sovback.pcx:
120mm.shp:
1tnk.shp:light tank
2tnk.shp:medium tank
3tnk.shp:heavy tank
4tnk.shp:mammoth tank
50cal.shp:
afld.shp:
afldmake.shp:
agun.shp:
agunmake.shp:
apc.shp:
apwr.shp:
apwrmake.shp:
armor.shp:
art-exp1.shp:
arty.shp:
atek.shp:
atekmake.shp:
atomicdn.shp:
atomicup.shp:
atomsfx.shp:
badr.shp:
bar3bhr.shp:
bar3blu.shp:
bar3red.shp:
bar3rhr.shp:
barb.shp:
barl.shp:
barr.shp:
barrmake.shp:
bio.shp:
biomake.shp:
bomb.shp:
bomblet.shp:
brik.shp:
brl3.shp:
burn-l.shp:
burn-m.shp:
burn-s.shp:
ca.shp:
chronbox.shp:
countrya.shp:
countrye.shp:
credsa.shp:
credsahr.shp:
credsu.shp:
credsuhr.shp:
cycl.shp:
dd.shp:
deviator.shp:
dog.shp:
dogbullt.shp:
dollar.shp:
dome.shp:
domemake.shp:
dragon.shp:
earth.shp:
ebtn-dn.shp:
electdog.shp:
empulse.shp:
fact.shp:
factmake.shp:
fb1.shp:
fb2.shp:
fball1.shp:
fcom.shp:
fenc.shp:
fire1.shp:
fire2.shp:
fire3.shp:
fire4.shp:
fix.shp:
fixmake.shp:
flagfly.shp:
flak.shp:
flmspt.shp:
fpls.shp:
fpower.shp:
frag1.shp:
ftnk.shp:
ftur.shp:
fturmake.shp:
gap.shp:
gapmake.shp:
gpsbox.shp:
gun.shp:
gunfire.shp:
gunmake.shp:
h2o_exp1.shp:
h2o_exp2.shp:
h2o_exp3.shp:
harv.shp:
heli.shp:
hind.shp:
hisc1-hr.shp:
hisc2-hr.shp:
hiscore1.shp:
hiscore2.shp:
hosp.shp:
hospmake.shp:
hpad.shp:
hpadmake.shp:
invulbox.shp:
invun.shp:
iron.shp:
ironmake.shp:
jeep.shp:
kenn.shp:
kennmake.shp:
litning.shp:
lrotor.shp:
lst.shp:
mcv.shp:
mgg.shp:
mgun.shp:
mhq.shp:
mig.shp:
mine.shp:
minigun.shp:
minp.shp:
minpmake.shp:
minv.shp:
minvmake.shp:
miss.shp:
missile.shp:
missile2.shp:
mlrs.shp:
mnly.shp:
mrj.shp:
napalm1.shp:
napalm2.shp:
napalm3.shp:
orca.shp:
parabomb.shp:
parabox.shp:
parach.shp:
patriot.shp:
pbox.shp:
pboxmake.shp:
pdox.shp:
pdoxmake.shp:
piff.shp:
piffpiff.shp:
powr.shp:
powrmake.shp:
pt.shp:
pumpmake.shp:
radarfrm.shp:
rapid.shp:
rrotor.shp:
sam.shp:
samfire.shp:
sammake.shp:
sbag.shp:
scrate.shp:
select.shp:
repair.shp:
shadow.shp:
silo.shp:
silomake.shp:
smig.shp:
smoke_m.shp:
smokey.shp:
smokland.shp:
sonarbox.shp:
speed.shp:
spen.shp:
spenmake.shp:
sputdoor.shp:
sputnik.shp:
ss.shp:
ssam.shp:
stealth2.shp:
stek.shp:
stekmake.shp:
stnk.shp:
syrd.shp:
syrdmake.shp:
tent.shp:
tentmake.shp:
time.shp:
timehr.shp:
tquake.shp:
tran.shp:
truk.shp:
tsla.shp:
tslamake.shp:
turr.shp:
twinkle1.shp:
twinkle2.shp:
twinkle3.shp:
u2.shp:
v19.shp:
v2.shp:
v2rl.shp:
veh-hit1.shp:
veh-hit2.shp:
wake.shp:
wcrate.shp:
weap.shp:
weap2.shp:
weapmake.shp:
wood.shp:
wwcrate.shp:
yak.shp:
#trans.icn:
#ali-tran.wsa:
#mltiplyr.wsa:
#sov-tran.wsa:

View File

@@ -1,135 +0,0 @@
# hires.mix filename list for the game asset browser
1tnkicon.shp:
2tnkicon.shp:
3tnkicon.shp:
4tnkicon.shp:
afldicon.shp:
agunicon.shp:
apcicon.shp:
apwricon.shp:
artyicon.shp:
atekicon.shp:
atomicon.shp:
badricon.shp:
barricon.shp:
brikicon.shp:
btn-dn.shp:
btn-pl.shp:
btn-st.shp:
btn-up.shp:
c1.shp:
c2.shp:
caicon.shp:
camicon.shp:
chan.shp:
clock.shp:
dd-bkgnd.shp:
dd-botm.shp:
dd-crnr.shp:
dd-edge.shp:
dd-left.shp:
dd-right.shp:
dd-top.shp:
ddicon.shp:
delphi.shp:
dogicon.shp:
domeicon.shp:
domficon.shp:
e1.shp:
e1icon.shp:
e2.shp:
e2icon.shp:
e3.shp:
e3icon.shp:
e4.shp:
e4icon.shp:
e5.shp:
e6.shp:
e6icon.shp:
e7.shp:
e7icon.shp:
einstein.shp:
facficon.shp:
facticon.shp:
fencicon.shp:
fixicon.shp:
fturicon.shp:
gapicon.shp:
gnrl.shp:
gpssicon.shp:
gunicon.shp:
harvicon.shp:
hboxicon.shp:
heliicon.shp:
hindicon.shp:
hpadicon.shp:
infxicon.shp:
ironicon.shp:
jeepicon.shp:
kennicon.shp:
lsticon.shp:
map.shp:
mcvicon.shp:
medi.shp:
mediicon.shp:
mggicon.shp:
migicon.shp:
mnlyicon.shp:
mrjicon.shp:
msloicon.shp:
natoradr.shp:
nradrfrm.shp:
pbmbicon.shp:
pboxicon.shp:
pdoxicon.shp:
pinficon.shp:
pips.shp:
power.shp:
powerbar.shp:
powricon.shp:
procicon.shp:
pticon.shp:
pulse.shp:
repair.shp:
samicon.shp:
sbagicon.shp:
sell.shp:
side1na.shp:
side1us.shp:
side2na.shp:
side2us.shp:
side3na.shp:
side3us.shp:
#sidebar.shp:will crash
siloicon.shp:
smigicon.shp:
sonricon.shp:
speficon.shp:
spenicon.shp:
spy.shp:
spyicon.shp:
ssicon.shp:
stekicon.shp:
strip.shp:
stripdn.shp:
stripna.shp:
stripup.shp:
stripus.shp:
syrdicon.shp:
syrficon.shp:
tabs.shp:
tenticon.shp:
thf.shp:
thficon.shp:
tranicon.shp:
trukicon.shp:
tslaicon.shp:
u2icon.shp:
uradrfrm.shp:
ussrradr.shp:
v2rlicon.shp:
warpicon.shp:
weaficon.shp:
weapicon.shp:
yakicon.shp:
#mouse.shp:Dune II format

View File

@@ -29,10 +29,6 @@ Packages:
~movies1.mix
~movies2.mix
PackageContents:
mods/ra/mix/conquer.yaml
mods/ra/mix/hires.yaml
Rules:
mods/ra/rules/defaults.yaml
mods/ra/rules/system.yaml