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 }