Passed
Push — master ( bdfaf8...931c46 )
by smiley
01:33
created

DTBLReader::readData()   C

Complexity

Conditions 13
Paths 27

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 25
nc 27
nop 0
dl 0
loc 43
rs 6.6166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
11
 * @author       smiley <[email protected]>
12
 * @copyright    2019 smiley
13
 * @license      MIT
14
 */
15
16
namespace codemasher\WildstarDB;
17
18
class DTBLReader extends ReaderAbstract{
19
20
	protected $FORMAT_HEADER = 'a4Signature/LVersion/QTableNameLength/x8/QRecordSize/QFieldCount/QDescriptionOffset/QRecordCount/QFullRecordSize/QEntryOffset/QNextId/QIDLookupOffset/x8';
21
22
	/**
23
	 * @param string $filename
24
	 *
25
	 * @return \codemasher\WildstarDB\ReaderInterface
26
	 * @throws \codemasher\WildstarDB\WSDBException
27
	 */
28
	public function read(string $filename):ReaderInterface{
29
		$this->loadFile($filename);
30
31
		if($this->header['Signature'] !== "\x4c\x42\x54\x44"){ // LBTD
32
			throw new WSDBException('invalid DTBL');
33
		}
34
35
		$this->readColumnHeaders();
36
		$this->readData();
37
38
		if(\count($this->data) !== $this->header['RecordCount']){
39
			throw new WSDBException('invalid row count');
40
		}
41
42
		return $this;
43
	}
44
45
	/**
46
	 * @return void
47
	 */
48
	protected function readColumnHeaders():void{
49
50
		// table name (UTF-16LE: length *2)
51
		$this->name = $this->decodeString(\fread($this->fh, $this->header['TableNameLength'] * 2));
52
53
		// skip forward
54
		\fseek($this->fh, $this->header['DescriptionOffset'] + $this->headerSize);
55
56
		// read the column headers (4+4+8+2+2+4 = 24 bytes)
57
		for($i = 0; $i < $this->header['FieldCount']; $i++){
58
			$this->cols[$i]['header'] = \unpack('LNameLength/x4/QNameOffset/SDataType/x2/x4', \fread($this->fh, 24));
59
		}
60
61
		$offset = $this->header['FieldCount'] * 24 + $this->header['DescriptionOffset'] + $this->headerSize;
62
63
		if($this->header['FieldCount'] % 2){
64
			$offset += 8;
65
		}
66
67
		// read the column names
68
		foreach($this->cols as $i => $col){
69
			\fseek($this->fh, $offset + $col['header']['NameOffset']);
70
71
			// column name (UTF-16LE: length *2)
72
			$this->cols[$i]['name'] = $this->decodeString(\fread($this->fh, $col['header']['NameLength'] * 2));
73
		}
74
75
		$this->logger->info($this->name.', fields: '.$this->header['FieldCount'].', rows: '.$this->header['RecordCount']);
76
	}
77
78
	/**
79
	 * @return void
80
	 * @throws \codemasher\WildstarDB\WSDBException
81
	 */
82
	protected function readData():void{
83
		\fseek($this->fh, $this->header['EntryOffset'] + $this->headerSize);
84
85
		$this->data = array_fill(0, $this->header['RecordCount'], null);
86
87
		// read a row
88
		foreach($this->data as $i => $_){
89
			$data = \fread($this->fh, $this->header['RecordSize']);
90
			$row  = [];
91
			$j    = 0;
92
			$skip = false;
93
94
			// loop through the columns
95
			foreach($this->cols as $c => $col){
96
97
				// skip 4 bytes if the string offset is 0 (determined by $skip), the current type is string and the next isn't
98
				if($skip === true && ($c > 0 && $this->cols[$c - 1]['header']['DataType'] === 130) && $col['header']['DataType'] !== 130){
99
					$j += 4;
100
				}
101
102
				switch($col['header']['DataType']){
103
					case 3:  // uint32
104
					case 11: // booleans (stored as uint32 0/1)
105
						$v = uint32(\substr($data, $j, 4)); $j += 4; break;
106
					case 4:  // float
107
						$v = \round(float(\substr($data, $j, 4)), 3); $j += 4; break;
108
					case 20: // uint64
109
						$v = uint64(\substr($data, $j, 8)); $j += 8; break;
110
					case 130: // string (UTF-16LE)
111
						$v = $this->readString($data, $j, $skip); $j += 8; break;
112
113
					default: $v = null;
114
				}
115
116
				$row[$col['name']] = $v;
117
			}
118
119
			// if we run into this, a horrible thing happened
120
			if(\count($row) !== $this->header['FieldCount']){
121
				throw new WSDBException('invalid field count');
122
			}
123
124
			$this->data[$i] = $row;
125
		}
126
127
	}
128
129
	/**
130
	 * @param string $data
131
	 * @param int    $j
132
	 * @param bool   $skip
133
	 *
134
	 * @return string
135
	 */
136
	protected function readString(string $data, int $j, bool &$skip):string{
137
		$o    = uint32(\substr($data, $j, 4));
138
		$p    = \ftell($this->fh);
139
		$skip = $o === 0;
140
141
		\fseek($this->fh, $this->header['EntryOffset'] + $this->headerSize + ($o > 0 ? $o : uint32(\substr($data, $j + 4, 4))));
142
143
		$v = '';
144
		// loop through the string until we hit 2 nul bytes or the void
145
		do{
146
			$s = \fread($this->fh, 2);
147
			$v .= $s;
148
		}
149
		while($s !== "\x00\x00" && $s !== '');
150
151
		\fseek($this->fh, $p);
152
153
		return $this->decodeString($v);
154
	}
155
156
}
157