use std::{error::Error, io};
use async_trait::async_trait;
use reqwest::Client;
use serde::Deserialize;
use tracing::{debug, error, info};
use uuid::Uuid;
use crate::{
core::{actors::client::MinecraftClient, event::MinecraftCommunication},
network::{
connection::PossibleReadValue,
packet::{Packet, PacketCodec},
},
protocol::{
minecraft::java::login::{
clientbound_loginsuccess::{ClientBoundLoginSuccess, Property},
ClientBoundEncryptionRequest, ServerBoundEncryptionResponse,
},
types::{Boolean, ByteArray, ProtocolString, VarInt},
},
proxy_modes::{client_only::ClientOnlyMessage, ClientProxyModeHandler},
EncryptionState,
};
use super::ClientOnlyMode;
#[derive(Debug, Deserialize)]
struct MojangResponse {
id: String,
name: String,
properties: Vec<Property>,
}
#[async_trait]
impl ClientProxyModeHandler<MinecraftCommunication<ClientOnlyMessage>> for ClientOnlyMode {
async fn handle_internal_client(
&self,
message: MinecraftCommunication<ClientOnlyMessage>,
actor: &mut MinecraftClient<MinecraftCommunication<ClientOnlyMessage>>,
) -> io::Result<()> {
match message {
MinecraftCommunication::Packet(data) => {
actor.conn.write_packet(&data).await?;
}
MinecraftCommunication::Shutdown => {
debug!("Shutting down client (Received Shutdown message)");
actor.conn.close().await?;
}
_ => {
info!("Unhandled message: {:?}", message);
}
}
Ok(())
}
async fn handle_external_client(
&self,
data: PossibleReadValue,
actor: &mut MinecraftClient<MinecraftCommunication<ClientOnlyMessage>>,
) -> io::Result<()> {
if let PossibleReadValue::Packet(data) = data {
let _ = actor
.server_sender
.send(MinecraftCommunication::Packet(data))
.await;
}
Ok(())
}
async fn initialize_client(
&self,
actor: &mut MinecraftClient<MinecraftCommunication<ClientOnlyMessage>>,
) -> io::Result<()> {
let mut server_initialised = false;
let mut threshold = VarInt(0);
while !server_initialised {
if let Some(msg) = actor.client_receiver.recv().await {
match msg {
MinecraftCommunication::CustomData(ClientOnlyMessage::ServerReady()) => {
debug!("Server Ready, waiting for Server Threshold");
server_initialised = true;
}
MinecraftCommunication::CustomData(ClientOnlyMessage::ServerThreshold(th)) => {
threshold.0 = th.0;
}
_ => {}
}
}
}
debug!("Server Initialised, Threshold: {}", threshold.0);
let mut encryption = EncryptionState::new();
let mut request_packet = Packet::new(0x01);
let enc_request = ClientBoundEncryptionRequest {
server_id: ProtocolString("".to_string()),
public_key: ByteArray(encryption.get_public_key_bytes()),
verify_token: ByteArray(encryption.get_verify_token()),
requires_authentication: Boolean(true),
};
request_packet.encode(&enc_request)?;
debug!("P -> C: Sending encryption request");
debug!("Public key length: {}", enc_request.public_key.0.len());
debug!("Verify token length: {}", enc_request.verify_token.0.len());
let mut compression_packet: Packet = Packet::new(0x03);
compression_packet.encode(&threshold)?;
actor.conn.write_packet(&compression_packet).await?;
actor.conn.enable_compression(threshold.0);
debug!("Waiting for client encryption response");
debug!("Sending packet: {:?}", request_packet);
actor.conn.write_packet(&request_packet).await?;
let response = actor.conn.read_packet().await?;
if response.id != 0x01 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Expected encryption response (0x01), got 0x{:02x}",
response.id
),
));
}
let enc_response = ServerBoundEncryptionResponse::try_from(&response)?;
debug!("Received encryption response from client");
let shared_secret = encryption.decrypt_shared_secret(&enc_response.shared_secret.0)?;
debug!(
"Decrypted shared secret length: {}, Raw Data: {:?}",
shared_secret.len(),
shared_secret
);
if shared_secret.len() != 16 {
error!("Invalid shared secret length: {}", shared_secret.len());
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid shared secret length",
));
}
let tokent_similar = encryption.verify_encrypted_token(&enc_response.verify_token.0);
if !tokent_similar {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Verify token mismatch",
));
}
debug!("Setting up encryption with shared secret");
encryption.set_shared_secret(shared_secret.clone());
actor.conn.enable_encryption(&encryption);
let server_hash = encryption.compute_server_id_hash("");
debug!("Generated server hash: {}", server_hash);
let url = format!(
"https://sessionserver.mojang.com/session/minecraft/hasJoined?serverId={}&username={}",
server_hash, actor.username
);
debug!("Verifying with Mojang API (URL: {})", url);
let client = Client::new();
let auth_response = client.get(&url).send().await.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Mojang API request failed: {:?}", e.source()),
)
})?;
let status = auth_response.status();
debug!("Mojang API response status: {}", status);
let response = match status.is_success() && status.as_u16() == 200 {
true => {
debug!("Authentication successful (200 OK)");
let response_body = auth_response.json::<MojangResponse>().await.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Failed to parse Mojang response: {}", e),
)
});
response_body?
}
false => {
let error_text = auth_response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
error!("Authentication failed: {} - {}", status, error_text);
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("Authentication failed: {} - {}", status, error_text),
));
}
};
debug!(
"Successfully authenticated user: {} ({})",
actor.username, response.id
);
if response.name != actor.username {
error!("Username mismatch: {} != {}", response.name, actor.username);
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Username mismatch",
));
}
let formatted_uuid = format!(
"{}-{}-{}-{}-{}",
&response.id[..8],
&response.id[8..12],
&response.id[12..16],
&response.id[16..20],
&response.id[20..]
);
let uuid = Uuid::parse_str(&formatted_uuid).map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("Invalid UUID: {}", e))
})?;
let login_success = ClientBoundLoginSuccess {
uuid,
username: ProtocolString(actor.username.to_string()),
properties: response.properties,
};
let mut success_packet = Packet::from(&login_success);
let filtered_properties: Vec<Property> = login_success
.properties
.into_iter()
.filter(|p| p.name.0 == "textures") .collect();
for prop in filtered_properties {
success_packet.encode(&prop.name)?;
success_packet.encode(&prop.value)?;
success_packet.encode(&Boolean(prop.signature.is_some()))?;
if let Some(sig) = prop.signature {
success_packet.encode(&sig)?;
}
}
actor.conn.write_packet(&success_packet).await?;
let login_aknowledged = actor.conn.read_packet().await?;
if login_aknowledged.id != 0x03 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Expected login aknowledged (0x03), got 0x{:02x}",
login_aknowledged.id
),
));
}
actor
.server_sender
.send(MinecraftCommunication::CustomData(
ClientOnlyMessage::ClientLoginAknowledged(login_aknowledged),
))
.await
.unwrap();
info!(
"Succes Packet prepared for authenticated user: {}",
actor.username
);
debug!("Initializing client offline proxy mode");
Ok(())
}
}