Passed
Push — master ( 96b3cc...8ff2ce )
by smiley
01:25
created

DTBLReader::saveToFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
c 0
b 0
f 0
rs 10
cc 2
nc 2
nop 2
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/QUnknown1/QRecordSize/QFieldCount/QDescriptionOffset/QRecordCount/QFullRecordSize/QEntryOffset/QNextId/QIDLookupOffset/QUnknown2';
21
	protected $FORMAT_COLUMN = 'LNameLength/LUnknown1/QNameOffset/SDataType/SUnknown2/LUnknown3';
22
23
	/**
24
	 * @return void
25
	 * @throws \codemasher\WildstarDB\WSDBException
26
	 */
27
	protected function init():void{
28
29
		if($this->header['Signature'] !== "\x4c\x42\x54\x44"){ // LBTD
30
			throw new WSDBException('invalid DTBL');
31
		}
32
33
		$this->name = $this->decodeString(fread($this->fh, $this->header['TableNameLength'] * 2));
34
35
		$this->logger->info($this->name.', fields: '.$this->header['FieldCount'].', rows: '.$this->header['RecordCount']);
36
37
		fseek($this->fh, $this->header['DescriptionOffset'] + 0x60);
38
39
		for($i = 0; $i < $this->header['FieldCount']; $i++){
40
			$this->cols[$i]['header'] = unpack($this->FORMAT_COLUMN, fread($this->fh, 0x18));
41
		}
42
43
		$offset = $this->header['FieldCount'] * 0x18 + $this->header['DescriptionOffset'] + 0x60;
44
45
		if($this->header['FieldCount'] % 2){
46
			$offset += 8;
47
		}
48
49
		foreach($this->cols as $i => $col){
50
			fseek($this->fh, $offset + $col['header']['NameOffset']);
51
52
			$this->cols[$i]['name'] = $this->decodeString(fread($this->fh, $col['header']['NameLength'] * 2));
53
		}
54
55
	}
56
57
	/**
58
	 * @param string $filename
59
	 *
60
	 * @return \codemasher\WildstarDB\ReaderInterface
61
	 * @throws \codemasher\WildstarDB\WSDBException
62
	 */
63
	public function read(string $filename):ReaderInterface{
64
		$this->loadFile($filename);
65
		$this->init();
66
67
		fseek($this->fh, $this->header['EntryOffset'] + 0x60);
68
69
		for($i = 0; $i < $this->header['RecordCount']; $i++){
70
			$data = fread($this->fh, $this->header['RecordSize']);
71
			$row  = [];
72
			$j    = 0;
73
			$skip = false;
74
75
			foreach($this->cols as $c => $col){
76
77
				if($skip === true && ($c > 0 && $this->cols[$c - 1]['header']['DataType'] === 130) && $col['header']['DataType'] !== 130){
78
					$j += 4;
79
				}
80
81
				switch($col['header']['DataType']){
82
					case 3:  // uint32
83
					case 11: // booleans (stored as uint32 0/1)
84
						$v = unpack('L', substr($data, $j, 4))[1]; $j += 4; break;
85
					case 4:  // float
86
						$v = round(unpack('f', substr($data, $j, 4))[1], 3); $j += 4; break;
87
					case 20: // uint64
88
						$v = unpack('Q', substr($data, $j, 8))[1]; $j += 8; break;
89
					case 130: // string
90
						$v = $this->readString($data, $j, $skip); $j += 8; break;
91
92
					default: $v = null;
93
				}
94
95
				$row[$col['name']] = $v;
96
			}
97
98
			if(count($row) !== $this->header['FieldCount']){
99
				throw new WSDBException('invalid field count');
100
			}
101
102
			$this->data[$i] = $row;
103
		}
104
105
		fclose($this->fh);
106
107
		if(count($this->data) !== $this->header['RecordCount']){
108
			throw new WSDBException('invalid row count');
109
		}
110
111
		return $this;
112
	}
113
114
	/**
115
	 * @param string $data
116
	 * @param int    $j
117
	 * @param bool   $skip
118
	 *
119
	 * @return string
120
	 */
121
	protected function readString(string $data, int $j, bool &$skip):string{
122
		$o    = unpack('L', substr($data, $j, 4))[1];
123
		$p    = ftell($this->fh);
124
		$skip = $o === 0;
125
126
		fseek($this->fh, $this->header['EntryOffset'] + 0x60 + ($o > 0 ? $o : unpack('L', substr($data, $j + 4, 4))[1]));
127
128
		$v = '';
129
130
		do{
131
			$s = fread($this->fh, 2);
132
			$v .= $s;
133
		}
134
		while($s !== "\x00\x00" && $s !== '');
135
136
		fseek($this->fh, $p);
137
138
		return $this->decodeString($v);
139
	}
140
141
}
142