1 | <?php |
||
2 | /** |
||
3 | * Class DTBLReader |
||
4 | * |
||
5 | * @link https://arctium.io/wiki/index.php?title=WildStar_Client_Database_(.tbl) |
||
6 | * @link https://bitbucket.org/mugadr_m/wildstar-studio/src/973583416d4436e4980de840c2c91cfc5972fb2a/WildstarStudio/DataTable.h |
||
7 | * |
||
8 | * @filesource DTBLReader.php |
||
9 | * @created 04.01.2019 |
||
10 | * @package codemasher\WildstarDB\Archive |
||
11 | * @author smiley <[email protected]> |
||
12 | * @copyright 2019 smiley |
||
13 | * @license MIT |
||
14 | */ |
||
15 | |||
16 | namespace codemasher\WildstarDB\Archive; |
||
17 | |||
18 | use codemasher\WildstarDB\WSDBException; |
||
19 | |||
20 | use function array_fill, count, fread, fseek, ftell, round, substr, unpack; |
||
21 | use function codemasher\WildstarDB\{float, uint32, uint64}; |
||
0 ignored issues
–
show
The type
codemasher\WildstarDB\uint64 was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() The type
codemasher\WildstarDB\float was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||
22 | |||
23 | final class DTBLReader extends ReaderAbstract{ |
||
24 | |||
25 | /** |
||
26 | * @var string |
||
27 | * @internal |
||
28 | */ |
||
29 | protected $FORMAT_HEADER = 'a4Signature/LVersion/QTableNameLength/x8/QRecordSize/QFieldCount/QDescriptionOffset'. |
||
30 | '/QRecordCount/QFullRecordSize/QEntryOffset/QNextId/QIDLookupOffset/x8'; |
||
31 | |||
32 | /** |
||
33 | * @var bool |
||
34 | * @internal |
||
35 | */ |
||
36 | private $skip; |
||
37 | |||
38 | /** |
||
39 | * @var int |
||
40 | * @internal |
||
41 | */ |
||
42 | private $pos; |
||
43 | |||
44 | /** |
||
45 | * @var string |
||
46 | * @internal |
||
47 | */ |
||
48 | private $rowdata; |
||
49 | |||
50 | /** |
||
51 | * @param string $filename |
||
52 | * |
||
53 | * @return \codemasher\WildstarDB\Archive\ReaderInterface |
||
54 | * @throws \codemasher\WildstarDB\WSDBException |
||
55 | */ |
||
56 | public function read(string $filename):ReaderInterface{ |
||
57 | $this->loadFile($filename); |
||
58 | |||
59 | if($this->header['Signature'] !== "\x4c\x42\x54\x44"){ // LBTD |
||
60 | throw new WSDBException('invalid DTBL'); |
||
61 | } |
||
62 | |||
63 | $this->readColumnHeaders(); |
||
64 | $this->readData(); |
||
65 | |||
66 | if(count($this->data) !== $this->header['RecordCount']){ |
||
67 | throw new WSDBException('invalid row count'); |
||
68 | } |
||
69 | |||
70 | return $this; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * @return void |
||
75 | */ |
||
76 | private function readColumnHeaders():void{ |
||
77 | |||
78 | // table name (UTF-16LE: length *2) |
||
79 | $this->name = $this->decodeString(fread($this->fh, $this->header['TableNameLength'] * 2)); |
||
80 | |||
81 | // skip forward |
||
82 | fseek($this->fh, $this->header['DescriptionOffset'] + $this->headerSize); |
||
83 | |||
84 | // read the column headers (4+4+8+2+2+4 = 24 bytes) |
||
85 | for($i = 0; $i < $this->header['FieldCount']; $i++){ |
||
86 | $this->cols[$i]['header'] = unpack('LNameLength/x4/QNameOffset/SDataType/x2/x4', fread($this->fh, 24)); |
||
87 | } |
||
88 | |||
89 | $offset = $this->header['FieldCount'] * 24 + $this->header['DescriptionOffset'] + $this->headerSize; |
||
90 | |||
91 | if($this->header['FieldCount'] % 2){ |
||
92 | $offset += 8; |
||
93 | } |
||
94 | |||
95 | // read the column names |
||
96 | foreach($this->cols as $i => $col){ |
||
97 | fseek($this->fh, $offset + $col['header']['NameOffset']); |
||
98 | |||
99 | // column name (UTF-16LE: length *2) |
||
100 | $this->cols[$i]['name'] = $this->decodeString(fread($this->fh, $col['header']['NameLength'] * 2)); |
||
101 | } |
||
102 | |||
103 | $this->logger->info($this->name.', fields: '.$this->header['FieldCount'].', rows: '.$this->header['RecordCount']); |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * @return void |
||
108 | * @throws \codemasher\WildstarDB\WSDBException |
||
109 | */ |
||
110 | private function readData():void{ |
||
111 | fseek($this->fh, $this->header['EntryOffset'] + $this->headerSize); |
||
112 | |||
113 | $this->data = array_fill(0, $this->header['RecordCount'], null); |
||
114 | |||
115 | // read a row |
||
116 | foreach($this->data as $i => $_){ |
||
117 | $this->rowdata = fread($this->fh, $this->header['RecordSize']); |
||
118 | $this->pos = 0; |
||
119 | $this->skip = false; |
||
120 | |||
121 | // loop through the columns |
||
122 | foreach($this->cols as $c => $col){ |
||
123 | |||
124 | // skip 4 bytes if the string offset is 0 (determined by $skip), the current type is string and the next isn't |
||
125 | if($this->skip === true && ($c > 0 && $this->cols[$c - 1]['header']['DataType'] === 130) && $col['header']['DataType'] !== 130){ |
||
126 | $this->pos += 4; |
||
127 | } |
||
128 | |||
129 | $this->data[$i][$col['name']] = $this->getValue($col['header']['DataType']); |
||
130 | } |
||
131 | |||
132 | // if we run into this, a horrible thing happened |
||
133 | if(count($this->data[$i]) !== $this->header['FieldCount']){ |
||
134 | throw new WSDBException('invalid field count'); |
||
135 | } |
||
136 | |||
137 | } |
||
138 | |||
139 | } |
||
140 | |||
141 | /** |
||
142 | * @param int $datatype |
||
143 | * |
||
144 | * @return int|float|string|null |
||
145 | */ |
||
146 | private function getValue(int $datatype){ |
||
147 | |||
148 | switch($datatype){ |
||
149 | case 3: // uint32 |
||
150 | case 11: // booleans (stored as uint32 0/1) |
||
151 | $v = uint32(substr($this->rowdata, $this->pos, 4)); |
||
152 | $this->pos += 4; |
||
153 | break; |
||
154 | case 4: // float |
||
155 | $v = round(float(substr($this->rowdata, $this->pos, 4)), 3); // @todo: determine round precision |
||
156 | $this->pos += 4; |
||
157 | break; |
||
158 | case 20: // uint64 |
||
159 | $v = uint64(substr($this->rowdata, $this->pos, 8)); |
||
160 | $this->pos += 8; |
||
161 | break; |
||
162 | case 130: // string (UTF-16LE) |
||
163 | $v = $this->readString(); |
||
164 | $this->pos += 8; |
||
165 | break; |
||
166 | |||
167 | default: |
||
168 | $v = null; |
||
169 | } |
||
170 | |||
171 | return $v; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @return string |
||
176 | */ |
||
177 | private function readString():string{ |
||
178 | $o = uint32(substr($this->rowdata, $this->pos, 4)); |
||
179 | $p = ftell($this->fh); |
||
180 | $this->skip = $o === 0; |
||
181 | |||
182 | fseek($this->fh, $this->header['EntryOffset'] + $this->headerSize + ($o > 0 ? $o : uint32(substr($this->rowdata, $this->pos + 4, 4)))); |
||
183 | |||
184 | $v = ''; |
||
185 | // loop through the string until we hit 2 nul bytes or the void |
||
186 | do{ |
||
187 | $s = fread($this->fh, 2); |
||
188 | $v .= $s; |
||
189 | } |
||
190 | while($s !== "\x00\x00" && $s !== ''); |
||
191 | |||
192 | fseek($this->fh, $p); |
||
193 | |||
194 | return $this->decodeString($v); |
||
195 | } |
||
196 | |||
197 | } |
||
198 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths