diff --git a/examples/master_querant.rs b/examples/master_querant.rs index 05b995e..9bf9d18 100644 --- a/examples/master_querant.rs +++ b/examples/master_querant.rs @@ -126,7 +126,7 @@ fn main() -> GDResult<()> { ) } "mc" => println!("{:#?}", mc::query(ip, port)?), - "mc_java" => println!("{:#?}", mc::query_java(ip, port)?), + "mc_java" => println!("{:#?}", mc::query_java(ip, port, None)?), "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), "mc_legacy_vb1_8" => { diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 1bfcf9f..84ba123 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -1,9 +1,28 @@ use gamedig::games::mc; +use gamedig::protocols::minecraft::RequestSettings; fn main() { // 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".parse().unwrap(), None); + // This will fail if no server is available locally! + + match response { + Err(error) => println!("Couldn't query, error: {}", error), + Ok(r) => println!("{:#?}", r), + } + + // This is an example to query a server with a hostname to be specified in the + // packet. Passing -1 on the protocol_version means anything, note that + // an invalid value here might result in server not responding. + let response = mc::query_java( + &"209.222.114.62".parse().unwrap(), + Some(25565), + Some(RequestSettings { + hostname: "mc.hypixel.net".to_string(), + protocol_version: -1, + }), + ); match response { Err(error) => println!("Couldn't query, error: {}", error), diff --git a/src/games/mc.rs b/src/games/mc.rs index 368f851..9fe2db2 100644 --- a/src/games/mc.rs +++ b/src/games/mc.rs @@ -1,3 +1,4 @@ +use crate::protocols::minecraft::RequestSettings; use crate::{ protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, GDErrorKind, @@ -8,7 +9,7 @@ use std::net::{IpAddr, SocketAddr}; /// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy /// (1.6 -> 1.4 -> Beta 1.8)). pub fn query(address: &IpAddr, port: Option) -> GDResult { - if let Ok(response) = query_java(address, port) { + if let Ok(response) = query_java(address, port, None) { return Ok(response); } @@ -24,8 +25,16 @@ pub fn query(address: &IpAddr, port: Option) -> GDResult { } /// Query a Java Server. -pub fn query_java(address: &IpAddr, port: Option) -> GDResult { - minecraft::query_java(&SocketAddr::new(*address, port_or_java_default(port)), None) +pub fn query_java( + address: &IpAddr, + port: Option, + request_settings: Option, +) -> GDResult { + minecraft::query_java( + &SocketAddr::new(*address, port_or_java_default(port)), + None, + request_settings, + ) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). diff --git a/src/games/mod.rs b/src/games/mod.rs index 0613f53..f3bdae3 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -159,7 +159,7 @@ pub fn query_with_timeout( Protocol::Minecraft(version) => { match version { Some(protocols::minecraft::Server::Java) => { - protocols::minecraft::query_java(&socket_addr, timeout_settings).map(Box::new)? + protocols::minecraft::query_java(&socket_addr, timeout_settings, None).map(Box::new)? } Some(protocols::minecraft::Server::Bedrock) => { protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)? diff --git a/src/protocols/minecraft/protocol/java.rs b/src/protocols/minecraft/protocol/java.rs index 19eb080..bed40e6 100644 --- a/src/protocols/minecraft/protocol/java.rs +++ b/src/protocols/minecraft/protocol/java.rs @@ -11,33 +11,28 @@ use crate::{ use std::net::SocketAddr; +use crate::protocols::minecraft::{as_string, RequestSettings}; use byteorder::LittleEndian; use serde_json::Value; -#[rustfmt::skip] -const PAYLOAD: [u8; 17] = [ - //Packet ID (0) - 0x00, - //Protocol Version (-1 to determine version) - 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, - //Server address (can be anything) - 0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67, - //Server port (can be anything) - 0x00, 0x00, - //Next state (1 for status) - 0x01 -]; - pub struct Java { socket: TcpSocket, + request_settings: RequestSettings, } impl Java { - fn new(address: &SocketAddr, timeout_settings: Option) -> GDResult { + fn new( + address: &SocketAddr, + timeout_settings: Option, + request_settings: Option, + ) -> GDResult { let socket = TcpSocket::new(address)?; socket.apply_timeout(timeout_settings)?; - Ok(Self { socket }) + Ok(Self { + socket, + request_settings: request_settings.unwrap_or_default(), + }) } fn send(&mut self, data: Vec) -> GDResult<()> { @@ -57,7 +52,24 @@ impl Java { } fn send_handshake(&mut self) -> GDResult<()> { - self.send(PAYLOAD.to_vec())?; + let handshake_payload = [ + &[ + // Packet ID (0) + 0x00, + ], // Protocol Version (-1 to determine version) + as_varint(self.request_settings.protocol_version).as_slice(), + // Server address (can be anything) + as_string(&self.request_settings.hostname)?.as_slice(), + // Server port (can be anything) + &self.socket.port().to_le_bytes(), + &[ + // Next state (1 for status) + 0x01, + ], + ] + .concat(); + + self.send(handshake_payload)?; Ok(()) } @@ -143,7 +155,11 @@ impl Java { }) } - pub fn query(address: &SocketAddr, timeout_settings: Option) -> GDResult { - Self::new(address, timeout_settings)?.get_info() + pub fn query( + address: &SocketAddr, + timeout_settings: Option, + request_settings: Option, + ) -> GDResult { + Self::new(address, timeout_settings, request_settings)?.get_info() } } diff --git a/src/protocols/minecraft/protocol/mod.rs b/src/protocols/minecraft/protocol/mod.rs index 66c9ed7..5b79e07 100644 --- a/src/protocols/minecraft/protocol/mod.rs +++ b/src/protocols/minecraft/protocol/mod.rs @@ -1,3 +1,4 @@ +use crate::protocols::minecraft::types::RequestSettings; use crate::{ protocols::minecraft::{ protocol::{ @@ -26,7 +27,7 @@ mod legacy_v1_6; /// 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: &SocketAddr, timeout_settings: Option) -> GDResult { - if let Ok(response) = query_java(address, timeout_settings.clone()) { + if let Ok(response) = query_java(address, timeout_settings.clone(), None) { return Ok(response); } @@ -42,8 +43,12 @@ pub fn query(address: &SocketAddr, timeout_settings: Option) -> } /// Query a Java Server. -pub fn query_java(address: &SocketAddr, timeout_settings: Option) -> GDResult { - Java::query(address, timeout_settings) +pub fn query_java( + address: &SocketAddr, + timeout_settings: Option, + request_settings: Option, +) -> GDResult { + Java::query(address, timeout_settings, request_settings) } /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). diff --git a/src/protocols/minecraft/types.rs b/src/protocols/minecraft/types.rs index 4f8296a..1ddfb0f 100644 --- a/src/protocols/minecraft/types.rs +++ b/src/protocols/minecraft/types.rs @@ -8,7 +8,7 @@ use crate::{ types::{CommonPlayer, CommonResponse, GenericPlayer}, GenericResponse, }, - GDErrorKind::{PacketBad, UnknownEnumCast}, + GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast}, GDResult, }; @@ -88,6 +88,28 @@ pub struct JavaResponse { pub server_type: Server, } +/// Java-only additional request settings. +pub struct RequestSettings { + /// Some Minecraft servers do not respond as expected if this + /// isn't a specific value, `mc.hypixel.net` is an example. + pub hostname: String, + /// Specifies the client [protocol version number](https://wiki.vg/Protocol_version_numbers), + /// `-1` means anything. + pub protocol_version: i32, +} + +impl Default for RequestSettings { + /// `hostname`: "gamedig" + /// + /// `protocol_version`: -1 + fn default() -> Self { + Self { + hostname: "gamedig".to_string(), + protocol_version: -1, + } + } +} + impl CommonResponse for JavaResponse { fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) } @@ -238,3 +260,14 @@ pub(crate) fn get_string(buffer: &mut Buffer) -> GDResult GDResult> { + let length = value + .len() + .try_into() + .map_err(|e| InvalidInput.context(e))?; + let mut buf = as_varint(length); + buf.extend(value.as_bytes()); + + Ok(buf) +} diff --git a/src/socket.rs b/src/socket.rs index 64988db..042b6b4 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -20,16 +20,20 @@ pub trait Socket { fn send(&mut self, data: &[u8]) -> GDResult<()>; fn receive(&mut self, size: Option) -> GDResult>; + + fn port(&self) -> u16; } pub struct TcpSocket { socket: net::TcpStream, + address: SocketAddr, } impl Socket for TcpSocket { fn new(address: &SocketAddr) -> GDResult { Ok(Self { socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?, + address: *address, }) } @@ -54,6 +58,8 @@ impl Socket for TcpSocket { Ok(buf) } + + fn port(&self) -> u16 { self.address.port() } } pub struct UdpSocket { @@ -96,6 +102,8 @@ impl Socket for UdpSocket { Ok(buf[.. number_of_bytes_received].to_vec()) } + + fn port(&self) -> u16 { self.address.port() } } #[cfg(test)]