#region Copyright & License Information /* * Copyright 2007-2022 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 OpenRA.FileSystem; using OpenRA.Primitives; namespace OpenRA.Mods.Cnc.FileSystem { /// /// This class supports loading unencrypted V3 .meg files using /// reference documentation from here https://modtools.petrolution.net/docs/MegFileFormat /// public class MegV3Loader : IPackageLoader { const uint UnencryptedMegID = 0xFFFFFFFF; // Float value 0.99, but it is simpler to read and compare as an integer const uint MegVersion = 0x3F7D70A4; public bool TryParsePackage(Stream s, string filename, OpenRA.FileSystem.FileSystem context, out IReadOnlyPackage package) { var position = s.Position; var id = s.ReadUInt32(); var version = s.ReadUInt32(); s.Position = position; if (id != UnencryptedMegID || version != MegVersion) { package = null; return false; } package = new MegFile(s, filename); return true; } public sealed class MegFile : IReadOnlyPackage { readonly Stream s; readonly Dictionary contents = new Dictionary(); public MegFile(Stream s, string filename) { Name = filename; this.s = s; var id = s.ReadUInt32(); var version = s.ReadUInt32(); if (id != UnencryptedMegID || version != MegVersion) throw new Exception("Invalid file signature for meg file"); var headerSize = s.ReadUInt32(); var numStrings = s.ReadUInt32(); var numFiles = s.ReadUInt32(); var stringsSize = s.ReadUInt32(); var stringsStart = s.Position; var filenames = new List(); // The file names are an indexed array of strings for (var i = 0; i < numStrings; i++) { var length = s.ReadUInt16(); filenames.Add(s.ReadASCII(length)); } // The header indicates where we should be, so verify it if (s.Position != stringsSize + stringsStart) throw new Exception("File name table in .meg file inconsistent"); // Now we load each file entry and associated info for (var i = 0; i < numFiles; i++) { // Ignore flags, crc, index s.Position += 10; var size = s.ReadUInt32(); var offset = s.ReadUInt32(); var nameIndex = s.ReadUInt16(); contents[filenames[nameIndex]] = (offset, (int)size); } if (s.Position != headerSize) throw new Exception("Expected to be at data start offset"); } public string Name { get; } public IEnumerable Contents => contents.Keys; public bool Contains(string filename) { return contents.ContainsKey(filename); } public void Dispose() { s.Dispose(); } public Stream GetStream(string filename) { // Look up the index of the filename if (!contents.TryGetValue(filename, out var index)) return null; return SegmentStream.CreateWithoutOwningStream(s, index.Offset, index.Length); } public IReadOnlyPackage OpenPackage(string filename, OpenRA.FileSystem.FileSystem context) { var childStream = GetStream(filename); if (childStream == null) return null; if (context.TryParsePackage(childStream, filename, out var package)) return package; childStream.Dispose(); return null; } } } }