twgame_core/
net_msg.rs

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    /// contains the ddnet version
406    ClIsDdnet(i32),
407    ClShowOthers(i32),
408    ClShowDistance(ClShowDistance),
409    ClCommand(ClCommand<'a>),
410}
411
412// copied from https://github.com/heinrich5991/libtw2/blob/2872de4573e65d1690f1a5f344311df86d554eb4/tools/src/warn_stdout.rs
413pub 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    // TODO: warning as error?
424    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    // TODO: warning as error?
477    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)), // TODO
506            Game::DeClientLeave(_) => Err(Error::NonClientGameMsg07(msg)), // TODO
507            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)), // TODO,
513            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)), // TODO,
517            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}