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