1 module zgrf.filetable.version1xx; 2 3 import core.stdc.stdio : SEEK_SET, SEEK_END; 4 import std.zlib : crc32; 5 import std.uni : toLower; 6 7 import zgrf.constants; 8 import zgrf.types; 9 import zgrf.filetable.common; 10 11 private const(wstring)[4] specialFileExtensions = [ 12 ".gnd", 13 ".gat", 14 ".act", 15 ".str" 16 ]; 17 18 private bool hasSpecialExtension(const wstring name) 19 { 20 import std.uni : toLower; 21 import std.algorithm : endsWith; 22 23 auto lowerCaseName = name.toLower; 24 foreach (ext; specialFileExtensions) 25 { 26 if (lowerCaseName.endsWith(ext)) 27 { 28 return true; 29 } 30 } 31 return false; 32 } 33 34 /** 35 * Fills the provided GRFFiletable files with [GRFFile] of the input grf. 36 * 37 * Params: 38 * grf = The grf to read the files from 39 * files = The target filetable to store the [GRFFile] to 40 * filters = The filters to check for when reading the files 41 */ 42 void fill(ref GRF grf, ref GRFFiletable files, const(wstring)[] filters = []) 43 in (grf.filehandle.isOpen(), "Filehandle must be open to read the filetable") 44 in (grf.header.grfVersion <= 0x103, "Maximum GRF version allowed for this filetable is 0x103") 45 in (grf.filesize > grf.header.filetableOffset, "GRF filesize < Filetable offset") 46 { 47 grf.filehandle.seek(grf.header.filetableOffset, SEEK_SET); 48 49 const filetablesize = grf.filesize - grf.header.filetableOffset; 50 ubyte[] buffer = new ubyte[filetablesize]; 51 grf.filehandle.rawRead(buffer); 52 53 ulong offset = 0; 54 55 if (filters.length > 0) 56 { 57 foreach (i; 0 .. grf.header.filecount) 58 { 59 GRFFile file = extractFile(buffer, offset, grf.header.grfVersion); 60 if (inFilter(file, filters) && !isDirectory(file)) 61 { 62 file.offset_ft += grf.header.filetableOffset + HEADER_LEN; 63 file.hash = crc32(0, file.name.toLower); 64 file.grf = &grf; 65 if (hasSpecialExtension(file.name)) 66 { 67 file.flags |= FileFlags.DES; 68 } 69 else 70 { 71 file.flags |= FileFlags.MIXCRYPT; 72 } 73 74 files.require(file.hash, file); 75 } 76 } 77 } 78 else 79 { 80 foreach (i; 0 .. grf.header.filecount) 81 { 82 GRFFile file = extractFile(buffer, offset, grf.header.grfVersion); 83 if (!isDirectory(file)) 84 { 85 file.offset_ft += grf.header.filetableOffset + HEADER_LEN; 86 file.hash = crc32(0, file.name.toLower); 87 file.grf = &grf; 88 if (hasSpecialExtension(file.name)) 89 { 90 file.flags |= FileFlags.DES; 91 } 92 else 93 { 94 file.flags |= FileFlags.MIXCRYPT; 95 } 96 files.require(file.hash, file); 97 } 98 } 99 } 100 } 101 102 /// ditto 103 void fill(ref GRF grf, const(wstring)[] filters = []) 104 in (grf.filehandle.isOpen(), "Filehandle must be open to read the filetable") 105 in (grf.header.grfVersion <= 0x103, "Maximum GRF version allowed for this filetable is 0x103") 106 { 107 fill(grf, grf.files, filters); 108 } 109 110 private GRFFile extractFile(ref ubyte[] buffer, ref ulong offset, uint grfVersion) pure 111 { 112 import std.system : Endian; 113 import std.bitmanip : peek; 114 115 GRFFile file; 116 117 file.offset_ft = offset; 118 119 const filenameLength = buffer.peek!(uint, Endian.littleEndian)(&offset); 120 assert(filenameLength <= FILENAME_LENGTH); 121 122 ulong filenameLength2; 123 124 if (grfVersion < 0x101) 125 { 126 import core.stdc.string : strlen; 127 128 filenameLength2 = strlen(cast(char*)(buffer.ptr + offset)); 129 } 130 else 131 { 132 offset += 2; 133 filenameLength2 = filenameLength - (filenameLength % 8); 134 } 135 136 auto filenameBuffer = buffer[offset .. (offset + filenameLength2)]; 137 138 import zgrf.bits : swapNibbles; 139 140 swapNibbles(filenameBuffer); 141 142 if (grfVersion >= 0x101) { 143 import zgrf.crypto.mixcrypt; 144 145 filenameBuffer = decrypt(filenameBuffer, [], filenameLength2); 146 offset -= 2; 147 } 148 file.rawName = filenameBuffer.dup; 149 150 offset += filenameLength; 151 152 file.compressed_size = buffer.peek!(uint, Endian.littleEndian)(&offset); 153 file.compressed_size_padded = buffer.peek!(uint, Endian.littleEndian)(&offset); 154 file.size = buffer.peek!(uint, Endian.littleEndian)(&offset); 155 file.flags = cast(FileFlags) buffer.peek!ubyte(&offset); 156 file.offset = buffer.peek!(uint, Endian.littleEndian)(&offset); 157 import zencoding.windows949 : fromWindows949; 158 159 file.name = fromWindows949(file.rawName); 160 161 // Update compressed sizes 162 file.compressed_size -= file.size + 0x02CB; 163 file.compressed_size_padded -= 0x92CB; 164 165 return file; 166 }