Skip to content

Commit db3bf0c

Browse files
committed
feat(join)!: make join protocol final working
1 parent 4cf55d1 commit db3bf0c

6 files changed

Lines changed: 141 additions & 36 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ sqlx = { version = "0.8.6", features = [ "runtime-tokio", "postgres", "macros",
2525
toml = "1.0.3+spec-1.1.0"
2626
url = "2.5.8"
2727
poise = "0.6.1"
28-
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
28+
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
29+
flate2 = "1.1.9"

src/cli.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pub enum Commands {
6868
Join {
6969
#[arg(value_name = "<IP>[:PORT]")]
7070
address: String,
71+
72+
#[arg(value_name = "Protocol version")]
73+
protocol: i32,
7174
},
7275

7376
#[command(

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ async fn main() -> Result<()> {
105105
tasks::run_query(address).await;
106106
}
107107

108-
cli::Commands::Join { address } => {
109-
tasks::run_join(address).await;
108+
cli::Commands::Join { address, protocol } => {
109+
tasks::run_join(address, protocol).await;
110110
}
111111

112112
cli::Commands::Crawl { cidr } => {

src/minecraft/join.rs

Lines changed: 130 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1+
use std::io::Read;
2+
use std::io::Cursor;
13
use std::net::Ipv4Addr;
24
use std::time::Duration;
5+
use flate2::read::ZlibDecoder;
6+
use futures::TryFutureExt;
37
use tokio::net::TcpStream;
4-
use tokio::io::AsyncWriteExt;
8+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
59
use tokio::time::timeout;
610
use crate::minecraft::Join;
7-
use crate::minecraft::utils::{Handshake, write_varint, read_varint, prepend_length, MinecraftPacket, encode_string, encode_uuid};
11+
use crate::minecraft::utils::{Handshake, write_varint, read_varint, prepend_length, MinecraftPacket, encode_string};
12+
use uuid::Uuid;
813

9-
// TODO: Make this work
10-
// It have every time the same error:
11-
// [20:13:41 INFO]: /[0:0:0:0:0:0:0:1]:58067 lost connection: Internal Exception: io.netty.handler.codec.DecoderException: Failed to decode packet 'serverbound/minecraft:hello'
12-
// I used this docs here https://minecraft.wiki/w/Java_Edition_protocol/FAQ#What's_the_normal_login_sequence_for_a_client?
1314
pub async fn execute_join_check(ip: Ipv4Addr, port: u16, timeout_dur: Duration, username: &str, protocol: i32) -> Result<Join, String> {
1415
let mut stream = timeout(timeout_dur, TcpStream::connect((ip, port)))
1516
.await
1617
.map_err(|_| "Connect Timeout")?
1718
.map_err(|e| e.to_string())?;
1819

19-
// Send handshake
2020
let handshake = Handshake {
2121
protocol,
2222
address: ip,
@@ -25,36 +25,140 @@ pub async fn execute_join_check(ip: Ipv4Addr, port: u16, timeout_dur: Duration,
2525
}.serialize();
2626
stream.write_all(&handshake).await.map_err(|e| e.to_string())?;
2727

28-
// Login Start Packet
28+
// Login start packet
2929
let mut login_start = Vec::new();
3030
write_varint(0x00, &mut login_start);
3131
login_start.extend(encode_string(username));
32-
login_start.push(0x01);
33-
// Used here 0x0U0
34-
login_start.extend(encode_uuid("00000000-0000-0000-0000-000000000000"));
32+
33+
// The protocol needs an uuid after 1.17.3
34+
if protocol > 761 {
35+
let uuid = Uuid::nil();
36+
login_start.extend_from_slice(uuid.as_bytes());
37+
}
3538

3639
let final_login = prepend_length(login_start);
3740
stream.write_all(&final_login).await.map_err(|e| e.to_string())?;
3841
stream.flush().await.map_err(|e| e.to_string())?;
3942

40-
let packet_length = read_varint(&mut stream).await? as usize;
41-
let packet_id = read_varint(&mut stream).await?;
43+
let result = timeout(timeout_dur, async {
44+
let _packet_length = read_varint(&mut stream).await?;
45+
let mut packet_id = read_varint(&mut stream).await?;
46+
47+
if packet_id == 0x03 {
48+
let _threshold = read_varint(&mut stream).await?;
49+
50+
let total_len = read_varint(&mut stream).await? as usize;
51+
let uncompressed_len = read_varint(&mut stream).await? as usize;
52+
53+
let mut packet_data = Vec::new();
54+
if uncompressed_len == 0 {
55+
let remaining = total_len - 1;
56+
let mut buf = vec![0u8; remaining];
57+
stream.read_exact(&mut buf).await.map_err(|e| e.to_string())?;
58+
packet_data = buf;
59+
} else {
60+
let compressed_len = total_len - get_varint_size(uncompressed_len as i32);
61+
let mut compressed_buf = vec![0u8; compressed_len];
62+
stream.read_exact(&mut compressed_buf).await.map_err(|e| e.to_string())?;
63+
64+
let mut decoder = ZlibDecoder::new(&compressed_buf[..]);
65+
decoder.read_to_end(&mut packet_data).map_err(|e: std::io::Error| e.to_string())?;
66+
}
67+
68+
let mut cursor = Cursor::new(packet_data);
69+
packet_id = read_varint(&mut cursor).await?;
70+
71+
return handle_packet(packet_id, cursor).await;
72+
}
73+
74+
handle_packet_stream(packet_id, &mut stream).await
75+
}).await.map_err(|_| "Timeout while reading answer".to_string())??;
76+
77+
Ok(result)
78+
}
79+
80+
async fn handle_packet_stream(packet_id: i32, stream: &mut TcpStream) -> Result<Join, String> {
81+
match packet_id {
82+
0x00 => {
83+
let reason_json = read_string_packet(stream).await?;
84+
let plain_reason = crate::minecraft::utils::parse_plain(&reason_json);
85+
let lower_reason = plain_reason.to_lowercase();
86+
87+
let online_mode_keywords = [
88+
"failed to verify", "not authenticated", "premium account",
89+
"authentication servers", "encryption", "online-mode",
90+
"requires mojang", "requires microsoft"
91+
];
4292

43-
let packet_id = packet_id;
93+
let whitelist_keywords = [
94+
"whitelist", "whitelisted", "not allowed"
95+
];
4496

45-
Err(format!("Packet: 0x{:02X}", packet_id))
97+
let is_cracked = !online_mode_keywords.iter().any(|&k| lower_reason.contains(k));
98+
let is_whitelist = whitelist_keywords.iter().any(|&k| lower_reason.contains(k));
99+
100+
Ok(Join {
101+
cracked: is_cracked,
102+
whitelist: is_whitelist,
103+
kick_message: Some(plain_reason)
104+
})
105+
}
106+
0x01 => Ok(Join { cracked: false, whitelist: false, kick_message: None }),
107+
0x02 => Ok(Join { cracked: true, whitelist: false, kick_message: None }),
108+
_ => Err(format!("Unknown packet: 0x{:02X}", packet_id)),
109+
}
46110
}
47111

48-
fn read_varint_from_slice(slice: &mut &[u8]) -> Result<i32, String> {
49-
let mut res = 0;
50-
let mut pos = 0;
51-
while pos < 32 {
52-
if slice.is_empty() { return Err("Unexpected end of slice".into()); }
53-
let byte = slice[0];
54-
*slice = &slice[1..];
55-
res |= ((byte & 0x7F) as i32) << pos;
56-
if (byte & 0x80) == 0 { return Ok(res); }
57-
pos += 7;
112+
async fn handle_packet(packet_id: i32, mut cursor: Cursor<Vec<u8>>) -> Result<Join, String> {
113+
match packet_id {
114+
0x00 => {
115+
let len = read_varint(&mut cursor).await? as usize;
116+
let mut buf = vec![0u8; len];
117+
118+
AsyncReadExt::read_exact(&mut cursor, &mut buf).map_err(|e| e.to_string()).await?;
119+
120+
let reason_json = String::from_utf8_lossy(&buf).to_string();
121+
let plain_reason = crate::minecraft::utils::parse_plain(&reason_json);
122+
let lower_reason = plain_reason.to_lowercase();
123+
124+
let online_mode_keywords = [
125+
"failed to verify", "not authenticated", "premium account",
126+
"authentication servers", "encryption", "online-mode",
127+
"requires mojang", "requires microsoft"
128+
];
129+
130+
let whitelist_keywords = [
131+
"whitelist", "whitelisted", "not allowed"
132+
];
133+
134+
let is_cracked = !online_mode_keywords.iter().any(|&k| lower_reason.contains(k));
135+
let is_whitelist = whitelist_keywords.iter().any(|&k| lower_reason.contains(k));
136+
137+
Ok(Join {
138+
cracked: is_cracked,
139+
whitelist: is_whitelist,
140+
kick_message: Some(plain_reason)
141+
})
142+
}
143+
0x01 => Ok(Join { cracked: false, whitelist: false, kick_message: None }),
144+
0x02 => Ok(Join { cracked: true, whitelist: false, kick_message: None }),
145+
_ => Err(format!("Unknown packet after decompression: 0x{:02X}", packet_id)),
146+
}
147+
}
148+
149+
fn get_varint_size(mut value: i32) -> usize {
150+
let mut size = 0;
151+
loop {
152+
size += 1;
153+
if (value as u32 & !0x7F) == 0 { break; }
154+
value = (value as u32 >> 7) as i32;
58155
}
59-
Err("VarInt too big".into())
156+
size
157+
}
158+
159+
async fn read_string_packet(stream: &mut TcpStream) -> Result<String, String> {
160+
let len = read_varint(stream).await?;
161+
let mut buf = vec![0u8; len as usize];
162+
stream.read_exact(&mut buf).await.map_err(|e| e.to_string())?;
163+
Ok(String::from_utf8_lossy(&buf).to_string())
60164
}

src/minecraft/utils.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::net::Ipv4Addr;
22
use serde_json::Value;
3-
use tokio::io::AsyncReadExt;
4-
use tokio::net::TcpStream;
3+
use tokio::io::{AsyncRead, AsyncReadExt};
54
use uuid::Uuid;
65

76
// Minecraft packet decoder/encoder
@@ -14,7 +13,7 @@ pub fn write_varint(val: i32, buf: &mut Vec<u8>) {
1413
buf.push(v as u8);
1514
}
1615

17-
pub async fn read_varint(stream: &mut TcpStream) -> Result<i32, String> {
16+
pub async fn read_varint<R: AsyncRead + Unpin>(stream: &mut R) -> Result<i32, String> {
1817
let mut res = 0;
1918
for i in 0..5 {
2019
let b = stream.read_u8().await.map_err(|e| e.to_string())?;

src/tasks.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,18 @@ pub async fn run_query(target: String) {
8383
}).await;
8484
}
8585

86-
pub async fn run_join(target: String) {
86+
pub async fn run_join(target: String, protocol: i32) {
8787
TaskManager::spawn("Join", move |_cancel_token| async move {
8888
logger::info(format!("Starting Join-Check for {}", target.clone().hex(DefaultColor::Highlight.hex())))
8989
.prefix("Join").send().await;
90-
logger::warning("Please note this feature is in development".on_yellow())
91-
.prefix("Join").send().await;
9290

9391
let parts = target.split(':').collect::<Vec<&str>>();
9492
let ip = Ipv4Addr::from_str(parts[0]).unwrap(); // TODO
9593
let port = parts.get(1).and_then(|p| p.parse::<u16>().ok()).unwrap_or(25565);
9694

9795
let username = "ServerRawler";
9896

99-
match execute_join_check(ip, port, Duration::from_secs(7), username, 770).await {
97+
match execute_join_check(ip, port, Duration::from_secs(7), username, protocol).await {
10098
Ok(result) => {
10199
logger::success(format!("Join-Check completed for {}:", target.hex(DefaultColor::Highlight.hex())))
102100
.prefix("Join").send().await;

0 commit comments

Comments
 (0)