DTBLReader::read()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 15
rs 10
c 0
b 0
f 0
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
Bug introduced by
The type codemasher\WildstarDB\uint32 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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

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