late-java-core 2.2.9

A Rust library for launching Minecraft Java Edition
use crate::error::{Result, LateJavaCoreError};
use serde::{Deserialize, Serialize};
use std::net::{TcpStream, SocketAddr};
use std::time::Duration;
use std::io::{Read, Write};

/// Información del servidor
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
    pub version: ServerVersion,
    pub players: Players,
    pub description: ServerDescription,
    pub favicon: Option<String>,
    pub modinfo: Option<ModInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerVersion {
    pub name: String,
    pub protocol: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Players {
    pub max: i32,
    pub online: i32,
    pub sample: Option<Vec<Player>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Player {
    pub name: String,
    pub id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerDescription {
    pub text: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModInfo {
    pub r#type: String,
    pub mod_list: Vec<Mod>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mod {
    pub modid: String,
    pub version: String,
}

/// Resultado del ping
#[derive(Debug, Clone)]
pub struct PingResult {
    pub latency: u64,
    pub server_info: ServerInfo,
}

/// Cliente de ping de servidor
pub struct StatusClient;

impl StatusClient {
    /// Hacer ping a un servidor
    pub fn ping(host: &str, port: u16) -> Result<PingResult> {
        let addr: SocketAddr = format!("{}:{}", host, port).parse()
            .map_err(|_| LateJavaCoreError::Network(reqwest::Error::from(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid address"))))?;

        let start = std::time::Instant::now();
        
        // Conectar al servidor
        let mut stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5))
            .map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;

        // Enviar handshake
        let handshake = Self::create_handshake(host, port);
        stream.write_all(&handshake)
            .map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;

        // Enviar status request
        let status_request = Self::create_status_request();
        stream.write_all(&status_request)
            .map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;

        // Leer respuesta
        let mut buffer = Vec::new();
        stream.read_to_end(&mut buffer)
            .map_err(|e| LateJavaCoreError::Network(reqwest::Error::from(e)))?;

        let latency = start.elapsed().as_millis() as u64;

        // Parsear respuesta
        let server_info = Self::parse_response(&buffer)?;

        Ok(PingResult {
            latency,
            server_info,
        })
    }

    /// Crear handshake packet
    fn create_handshake(host: &str, port: u16) -> Vec<u8> {
        let mut buffer = Vec::new();
        
        // Packet ID (0x00)
        buffer.push(0x00);
        
        // Protocol version (754 for 1.16.5)
        buffer.extend_from_slice(&754i32.to_be_bytes());
        
        // Server address
        let host_bytes = host.as_bytes();
        buffer.extend_from_slice(&(host_bytes.len() as i32).to_be_bytes());
        buffer.extend_from_slice(host_bytes);
        
        // Server port
        buffer.extend_from_slice(&port.to_be_bytes());
        
        // Next state (1 for status)
        buffer.push(0x01);
        
        // Prepend packet length
        let packet_length = buffer.len() as i32;
        let mut packet = Vec::new();
        packet.extend_from_slice(&packet_length.to_be_bytes());
        packet.extend(buffer);
        
        packet
    }

    /// Crear status request packet
    fn create_status_request() -> Vec<u8> {
        let mut buffer = Vec::new();
        
        // Packet ID (0x00)
        buffer.push(0x00);
        
        // Prepend packet length
        let packet_length = buffer.len() as i32;
        let mut packet = Vec::new();
        packet.extend_from_slice(&packet_length.to_be_bytes());
        packet.extend(buffer);
        
        packet
    }

    /// Parsear respuesta del servidor
    fn parse_response(buffer: &[u8]) -> Result<ServerInfo> {
        if buffer.len() < 5 {
            return Err(LateJavaCoreError::Minecraft("Response too short".to_string()));
        }

        // Leer packet length
        let packet_length = i32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) as usize;
        
        if buffer.len() < packet_length + 4 {
            return Err(LateJavaCoreError::Minecraft("Incomplete packet".to_string()));
        }

        // Leer packet ID
        let packet_id = buffer[4];
        if packet_id != 0x00 {
            return Err(LateJavaCoreError::Minecraft("Invalid packet ID".to_string()));
        }

        // Leer JSON string length
        let json_length = i32::from_be_bytes([buffer[5], buffer[6], buffer[7], buffer[8]]) as usize;
        
        if buffer.len() < 9 + json_length {
            return Err(LateJavaCoreError::Minecraft("Incomplete JSON".to_string()));
        }

        // Leer JSON
        let json_bytes = &buffer[9..9 + json_length];
        let json_str = std::str::from_utf8(json_bytes)
            .map_err(|_| LateJavaCoreError::Minecraft("Invalid UTF-8 in JSON".to_string()))?;

        // Parsear JSON
        let server_info: ServerInfo = serde_json::from_str(json_str)
            .map_err(|e| LateJavaCoreError::Json(e))?;

        Ok(server_info)
    }
}

/// Función de conveniencia para hacer ping
pub fn ping_server(host: &str, port: u16) -> Result<PingResult> {
    StatusClient::ping(host, port)
}