Skip to content

Instantly share code, notes, and snippets.

@penev92
Created June 9, 2015 13:18
Show Gist options
  • Save penev92/f19f0e875648d887a8f4 to your computer and use it in GitHub Desktop.
Save penev92/f19f0e875648d887a8f4 to your computer and use it in GitHub Desktop.
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
namespace OpenRA.FileSystem
{
// Useful literature:
// https://msdn.microsoft.com/en-us/library/bb417343.aspx#cabinet_format
// http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf
// https://msdn.microsoft.com/en-us/library/cc483131%28v=exchg.80%29.aspx
public sealed class MicrosoftCABFile : IFolder, IDisposable
{
struct CFHeader
{
public uint cbCabinet; /* size of this cabinet file in bytes */
public uint coffFiles; /* absolute offset of the first CFFILE entry */
public byte versionMinor; /* cabinet file format version, minor */
public byte versionMajor; /* cabinet file format version, major */
public ushort cFolders; /* number of CFFOLDER entries in this cabinet */
public ushort cFiles; /* number of CFFILE entries in this cabinet */
public ushort flags; /* cabinet file option indicators */
public ushort setID; /* must be the same for all cabinets in a set */
public ushort iCabinet; /* number of this cabinet file in a set */
public ushort cbCFHeader; /* (optional) size of per-cabinet reserved area */
public byte cbCFFolder; /* (optional) size of per-folder reserved area */
public byte cbCFData; /* (optional) size of per-datablock reserved area */
public byte[] abReserve; /* (optional) per-cabinet reserved area */
public byte[] szCabinetPrev; /* (optional) name of previous cabinet file */
public byte[] szDiskPrev; /* (optional) name of previous disk */
public byte[] szCabinetNext; /* (optional) name of next cabinet file */
public byte[] szDiskNext; /* (optional) name of next disk */
public CFHeader(BinaryReader reader)
{
cbCFHeader = 0;
cbCFFolder = 0;
cbCFData = 0;
szCabinetPrev = szDiskPrev = szCabinetNext = szDiskNext = new byte[0];
// Reserved field, set to zero.
reader.ReadInt32();
cbCabinet = reader.ReadUInt32();
// Reserved field, set to zero.
reader.ReadInt32();
coffFiles = reader.ReadUInt32();
// Reserved field, set to zero.
reader.ReadInt32();
versionMinor = reader.ReadByte();
versionMajor = reader.ReadByte();
cFolders = reader.ReadUInt16();
cFiles = reader.ReadUInt16();
flags = reader.ReadUInt16();
var hasPreviousCabinet = (flags & 1) != 0;
var hasNextCabinet = (flags & 2) != 0;
var hasReserved = (flags & 4) != 0;
setID = reader.ReadUInt16();
iCabinet = reader.ReadUInt16();
if (hasReserved)
{
cbCFHeader = reader.ReadUInt16();
cbCFFolder = reader.ReadByte();
cbCFData = reader.ReadByte();
}
abReserve = reader.ReadBytes(cbCFHeader);
if (hasPreviousCabinet)
{
szCabinetPrev = reader.ReadBytes(255);
szDiskPrev = reader.ReadBytes(255);
}
if (hasNextCabinet)
{
szCabinetNext = reader.ReadBytes(255);
szDiskNext = reader.ReadBytes(255);
}
}
}
struct CFFolder
{
public uint coffCabStart; /* absolute offset of the first CFDATA block in this folder */
public ushort cCFData; /* number of CFDATA blocks in this folder */
ushort typeCompress; /* compression type indicator */
byte[] abReserve; /* (optional) per-folder reserved area */
public CFFolder(BinaryReader reader, byte cbCFFolder)
{
coffCabStart = reader.ReadUInt32();
cCFData = reader.ReadUInt16();
typeCompress = reader.ReadUInt16();
abReserve = reader.ReadBytes(cbCFFolder);
}
}
struct CFFile
{
public string FileName;
public uint cbFile; /* uncompressed size of this file in bytes */
public uint uoffFolderStart; /* uncompressed offset of this file in the folder */
public ushort iFolder; /* index into the CFFOLDER area */
ushort date; /* date stamp for this file */
ushort time; /* time stamp for this file */
ushort attribs; /* attribute flags for this file */
public CFFile(BinaryReader reader)
{
cbFile = reader.ReadUInt32();
uoffFolderStart = reader.ReadUInt32();
iFolder = reader.ReadUInt16();
date = reader.ReadUInt16();
time = reader.ReadUInt16();
attribs = reader.ReadUInt16();
var name = new List<byte>();
while (true)
{
var current = reader.ReadByte();
if (current == '\0')
break;
name.Add(current);
}
FileName = System.Text.Encoding.UTF8.GetString(name.ToArray());
}
}
struct CFData
{
uint csum; /* checksum of this CFDATA entry */
ushort cbData; /* number of compressed bytes in this block */
public ushort cbUncomp; /* number of uncompressed bytes in this block */
byte[] abReserve; /* (optional) per-datablock reserved area */
public byte[] Data; /* compressed data bytes */
public CFData(BinaryReader reader, ushort cbCFHeader, byte cbCFData)
{
csum = reader.ReadUInt32();
cbData = reader.ReadUInt16();
cbUncomp = reader.ReadUInt16();
abReserve = reader.ReadBytes(cbCFData);
// A 0x43, 0x4B header
var signature = reader.ReadChars(2);
if (string.Concat(signature) != "CK")
csum = 0;
Data = reader.ReadBytes(cbData-2);
}
}
string filename;
public MicrosoftCABFile(string filename)
{
using (var stream = GlobalFileSystem.Open(filename))
using (var reader = new BinaryReader(stream))
{
var signature = reader.ReadChars(4);
if (string.Concat(signature) != "MSCF")
throw new InvalidDataException("Not a Microsoft CAB package!");
var header = new CFHeader(reader);
var cfFolders = new List<CFFolder>();
for (var i = 0; i < header.cFolders; i++)
cfFolders.Add(new CFFolder(reader, header.cbCFFolder));
var cfFiles = new List<CFFile>();
stream.Seek(header.coffFiles, SeekOrigin.Begin);
for (var i = 0; i < header.cFiles; i++)
cfFiles.Add(new CFFile(reader));
var folderNumber = 0;
foreach (var folder in cfFolders)
{
stream.Seek(folder.coffCabStart, SeekOrigin.Begin);
var cfData = new List<CFData>();
for (var i = 0; i < folder.cCFData; i++)
{
var cfdata = new CFData(reader, header.cbCFHeader, header.cbCFData);
cfData.Add(cfdata);
}
var fileName = cfFiles.First(x => x.iFolder == folderNumber).FileName;
//var inf = new List<byte>();
using (var inflaterStream = new InflaterStream(cfData))
using (var outputStream = File.OpenWrite(fileName))
using (var originalStream = File.OpenRead(@"\ra2_original\language.mix"))
{
var buffer = new byte[32768];
while (inflaterStream.CanRead)
{
var output = inflaterStream.Read(buffer, 0, 0);
//inf.AddRange(buffer.Take(output));
var write = buffer.Take(output).ToArray();
//var compare = originalStream.ReadBytes(output);
//for (var i = 0; i < output; i++)
// if (write[i] != compare[i])
// Console.WriteLine("asdf");
outputStream.Write(write, 0, output);
outputStream.Flush();
}
}
folderNumber++;
}
}
}
public static byte[] FlateDecode(byte[] inp, bool strict)
{
byte[] inflatedData;
using (var stream = new MemoryStream(inp))
using (var inflater = new InflaterInputStream(stream, new Inflater(true)))
using (var outputStream = new MemoryStream())
{
var b = new byte[strict ? 4092 : 1];
try
{
int n;
while ((n = inflater.Read(b, 0, b.Length)) > 0)
outputStream.Write(b, 0, n);
inflatedData = outputStream.ToArray();
inflater.Close();
outputStream.Close();
}
catch
{
inflatedData = new byte[0];
if (!strict)
inflatedData = outputStream.ToArray();
inflater.Close();
outputStream.Close();
}
}
return inflatedData;
}
public Stream GetContent(string filename)
{
return null;
}
public IEnumerable<uint> ClassicHashes()
{
yield break;
}
public IEnumerable<uint> CrcHashes()
{
yield break;
}
public IEnumerable<string> AllFileNames()
{
yield break;
}
public bool Exists(string filename)
{
return false;
}
public int Priority { get { return 500 + 0; } }
public string Name { get { return filename; } }
public void Write(Dictionary<string, byte[]> contents)
{
throw new NotImplementedException("Cannot save CAB archives.");
}
public void Dispose()
{
}
class InflaterStream : Stream
{
Queue<CFData> data;
public InflaterStream(IEnumerable<CFData> data)
{
this.data = new Queue<CFData>(data);
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
var d = data.Dequeue();
var tmp = FlateDecode(d.Data, false);
Array.Copy(tmp, buffer, d.cbUncomp);
return d.cbUncomp;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return data.Count > 0; }
}
public override bool CanSeek
{
get { throw new NotImplementedException(); }
}
public override bool CanWrite
{
get { throw new NotImplementedException(); }
}
public override long Length
{
get { return data.Count; }
}
public override long Position { get; set; }
public static byte[] FlateDecode(byte[] inp, bool strict)
{
byte[] inflatedData;
using (var stream = new MemoryStream(inp))
using (var inflater = new InflaterInputStream(stream, new Inflater(true)))
using (var outputStream = new MemoryStream())
{
var b = new byte[strict ? 4092 : 1];
try
{
int n;
while ((n = inflater.Read(b, 0, b.Length)) > 0)
outputStream.Write(b, 0, n);
inflatedData = outputStream.ToArray();
inflater.Close();
outputStream.Close();
}
catch
{
inflatedData = new byte[0];
if (!strict)
inflatedData = outputStream.ToArray();
inflater.Close();
outputStream.Close();
}
}
return inflatedData;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment