diff --git a/CHANGELOG.md b/CHANGELOG.md index a1dafc3..4970d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Who knows what the future holds... # 0.0.7 - ??/??/2022 +### Changes: +[Minecraft](https://www.minecraft.com) bedrock edition support. +Also added a `query_legacy_specific` method to the mc game queries. + +### Breaking: +Removed `query_specific` from the mc protocol in favor of `query_java`, `query_legacy` and `query_legacy_specific`. # 0.0.6 - 28/11/2022 [Minecraft](https://www.minecraft.com) support (bedrock not supported yet). diff --git a/GAMES.md b/GAMES.md index 223f9c0..712cd02 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,35 +1,35 @@ A supported game is defined as a game that has been successfully tested, other games that use the implemented protocols might work too, but it isn't guaranteed. # Supported games: -| Game | Use name | Protocol | Notes | -|------------------------------------|----------|---------------------------|-------------------------------------------------------------------------------------------| -| Team Fortress 2 | TF2 | Valve Protocol | | -| The Ship | TS | Valve Protocol (*Altered) | | -| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | -| Counter-Strike: Source | CSS | Valve Protocol | | -| Day of Defeat: Source | DODS | Valve Protocol | | -| Left 4 Dead | L4D | Valve Protocol | | -| Left 4 Dead 2 | L4D2 | Valve Protocol | | -| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | -| Alien Swarm | ALIENS | Valve Protocol | | -| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | -| Insurgency | INS | Valve Protocol | | -| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | -| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | -| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | -| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | -| Minecraft | MC | Proprietary | Bedrock not supported yet. | -| 7 Days To Die | SDTD | Valve Protocol | | -| ARK: Survival Evolved | ASE | Valve Protocol | | -| Unturned | UNTURNED | Valve Protocol | | -| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | -| Team Fortress Classic | TFC | Valve Protocol | | -| Sven Co-op | SC | Valve Protocol (GoldSrc) | | -| Rust | RUST | Valve Protocol | | -| Counter-Strike | CS | Valve Protocol (GoldSrc) | | -| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | -| Day of Infamy | DOI | Valve Protocol | | -| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | +| Game | Use name | Protocol | Notes | +|------------------------------------|----------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Team Fortress 2 | TF2 | Valve Protocol | | +| The Ship | TS | Valve Protocol (*Altered) | | +| Counter-Strike: Global Offensive | CSGO | Valve Protocol | The server must have the cvar `host_players_show` set to `2` to get the full player list. | +| Counter-Strike: Source | CSS | Valve Protocol | | +| Day of Defeat: Source | DODS | Valve Protocol | | +| Left 4 Dead | L4D | Valve Protocol | | +| Left 4 Dead 2 | L4D2 | Valve Protocol | | +| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | | +| Alien Swarm | ALIENS | Valve Protocol | | +| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | | +| Insurgency | INS | Valve Protocol | | +| Insurgency: Sandstorm | INSS | Valve Protocol | Use the query port. | +| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | | +| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | | +| Day of Defeat | DOD | Valve Protocol (GoldSrc) | | +| Minecraft | MC | Proprietary | Bedrock edition provides a different response compared to the Java edition, query specifically for bedrock to get them, otherwise, only matching fields will be provided. | +| 7 Days To Die | SDTD | Valve Protocol | | +| ARK: Survival Evolved | ASE | Valve Protocol | | +| Unturned | UNTURNED | Valve Protocol | | +| The Forest | TF | Valve Protocol (GoldSrc) | Use the query port. | +| Team Fortress Classic | TFC | Valve Protocol | | +| Sven Co-op | SC | Valve Protocol (GoldSrc) | | +| Rust | RUST | Valve Protocol | | +| Counter-Strike | CS | Valve Protocol (GoldSrc) | | +| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Use the query port. | +| Day of Infamy | DOI | Valve Protocol | | +| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | | ## Planned to add support: _ diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 06f3d26..0422ebd 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,10 +1,10 @@ -A protocol is defined as proprietary if it is being used only for a single scope. +A protocol is defined as proprietary if it is being used only for a single scope (or series, like Minecraft). # Supported protocols: -| Name | For | Proprietary? | Documentation reference | Notes | -|----------------|-------|--------------|---------------------------------------------------------------------------|----------------------------------------| -| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | -| Minecraft | Games | Yes | [List Server Protocol](https://wiki.vg/Server_List_Ping) | Bedrock not yet supported. | +| Name | For | Proprietary? | Documentation reference | Notes | +|----------------|-------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| Valve Protocol | Games | No | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | Multi-packet decompression not tested. | +| Minecraft | Games | Yes | Java: [List Server Protocol](https://wiki.vg/Server_List_Ping)
Bedrock: [Node-GameDig Source](https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js) | | ## Planned to add support: _ diff --git a/README.md b/README.md index 49bd483..61760c7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Team Fortress 2 query example: use gamedig::games::tf2; fn main() { - let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port + let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 4ab5de0..dd181ba 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -1,7 +1,7 @@ use std::env; -use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, protocols, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; -use gamedig::protocols::minecraft::{LegacyGroup, Server}; +use gamedig::{aliens, arma2oa, ase, asrd, cs, cscz, csgo, css, dod, dods, doi, GDResult, gm, hl2dm, hldms, ins, insmic, inss, l4d, l4d2, mc, rust, sc, sdtd, tf, tf2, tfc, ts, unturned}; +use gamedig::protocols::minecraft::LegacyGroup; use gamedig::protocols::valve; use gamedig::protocols::valve::App; @@ -53,10 +53,11 @@ fn main() -> GDResult<()> { "_gld_f" => println!("{:#?}", valve::query(ip, port.unwrap(), App::GoldSrc(true), None, None)?), "mc" => println!("{:#?}", mc::query(ip, port)?), "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), - "_mc_legacy_vb1_8" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), ip, port.unwrap(), None)?), - "_mc_legacy_v1_4" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), ip, port.unwrap(), None)?), - "_mc_legacy_v1_6" => println!("{:#?}", protocols::minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), ip, port.unwrap(), None)?), + "mc_legacy_vb1_8" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::VB1_8, ip, port)?), + "mc_legacy_v1_4" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_4, ip, port)?), + "mc_legacy_v1_6" => println!("{:#?}", mc::query_legacy_specific(LegacyGroup::V1_6, ip, port)?), "7dtd" => println!("{:#?}", sdtd::query(ip, port)?), "ase" => println!("{:#?}", ase::query(ip, port)?), "unturned" => println!("{:#?}", unturned::query(ip, port)?), diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 59d34e1..f675508 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -2,7 +2,9 @@ use gamedig::games::mc; fn main() { - let response = mc::query("localhost", None); //or Some(25565), None is the default protocol port (which is 25565) + //or Some(), None is the default protocol port (which is 25565 for java and 19132 for bedrock) + let response = mc::query("127.0.0.1", None); + match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/examples/tf2.rs b/examples/tf2.rs index e79cb7f..f901d01 100644 --- a/examples/tf2.rs +++ b/examples/tf2.rs @@ -2,7 +2,7 @@ use gamedig::games::tf2; fn main() { - let response = tf2::query("localhost", None); //or Some(27015), None is the default protocol port (which is 27015) + let response = tf2::query("127.0.0.1", None); //or Some(27015), None is the default protocol port (which is 27015) match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:#?}", r) diff --git a/src/errors.rs b/src/errors.rs index 148aa7d..5f5cae4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -38,6 +38,8 @@ pub enum GDError { AutoQuery, /// A protocol-defined expected format was not met. ProtocolFormat(String), + /// Couldn't parse a value. + TypeParse(String), } impl fmt::Display for GDError { @@ -57,6 +59,7 @@ impl fmt::Display for GDError { GDError::JsonParse(details) => write!(f, "Json parse: {details}"), GDError::AutoQuery => write!(f, "Auto query failed."), GDError::ProtocolFormat(details) => write!(f, "Protocol rule: {details}"), + GDError::TypeParse(details) => write!(f, "Type parse: {details}"), } } } diff --git a/src/games/mc.rs b/src/games/mc.rs index e9b68d8..32443e7 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,39 +1,54 @@ use crate::{GDError, GDResult}; use crate::protocols::minecraft; -use crate::protocols::minecraft::{Server, Response, LegacyGroup}; +use crate::protocols::minecraft::{Response, LegacyGroup, BedrockResponse}; -/// Query with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: Option) -> GDResult { - minecraft::query(address, port_or_default(port), None) -} - -/// Query a Java Server. -pub fn query_java(address: &str, port: Option) -> GDResult { - minecraft::query_specific(Server::Java, address, port_or_default(port), None) -} - -/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). -pub fn query_legacy(address: &str, port: Option) -> GDResult { - let unwrapped_port = port_or_default(port); - - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_6), address, unwrapped_port, None) { + if let Ok(response) = query_java(address, port) { return Ok(response); } - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::V1_4), address, unwrapped_port, None) { - return Ok(response); + if let Ok(response) = query_bedrock(address, port) { + return Ok(Response::from_bedrock_response(response)); } - if let Ok(response) = minecraft::query_specific(Server::Legacy(LegacyGroup::VB1_8), address, unwrapped_port, None) { + if let Ok(response) = query_legacy(address, port) { return Ok(response); } Err(GDError::AutoQuery) } -fn port_or_default(port: Option) -> u16 { +/// Query a Java Server. +pub fn query_java(address: &str, port: Option) -> GDResult { + minecraft::query_java(address, port_or_java_default(port), None) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: Option) -> GDResult { + minecraft::query_legacy(address, port_or_java_default(port), None) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: Option) -> GDResult { + minecraft::query_legacy_specific(group, address, port_or_java_default(port), None) +} + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: Option) -> GDResult { + minecraft::query_bedrock(address, port_or_bedrock_default(port), None) +} + +fn port_or_java_default(port: Option) -> u16 { match port { None => 25565, Some(port) => port } } + +fn port_or_bedrock_default(port: Option) -> u16 { + match port { + None => 19132, + Some(port) => port + } +} diff --git a/src/protocols/minecraft/protocol/bedrock.rs b/src/protocols/minecraft/protocol/bedrock.rs new file mode 100644 index 0000000..f406f4f --- /dev/null +++ b/src/protocols/minecraft/protocol/bedrock.rs @@ -0,0 +1,100 @@ + +/* +This file has code that has been documented by the NodeJS GameDig library (MIT) from +https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js +*/ + +use crate::{GDError, GDResult}; +use crate::protocols::minecraft::{BedrockResponse, GameMode, Server}; +use crate::protocols::types::TimeoutSettings; +use crate::socket::{Socket, UdpSocket}; +use crate::utils::buffer::{get_string_utf8_le_unended, get_u16_be, get_u64_le, get_u8}; +use crate::utils::error_by_expected_size; + +pub struct Bedrock { + socket: UdpSocket +} + +impl Bedrock { + fn new(address: &str, port: u16, timeout_settings: Option) -> GDResult { + let socket = UdpSocket::new(address, port)?; + socket.apply_timeout(timeout_settings)?; + + Ok(Self { + socket + }) + } + + fn send_status_request(&mut self) -> GDResult<()> { + self.socket.send(&[ + // Message ID, ID_UNCONNECTED_PING + 0x01, + // Nonce / timestamp + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + // Magic + 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78, + // Client GUID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?; + + Ok(()) + } + + fn get_info(&mut self) -> GDResult { + self.send_status_request()?; + + let buf = self.socket.receive(None)?; + let mut pos = 0; + + if get_u8(&buf, &mut pos)? != 0x1c { + return Err(GDError::PacketBad("Invalid message id.".to_string())); + } + + // Checking for our nonce directly from a u64 (as the nonce is 8 bytes). + if get_u64_le(&buf, &mut pos)? != 9833440827789222417 { + return Err(GDError::PacketBad("Invalid nonce.".to_string())); + } + + // These 8 bytes are identical to the serverId string we receive in decimal below + pos += 8; + + // Verifying the magic value (as we need 16 bytes, cast to two u64 values) + if get_u64_le(&buf, &mut pos)? != 18374403896610127616 { + return Err(GDError::PacketBad("Invalid magic (part 1).".to_string())); + } + + if get_u64_le(&buf, &mut pos)? != 8671175388723805693 { + return Err(GDError::PacketBad("Invalid magic (part 2).".to_string())); + } + + let remaining_length = get_u16_be(&buf, &mut pos)? as usize; + error_by_expected_size(remaining_length, buf.len() - pos)?; + + let binding = get_string_utf8_le_unended(&buf, &mut pos)?; + let status: Vec<&str> = binding.split(";").collect(); + + // We must have at least 6 values + if status.len() < 6 { + return Err(GDError::PacketBad("Not enough status parts.".to_string())); + } + + Ok(BedrockResponse { + edition: status[0].to_string(), + name: status[1].to_string(), + version_name: status[3].to_string(), + version_protocol: status[2].to_string(), + max_players: status[5].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, + online_players: status[4].parse().map_err(|_| GDError::TypeParse("couldn't parse.".to_string()))?, + id: status.get(6).and_then(|v| Some(v.to_string())), + map: status.get(7).and_then(|v| Some(v.to_string())), + game_mode: match status.get(8) { + None => None, + Some(v) => Some(GameMode::from_bedrock(v)?) + }, + server_type: Server::Bedrock + }) + } + + pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::new(address, port, timeout_settings)?.get_info() + } +} diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index bcd7c87..a3ae26a 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,5 +1,6 @@ use crate::{GDError, GDResult}; -use crate::protocols::minecraft::{LegacyGroup, Response, Server}; +use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, Response}; +use crate::protocols::minecraft::protocol::bedrock::Bedrock; use crate::protocols::minecraft::protocol::java::Java; use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4; use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6; @@ -10,36 +11,57 @@ mod java; mod legacy_v1_4; mod legacy_v1_6; mod legacy_bv1_8; +mod bedrock; -/// Queries a Minecraft server with all the protocol variants one by one (Java -> Legacy (1.6 -> 1.4 -> Beta 1.8)). +/// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &str, port: u16, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_specific(Server::Java, address, port, timeout_settings.clone()) { + if let Ok(response) = query_java(address, port, timeout_settings.clone()) { return Ok(response); } - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_6), address, port, timeout_settings.clone()) { - return Ok(response); + if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) { + return Ok(Response::from_bedrock_response(response)); } - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::V1_4), address, port, timeout_settings.clone()) { - return Ok(response); - } - - if let Ok(response) = query_specific(Server::Legacy(LegacyGroup::VB1_8), address, port, timeout_settings.clone()) { + if let Ok(response) = query_legacy(address, port, timeout_settings) { return Ok(response); } Err(GDError::AutoQuery) } -/// Queries a specific Minecraft Server type. -pub fn query_specific(mc_type: Server, address: &str, port: u16, timeout_settings: Option) -> GDResult { - match mc_type { - Server::Java => Java::query(address, port, timeout_settings), - Server::Legacy(category) => match category { - LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), - LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), - LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings), - } +/// Query a Java Server. +pub fn query_java(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Java::query(address, port, timeout_settings) +} + +/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). +pub fn query_legacy(address: &str, port: u16, timeout_settings: Option) -> GDResult { + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) { + return Ok(response); + } + + if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) { + return Ok(response); + } + + Err(GDError::AutoQuery) +} + +/// Query a specific (Java) Legacy Server. +pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option) -> GDResult { + match group { + LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings), + LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings), + LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings) } } + +/// Query a Bedrock Server. +pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option) -> GDResult { + Bedrock::query(address, port, timeout_settings) +} diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 46cfa0c..05999a4 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -1,31 +1,8 @@ /* - -This file contains lightly modified versions of the original code. (using only the varint parts) -Code reference: https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs - -MIT License - -Copyright (c) 2021-2022 Jaiden Bernard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +Although its a lightly modified version, this file contains code +by Jaiden Bernard (2021-2022 - MIT) from +https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs */ use crate::{GDError, GDResult}; @@ -37,7 +14,9 @@ pub enum Server { /// Java Edition. Java, /// Legacy Java. - Legacy(LegacyGroup) + Legacy(LegacyGroup), + /// Bedrock Edition. + Bedrock } /// Legacy Java (Versions) Groups. @@ -83,6 +62,67 @@ pub struct Response { pub server_type: Server } +/// A Bedrock Edition query response. +#[derive(Debug)] +pub struct BedrockResponse { + /// Server edition. + pub edition: String, + /// Server name. + pub name: String, + /// Version name, example: "1.19.40". + pub version_name: String, + /// Version protocol, example: 760 (for 1.19.2). + pub version_protocol: String, + /// Number of server capacity. + pub max_players: u32, + /// Number of online players. + pub online_players: u32, + /// Server id. + pub id: Option, + /// The map. + pub map: Option, + /// Game mode. + pub game_mode: Option, + /// Tell's the server type. + pub server_type: Server +} + +impl Response { + pub fn from_bedrock_response(response: BedrockResponse) -> Self { + Self { + version_name: response.version_name, + version_protocol: 0, + max_players: response.max_players, + online_players: response.online_players, + sample_players: None, + description: response.name, + favicon: None, + previews_chat: None, + enforces_secure_chat: None, + server_type: Server::Bedrock + } + } +} + +/// A server's game mode (used only by Bedrock servers). +#[derive(Debug)] +pub enum GameMode { + Survival, Creative, Hardcore, Spectator, Adventure +} + +impl GameMode { + pub fn from_bedrock(value: &&str) -> GDResult { + match *value { + "Survival" => Ok(GameMode::Survival), + "Creative" => Ok(GameMode::Creative), + "Hardcore" => Ok(GameMode::Hardcore), + "Spectator" => Ok(GameMode::Spectator), + "Adventure" => Ok(GameMode::Adventure), + _ => Err(GDError::UnknownEnumCast) + } + } +} + pub fn get_varint(buf: &[u8], pos: &mut usize) -> GDResult { let mut result = 0; diff --git a/src/utils.rs b/src/utils.rs index 1f90729..8f4c7f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -98,6 +98,19 @@ pub mod buffer { Ok(value) } + pub fn get_string_utf8_le_unended(buf: &[u8], pos: &mut usize) -> GDResult { + let sub_buf = &buf[*pos..]; + if sub_buf.len() == 0 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet for getting an utf8 LE string.".to_string())); + } + + let value = std::str::from_utf8(&sub_buf) + .map_err(|_| GDError::PacketBad("Badly formatted utf8 LE string.".to_string()))?.to_string(); + + *pos += value.len(); + Ok(value) + } + pub fn get_string_utf16_be(buf: &[u8], pos: &mut usize) -> GDResult { let sub_buf = &buf[*pos..]; if sub_buf.len() == 0 { @@ -199,6 +212,16 @@ mod tests { assert_eq!(pos, 6); } + #[test] + fn get_string_utf8_le_unended_test() { + let data = [72, 101, 108, 108, 111]; + let mut pos = 0; + assert_eq!(buffer::get_string_utf8_le_unended(&data, &mut pos).unwrap(), "Hello"); + assert_eq!(pos, 5); + assert!(buffer::get_string_utf8_le_unended(&data, &mut pos).is_err()); + assert_eq!(pos, 5); + } + #[test] fn get_string_utf16_be_test() { let data = [0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f];