msp/server/
bedrock_server.rs

1use serde::Serialize;
2
3use crate::{
4    conf::Conf,
5    share::{create_udp_socket, UdpReader},
6    MspErr,
7};
8
9const MAGIC_BYTES: &[u8] = &[
10    0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78,
11];
12
13/// Bedrock server info type.
14///
15/// For the meaning of `motd_line_1` and `motd_line_2` refer to the following examples and images:
16///
17/// ```text
18/// motd_line_1 = "Dedicated Server"
19/// motd_line_2 = "Bedrock level"
20/// ```
21/// Result:
22///
23/// <img src="https://wiki.vg/images/b/bb/Server_ID_String_Example.png" alt="Server ID String Example.png" />
24#[derive(Serialize, Debug)]
25pub struct BedrockServer {
26    /// MCPE or MCEE(Education Edition) for Education Edition
27    pub edition: String,
28    /// MOTD line 1 for upstream display.
29    pub motd_line_1: String,
30    /// Protocol version.
31    pub protocol_version: i32,
32    /// Version name.
33    pub version_name: String,
34    /// Online players.
35    pub online_players: i32,
36    /// Max players.
37    pub max_players: i32,
38    /// Server unique id.
39    pub server_id: String,
40    /// MOTD line 2 for downstream display.
41    pub motd_line_2: String,
42    /// Game mode.
43    pub game_mode: String,
44    /// Game mode id.
45    pub game_mode_id: u8,
46    /// Ports required to connect to the server using IPv4.
47    pub port_ipv4: u16,
48    /// Ports required to connect to the server using IPv6.
49    pub port_ipv6: u16,
50}
51
52impl std::fmt::Display for BedrockServer {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(
55            f,
56            "{}",
57            serde_json::to_string_pretty(self).map_err(|_| std::fmt::Error)?
58        )
59    }
60}
61
62pub fn get_bedrock_server_status(conf: &Conf) -> Result<BedrockServer, MspErr> {
63    let socket = create_udp_socket(&conf.socket_conf)?;
64
65    let packet = [
66        // Packet ID
67        &[0x01],
68        // Time
69        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].as_slice(),
70        // MAGIC
71        MAGIC_BYTES,
72    ]
73    .concat();
74
75    socket.send_to(packet.as_slice(), conf)?;
76
77    let mut udp_reader = UdpReader::create_with_idx(socket, 0);
78
79    match udp_reader.read_bufs(1)?.get(0) {
80        Some(&first_buf) if first_buf != 0x1C => {
81            return Err(MspErr::DataErr(format!(
82                "Packet response excepted start with: 0x1C, but got: 0x{:02X}",
83                first_buf
84            )));
85        }
86        Some(_) | None => {}
87    };
88
89    // Drop time data(8 bytes)
90    udp_reader.set_current_idx_forward(8);
91
92    let _server_guid = udp_reader.read_bufs(8)?;
93    let _magic_bytes = udp_reader.read_bufs(16)?;
94    let server_info_len = match udp_reader.read_bufs(2)?.try_into() {
95        Ok(len) => u16::from_be_bytes(len) as usize,
96        Err(_) => {
97            return Err(MspErr::DataErr("Cannot convert to u16.".into()));
98        }
99    };
100    let server_info_buf = udp_reader.read_bufs(server_info_len)?;
101    let server_info = String::from_utf8_lossy(server_info_buf.as_slice());
102
103    println!("{:?}", server_info);
104
105    let server_info_split = server_info.split(";").collect::<Vec<_>>();
106
107    if server_info_split.len() < 10 {
108        return Err(MspErr::DataErr(format!(
109            "Expected return at least 10 parts of server information, but {}  were obtained.",
110            server_info_split.len()
111        )));
112    }
113
114    Ok(BedrockServer {
115        edition: server_info_split[0].into(),
116        motd_line_1: server_info_split[1].into(),
117        protocol_version: server_info_split[2].parse()?,
118        version_name: server_info_split[3].into(),
119        online_players: server_info_split[4].parse()?,
120        max_players: server_info_split[5].parse()?,
121        server_id: server_info_split[6].into(),
122        motd_line_2: server_info_split[7].into(),
123        game_mode: server_info_split[8].into(),
124        game_mode_id: server_info_split[9].parse()?,
125        port_ipv4: if let Some(&p4) = server_info_split.get(10) {
126            p4.parse()?
127        } else {
128            conf.port
129        },
130        port_ipv6: if let Some(&p6) = server_info_split.get(11) {
131            p6.parse()?
132        } else {
133            0
134        },
135    })
136}