Move classic-mod package loaders and support files to Mods.Cnc.
This commit is contained in:
@@ -1,153 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class AudioBagLoader : IPackageLoader
|
||||
{
|
||||
sealed class BagFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, IdxEntry> index;
|
||||
|
||||
public BagFile(Stream s, List<IdxEntry> entries, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
s.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
|
||||
var waveHeaderMemoryStream = new MemoryStream();
|
||||
|
||||
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
|
||||
|
||||
if ((entry.Flags & 2) > 0)
|
||||
{
|
||||
// PCM
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)1));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
if ((entry.Flags & 8) > 0)
|
||||
{
|
||||
// IMA ADPCM
|
||||
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
|
||||
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
|
||||
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
|
||||
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)17));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Construct a merged stream
|
||||
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
|
||||
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
|
||||
|
||||
return mergedStream;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// A bag file is always accompanied with an .idx counterpart
|
||||
// For example: audio.bag requires the audio.idx file
|
||||
var indexFilename = Path.ChangeExtension(filename, ".idx");
|
||||
List<IdxEntry> entries = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Build the index and dispose the stream, it is no longer needed after this
|
||||
using (var indexStream = context.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
}
|
||||
catch
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new BagFile(s, entries, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class BigLoader : IPackageLoader
|
||||
{
|
||||
sealed class BigFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
try
|
||||
{
|
||||
/* var signature = */ s.ReadASCII(4);
|
||||
|
||||
// Total archive size.
|
||||
s.ReadUInt32();
|
||||
|
||||
var entryCount = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
entryCount = int2.Swap(entryCount);
|
||||
|
||||
// First entry offset? This is apparently bogus for EA's .big files
|
||||
// and we don't have to try seeking there since the entries typically start next in EA's .big files.
|
||||
s.ReadUInt32();
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new Entry(s);
|
||||
index.Add(entry.Path, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
readonly Stream s;
|
||||
readonly uint offset;
|
||||
readonly uint size;
|
||||
public readonly string Path;
|
||||
|
||||
public Entry(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
|
||||
offset = s.ReadUInt32();
|
||||
size = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
offset = int2.Swap(offset);
|
||||
size = int2.Swap(size);
|
||||
}
|
||||
|
||||
Path = s.ReadASCIIZ();
|
||||
}
|
||||
|
||||
public Stream GetData()
|
||||
{
|
||||
s.Position = offset;
|
||||
return new MemoryStream(s.ReadBytes((int)size));
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
return index[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
// Take a peek at the file signature
|
||||
var signature = s.ReadASCII(4);
|
||||
s.Position -= 4;
|
||||
|
||||
if (signature != "BIGF")
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new BigFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
public readonly string Filename;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
public readonly uint SampleRate;
|
||||
public readonly uint Flags;
|
||||
public readonly uint ChunkSize;
|
||||
|
||||
public IdxEntry(Stream s)
|
||||
{
|
||||
var name = s.ReadASCII(16);
|
||||
var pos = name.IndexOf('\0');
|
||||
if (pos != 0)
|
||||
name = name.Substring(0, pos);
|
||||
|
||||
Filename = string.Concat(name, ".wav");
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
SampleRate = s.ReadUInt32();
|
||||
Flags = s.ReadUInt32();
|
||||
ChunkSize = s.ReadUInt32();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(Filename, Offset, Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class MixLoader : IPackageLoader
|
||||
{
|
||||
public sealed class MixFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
|
||||
public MixFile(Stream s, string filename, HashSet<string> allPossibleFilenames)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
try
|
||||
{
|
||||
// Detect format type
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
{
|
||||
long unused;
|
||||
entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused);
|
||||
}
|
||||
else
|
||||
entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart);
|
||||
|
||||
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)), allPossibleFilenames);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries, HashSet<string> allPossibleFilenames)
|
||||
{
|
||||
var classicIndex = new Dictionary<string, PackageEntry>();
|
||||
var crcIndex = new Dictionary<string, PackageEntry>();
|
||||
|
||||
// Try and find a local mix database
|
||||
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
|
||||
var dbNameCRC = PackageEntry.HashFilename("local mix database.dat", PackageHashType.CRC32);
|
||||
foreach (var kv in entries)
|
||||
{
|
||||
if (kv.Key == dbNameClassic || kv.Key == dbNameCRC)
|
||||
{
|
||||
using (var content = GetContent(kv.Value))
|
||||
{
|
||||
var db = new XccLocalDatabase(content);
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var filename in allPossibleFilenames)
|
||||
{
|
||||
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
var crcHash = PackageEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
PackageEntry e;
|
||||
|
||||
if (entries.TryGetValue(classicHash, out e))
|
||||
classicIndex.Add(filename, e);
|
||||
|
||||
if (entries.TryGetValue(crcHash, out e))
|
||||
crcIndex.Add(filename, e);
|
||||
}
|
||||
|
||||
var bestIndex = crcIndex.Count > classicIndex.Count ? crcIndex : classicIndex;
|
||||
|
||||
var unknown = entries.Count - bestIndex.Count;
|
||||
if (unknown > 0)
|
||||
Log.Write("debug", "{0}: failed to resolve filenames for {1} unknown hashes".F(Name, unknown));
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
static List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles * PackageEntry.Size;
|
||||
return items;
|
||||
}
|
||||
|
||||
static MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = s.ReadBytes(80);
|
||||
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
|
||||
var fish = new Blowfish(blowfishKey);
|
||||
|
||||
// Decrypt first block to work out the header length
|
||||
var ms = Decrypt(ReadBlocks(s, offset + 80, 1), fish);
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles * PackageEntry.Size) / 8;
|
||||
headerEnd = offset + 80 + blockCount * 8;
|
||||
|
||||
return Decrypt(ReadBlocks(s, offset + 80, blockCount), fish);
|
||||
}
|
||||
|
||||
static MemoryStream Decrypt(uint[] h, Blowfish fish)
|
||||
{
|
||||
var decrypted = fish.Decrypt(h);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
foreach (var t in decrypted)
|
||||
writer.Write(t);
|
||||
writer.Flush();
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
if (offset + (count * 2) > s.Length)
|
||||
throw new ArgumentException("Bytes to read {0} and offset {1} greater than stream length {2}.".F(count * 2, offset, s.Length));
|
||||
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// A block is a single encryption unit (represented as two 32-bit integers)
|
||||
var ret = new uint[2 * count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Stream GetContent(PackageEntry entry)
|
||||
{
|
||||
Stream parentStream;
|
||||
var baseOffset = dataStart + entry.Offset;
|
||||
var nestedOffset = baseOffset + SegmentStream.GetOverallNestedOffset(s, out parentStream);
|
||||
|
||||
// Special case FileStream - instead of creating an in-memory copy,
|
||||
// just reference the portion of the on-disk file that we need to save memory.
|
||||
// We use GetType instead of 'is' here since we can't handle any derived classes of FileStream.
|
||||
if (parentStream.GetType() == typeof(FileStream))
|
||||
{
|
||||
var path = ((FileStream)parentStream).Name;
|
||||
return new SegmentStream(File.OpenRead(path), nestedOffset, entry.Length);
|
||||
}
|
||||
|
||||
// For all other streams, create a copy in memory.
|
||||
// This uses more memory but is the only way in general to ensure the returned streams won't clash.
|
||||
s.Seek(baseOffset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)entry.Length));
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
return GetContent(e);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, PackageEntry> Index
|
||||
{
|
||||
get
|
||||
{
|
||||
var absoluteIndex = index.ToDictionary(e => e.Key, e => new PackageEntry(e.Value.Hash, (uint)(e.Value.Offset + dataStart), e.Value.Length));
|
||||
return new ReadOnlyDictionary<string, PackageEntry>(absoluteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
var childStream = GetStream(filename);
|
||||
if (childStream == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(childStream, filename, out package))
|
||||
return package;
|
||||
|
||||
childStream.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the global mix database
|
||||
Stream mixDatabase;
|
||||
var allPossibleFilenames = new HashSet<string>();
|
||||
if (context.TryOpen("global mix database.dat", out mixDatabase))
|
||||
using (var db = new XccGlobalDatabase(mixDatabase))
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
|
||||
package = new MixFile(s, filename, allPossibleFilenames);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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 System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public enum PackageHashType { Classic, CRC32 }
|
||||
|
||||
public class PackageEntry
|
||||
{
|
||||
public const int Size = 12;
|
||||
public readonly uint Hash;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public PackageEntry(uint hash, uint offset, uint length)
|
||||
{
|
||||
Hash = hash;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public PackageEntry(Stream s)
|
||||
{
|
||||
Hash = s.ReadUInt32();
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(Hash);
|
||||
w.Write(Offset);
|
||||
w.Write(Length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string filename;
|
||||
if (names.TryGetValue(Hash, out filename))
|
||||
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(filename, Offset, Length);
|
||||
else
|
||||
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
|
||||
}
|
||||
|
||||
public static uint HashFilename(string name, PackageHashType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PackageHashType.Classic:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
if (name.Length % 4 != 0)
|
||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.ASCII.GetBytes(name)))
|
||||
{
|
||||
var len = name.Length >> 2;
|
||||
uint result = 0;
|
||||
|
||||
while (len-- != 0)
|
||||
result = ((result << 1) | (result >> 31)) + ms.ReadUInt32();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
case PackageHashType.CRC32:
|
||||
{
|
||||
name = name.ToUpperInvariant();
|
||||
var l = name.Length;
|
||||
var a = l >> 2;
|
||||
if ((l & 3) != 0)
|
||||
{
|
||||
name += (char)(l - (a << 2));
|
||||
var 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));
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<uint, string> names = new Dictionary<uint, string>();
|
||||
|
||||
public static void AddStandardName(string s)
|
||||
{
|
||||
var hash = HashFilename(s, PackageHashType.Classic); // RA1 and TD
|
||||
names.Add(hash, s);
|
||||
var crcHash = HashFilename(s, PackageHashType.CRC32); // TS
|
||||
names.Add(crcHash, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class PakFileLoader : IPackageLoader
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
sealed class PakFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(Stream stream, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.stream = stream;
|
||||
|
||||
try
|
||||
{
|
||||
var offset = stream.ReadUInt32();
|
||||
while (offset != 0)
|
||||
{
|
||||
var file = stream.ReadASCIIZ();
|
||||
var next = stream.ReadUInt32();
|
||||
var length = (next == 0 ? (uint)stream.Length : next) - offset;
|
||||
|
||||
// Ignore duplicate files
|
||||
if (index.ContainsKey(file))
|
||||
continue;
|
||||
|
||||
index.Add(file, new Entry { Offset = offset, Length = length, Filename = file });
|
||||
offset = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
stream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
var data = stream.ReadBytes((int)entry.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
if (!filename.EndsWith(".pak", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new PakFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user