diff --git a/OpenRA.Game/FileSystem/FileSystem.cs b/OpenRA.Game/FileSystem/FileSystem.cs index 6a2b17b05a..49386e7cec 100644 --- a/OpenRA.Game/FileSystem/FileSystem.cs +++ b/OpenRA.Game/FileSystem/FileSystem.cs @@ -21,6 +21,8 @@ namespace OpenRA.FileSystem { public IEnumerable MountedPackages { get { return mountedPackages.Keys; } } readonly Dictionary mountedPackages = new Dictionary(); + readonly Dictionary explicitMounts = new Dictionary(); + Cache> fileIndex = new Cache>(_ => new List()); public IReadWritePackage CreatePackage(string filename, Dictionary content) @@ -54,7 +56,13 @@ namespace OpenRA.FileSystem if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase)) return new InstallShieldCABExtractor(this, filename); - return new Folder(filename); + IReadOnlyPackage parent; + string subPath = null; + if (TryGetPackageContaining(filename, out parent, out subPath)) + if (parent is Folder) + return new Folder(Path.Combine(((Folder)parent).Name, subPath)); + + return new Folder(Platform.ResolvePath(filename)); } public IReadWritePackage OpenWritablePackage(string filename) @@ -67,15 +75,13 @@ namespace OpenRA.FileSystem return new Folder(filename); } - public void Mount(string name) + public void Mount(string name, string explicitName = null) { var optional = name.StartsWith("~"); if (optional) name = name.Substring(1); - name = Platform.ResolvePath(name); - - Action a = () => Mount(OpenPackage(name)); + Action a = () => Mount(OpenPackage(name), explicitName); if (optional) try { a(); } catch { } @@ -83,7 +89,7 @@ namespace OpenRA.FileSystem a(); } - public void Mount(IReadOnlyPackage package) + public void Mount(IReadOnlyPackage package, string explicitName = null) { var mountCount = 0; if (mountedPackages.TryGetValue(package, out mountCount)) @@ -101,6 +107,10 @@ namespace OpenRA.FileSystem { // Mounting the package for the first time mountedPackages.Add(package, 1); + + if (explicitName != null) + explicitMounts.Add(explicitName, package); + foreach (var filename in package.Contents) fileIndex[filename].Add(package); } @@ -118,6 +128,7 @@ namespace OpenRA.FileSystem packagesForFile.RemoveAll(p => p == package); mountedPackages.Remove(package); + explicitMounts.Remove(package.Name); package.Dispose(); } else @@ -132,14 +143,15 @@ namespace OpenRA.FileSystem package.Dispose(); mountedPackages.Clear(); + explicitMounts.Clear(); fileIndex = new Cache>(_ => new List()); } public void LoadFromManifest(Manifest manifest) { UnmountAll(); - foreach (var pkg in manifest.Packages) - Mount(pkg); + foreach (var kv in manifest.Packages) + Mount(kv.Key, kv.Value); } Stream GetFromCache(string filename) @@ -162,58 +174,64 @@ namespace OpenRA.FileSystem return s; } - public bool TryOpen(string name, out Stream s) + public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename) { - var filename = name; - var packageName = string.Empty; - - // Used for faction specific packages; rule out false positive on Windows C:\ drive notation - var explicitPackage = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name)); - if (explicitPackage) + var explicitSplit = path.IndexOf('|'); + if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package)) { - var divide = name.Split(':'); - packageName = divide.First(); - filename = divide.Last(); + filename = path.Substring(explicitSplit + 1); + return true; } - // Check the cache for a quick lookup if the package name is unknown - // TODO: This disables caching for explicit package requests - if (filename.IndexOfAny(new[] { '/', '\\' }) == -1 && !explicitPackage) + package = fileIndex[path].LastOrDefault(x => x.Contains(path)); + filename = path; + + return package != null; + } + + public bool TryOpen(string filename, out Stream s) + { + var explicitSplit = filename.IndexOf('|'); + if (explicitSplit > 0) { - s = GetFromCache(filename); - if (s != null) - return true; + IReadOnlyPackage explicitPackage; + if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage)) + { + s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1)); + if (s != null) + return true; + } } + s = GetFromCache(filename); + if (s != null) + return true; + // Ask each package individually - IReadOnlyPackage package; - if (explicitPackage && !string.IsNullOrEmpty(packageName)) - package = mountedPackages.Keys.LastOrDefault(x => x.Name == packageName); - else - package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename)); - + // TODO: This fallback can be removed once the filesystem cleanups are complete + var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename)); if (package != null) { s = package.GetStream(filename); - return true; + return s != null; } s = null; return false; } - public bool Exists(string name) + public bool Exists(string filename) { - var explicitPackage = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name)); - if (explicitPackage) + var explicitSplit = filename.IndexOf('|'); + if (explicitSplit > 0) { - var divide = name.Split(':'); - var packageName = divide.First(); - var filename = divide.Last(); - return mountedPackages.Keys.Where(n => n.Name == packageName).Any(f => f.Contains(filename)); + IReadOnlyPackage explicitPackage; + if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage)) + if (explicitPackage.Contains(filename.Substring(explicitSplit + 1))) + return true; } - else - return mountedPackages.Keys.Any(f => f.Contains(name)); + + return fileIndex.ContainsKey(filename); } } } diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index fa53465508..e1938a0b2b 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -35,11 +35,12 @@ namespace OpenRA { public readonly ModMetadata Mod; public readonly string[] - Packages, Rules, ServerTraits, + Rules, ServerTraits, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Weapons, Voices, Notifications, Music, Translations, TileSets, ChromeMetrics, MapCompatibility, Missions; + public readonly IReadOnlyDictionary Packages; public readonly IReadOnlyDictionary MapFolders; public readonly MiniYaml LoadScreen; public readonly MiniYaml LobbyDefaults; @@ -70,7 +71,11 @@ namespace OpenRA // TODO: Use fieldloader MapFolders = YamlDictionary(yaml, "MapFolders", true); - Packages = YamlList(yaml, "Packages", true); + + MiniYaml packages; + if (yaml.TryGetValue("Packages", out packages)) + Packages = packages.ToDictionary(x => Platform.ResolvePath(x), x => x.Value).AsReadOnly(); + Rules = YamlList(yaml, "Rules", true); Sequences = YamlList(yaml, "Sequences", true); VoxelSequences = YamlList(yaml, "VoxelSequences", true); diff --git a/mods/ts/audio/speech-generic.yaml b/mods/ts/audio/speech-generic.yaml index 9346869516..b95ebdefde 100644 --- a/mods/ts/audio/speech-generic.yaml +++ b/mods/ts/audio/speech-generic.yaml @@ -1,7 +1,7 @@ Speech: Prefixes: - gdi: speech01.mix: - nod: speech02.mix: + gdi: speech-gdi| + nod: speech-nod| Notifications: AirUnitLost: 00-i074 AirstrikeReady: 00-n160 diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 69242bbca7..5badd432ed 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -32,13 +32,13 @@ Packages: isosnow.mix isotemp.mix local.mix - sidec01.mix - sidec02.mix + sidec01.mix: sidebar-gdi + sidec02.mix: sidebar-nod sno.mix snow.mix sounds.mix - speech01.mix # EVA - speech02.mix # Cabal + speech01.mix: speech-gdi + speech02.mix: speech-nod tem.mix temperat.mix # Firestorm diff --git a/mods/ts/rules/palettes.yaml b/mods/ts/rules/palettes.yaml index 8bcf18bf14..50a462827d 100644 --- a/mods/ts/rules/palettes.yaml +++ b/mods/ts/rules/palettes.yaml @@ -43,7 +43,7 @@ Filename: anim.pal PaletteFromFile@sidebar: Name: sidebar - Filename: sidec02.mix:sidebar.pal + Filename: sidebar-nod|sidebar.pal PaletteFromPaletteWithAlpha@clock: Name: iconclock BasePalette: sidebar diff --git a/mods/ts/sequences/infantry.yaml b/mods/ts/sequences/infantry.yaml index 52411d9e59..23aaed6c00 100644 --- a/mods/ts/sequences/infantry.yaml +++ b/mods/ts/sequences/infantry.yaml @@ -66,7 +66,7 @@ e1.gdi: ShadowStart: 190 die6: electro Length: * - icon: sidec01.mix:e1icon + icon: sidebar-gdi|e1icon e1.nod: Defaults: e1 @@ -136,7 +136,7 @@ e1.nod: ShadowStart: 190 die6: electro Length: * - icon: sidec02.mix:e1icon + icon: sidebar-nod|e1icon e2: Defaults: @@ -477,7 +477,7 @@ engineer.gdi: ShadowStart: 190 die6: electro Length: * - icon: sidec01.mix:engnicon + icon: sidebar-gdi|engnicon engineer.nod: Defaults: engineer @@ -539,7 +539,7 @@ engineer.nod: ShadowStart: 190 die6: electro Length: * - icon: sidec02.mix:engnicon + icon: sidebar-nod|engnicon umagon: Defaults: diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml index fd76b94139..10ee981ff3 100644 --- a/mods/ts/sequences/structures.yaml +++ b/mods/ts/sequences/structures.yaml @@ -912,7 +912,7 @@ napuls.gdi: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec01.mix:empicon + icon: sidebar-gdi|empicon Offset: 0, 0 UseTilesetCode: false @@ -940,7 +940,7 @@ napuls.nod: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec02.mix:empicon + icon: sidebar-nod|empicon Offset: 0, 0 UseTilesetCode: false @@ -1190,7 +1190,7 @@ proc.gdi: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec01.mix:reficon + icon: sidebar-gdi|reficon Offset: 0, 0 UseTilesetCode: false @@ -1232,7 +1232,7 @@ proc.nod: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec02.mix:reficon + icon: sidebar-nod|reficon Offset: 0, 0 UseTilesetCode: false @@ -1323,7 +1323,7 @@ gasilo.gdi: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec01.mix:siloicon + icon: sidebar-gdi|siloicon Offset: 0, 0 UseTilesetCode: false @@ -1367,7 +1367,7 @@ gasilo.nod: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec02.mix:siloicon + icon: sidebar-nod|siloicon Offset: 0, 0 UseTilesetCode: false @@ -1431,7 +1431,7 @@ gadept.gdi: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec01.mix:fixicon + icon: sidebar-gdi|fixicon Offset: 0, 0 UseTilesetCode: false @@ -1495,7 +1495,7 @@ gadept.nod: UseTilesetCode: false ZOffset: 512 BlendMode: Additive - icon: sidec02.mix:fixicon + icon: sidebar-nod|fixicon Offset: 76, 66 UseTilesetCode: false diff --git a/mods/ts/sequences/vehicles.yaml b/mods/ts/sequences/vehicles.yaml index 4844ad4391..753bc8a19f 100644 --- a/mods/ts/sequences/vehicles.yaml +++ b/mods/ts/sequences/vehicles.yaml @@ -2,13 +2,13 @@ mcv.gdi: emp-overlay: emp_fx01 Length: * BlendMode: Additive - icon: sidec01.mix:mcvicon + icon: sidebar-gdi|mcvicon mcv.nod: emp-overlay: emp_fx01 Length: * BlendMode: Additive - icon: sidec02.mix:mcvicon + icon: sidebar-nod|mcvicon apc: emp-overlay: emp_fx01 @@ -22,7 +22,7 @@ harv.gdi: BlendMode: Additive harvest: harvestr Length: * - icon: sidec01.mix:harvicon + icon: sidebar-gdi|harvicon harv.nod: emp-overlay: emp_fx01 @@ -30,7 +30,7 @@ harv.nod: BlendMode: Additive harvest: harvestr Length: * - icon: sidec02.mix:harvicon + icon: sidebar-nod|harvicon hvr: emp-overlay: emp_fx01 @@ -56,7 +56,7 @@ lpst.gdi: emp-overlay: emp_fx01 Length: * BlendMode: Additive - icon: sidec01.mix:lpsticon + icon: sidebar-gdi|lpsticon lpst.nod: idle: gadpsa @@ -69,7 +69,7 @@ lpst.nod: emp-overlay: emp_fx01 Length: * BlendMode: Additive - icon: sidec02.mix:lpsticon + icon: sidebar-nod|lpsticon repair: emp-overlay: emp_fx01