SHARE
TWEET

MTFExtractor

Dr_Asik Mar 3rd, 2012 140 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4.  
  5. namespace MTFExtractor {
  6.     struct DataEntry {
  7.         public string Path;
  8.         public int Offset;
  9.         public int Size;
  10.     }
  11.  
  12.  
  13.     class MtfExtractor {
  14.         List<string> filesToExtract;
  15.         List<DataEntry> dataEntries;
  16.         public MtfExtractor(List<string> mtfFiles) {
  17.             filesToExtract = mtfFiles;
  18.         }
  19.  
  20.         public void Execute() {
  21.             foreach (var f in filesToExtract) {
  22.                 Console.WriteLine("Processing {0}...", f);
  23.                 try {
  24.                     using (var fileStream = new FileStream(f, FileMode.Open))
  25.                     using (var binaryReader = new BinaryReader(fileStream)) {
  26.                         ProcessFile(binaryReader);
  27.                     }
  28.                 }
  29.                 catch (Exception e) {
  30.                     var errorMessage = string.Format("Error processing file {0} : {1}\n", f, e.Message);
  31.                     File.AppendAllText("log.txt", errorMessage);
  32.                     Console.WriteLine(errorMessage);
  33.                 }
  34.             }
  35.         }
  36.  
  37.         void ProcessFile(BinaryReader binaryReader) {
  38.             dataEntries = new List<DataEntry>();
  39.             int numEntries = binaryReader.ReadInt32();
  40.             Console.WriteLine("{0} entries found.", numEntries);
  41.             for (int i = 0; i < numEntries; ++i) {
  42.                 CreateEntry(binaryReader);
  43.             }
  44.  
  45.             for (int i = 0; i < numEntries; ++i) {
  46.                 ProcessEntry(dataEntries[i], binaryReader);
  47.             }
  48.         }
  49.  
  50.         void ProcessEntry(DataEntry dataEntry, BinaryReader binaryReader) {
  51.             binaryReader.BaseStream.Seek(dataEntry.Offset, SeekOrigin.Begin);
  52.             var fileInfo = new FileInfo(dataEntry.Path);
  53.             fileInfo.Directory.Create();
  54.             using (var file = File.Create(dataEntry.Path)) {
  55.                 ProcessData(binaryReader, file, dataEntry);
  56.             }
  57.         }
  58.  
  59.         void ProcessData(BinaryReader binaryReader, FileStream file, DataEntry entry) {
  60.             // Check if it's compressed or not
  61.             int idSize = 4;
  62.             var ID = binaryReader.ReadBytes(idSize);
  63.             // If it's compressed, the first two bytes are AF BE or AE BE
  64.             if (ID[1] == 0xBE && (ID[0] == 0xAF || ID[0] == 0xAE)) {
  65.                 ProcessCompressedData(binaryReader, file, entry);
  66.             }
  67.             else {
  68.                 // otherwise these bytes are part of the data, so seek back
  69.                 binaryReader.BaseStream.Seek(-idSize, SeekOrigin.Current);
  70.                 ProcessUncompressedData(binaryReader, file, entry);
  71.             }
  72.         }
  73.  
  74.         // If it's uncompressed, just copy-paste the data in the destination file.
  75.         void ProcessUncompressedData(BinaryReader binaryReader, FileStream file, DataEntry entry) {
  76.             file.Write(binaryReader.ReadBytes(entry.Size), 0, entry.Size);
  77.         }
  78.  
  79.  
  80.         // If it's compressed... argh.
  81.         // Decompression pseudo-code from http://wiki.xentax.com/index.php/Darkstone
  82.         void ProcessCompressedData(BinaryReader binaryReader, FileStream file, DataEntry entry) {
  83.             // Skip "numBlocks" and "flags", they're of no use
  84.             binaryReader.BaseStream.Seek(8, SeekOrigin.Current);
  85.             var decompressedBytes = new byte[entry.Size];
  86.             var outputIndex = 0;
  87.  
  88.             // "This is repeated until decompressed buffer reach the Decompressed File Length in MTF header."
  89.             while (outputIndex < entry.Size) {
  90.  
  91.                 // "First byte of each chunk describes what to do with next data you read."
  92.                 byte indicator = binaryReader.ReadByte();
  93.  
  94.                 // "You need to check every bit:"
  95.                 for (int j = 0; j < 8; ++j) {
  96.                     if ((indicator & (1 << j)) != 0) {
  97.                         // "If bit=1 then just copy 1 byte from compressed buffer to decompressed buffer"
  98.                         decompressedBytes[outputIndex++] = binaryReader.ReadByte();
  99.                     }
  100.                     else {
  101.                         // "If bit=0 then read 10 bits for X and 6 bits for Y" - "07 0C means X = 3 and Y = 7"
  102.                         // This is very confusing. What is really meant is:
  103.                         // Read 2 bytes, swap them (reading an Int16 in Little-Endian does the trick)
  104.                         ushort word = (ushort)binaryReader.ReadInt16();
  105.  
  106.                         // For some reason, if and only if word == 0, we end up writing past our output buffer.
  107.                         // It appears we can safely ignore all of these.
  108.                         if (word == 0) {
  109.                             break;
  110.                         }
  111.  
  112.                         byte X = (byte)(word >> 10); // The first 6 bits are then X                        
  113.                         ushort Y = (ushort)(word & 0x3FF); // And the last 10 bits are Y
  114.  
  115.                         // "copy X+3 bytes from offset Y of decompressed buffer at the end of decompressed buffer"
  116.                         for (int k = 0; k < X + 3; ++k) {
  117.                             decompressedBytes[outputIndex] = decompressedBytes[outputIndex - Y];
  118.                             ++outputIndex;
  119.                         }
  120.                     }
  121.                 }
  122.             }
  123.             file.Write(decompressedBytes, 0, decompressedBytes.Length);
  124.         }
  125.  
  126.         void CreateEntry(BinaryReader binaryReader) {
  127.             int stringLength = binaryReader.ReadInt32();
  128.             var entry = new DataEntry {
  129.                 Path = UnsafeAsciiBytesToString(binaryReader.ReadBytes(stringLength)),
  130.                 Offset = binaryReader.ReadInt32(),
  131.                 Size = binaryReader.ReadInt32()
  132.             };
  133.             dataEntries.Add(entry);
  134.         }
  135.  
  136.         string UnsafeAsciiBytesToString(byte[] buffer) {
  137.             unsafe {
  138.                 fixed (byte* pAscii = buffer) {
  139.                     return new string((sbyte*)pAscii);
  140.                 }
  141.             }
  142.         }
  143.  
  144.  
  145.         static void Main(string[] args) {
  146.             var mtfFiles = new List<string>();
  147.  
  148.             // If file specified, extract that file, otherwise extract all mtf files in current working directory
  149.             if (args.Length == 1) {
  150.                 mtfFiles.Add(args[0]);
  151.             }
  152.             else {
  153.                 foreach (var f in Directory.EnumerateFiles(Environment.CurrentDirectory)) {
  154.                     if (f.EndsWith(".MTF", StringComparison.CurrentCultureIgnoreCase)) {
  155.                         mtfFiles.Add(f);
  156.                     }
  157.                 }
  158.             }
  159.             new MtfExtractor(mtfFiles).Execute();
  160.             Console.WriteLine("All done!");
  161.         }
  162.     }
  163. }
RAW Paste Data
Pastebin PRO Summer Special!
Get 40% OFF on Pastebin PRO accounts!
Top