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