1pub use libtw2_gamenet_ddnet::msg::game::Game as DdnetGameMsg;
2pub use libtw2_gamenet_teeworlds_0_7::msg::game::Game as Tw07GameMsg;
3use libtw2_gamenet_teeworlds_0_7::msg::Game;
4use libtw2_packer::Unpacker;
5use std::fmt;
6use warn::Warn;
7
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub enum NetVersion {
10 V06,
11 V07,
12 Unknown,
13}
14
15pub enum Error<'a> {
16 NonClientGameMsg06(DdnetGameMsg<'a>),
17 NonClientGameMsg07(Tw07GameMsg<'a>),
18 NetMsgParseError(libtw2_gamenet_ddnet::error::Error),
19}
20
21impl fmt::Display for Error<'_> {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 use Error::*;
24 match self {
25 NonClientGameMsg06(msg) => write!(f, "NonClientGameMsg06 {:?}", msg),
26 NonClientGameMsg07(msg) => write!(f, "NonClientGameMsg07 {:?}", msg),
27 NetMsgParseError(err) => write!(f, "NetMsgParseError {:?}", err),
28 }
29 }
30}
31
32#[derive(Debug)]
33pub enum Chat {
34 None,
35 All,
36 Team,
37 Whisper,
38}
39
40impl From<libtw2_gamenet_teeworlds_0_7::enums::Chat> for Chat {
41 fn from(chat: libtw2_gamenet_teeworlds_0_7::enums::Chat) -> Chat {
42 use Chat::*;
43 match chat {
44 libtw2_gamenet_teeworlds_0_7::enums::Chat::None => None,
45 libtw2_gamenet_teeworlds_0_7::enums::Chat::All => All,
46 libtw2_gamenet_teeworlds_0_7::enums::Chat::Team => Team,
47 libtw2_gamenet_teeworlds_0_7::enums::Chat::Whisper => Whisper,
48 }
49 }
50}
51
52pub struct ClSay<'a> {
53 pub mode: Chat,
54 pub target: i32,
55 pub message: &'a [u8],
56}
57
58impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClSay<'a>> for ClSay<'a> {
59 fn from(cl_say: libtw2_gamenet_ddnet::msg::game::ClSay<'a>) -> ClSay<'a> {
60 ClSay {
61 mode: if cl_say.team { Chat::Team } else { Chat::All },
62 target: -1,
63 message: cl_say.message,
64 }
65 }
66}
67
68impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClSay<'a>> for ClSay<'a> {
69 fn from(cl_say: libtw2_gamenet_teeworlds_0_7::msg::game::ClSay<'a>) -> ClSay<'a> {
70 ClSay {
71 mode: cl_say.mode.into(),
72 target: cl_say.target,
73 message: cl_say.message,
74 }
75 }
76}
77
78#[derive(Debug)]
79pub enum Team {
80 Spectators = -1,
81 Red,
82 Blue,
83}
84
85impl From<libtw2_gamenet_ddnet::enums::Team> for Team {
86 fn from(team: libtw2_gamenet_ddnet::enums::Team) -> Team {
87 use Team::*;
88 match team {
89 libtw2_gamenet_ddnet::enums::Team::Spectators => Spectators,
90 libtw2_gamenet_ddnet::enums::Team::Red => Red,
91 libtw2_gamenet_ddnet::enums::Team::Blue => Blue,
92 }
93 }
94}
95
96impl From<libtw2_gamenet_teeworlds_0_7::enums::Team> for Team {
97 fn from(team: libtw2_gamenet_teeworlds_0_7::enums::Team) -> Team {
98 use Team::*;
99 match team {
100 libtw2_gamenet_teeworlds_0_7::enums::Team::Spectators => Spectators,
101 libtw2_gamenet_teeworlds_0_7::enums::Team::Red => Red,
102 libtw2_gamenet_teeworlds_0_7::enums::Team::Blue => Blue,
103 }
104 }
105}
106
107#[derive(Debug)]
108pub enum Spec {
109 Freeview,
110 Player,
111 Flagred,
112 Flagblue,
113}
114
115impl From<libtw2_gamenet_teeworlds_0_7::enums::Spec> for Spec {
116 fn from(spec: libtw2_gamenet_teeworlds_0_7::enums::Spec) -> Spec {
117 use Spec::*;
118 match spec {
119 libtw2_gamenet_teeworlds_0_7::enums::Spec::Freeview => Freeview,
120 libtw2_gamenet_teeworlds_0_7::enums::Spec::Player => Player,
121 libtw2_gamenet_teeworlds_0_7::enums::Spec::Flagred => Flagred,
122 libtw2_gamenet_teeworlds_0_7::enums::Spec::Flagblue => Flagblue,
123 }
124 }
125}
126
127pub struct ClSetSpectatorMode {
128 pub spec_mode: Spec,
129 pub spectator_id: i32,
130}
131
132impl From<libtw2_gamenet_ddnet::msg::game::ClSetSpectatorMode> for ClSetSpectatorMode {
133 fn from(
134 cl_set_spectator_mode: libtw2_gamenet_ddnet::msg::game::ClSetSpectatorMode,
135 ) -> ClSetSpectatorMode {
136 ClSetSpectatorMode {
137 spec_mode: if cl_set_spectator_mode.spectator_id == -1 {
138 Spec::Freeview
139 } else {
140 Spec::Player
141 },
142 spectator_id: cl_set_spectator_mode.spectator_id,
143 }
144 }
145}
146
147impl From<libtw2_gamenet_teeworlds_0_7::msg::game::ClSetSpectatorMode> for ClSetSpectatorMode {
148 fn from(
149 cl_set_spectator_mode: libtw2_gamenet_teeworlds_0_7::msg::game::ClSetSpectatorMode,
150 ) -> ClSetSpectatorMode {
151 ClSetSpectatorMode {
152 spec_mode: cl_set_spectator_mode.spec_mode.into(),
153 spectator_id: cl_set_spectator_mode.spectator_id,
154 }
155 }
156}
157
158#[repr(i32)]
159#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)]
160pub enum Emoticon {
161 Oop,
162 Exclamation,
163 Hearts,
164 Drop,
165 Dotdot,
166 Music,
167 Sorry,
168 Ghost,
169 Sushi,
170 Splattee,
171 Deviltee,
172 Zomg,
173 Zzz,
174 Wtf,
175 Eyes,
176 Question,
177}
178
179impl From<libtw2_gamenet_ddnet::enums::Emoticon> for Emoticon {
180 fn from(emoticon: libtw2_gamenet_ddnet::enums::Emoticon) -> Emoticon {
181 use Emoticon::*;
182 match emoticon {
183 libtw2_gamenet_ddnet::enums::Emoticon::Oop => Oop,
184 libtw2_gamenet_ddnet::enums::Emoticon::Exclamation => Exclamation,
185 libtw2_gamenet_ddnet::enums::Emoticon::Hearts => Hearts,
186 libtw2_gamenet_ddnet::enums::Emoticon::Drop => Drop,
187 libtw2_gamenet_ddnet::enums::Emoticon::Dotdot => Dotdot,
188 libtw2_gamenet_ddnet::enums::Emoticon::Music => Music,
189 libtw2_gamenet_ddnet::enums::Emoticon::Sorry => Sorry,
190 libtw2_gamenet_ddnet::enums::Emoticon::Ghost => Ghost,
191 libtw2_gamenet_ddnet::enums::Emoticon::Sushi => Sushi,
192 libtw2_gamenet_ddnet::enums::Emoticon::Splattee => Splattee,
193 libtw2_gamenet_ddnet::enums::Emoticon::Deviltee => Deviltee,
194 libtw2_gamenet_ddnet::enums::Emoticon::Zomg => Zomg,
195 libtw2_gamenet_ddnet::enums::Emoticon::Zzz => Zzz,
196 libtw2_gamenet_ddnet::enums::Emoticon::Wtf => Wtf,
197 libtw2_gamenet_ddnet::enums::Emoticon::Eyes => Eyes,
198 libtw2_gamenet_ddnet::enums::Emoticon::Question => Question,
199 }
200 }
201}
202
203impl From<libtw2_gamenet_teeworlds_0_7::enums::Emoticon> for Emoticon {
204 fn from(emoticon: libtw2_gamenet_teeworlds_0_7::enums::Emoticon) -> Emoticon {
205 use Emoticon::*;
206 match emoticon {
207 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Oop => Oop,
208 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Exclamation => Exclamation,
209 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Hearts => Hearts,
210 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Drop => Drop,
211 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Dotdot => Dotdot,
212 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Music => Music,
213 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Sorry => Sorry,
214 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Ghost => Ghost,
215 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Sushi => Sushi,
216 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Splattee => Splattee,
217 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Deviltee => Deviltee,
218 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Zomg => Zomg,
219 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Zzz => Zzz,
220 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Wtf => Wtf,
221 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Eyes => Eyes,
222 libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Question => Question,
223 }
224 }
225}
226
227pub struct ClCallVote<'a> {
228 pub type_: &'a [u8],
229 pub value: &'a [u8],
230 pub reason: &'a [u8],
231 pub force: bool,
232}
233
234impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClCallVote<'a>> for ClCallVote<'a> {
235 fn from(cl_call_vote: libtw2_gamenet_ddnet::msg::game::ClCallVote<'a>) -> ClCallVote<'a> {
236 ClCallVote {
237 type_: cl_call_vote.type_,
238 value: cl_call_vote.value,
239 reason: cl_call_vote.reason,
240 force: false,
241 }
242 }
243}
244
245impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClCallVote<'a>> for ClCallVote<'a> {
246 fn from(
247 cl_call_vote: libtw2_gamenet_teeworlds_0_7::msg::game::ClCallVote<'a>,
248 ) -> ClCallVote<'a> {
249 ClCallVote {
250 type_: cl_call_vote.type_,
251 value: cl_call_vote.value,
252 reason: cl_call_vote.reason,
253 force: cl_call_vote.force,
254 }
255 }
256}
257
258pub struct Skin06<'a> {
259 pub skin: &'a [u8],
260 pub use_custom_color: bool,
261 pub color_body: i32,
262 pub color_feet: i32,
263}
264
265pub struct Skin07<'a> {
266 pub skin_part_names: [&'a [u8]; 6],
267 pub use_custom_colors: [bool; 6],
268 pub skin_part_colors: [i32; 6],
269}
270
271pub enum Skin<'a> {
272 V6(Skin06<'a>),
273 V7(Skin07<'a>),
274}
275
276impl fmt::Display for Skin<'_> {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 match self {
279 Skin::V6(v6) => {
280 let Skin06 {
281 skin,
282 use_custom_color,
283 color_body,
284 color_feet,
285 } = v6;
286 let skin = String::from_utf8_lossy(skin);
287 write!(f, "{{ skin={skin:?} use_custom_color={use_custom_color} color_body={color_body} color_feet={color_feet} }}")
288 }
289 Skin::V7(v7) => {
290 let Skin07 {
291 skin_part_names,
292 use_custom_colors,
293 skin_part_colors,
294 } = v7;
295 write!(f, "{{")?;
296 for i in 0..6 {
297 let name = String::from_utf8_lossy(skin_part_names[i]);
298 let custom = use_custom_colors[i];
299 let color = skin_part_colors[i];
300 write!(f, "(part={name} use_custom_color={custom} color={color})")?;
301 if i != 5 {
302 write!(f, " ")?;
303 }
304 }
305 write!(f, "}}")
306 }
307 }
308 }
309}
310
311pub struct ClPlayerInfo<'a> {
312 pub name: &'a [u8],
313 pub clan: &'a [u8],
314 pub country: i32,
315 pub skin: Skin<'a>,
316}
317
318impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClStartInfo<'a>> for ClPlayerInfo<'a> {
319 fn from(cl_start_info: libtw2_gamenet_ddnet::msg::game::ClStartInfo<'a>) -> ClPlayerInfo<'a> {
320 ClPlayerInfo {
321 name: cl_start_info.name,
322 clan: cl_start_info.clan,
323 country: cl_start_info.country,
324 skin: Skin::V6(Skin06 {
325 skin: cl_start_info.skin,
326 use_custom_color: cl_start_info.use_custom_color,
327 color_body: cl_start_info.color_body,
328 color_feet: cl_start_info.color_feet,
329 }),
330 }
331 }
332}
333
334impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClChangeInfo<'a>> for ClPlayerInfo<'a> {
335 fn from(cl_change_info: libtw2_gamenet_ddnet::msg::game::ClChangeInfo<'a>) -> ClPlayerInfo<'a> {
336 ClPlayerInfo {
337 name: cl_change_info.name,
338 clan: cl_change_info.clan,
339 country: cl_change_info.country,
340 skin: Skin::V6(Skin06 {
341 skin: cl_change_info.skin,
342 use_custom_color: cl_change_info.use_custom_color,
343 color_body: cl_change_info.color_body,
344 color_feet: cl_change_info.color_feet,
345 }),
346 }
347 }
348}
349
350impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClStartInfo<'a>> for ClPlayerInfo<'a> {
351 fn from(
352 cl_start_info: libtw2_gamenet_teeworlds_0_7::msg::game::ClStartInfo<'a>,
353 ) -> ClPlayerInfo<'a> {
354 ClPlayerInfo {
355 name: cl_start_info.name,
356 clan: cl_start_info.clan,
357 country: cl_start_info.country,
358 skin: Skin::V7(Skin07 {
359 skin_part_names: cl_start_info.skin_part_names,
360 use_custom_colors: cl_start_info.use_custom_colors,
361 skin_part_colors: cl_start_info.skin_part_colors,
362 }),
363 }
364 }
365}
366
367pub struct ClShowDistance {
368 pub x: i32,
369 pub y: i32,
370}
371
372impl From<libtw2_gamenet_ddnet::msg::game::ClShowDistance> for ClShowDistance {
373 fn from(cl_show_distance: libtw2_gamenet_ddnet::msg::game::ClShowDistance) -> ClShowDistance {
374 ClShowDistance {
375 x: cl_show_distance.x,
376 y: cl_show_distance.y,
377 }
378 }
379}
380
381pub struct ClCommand<'a> {
382 pub name: &'a [u8],
383 pub arguments: &'a [u8],
384}
385
386impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClCommand<'a>> for ClCommand<'a> {
387 fn from(cl_command: libtw2_gamenet_teeworlds_0_7::msg::game::ClCommand) -> ClCommand {
388 ClCommand {
389 name: cl_command.name,
390 arguments: cl_command.arguments,
391 }
392 }
393}
394
395pub enum ClNetMessage<'a> {
396 ClSay(ClSay<'a>),
397 ClSetTeam(Team),
398 ClSetSpectatorMode(ClSetSpectatorMode),
399 ClStartInfo(ClPlayerInfo<'a>),
400 ClChangeInfo(ClPlayerInfo<'a>),
401 ClKill,
402 ClEmoticon(Emoticon),
403 ClVote(i32),
404 ClCallVote(ClCallVote<'a>),
405 ClIsDdnet(i32),
407 ClShowOthers(i32),
408 ClShowDistance(ClShowDistance),
409 ClCommand(ClCommand<'a>),
410}
411
412pub struct Stdout;
414impl<W: fmt::Debug> Warn<W> for Stdout {
415 fn warn(&mut self, warning: W) {
416 println!("WARN: {:?}", warning);
417 }
418}
419
420#[allow(clippy::result_large_err)]
421fn parse_ddnet(buf: &[u8]) -> Result<ClNetMessage, Error> {
422 let mut b = Unpacker::new(buf);
423 match DdnetGameMsg::decode(&mut Stdout, &mut b) {
425 Ok(msg) => match msg {
426 DdnetGameMsg::SvMotd(_)
427 | DdnetGameMsg::SvBroadcast(_)
428 | DdnetGameMsg::SvChat(_)
429 | DdnetGameMsg::SvKillMsg(_)
430 | DdnetGameMsg::SvKillMsgTeam(_)
431 | DdnetGameMsg::SvSoundGlobal(_)
432 | DdnetGameMsg::SvTuneParams(_)
433 | DdnetGameMsg::SvReadyToEnter(_)
434 | DdnetGameMsg::SvWeaponPickup(_)
435 | DdnetGameMsg::SvEmoticon(_)
436 | DdnetGameMsg::SvVoteClearOptions(_)
437 | DdnetGameMsg::SvVoteOptionListAdd(_)
438 | DdnetGameMsg::SvVoteOptionAdd(_)
439 | DdnetGameMsg::SvVoteOptionRemove(_)
440 | DdnetGameMsg::SvVoteSet(_)
441 | DdnetGameMsg::SvVoteStatus(_)
442 | DdnetGameMsg::SvDdraceTime(_)
443 | DdnetGameMsg::SvRecord(_)
444 | DdnetGameMsg::Unused(_)
445 | DdnetGameMsg::Unused2(_)
446 | DdnetGameMsg::SvTeamsState(_)
447 | DdnetGameMsg::SvMyOwnMessage(_)
448 | DdnetGameMsg::SvDdraceTimeLegacy(_)
449 | DdnetGameMsg::SvRecordLegacy(_)
450 | DdnetGameMsg::SvTeamsStateLegacy(_) => Err(Error::NonClientGameMsg06(msg)),
451 DdnetGameMsg::ClSay(msg) => Ok(ClNetMessage::ClSay(msg.into())),
452 DdnetGameMsg::ClSetTeam(msg) => Ok(ClNetMessage::ClSetTeam(msg.team.into())),
453 DdnetGameMsg::ClSetSpectatorMode(msg) => {
454 Ok(ClNetMessage::ClSetSpectatorMode(msg.into()))
455 }
456 DdnetGameMsg::ClStartInfo(msg) => Ok(ClNetMessage::ClStartInfo(msg.into())),
457 DdnetGameMsg::ClChangeInfo(msg) => Ok(ClNetMessage::ClChangeInfo(msg.into())),
458 DdnetGameMsg::ClKill(_) => Ok(ClNetMessage::ClKill),
459 DdnetGameMsg::ClEmoticon(msg) => Ok(ClNetMessage::ClEmoticon(msg.emoticon.into())),
460 DdnetGameMsg::ClVote(msg) => Ok(ClNetMessage::ClVote(msg.vote)),
461 DdnetGameMsg::ClCallVote(msg) => Ok(ClNetMessage::ClCallVote(msg.into())),
462 DdnetGameMsg::ClShowOthersLegacy(msg) => {
463 Ok(ClNetMessage::ClShowOthers(msg.show as i32))
464 }
465 DdnetGameMsg::ClShowDistance(msg) => Ok(ClNetMessage::ClShowDistance(msg.into())),
466 DdnetGameMsg::ClShowOthers(msg) => Ok(ClNetMessage::ClShowOthers(msg.show)),
467 DdnetGameMsg::ClIsDdnetLegacy(_msg) => Ok(ClNetMessage::ClIsDdnet(0)),
468 },
469 Err(err) => Err(Error::NetMsgParseError(err)),
470 }
471}
472
473#[allow(clippy::result_large_err)]
474fn parse_teeworlds_07(buf: &[u8]) -> Result<ClNetMessage, Error> {
475 let mut b = Unpacker::new(buf);
476 match Tw07GameMsg::decode(&mut Stdout, &mut b) {
478 Ok(msg) => match msg {
479 Game::SvMotd(_)
480 | Game::SvBroadcast(_)
481 | Game::SvChat(_)
482 | Game::SvTeam(_)
483 | Game::SvKillMsg(_)
484 | Game::SvTuneParams(_)
485 | Game::SvExtraProjectile(_)
486 | Game::SvReadyToEnter(_)
487 | Game::SvWeaponPickup(_)
488 | Game::SvEmoticon(_)
489 | Game::SvVoteClearOptions(_)
490 | Game::SvVoteOptionListAdd(_)
491 | Game::SvVoteOptionAdd(_)
492 | Game::SvVoteOptionRemove(_)
493 | Game::SvVoteSet(_)
494 | Game::SvVoteStatus(_)
495 | Game::SvServerSettings(_)
496 | Game::SvClientInfo(_)
497 | Game::SvGameInfo(_)
498 | Game::SvClientDrop(_)
499 | Game::SvGameMsg(_)
500 | Game::SvRaceFinish(_)
501 | Game::SvCheckpoint(_)
502 | Game::SvCommandInfo(_)
503 | Game::SvCommandInfoRemove(_)
504 | Game::SvSkinChange(_) => Err(Error::NonClientGameMsg07(msg)),
505 Game::DeClientEnter(_) => Err(Error::NonClientGameMsg07(msg)), Game::DeClientLeave(_) => Err(Error::NonClientGameMsg07(msg)), Game::ClSay(msg) => Ok(ClNetMessage::ClSay(msg.into())),
508 Game::ClSetTeam(msg) => Ok(ClNetMessage::ClSetTeam(msg.team.into())),
509 Game::ClSetSpectatorMode(msg) => Ok(ClNetMessage::ClSetSpectatorMode(msg.into())),
510 Game::ClStartInfo(msg) => Ok(ClNetMessage::ClStartInfo(msg.into())),
511 Game::ClKill(_) => Ok(ClNetMessage::ClKill),
512 Game::ClReadyChange(_) => Err(Error::NonClientGameMsg07(msg)), Game::ClEmoticon(msg) => Ok(ClNetMessage::ClEmoticon(msg.emoticon.into())),
514 Game::ClVote(msg) => Ok(ClNetMessage::ClVote(msg.vote)),
515 Game::ClCallVote(msg) => Ok(ClNetMessage::ClCallVote(msg.into())),
516 Game::ClSkinChange(_) => Err(Error::NonClientGameMsg07(msg)), Game::ClCommand(msg) => Ok(ClNetMessage::ClCommand(msg.into())),
518 },
519 Err(err) => Err(Error::NetMsgParseError(err)),
520 }
521}
522
523#[allow(clippy::result_large_err)]
524pub fn parse_net_msg<'a>(
525 buf: &'a [u8],
526 net_version: &mut NetVersion,
527) -> Result<ClNetMessage<'a>, Error<'a>> {
528 match *net_version {
529 NetVersion::V06 => parse_ddnet(buf),
530 NetVersion::V07 => parse_teeworlds_07(buf),
531 NetVersion::Unknown => match parse_ddnet(buf) {
532 Ok(msg) => {
533 *net_version = NetVersion::V06;
534 Ok(msg)
535 }
536 Err(err) => {
537 if let Ok(msg) = parse_teeworlds_07(buf) {
538 *net_version = NetVersion::V07;
539 Ok(msg)
540 } else {
541 Err(err)
542 }
543 }
544 },
545 }
546}