1 module zthor.filetable; 2 3 import std.typecons : Flag, Yes; 4 import zgrf.compression; 5 import zthor.constants; 6 import zthor.exception; 7 import zthor.types; 8 9 void fill(ref THOR thor, ref THORFiletable files, const(wstring)[] filters = [], 10 Flag!"includeRemovals" includeRemovals = Yes.includeRemovals) 11 in (thor.filehandle.isOpen(), "Filehandle must be open to read the filetable") 12 in (thor.filesize > thor.header.filetableOffset, "THOR filesize < Filetable offset") 13 { 14 import core.stdc.stdio : SEEK_SET; 15 import std.exception : enforce; 16 import std.format : format; 17 import std.string : toLower; 18 import std.zlib : crc32; 19 20 thor.filehandle.seek(thor.header.filetableOffset, SEEK_SET); 21 22 ubyte[] zbuffer = new ubyte[thor.header.filetableCompressedSize]; 23 24 auto actualRead = thor.filehandle.rawRead(zbuffer); 25 26 enforce!ThorException(actualRead.length == thor.header.filetableCompressedSize, 27 format("Read compressed filetable size (%d bytes) differs from header (%d bytes)", 28 actualRead.length, thor.header.filetableCompressedSize)); 29 30 ubyte[] buffer = uncompress(zbuffer); 31 32 ulong offset = 0; 33 34 if (filters.length > 0) 35 { 36 foreach (i; 0 .. thor.header.filecount) 37 { 38 THORFile file = extractFile(buffer, offset); 39 file.thor = &thor; 40 if (inFilter(file, filters) && (includeRemovals || !(file.flags & FileFlags.remove))) 41 { 42 file.hash = crc32(0, file.name.toLower); 43 files.require(file.hash, file); 44 } 45 } 46 } 47 else 48 { 49 foreach (i; 0 .. thor.header.filecount) 50 { 51 THORFile file = extractFile(buffer, offset); 52 file.thor = &thor; 53 if (includeRemovals || !(file.flags & FileFlags.remove)) 54 { 55 file.hash = crc32(0, file.name.toLower); 56 files.require(file.hash, file); 57 } 58 } 59 } 60 } 61 62 void fillSingleFile(ref THOR thor, ref THORFiletable files, const(wstring)[] filters = [], 63 Flag!"includeRemovals" includeRemovals = Yes.includeRemovals) 64 in (thor.filehandle.isOpen(), "Filehandle must be open to read the filetable") 65 in (thor.filesize > cast(ushort) thor.header.containerMode, "THOR filesize < Header size") 66 { 67 import core.stdc.stdio : SEEK_SET; 68 import std.bitmanip : read; 69 import std.string : toLower; 70 import std.system : Endian; 71 import std.zlib : crc32; 72 73 thor.filehandle.seek(cast(ushort) thor.header.containerMode, SEEK_SET); 74 75 THORFile file; 76 77 auto buffer = thor.filehandle.rawRead(new ubyte[4]); 78 file.compressed_size = read!(uint, Endian.littleEndian)(buffer); 79 buffer = thor.filehandle.rawRead(new ubyte[4]); 80 file.size = read!(uint, Endian.littleEndian)(buffer); 81 82 const filenameLen = thor.filehandle.rawRead(new ubyte[1])[0]; 83 84 if (filenameLen > 0) 85 { 86 file.rawName = new ubyte[filenameLen]; 87 thor.filehandle.rawRead(file.rawName); 88 89 import zencoding.windows949 : fromWindows949; 90 91 file.name = fromWindows949(file.rawName); 92 } 93 94 file.offset = cast(uint) thor.filehandle.tell(); 95 file.thor = &thor; 96 97 if (filters.length > 0 && inFilter(file, filters) && (includeRemovals || !(file.flags & FileFlags.remove))) 98 { 99 file.hash = crc32(0, file.name.toLower); 100 files.require(file.hash, file); 101 } 102 else if (filters.length == 0 && (includeRemovals || !(file.flags & FileFlags.remove))) 103 { 104 file.hash = crc32(0, file.name.toLower); 105 files.require(file.hash, file); 106 } 107 } 108 109 private THORFile extractFile(ref ubyte[] buffer, ref ulong offset) 110 { 111 THORFile file; 112 113 const filenameLen = buffer[offset]; 114 offset += 1; 115 116 if (filenameLen > 0) 117 { 118 file.rawName = buffer[offset .. (offset + filenameLen)].dup; 119 offset += filenameLen; 120 121 import zencoding.windows949 : fromWindows949; 122 123 file.name = fromWindows949(file.rawName); 124 } 125 126 import std.conv : to; 127 import zthor.constants : FileFlags; 128 129 file.flags = buffer[offset].to!FileFlags; 130 offset += 1; 131 132 if (file.flags != FileFlags.remove) 133 { 134 import std.system : Endian; 135 import std.bitmanip : peek; 136 137 file.offset = buffer.peek!(uint, Endian.littleEndian)(&offset); 138 file.compressed_size = buffer.peek!(uint, Endian.littleEndian)(&offset); 139 file.size = buffer.peek!(uint, Endian.littleEndian)(&offset); 140 } 141 142 return file; 143 } 144 145 /** 146 * Checks if a given [THORFile] matches one of our filters. 147 * 148 * The check performs a case insensitive glob match. 149 * 150 * Params: 151 * file = The file to check 152 * filterList = The array of filters to check against 153 * 154 * Returns: 155 * Whether the file matches one of the provided filters 156 */ 157 bool inFilter(in ref THORFile file, in ref const(wstring)[] filterList) 158 { 159 if (filterList.length == 0) 160 { 161 return true; 162 } 163 164 foreach (const filterString; filterList) 165 { 166 import std.path : globMatch, CaseSensitive; 167 168 if (globMatch!(CaseSensitive.no)(file.name, filterString)) 169 { 170 return true; 171 } 172 } 173 return false; 174 } 175