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 | } |