1: <?php declare(strict_types=1);
2:
3: /**
4: * Clansuite Server Query
5: *
6: * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
7: * SPDX-License-Identifier: MIT
8: *
9: * For the full copyright and license information, please view
10: * the LICENSE file that was distributed with this source code.
11: */
12:
13: namespace Clansuite\ServerQuery\Util;
14:
15: use function ord;
16: use function strlen;
17: use function substr;
18: use function unpack;
19:
20: /**
21: * Utility class for reading binary data from network packets, providing methods to extract various data types.
22: */
23: final class PacketReader
24: {
25: private int $pos = 0;
26:
27: /**
28: * Constructor.
29: */
30: public function __construct(private string $buffer)
31: {
32: $this->pos = 0;
33: }
34:
35: /**
36: * remaining method.
37: */
38: public function remaining(): int
39: {
40: return strlen($this->buffer) - $this->pos;
41: }
42:
43: /**
44: * pos method.
45: */
46: public function pos(): int
47: {
48: return $this->pos;
49: }
50:
51: /**
52: * readInt32 method.
53: */
54: public function readInt32(): ?int
55: {
56: if ($this->remaining() < 4) {
57: return null;
58: }
59: $data = unpack('l', substr($this->buffer, $this->pos, 4));
60:
61: if ($data === false || !isset($data[1])) {
62: return null;
63: }
64:
65: $v = (int) $data[1];
66: $this->pos += 4;
67:
68: return $v;
69: }
70:
71: /**
72: * readUint32 method.
73: */
74: public function readUint32(): ?int
75: {
76: if ($this->remaining() < 4) {
77: return null;
78: }
79: $data = unpack('V', substr($this->buffer, $this->pos, 4));
80:
81: if ($data === false || !isset($data[1])) {
82: return null;
83: }
84:
85: $v = (int) $data[1];
86: $this->pos += 4;
87:
88: return $v;
89: }
90:
91: /**
92: * readUint16 method.
93: */
94: public function readUint16(): ?int
95: {
96: if ($this->remaining() < 2) {
97: return null;
98: }
99: $data = unpack('v', substr($this->buffer, $this->pos, 2));
100:
101: if ($data === false || !isset($data[1])) {
102: return null;
103: }
104:
105: $v = (int) $data[1];
106: $this->pos += 2;
107:
108: return $v;
109: }
110:
111: /**
112: * readUint8 method.
113: */
114: public function readUint8(): ?int
115: {
116: if ($this->remaining() < 1) {
117: return null;
118: }
119: $v = ord($this->buffer[$this->pos]);
120: $this->pos++;
121:
122: return $v;
123: }
124:
125: /**
126: * readFloat method.
127: */
128: public function readFloat(): ?float
129: {
130: if ($this->remaining() < 4) {
131: return null;
132: }
133: $data = unpack('f', substr($this->buffer, $this->pos, 4));
134:
135: if ($data === false || !isset($data[1])) {
136: return null;
137: }
138:
139: $v = (float) $data[1];
140: $this->pos += 4;
141:
142: return $v;
143: }
144:
145: /**
146: * readString method.
147: */
148: public function readString(): ?string
149: {
150: $start = $this->pos;
151:
152: while ($this->pos < strlen($this->buffer) && $this->buffer[$this->pos] !== "\x00") {
153: $this->pos++;
154: }
155:
156: if ($this->pos >= strlen($this->buffer)) {
157: return null;
158: }
159: $s = substr($this->buffer, $start, $this->pos - $start);
160: $this->pos++; // skip null
161:
162: return $s;
163: }
164:
165: /**
166: * rest method.
167: */
168: public function rest(): string
169: {
170: $r = substr($this->buffer, $this->pos);
171: $this->pos = strlen($this->buffer);
172:
173: return $r;
174: }
175: }
176: