SHOW:
|
|
- or go back to the newest paste.
| 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 | - | binaryReader.BaseStream.Seek(entry.Offset, SeekOrigin.Begin); |
| 76 | + | |
| 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 | - | Console.ReadKey(false); |
| 161 | + | |
| 162 | } | |
| 163 | } |