PocketMine-MP 5.39.3 git-66148f13a91e4af6778ba9f200ca25ad8a04a584
Loading...
Searching...
No Matches
TextPacket.php
1<?php
2
3/*
4 * This file is part of BedrockProtocol.
5 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
6 *
7 * BedrockProtocol is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 */
12
13declare(strict_types=1);
14
15namespace pocketmine\network\mcpe\protocol;
16
17use pmmp\encoding\Byte;
18use pmmp\encoding\ByteBufferReader;
19use pmmp\encoding\ByteBufferWriter;
20use pmmp\encoding\VarInt;
22use function count;
23
25 public const NETWORK_ID = ProtocolInfo::TEXT_PACKET;
26
27 private const CATEGORY_MESSAGE_ONLY = 0;
28 private const CATEGORY_AUTHORED_MESSAGE = 1;
29 private const CATEGORY_MESSAGE_WITH_PARAMETERS = 2;
30
31 private const CATEGORY_DUMMY_STRINGS = [
32 self::CATEGORY_MESSAGE_ONLY => [
33 'raw',
34 'tip',
35 'systemMessage',
36 'textObjectWhisper',
37 'textObjectAnnouncement',
38 'textObject'
39 ],
40 self::CATEGORY_AUTHORED_MESSAGE => [
41 'chat',
42 'whisper',
43 'announcement'
44 ],
45 self::CATEGORY_MESSAGE_WITH_PARAMETERS => [
46 'translate',
47 'popup',
48 'jukeboxPopup',
49 ]
50 ];
51
52 public const TYPE_RAW = 0;
53 public const TYPE_CHAT = 1;
54 public const TYPE_TRANSLATION = 2;
55 public const TYPE_POPUP = 3;
56 public const TYPE_JUKEBOX_POPUP = 4;
57 public const TYPE_TIP = 5;
58 public const TYPE_SYSTEM = 6;
59 public const TYPE_WHISPER = 7;
60 public const TYPE_ANNOUNCEMENT = 8;
61 public const TYPE_JSON_WHISPER = 9;
62 public const TYPE_JSON = 10;
63 public const TYPE_JSON_ANNOUNCEMENT = 11;
64
65 public int $type;
66 public bool $needsTranslation = false;
67 public string $sourceName;
68 public string $message;
70 public array $parameters = [];
71 public string $xboxUserId = "";
72 public string $platformChatId = "";
73 public ?string $filteredMessage = null;
74
75 private static function messageOnly(int $type, string $message) : self{
76 $result = new self;
77 $result->type = $type;
78 //TODO: HACK! Empty message crashes or bugs out client in 1.21.130
79 $result->message = $message === "" ? " " : $message;
80 return $result;
81 }
82
86 private static function baseTranslation(int $type, string $key, array $parameters) : self{
87 $result = new self;
88 $result->type = $type;
89 $result->needsTranslation = true;
90 //TODO: HACK! Empty message crashes or bugs out client in 1.21.130
91 $result->message = $key === "" ? " " : $key;
92 $result->parameters = $parameters;
93 return $result;
94 }
95
96 public static function raw(string $message) : self{
97 return self::messageOnly(self::TYPE_RAW, $message);
98 }
99
103 public static function translation(string $key, array $parameters = []) : self{
104 return self::baseTranslation(self::TYPE_TRANSLATION, $key, $parameters);
105 }
106
107 public static function popup(string $message) : self{
108 return self::messageOnly(self::TYPE_POPUP, $message);
109 }
110
114 public static function translatedPopup(string $key, array $parameters = []) : self{
115 return self::baseTranslation(self::TYPE_POPUP, $key, $parameters);
116 }
117
121 public static function jukeboxPopup(string $key, array $parameters = []) : self{
122 return self::baseTranslation(self::TYPE_JUKEBOX_POPUP, $key, $parameters);
123 }
124
125 public static function tip(string $message) : self{
126 return self::messageOnly(self::TYPE_TIP, $message);
127 }
128
129 protected function decodePayload(ByteBufferReader $in) : void{
130 $this->needsTranslation = CommonTypes::getBool($in);
131
132 $category = Byte::readUnsigned($in);
133 $expectedDummyStrings = self::CATEGORY_DUMMY_STRINGS[$category] ?? throw new PacketDecodeException("Unknown category ID $category");
134 foreach($expectedDummyStrings as $k => $expectedDummyString){
135 $actual = CommonTypes::getString($in);
136 if($expectedDummyString !== $actual){
137 throw new PacketDecodeException("Dummy string mismatch for category $category at position $k: expected $expectedDummyString, got $actual");
138 }
139 }
140
141 $this->type = Byte::readUnsigned($in);
142 switch($this->type){
143 case self::TYPE_CHAT:
144 case self::TYPE_WHISPER:
146 case self::TYPE_ANNOUNCEMENT:
147 if($category !== self::CATEGORY_AUTHORED_MESSAGE){
148 throw new PacketDecodeException("Decoded TextPacket has invalid structure: type {$this->type} requires category CATEGORY_AUTHORED_MESSAGE");
149 }
150 $this->sourceName = CommonTypes::getString($in);
151 $this->message = CommonTypes::getString($in);
152 break;
153 case self::TYPE_RAW:
154 case self::TYPE_TIP:
155 case self::TYPE_SYSTEM:
156 case self::TYPE_JSON_WHISPER:
157 case self::TYPE_JSON:
158 case self::TYPE_JSON_ANNOUNCEMENT:
159 if($category !== self::CATEGORY_MESSAGE_ONLY){
160 throw new PacketDecodeException("Decoded TextPacket has invalid structure: type {$this->type} requires category CATEGORY_MESSAGE_ONLY");
161 }
162 $this->message = CommonTypes::getString($in);
163 break;
164 case self::TYPE_TRANSLATION:
165 case self::TYPE_POPUP:
166 case self::TYPE_JUKEBOX_POPUP:
167 if($category !== self::CATEGORY_MESSAGE_WITH_PARAMETERS){
168 throw new PacketDecodeException("Decoded TextPacket has invalid structure: type {$this->type} requires category CATEGORY_MESSAGE_WITH_PARAMETERS");
169 }
170 $this->message = CommonTypes::getString($in);
171 $count = VarInt::readUnsignedInt($in);
172 for($i = 0; $i < $count; ++$i){
173 $this->parameters[] = CommonTypes::getString($in);
174 }
175 break;
176 }
177
178 $this->xboxUserId = CommonTypes::getString($in);
179 $this->platformChatId = CommonTypes::getString($in);
180 $this->filteredMessage = CommonTypes::readOptional($in, CommonTypes::getString(...));
181 }
182
183 protected function encodePayload(ByteBufferWriter $out) : void{
184 CommonTypes::putBool($out, $this->needsTranslation);
185
186 $category = match ($this->type) {
187 self::TYPE_RAW,
188 self::TYPE_TIP,
189 self::TYPE_SYSTEM,
190 self::TYPE_JSON_WHISPER,
191 self::TYPE_JSON_ANNOUNCEMENT,
192 self::TYPE_JSON => self::CATEGORY_MESSAGE_ONLY,
193
194 self::TYPE_CHAT,
195 self::TYPE_WHISPER,
196 self::TYPE_ANNOUNCEMENT => self::CATEGORY_AUTHORED_MESSAGE,
197
198 self::TYPE_TRANSLATION,
199 self::TYPE_POPUP,
200 self::TYPE_JUKEBOX_POPUP => self::CATEGORY_MESSAGE_WITH_PARAMETERS,
201
202 default => throw new \LogicException("Invalid TextPacket type: $this->type")
203 };
204 Byte::writeUnsigned($out, $category);
205 foreach(self::CATEGORY_DUMMY_STRINGS[$category] as $dummyString){
206 CommonTypes::putString($out, $dummyString);
207 }
208
209 Byte::writeUnsigned($out, $this->type);
210 switch($this->type){
211 case self::TYPE_CHAT:
212 case self::TYPE_WHISPER:
214 case self::TYPE_ANNOUNCEMENT:
215 CommonTypes::putString($out, $this->sourceName);
216 case self::TYPE_RAW:
217 case self::TYPE_TIP:
218 case self::TYPE_SYSTEM:
219 case self::TYPE_JSON_WHISPER:
220 case self::TYPE_JSON:
221 case self::TYPE_JSON_ANNOUNCEMENT:
222 CommonTypes::putString($out, $this->message);
223 break;
224
225 case self::TYPE_TRANSLATION:
226 case self::TYPE_POPUP:
227 case self::TYPE_JUKEBOX_POPUP:
228 CommonTypes::putString($out, $this->message);
229 VarInt::writeUnsignedInt($out, count($this->parameters));
230 foreach($this->parameters as $p){
231 CommonTypes::putString($out, $p);
232 }
233 break;
234 }
235
236 CommonTypes::putString($out, $this->xboxUserId);
237 CommonTypes::putString($out, $this->platformChatId);
238 CommonTypes::writeOptional($out, $this->filteredMessage, CommonTypes::putString(...));
239 }
240
241 public function handle(PacketHandlerInterface $handler) : bool{
242 return $handler->handleText($this);
243 }
244}
static translation(string $key, array $parameters=[])
static jukeboxPopup(string $key, array $parameters=[])
handle(PacketHandlerInterface $handler)
static translatedPopup(string $key, array $parameters=[])