codemasher /
wildstar-database
| 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 Loading history...
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 Loading history...
|
|||
| 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