1 module zgrf.grf;
2 
3 import zgrf.types;
4 
5 /**
6  * Parses the header of the given GRF.
7  *
8  * The header will be available in the input grf
9  * class via grf.header.
10  *
11  * Params:
12  *  grf = The GRF file to read the header from
13  *
14  * Returns:
15  *  Input grf for easy chaining
16  */
17 ref GRF readHeader(return ref GRF grf)
18 in (grf.filehandle.isOpen(), "Filehandle of grf file must be open to read header")
19 {
20     if (grf.header.grfVersion > 0)
21     {
22         // Header has already been parsed
23         return grf;
24     }
25     import core.stdc.stdio : SEEK_SET;
26 
27     grf.filehandle.seek(0, SEEK_SET);
28 
29     import zgrf.constants : HEADER_LEN;
30 
31     ubyte[HEADER_LEN] buffer;
32     grf.filehandle.rawRead(buffer);
33 
34     import std.bitmanip : littleEndianToNative;
35 
36     grf.header.signature = buffer[0 .. 15];
37     grf.header.encryption = buffer[15 .. 30];
38     grf.header.filetableOffset = littleEndianToNative!uint(buffer[30 .. 34]) + HEADER_LEN;
39     grf.header.seed = littleEndianToNative!uint(buffer[34 .. 38]);
40     grf.header.rawFilecount = littleEndianToNative!uint(buffer[38 .. 42]);
41     grf.header.grfVersion = littleEndianToNative!uint(buffer[42 .. 46]);
42     grf.header.filecount = grf.header.rawFilecount - grf.header.seed - 7;
43 
44     return grf;
45 }
46 
47 /// ditto
48 ref VirtualGRF readHeader(return ref VirtualGRF vgrf)
49 {
50     foreach (ref grf; vgrf.grfs)
51     {
52         grf.readHeader();
53     }
54 
55     return vgrf;
56 }
57 
58 /**
59  * Parses the filetable of the given grf.
60  * If filters is provided then only the files which
61  * matches the filters will be loaded.
62  *
63  * The filters do not support wildcards it just
64  * checks if the filename starts with the same
65  * characters.
66  *
67  * Params:
68  *  grf = The grf to read the filetable from
69  *  filters = Array of filters
70  *
71  * Returns:
72  *  Input grf for easy chaining
73  */
74 ref GRF readFiletable(return ref GRF grf, const(wstring)[] filters = [])
75 in (grf.filehandle.isOpen(), "Filehandle of grf file must be open to read filetable")
76 in (grf.header.grfVersion > 0, "GRF header needs to be parsed")
77 {
78     if (grf.header.grfVersion > 0x100 && grf.header.grfVersion < 0x200)
79     {
80         import zgrf.filetable.version1xx;
81 
82         fill(grf, filters);
83     }
84     else if (grf.header.grfVersion >= 0x200)
85     {
86         import zgrf.filetable.version2xx;
87 
88         fill(grf, filters);
89     }
90 
91     return grf;
92 }
93 
94 /// ditto
95 ref VirtualGRF readFiletable(return ref VirtualGRF vgrf, const(wstring)[] filters = [])
96 {
97     foreach (ref grf; vgrf.grfs)
98     {
99         if (grf.header.grfVersion > 0x100 && grf.header.grfVersion < 0x200)
100         {
101             import zgrf.filetable.version1xx;
102 
103             fill(grf, vgrf.files, filters);
104         }
105         else
106         {
107             import zgrf.filetable.version2xx;
108 
109             fill(grf, vgrf.files, filters);
110         }
111     }
112 
113     return vgrf;
114 }
115 
116 /**
117  * Parses a grf file given optional filters.
118  *
119  * Calls [readHeader] and [readFiletable] on the input grf.
120  *
121  * Params:
122  *  grf = The GRF file to parse
123  *  filters = The filters to use when parsing the filetable
124  *
125  * Returns:
126  *  Input grf for easy chaining
127  */
128 ref GRF parse(return ref GRF grf, const(wstring)[] filters = [])
129 in (grf.filehandle.isOpen(), "Filehandle of grf file must be open valid to be able to parse")
130 {
131     return grf.readHeader().readFiletable(filters);
132 }
133 
134 /// ditto
135 ref VirtualGRF parse(return ref VirtualGRF vgrf, const(wstring)[] filters = [])
136 {
137     return vgrf.readHeader().readFiletable(filters);
138 }
139 
140 /**
141  * Get the unencrypted and uncompressed data of a file inside the input grf.
142  *
143  * This function will always allocate new memory and always call the decrypting
144  * and uncompressing routines. Means the returned data is not stored anywhere.
145  *
146  * Params:
147  *  grf = The grf to read the file from
148  *  file = The metadata about the file to be read
149  *
150  * Returns:
151  *  The unencrypted and uncompressed file data
152  */
153 ubyte[] getFileData(ref GRF grf, ref GRFFile file)
154 in (grf.filehandle.isOpen(), "Filehandle of grf file must be open to read file data")
155 {
156     import zgrf.constants : HEADER_LEN, FileFlags;
157 
158     grf.filehandle.seek(file.offset + HEADER_LEN);
159     scope ubyte[] encryptedData = new ubyte[file.compressed_size_padded];
160     grf.filehandle.rawRead(encryptedData);
161 
162     if (file.compressed_size_padded % 8 > 0)
163     {
164         // If the encrypted (supposibly padded) filesize is not a multiple of 8 bytes
165         // just fill the data up with zeros until it is
166         encryptedData.length += 8 - (file.compressed_size_padded % 8);
167     }
168 
169     scope ubyte[] decryptedData;
170     if (file.flags & FileFlags.MIXCRYPT)
171     {
172         import zgrf.crypto.mixcrypt;
173 
174         decryptedData = zgrf.crypto.mixcrypt.decrypt(encryptedData, [],
175                 file.compressed_size);
176     }
177     else if (file.flags & FileFlags.DES)
178     {
179         import zgrf.crypto.desbroken;
180 
181         decryptedData = encryptedData.dup;
182         const minsize = encryptedData.length < 20 * 8 ? encryptedData.length : 20 * 8;
183         const beginningData = zgrf.crypto.desbroken.decrypt(encryptedData[0 .. minsize], [],
184                 file.compressed_size);
185         decryptedData[0 .. beginningData.length] = beginningData;
186     }
187     else
188     {
189         decryptedData = encryptedData;
190     }
191 
192     import zgrf.compression : uncompress;
193 
194     file.data = uncompress(decryptedData, file.size);
195 
196     return file.data;
197 }
198 
199 /// ditto
200 ubyte[] getFileData(ref GRFFile file)
201 {
202     if (file.grf is null)
203     {
204         return [];
205     }
206 
207     return getFileData(*file.grf, file);
208 }
209 
210 /// ditto
211 ubyte[] getFileData(ref GRF grf, const wstring filename)
212 {
213     import std.zlib;
214 
215     const uint hash = crc32(0, filename);
216     if (hash in grf.files)
217     {
218         return getFileData(grf, grf.files[hash]);
219     }
220     else
221     {
222         return [];
223     }
224 }
225 
226 /// ditto
227 ubyte[] getFileData(ref VirtualGRF vgrf, const wstring filename)
228 {
229     import std.zlib;
230 
231     const uint hash = crc32(0, filename);
232     if (hash in vgrf.files)
233     {
234         auto file = vgrf.files[hash];
235         return getFileData(*file.grf, file);
236     }
237     else
238     {
239         return [];
240     }
241 }
242 
243 /**
244  * Open the internal file handle of the grf.
245  *
246  * Params:
247  *  grf = The grf to open the filehandle for
248  */
249 void open(ref GRF grf)
250 in (!grf.filehandle.isOpen(), "Filehandle is already open")
251 {
252     grf.filehandle.open(grf.filename, "rb");
253 }
254 
255 /// ditto
256 void open(ref VirtualGRF vgrf)
257 {
258     foreach (ref grf; vgrf.grfs)
259     {
260         grf.open();
261     }
262 }
263 
264 /**
265  * Close the internal file handle of the grf.
266  *
267  * Params:
268  *  grf = The grf to close the filehandle for
269  */
270 void close(ref GRF grf)
271 in (grf.filehandle.isOpen(), "Filehandle is not open, cannot close")
272 {
273     grf.filehandle.close();
274 }
275 
276 /// ditto
277 void close(ref VirtualGRF vgrf)
278 {
279     foreach (ref grf; vgrf.grfs)
280     {
281         grf.close();
282     }
283 }
284