View difference between Paste ID: DycDYxPe and LxCfike0
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
}