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