Clean up MixFile code.

This commit is contained in:
Paul Chote
2013-05-02 22:04:03 +12:00
parent 5af983a409
commit 9a9dc7eab1

View File

@@ -27,12 +27,11 @@ namespace OpenRA.FileFormats
public class MixFile : IFolder
{
readonly Dictionary<uint, PackageEntry> index;
readonly bool isRmix, isEncrypted;
readonly long dataStart;
readonly Stream s;
int priority;
// Create a new MixFile
// Save a mix to disk with the given contents
public MixFile(string filename, int priority, Dictionary<string, byte[]> contents)
{
this.priority = priority;
@@ -40,6 +39,8 @@ namespace OpenRA.FileFormats
File.Delete(filename);
s = File.Create(filename);
// TODO: Add a local mix database.dat for compatibility with XCC Mixer
Write(contents);
}
@@ -48,110 +49,94 @@ namespace OpenRA.FileFormats
this.priority = priority;
s = FileSystem.Open(filename);
BinaryReader reader = new BinaryReader(s);
uint signature = reader.ReadUInt32();
// Detect format type
var reader = new BinaryReader(s);
var isCncMix = reader.ReadUInt16() != 0;
var isEncrypted = false;
isRmix = 0 == (signature & ~(uint)(MixFileFlags.Checksum | MixFileFlags.Encrypted));
if (isRmix)
{
isEncrypted = 0 != (signature & (uint)MixFileFlags.Encrypted);
if (isEncrypted)
{
index = ParseRaHeader(s, out dataStart).ToDictionaryWithConflictLog(x => x.Hash,
"MixFile.RaHeader of {0}".F(filename), null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)
);
return;
}
}
// The C&C mix format doesn't contain any flags or encryption
if (isCncMix)
s.Seek(0, SeekOrigin.Begin);
else
s.Seek( 0, SeekOrigin.Begin );
isEncrypted = (reader.ReadUInt16() & 0x2) != 0;
isEncrypted = false;
index = ParseTdHeader(s, out dataStart).ToDictionaryWithConflictLog(x => x.Hash,
"MixFile.TdHeader of {0}".F(filename), null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)
var header = isEncrypted ? DecryptHeader(s) : s;
index = ParseHeader(header).ToDictionaryWithConflictLog(x => x.Hash,
"{0} ({1} format, Encrypted: {2})".F(filename, (isCncMix ? "C&C" : "RA/TS/RA2"), isEncrypted),
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)
);
dataStart = s.Position;
}
const long headerStart = 84;
List<PackageEntry> ParseRaHeader(Stream s, out long dataStart)
List<PackageEntry> ParseHeader(Stream s)
{
BinaryReader reader = new BinaryReader(s);
byte[] keyblock = reader.ReadBytes(80);
byte[] blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
var items = new List<PackageEntry>();
var reader = new BinaryReader(s);
var numFiles = reader.ReadUInt16();
/*uint dataSize = */reader.ReadUInt32();
uint[] h = ReadBlocks(reader, 1);
for (var i = 0; i < numFiles; i++)
items.Add(new PackageEntry(reader));
Blowfish fish = new Blowfish(blowfishKey);
MemoryStream ms = Decrypt( h, fish );
BinaryReader reader2 = new BinaryReader(ms);
ushort numFiles = reader2.ReadUInt16();
reader2.ReadUInt32(); /*datasize*/
s.Position = headerStart;
reader = new BinaryReader(s);
// Round up to the next full block
int blockCount = (13 + numFiles*PackageEntry.Size)/8;
h = ReadBlocks(reader, blockCount);
ms = Decrypt( h, fish );
dataStart = headerStart + 8*blockCount;
long ds;
return ParseTdHeader( ms, out ds );
return items;
}
static MemoryStream Decrypt( uint[] h, Blowfish fish )
MemoryStream DecryptHeader(Stream s)
{
uint[] decrypted = fish.Decrypt( h );
var reader = new BinaryReader(s);
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter( ms );
foreach( uint t in decrypted )
writer.Write( t );
// Decrypt blowfish key
var keyblock = reader.ReadBytes(80);
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
var fish = new Blowfish(blowfishKey);
// Decrypt first block to work out the header length
var headerStart = s.Position;
var ms = Decrypt(ReadBlocks(s, headerStart, 1), fish);
var numFiles = new BinaryReader(ms).ReadUInt16();
// Decrypt the full header - round bytes up to a full block
var blockCount = (13 + numFiles*PackageEntry.Size)/8;
return Decrypt(ReadBlocks(s, headerStart, 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.Position = 0;
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
uint[] ReadBlocks(BinaryReader r, int count)
uint[] ReadBlocks(Stream s, long offset, int count)
{
s.Seek(offset, SeekOrigin.Begin);
var r = new BinaryReader(s);
// A block is a single encryption unit (represented as two 32-bit integers)
uint[] ret = new uint[2*count];
for (int i = 0; i < ret.Length; i++)
var ret = new uint[2*count];
for (var i = 0; i < ret.Length; i++)
ret[i] = r.ReadUInt32();
return ret;
}
List<PackageEntry> ParseTdHeader(Stream s, out long dataStart)
{
List<PackageEntry> items = new List<PackageEntry>();
BinaryReader reader = new BinaryReader(s);
ushort numFiles = reader.ReadUInt16();
/*uint dataSize = */reader.ReadUInt32();
for (int i = 0; i < numFiles; i++)
items.Add(new PackageEntry(reader));
dataStart = s.Position;
return items;
}
public Stream GetContent(uint hash)
{
PackageEntry e;
if (!index.TryGetValue(hash, out e))
return null;
s.Seek( dataStart + e.Offset, SeekOrigin.Begin );
byte[] data = new byte[ e.Length ];
s.Read( data, 0, (int)e.Length );
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
var data = new byte[e.Length];
s.Read(data, 0, (int)e.Length);
return new MemoryStream(data);
}
@@ -191,14 +176,14 @@ namespace OpenRA.FileFormats
var items = new List<PackageEntry>();
foreach (var kv in contents)
{
uint length = (uint)kv.Value.Length;
uint hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key));
var length = (uint)kv.Value.Length;
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key));
items.Add(new PackageEntry(hash, dataSize, length));
dataSize += length;
}
// Write the new file
s.Seek(0,SeekOrigin.Begin);
s.Seek(0, SeekOrigin.Begin);
using (var writer = new BinaryWriter(s))
{
// Write file header
@@ -215,11 +200,4 @@ namespace OpenRA.FileFormats
}
}
}
[Flags]
enum MixFileFlags : uint
{
Checksum = 0x10000,
Encrypted = 0x20000,
}
}