magellan/Lucidiot.Magellan/MapArchiveEntry.cs

132 lines
4.9 KiB
C#

using System;
using System.IO;
namespace Lucidiot.Magellan {
/// <summary>
/// Represents a single file entry in an Magellan map archive.
/// </summary>
public struct MapArchiveEntry {
private string _name;
/// <summary>
/// Name of the file, without the extension. Up to 8 ASCII characters.
/// </summary>
public string Name {
get { return _name; }
set {
CheckString("Name", value, 8);
_name = value;
}
}
private string _ext;
/// <summary>
/// File extension. Up to 7 ASCII characters.
/// </summary>
/// <remarks>
/// While the format allows enough space for file extensions that go up to 7 ASCII characters,
/// only two to three characters have been seen in the wild. Not all existing implementations
/// of the format might support more than three characters.
/// </remarks>
public string Extension {
get { return _ext; }
set {
CheckString("Extension", value, 7);
_ext = value;
}
}
public string FullName {
get {
if (Extension == null || Extension == String.Empty)
return Name;
return String.Format("{0}.{1}", Name, Extension);
}
}
private uint _offset;
/// <summary>
/// Offset of the file's contents within the archive.
/// Should be an even number.
/// </summary>
public uint Offset {
get { return _offset; }
set { _offset = value; }
}
private uint _length;
/// <summary>
/// Length of the file's contents within the archive.
/// If the length is an odd number, an extra null byte is added after the file's contents
/// to ensure an even offset for the next file.
/// </summary>
public uint Length {
get { return _length; }
set { _length = value; }
}
private static void CheckString(string paramName, string value, int maxLength) {
if (value.Length > maxLength || !Helpers.IsASCII(value))
throw new ArgumentOutOfRangeException(paramName, "Only " + maxLength + " ASCII characters are supported.");
}
public MapArchiveEntry(string Name, string Extension, uint Offset, uint Length) {
CheckString("Name", Name, 8);
CheckString("Extension", Extension, 7);
this._name = Name;
this._ext = Extension;
this._offset = Offset;
this._length = Length;
}
/// <summary>
/// Parse a single MapArchiveEntry from a BinaryReader, leaving the reader's cursor positioned right after the entry.
/// </summary>
/// <param name="br">The BinaryReader to read from.</param>
/// <returns>A parsed MapArchiveEntry.</returns>
public static MapArchiveEntry Parse(BinaryReader br) {
string name = new string(br.ReadChars(8)).Trim("\0".ToCharArray());
br.ReadByte();
string ext = new string(br.ReadChars(7)).Trim("\0".ToCharArray());
uint offset = br.ReadUInt32();
uint length = br.ReadUInt32();
return new MapArchiveEntry(name, ext, offset, length);
}
/// <summary>
/// Maximum size of the buffer used to transfer data from a BinaryReader to a BinaryWriter during extraction.
/// Note that the buffer may be smaller if the MapArchiveEntry's length is lower than this size.
/// </summary>
private const int EXTRACT_BUFFER_SIZE = 32768;
/// <summary>
/// Extract the file designated by this entry from the archive into a file on the filesystem,
/// named after the name and extension set in the entry.
/// </summary>
/// <param name="br">The BinaryReader to extract from.</param>
/// <param name="destination">Existing directory where the file will be extracted.</param>
/// <exception cref="IOException">An error occurred while reading from the archive, creating the new file, or writing to the new file.</exception>
public void Extract(BinaryReader br, string destination) {
if (Length == 0) {
File.Create(destination).Close();
return;
}
br.BaseStream.Seek(Offset, SeekOrigin.Begin);
using (BinaryWriter bw = new BinaryWriter(File.Open(destination, FileMode.Create))) {
byte[] buffer = new byte[Math.Min(Length, EXTRACT_BUFFER_SIZE)];
uint copied = 0;
while (copied < Length) {
int toCopy = (int)Math.Min(Length - copied, EXTRACT_BUFFER_SIZE);
copied += (uint)br.Read(buffer, 0, toCopy);
bw.Write(buffer, 0, toCopy);
}
}
}
}
}