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