1 module zgrf.filetable.version2xx;
2 
3 import core.stdc.stdio : SEEK_SET, SEEK_END;
4 import std.system : Endian;
5 import std.zlib : crc32;
6 import std.uni : toLower;
7 import std.bitmanip : peek, read;
8 
9 import zgrf.constants;
10 import zgrf.types;
11 import zgrf.filetable.common;
12 
13 /**
14  * Fills the provided GRFFiletable files with [GRFFile] of the input grf.
15  *
16  * Params:
17  *  grf = The grf to read the files from
18  *  files = The target filetable to store the [GRFFile] to
19  *  filters = The filters to check for when reading the files
20  */
21 void fill(ref GRF grf, ref GRFFiletable files, const(wstring)[] filters = [])
22 in (grf.filehandle.isOpen(), "Filehandle must be open to read the filetable")
23 in (grf.header.grfVersion >= 0x200, "Minimum GRF version allowed for this filetable is 0x200")
24 {
25     grf.filehandle.seek(grf.header.filetableOffset, SEEK_SET);
26 
27     ubyte[] filetableSizesBuf = new ubyte[uint.sizeof * 2];
28     grf.filehandle.rawRead(filetableSizesBuf);
29 
30     const compressed_size = filetableSizesBuf.read!(uint, Endian.littleEndian);
31     const uncompressed_size = filetableSizesBuf.read!(uint, Endian.littleEndian);
32 
33     ubyte[] zbuffer = new ubyte[compressed_size];
34 
35     grf.filehandle.rawRead(zbuffer);
36 
37     ulong offset = 0;
38 
39     import zgrf.compression : uncompress;
40 
41     ubyte[] buffer = uncompress(zbuffer, uncompressed_size);
42     assert(buffer.length == uncompressed_size);
43 
44     if (filters.length > 0)
45     {
46         foreach (i; 0 .. grf.header.filecount)
47         {
48             GRFFile file = extractFile(buffer, offset, grf.header.grfVersion);
49             file.offset_ft += grf.header.filetableOffset + HEADER_LEN;
50             file.grf = &grf;
51             if (inFilter(file, filters) && !isDirectory(file))
52             {
53                 file.hash = crc32(0, file.name.toLower);
54                 files.require(file.hash, file);
55             }
56         }
57     }
58     else
59     {
60         foreach (i; 0 .. grf.header.filecount)
61         {
62             GRFFile file = extractFile(buffer, offset, grf.header.grfVersion);
63             if (!isDirectory(file))
64             {
65                 file.offset_ft += grf.header.filetableOffset + HEADER_LEN;
66                 file.grf = &grf;
67                 file.hash = crc32(0, file.name.toLower);
68                 files.require(file.hash, file);
69             }
70         }
71     }
72 }
73 
74 /// ditto
75 void fill(ref GRF grf, const(wstring)[] filters = [])
76 in (grf.filehandle.isOpen(), "Filehandle must be open to read the filetable")
77 in (grf.header.grfVersion >= 0x200, "Minimum GRF version allowed for this filetable is 0x200")
78 {
79     fill(grf, grf.files, filters);
80 }
81 
82 private GRFFile extractFile(ref ubyte[] buffer, ref ulong offset, uint /*grfVersion*/ ) pure
83 {
84 
85     GRFFile file;
86 
87     file.offset_ft = offset;
88 
89     import core.stdc.string : strlen;
90 
91     auto filenameLength = strlen(cast(char*)(buffer.ptr + offset)) + 1;
92     import std.conv : to;
93 
94     assert(filenameLength <= FILENAME_LENGTH,
95             "Filename too long (" ~ filenameLength.to!string ~ ") at offset " ~ offset.to!string);
96 
97     file.rawName = buffer[offset .. (offset + filenameLength)].dup;
98 
99     offset += filenameLength;
100 
101     file.compressed_size = buffer.peek!(uint, Endian.littleEndian)(&offset);
102     file.compressed_size_padded = buffer.peek!(uint, Endian.littleEndian)(&offset);
103     file.size = buffer.peek!(uint, Endian.littleEndian)(&offset);
104     file.flags = cast(FileFlags) buffer.peek!ubyte(&offset);
105     file.offset = buffer.peek!(uint, Endian.littleEndian)(&offset);
106     import zencoding.windows949 : fromWindows949;
107 
108     file.name = fromWindows949(file.rawName);
109 
110     return file;
111 }