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