diff --git a/OpenRA.FileFormats/FileFormats/XccGlobalDatabase.cs b/OpenRA.FileFormats/FileFormats/XccGlobalDatabase.cs new file mode 100644 index 0000000000..14a0a0cae9 --- /dev/null +++ b/OpenRA.FileFormats/FileFormats/XccGlobalDatabase.cs @@ -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(); + 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 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(); + } + } +} \ No newline at end of file diff --git a/OpenRA.FileFormats/FileFormats/XccLocalDatabase.cs b/OpenRA.FileFormats/FileFormats/XccLocalDatabase.cs new file mode 100644 index 0000000000..a117823d18 --- /dev/null +++ b/OpenRA.FileFormats/FileFormats/XccLocalDatabase.cs @@ -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 c; + while ((c = reader.ReadChar()) != 0) + chars.Add(c); + + Entries[i] = new string(chars.ToArray()); + } + } + + public XccLocalDatabase(IEnumerable 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(); + } + } +} \ No newline at end of file diff --git a/OpenRA.FileFormats/Filesystem/FileSystem.cs b/OpenRA.FileFormats/Filesystem/FileSystem.cs index c2e1004c45..26e6af6f92 100644 --- a/OpenRA.FileFormats/Filesystem/FileSystem.cs +++ b/OpenRA.FileFormats/Filesystem/FileSystem.cs @@ -18,9 +18,9 @@ namespace OpenRA.FileFormats { public static class FileSystem { - static List MountedFolders = new List(); - - static Cache> allFiles = new Cache>( _ => new List() ); + public static List MountedFolders = new List(); + static Cache> classicHashIndex = new Cache>( _ => new List() ); + static Cache> crcHashIndex = new Cache>( _ => new List() ); public static List FolderPaths = new List(); @@ -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 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("(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>( _ => new List() ); + classicHashIndex = new Cache>(_ => new List()); + crcHashIndex = new Cache>(_ => new List()); } 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> 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; } diff --git a/OpenRA.FileFormats/Filesystem/Folder.cs b/OpenRA.FileFormats/Filesystem/Folder.cs index 805e2746de..c109392501 100644 --- a/OpenRA.FileFormats/Filesystem/Folder.cs +++ b/OpenRA.FileFormats/Filesystem/Folder.cs @@ -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 contents) @@ -45,13 +44,21 @@ namespace OpenRA.FileFormats catch { return null; } } - public IEnumerable AllFileHashes() + public IEnumerable 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 CrcHashes() + { + yield break; + } + + public IEnumerable 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 contents) { diff --git a/OpenRA.FileFormats/Filesystem/InstallShieldPackage.cs b/OpenRA.FileFormats/Filesystem/InstallShieldPackage.cs index cf59751aa9..f58f86f1ef 100644 --- a/OpenRA.FileFormats/Filesystem/InstallShieldPackage.cs +++ b/OpenRA.FileFormats/Filesystem/InstallShieldPackage.cs @@ -18,13 +18,17 @@ namespace OpenRA.FileFormats public class InstallShieldPackage : IFolder { readonly Dictionary index = new Dictionary(); + readonly List 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(); 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 AllFileHashes() + public IEnumerable ClassicHashes() { return index.Keys; } + public IEnumerable CrcHashes() + { + yield break; + } + + public IEnumerable 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 contents) { diff --git a/OpenRA.FileFormats/Filesystem/MixFile.cs b/OpenRA.FileFormats/Filesystem/MixFile.cs index 10679de967..2dff06dfaa 100644 --- a/OpenRA.FileFormats/Filesystem/MixFile.cs +++ b/OpenRA.FileFormats/Filesystem/MixFile.cs @@ -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 AllFileHashes(); + IEnumerable ClassicHashes(); + IEnumerable CrcHashes(); + IEnumerable AllFileNames(); void Write(Dictionary contents); int Priority { get; } + string Name { get; } } public class MixFile : IFolder @@ -29,24 +33,31 @@ namespace OpenRA.FileFormats readonly Dictionary 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 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(); + 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 AllFileHashes() + static readonly uint[] Nothing = {}; + public IEnumerable ClassicHashes() { - return index.Keys; + if (type == PackageHashType.Classic) + return index.Keys; + + return Nothing; + } + + public IEnumerable CrcHashes() + { + if (type == PackageHashType.CRC32) + return index.Keys; + + return Nothing; + } + + public IEnumerable AllFileNames() + { + var lookup = new Dictionary(); + 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 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; } diff --git a/OpenRA.FileFormats/Filesystem/ZipFile.cs b/OpenRA.FileFormats/Filesystem/ZipFile.cs index caa0158e5e..3a1ddd1733 100644 --- a/OpenRA.FileFormats/Filesystem/ZipFile.cs +++ b/OpenRA.FileFormats/Filesystem/ZipFile.cs @@ -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 AllFileHashes() + public IEnumerable ClassicHashes() { foreach(ZipEntry entry in pkg) - yield return PackageEntry.HashFilename(entry.Name); + yield return PackageEntry.HashFilename(entry.Name, PackageHashType.Classic); + } + + public IEnumerable CrcHashes() + { + yield break; + } + + public IEnumerable 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 contents) { diff --git a/OpenRA.FileFormats/Manifest.cs b/OpenRA.FileFormats/Manifest.cs index 989574cb62..4fc63cc62f 100644 --- a/OpenRA.FileFormats/Manifest.cs +++ b/OpenRA.FileFormats/Manifest.cs @@ -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 Packages; public readonly MiniYaml LoadScreen; public readonly Dictionary> 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"); diff --git a/OpenRA.FileFormats/OpenRA.FileFormats.csproj b/OpenRA.FileFormats/OpenRA.FileFormats.csproj index 74f0b867e0..57030eeb48 100644 --- a/OpenRA.FileFormats/OpenRA.FileFormats.csproj +++ b/OpenRA.FileFormats/OpenRA.FileFormats.csproj @@ -139,6 +139,8 @@ + + diff --git a/OpenRA.FileFormats/PackageEntry.cs b/OpenRA.FileFormats/PackageEntry.cs index 2534444578..5839099838 100644 --- a/OpenRA.FileFormats/PackageEntry.cs +++ b/OpenRA.FileFormats/PackageEntry.cs @@ -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 Names = new Dictionary(); 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); } diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 28b59218cb..a484b2c2df 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -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; diff --git a/OpenRA.Game/GameRules/Rules.cs b/OpenRA.Game/GameRules/Rules.cs index 2b6842ff01..1215311c6e 100755 --- a/OpenRA.Game/GameRules/Rules.cs +++ b/OpenRA.Game/GameRules/Rules.cs @@ -25,7 +25,6 @@ namespace OpenRA public static Dictionary Music; public static Dictionary Movies; public static Dictionary TileSets; - public static Dictionary 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(), (k, _) => new MusicInfo(k.Key, k.Value)); Movies = LoadYamlRules(m.Movies, new List(), (k, v) => k.Value.Value); - PackageContents = LoadYamlRules(m.PackageContents, new List(), (k, v) => k.Value.Value); TileSets = new Dictionary(); foreach (var file in m.TileSets) diff --git a/OpenRA.Game/Map.cs b/OpenRA.Game/Map.cs index 7c37f94870..1bae44f2ba 100644 --- a/OpenRA.Game/Map.cs +++ b/OpenRA.Game/Map.cs @@ -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"); diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 51d9f11fb8..ac7e753e5f 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -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); diff --git a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs index 91da7972ee..d55e524501 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/AssetBrowserLogic.cs @@ -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 AvailableShps = new List(); + IFolder AssetSource = null; + List AvailableShps = new List(); [ObjectCreator.UseCtor] public AssetBrowserLogic(Widget widget, Action onExit, World world) @@ -42,9 +40,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic var sourceDropdown = panel.Get("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("SPRITE"); @@ -177,30 +182,22 @@ namespace OpenRA.Mods.RA.Widgets.Logic panel.Get("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("TITLE").GetText = () => filename; + item.Get("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() - { - { "Folders", SourceType.Folders }, - { "Packages", SourceType.Packages }, - }; - - Func setupItem = (o, itemTemplate) => + Func setupItem = (source, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => AssetSource == options[o], - () => { AssetSource = options[o]; PopulateAssetList(); }); - item.Get("LABEL").GetText = () => o; + () => AssetSource == source, + () => { AssetSource = source; PopulateAssetList(); }); + item.Get("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); - } } } } diff --git a/global mix database.dat b/global mix database.dat new file mode 100644 index 0000000000..6c7259f768 Binary files /dev/null and b/global mix database.dat differ diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 9b6a4fa3fb..a306881e58 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -29,8 +29,6 @@ Packages: ~scores2.mix ~transit.mix -PackageContents: - Rules: mods/cnc/rules/defaults.yaml mods/cnc/rules/system.yaml diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index dbebb7c280..f34fc4a7ac 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -21,8 +21,6 @@ Packages: ~main.mix conquer.mix -PackageContents: - Rules: mods/d2k/rules/system.yaml mods/d2k/rules/defaults.yaml diff --git a/mods/ra/mix/conquer.yaml b/mods/ra/mix/conquer.yaml deleted file mode 100644 index 83bba64bc1..0000000000 --- a/mods/ra/mix/conquer.yaml +++ /dev/null @@ -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: \ No newline at end of file diff --git a/mods/ra/mix/hires.yaml b/mods/ra/mix/hires.yaml deleted file mode 100644 index 61bf1bcb3e..0000000000 --- a/mods/ra/mix/hires.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 76cb2a1749..fc3c76de28 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -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