These implementations are often backed by a Dictionary, and tend to live a long time after being loaded. Ensure TrimExcess is called on the backing dictionaries to reduce the long term memory usage. In some cases, we can also preallocate the dictionary size for efficiency.
142 lines
3.7 KiB
C#
142 lines
3.7 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// This class supports loading unencrypted V3 .meg files using
|
|
/// reference documentation from here https://modtools.petrolution.net/docs/MegFileFormat.
|
|
/// </summary>
|
|
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<string, (uint Offset, int Length)> contents;
|
|
|
|
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<string>();
|
|
|
|
// 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
|
|
contents = new Dictionary<string, (uint Offset, int Length)>((int)numFiles);
|
|
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);
|
|
}
|
|
|
|
contents.TrimExcess();
|
|
|
|
if (s.Position != headerSize)
|
|
throw new Exception("Expected to be at data start offset");
|
|
}
|
|
|
|
public string Name { get; }
|
|
|
|
public IEnumerable<string> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|