diff --git a/Cargo.lock b/Cargo.lock index 144a840b..a1e4d3f0 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "azalea-auth" @@ -104,6 +104,7 @@ version = "0.1.0" dependencies = [ "azalea-auth", "azalea-protocol", + "tokio", ] [[package]] @@ -532,7 +533,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] @@ -596,7 +597,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" dependencies = [ - "socket2", + "socket2 0.3.19", "widestring", "winapi", "winreg", @@ -640,9 +641,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.109" +version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" +checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" [[package]] name = "linked-hash-map" @@ -652,10 +653,11 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] @@ -716,14 +718,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -842,6 +845,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + [[package]] name = "oorandom" version = "11.1.3" @@ -1203,6 +1212,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "syn" version = "1.0.82" @@ -1270,16 +1289,18 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" dependencies = [ "bytes", "libc", "memchr", "mio", "num_cpus", + "once_cell", "pin-project-lite", + "socket2 0.4.4", "tokio-macros", "winapi", ] @@ -1434,6 +1455,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.78" diff --git a/README.md b/README.md index 6502b0ae..10e94325 100755 --- a/README.md +++ b/README.md @@ -17,15 +17,15 @@ I named this Azalea because it sounds like a cool word and this is a cool librar Note that this doesn't work yet, it's just how I want the API to look. ```rs -use azalea::{Bot, Event}; +use azalea::{Account, Event}; -let bot = Bot::offline("bot"); -// or let bot = azalea::Bot::microsoft("access token").await; +let account = Account::offline("bot"); +// or let account = azalea::Account::microsoft("access token").await; -bot.join("localhost".try_into().unwrap()).await.unwrap(); +let bot = account.join("localhost".try_into().unwrap()).await.unwrap(); loop { - match bot.recv().await { + match bot.next().await { Event::Message(m) { if m.username == bot.username { return }; bot.chat(m.message).await; @@ -42,17 +42,17 @@ loop { You can use the `azalea::Bots` struct to control many bots as one unit. ```rs -use azalea::{Bot, Bots, Event, pathfinder}; +use azalea::{Account, Accounts, Event, pathfinder}; #[tokio::main] async fn main() { - let bots = Bots::new(); + let accounts = Accounts::new(); for i in 0..10 { - bots.add(Bot::offline(format!("bot{}", i))); + accounts.add(Account::offline(format!("bot{}", i))); } - bots.join("localhost".try_into().unwrap()).await.unwrap(); + let bots = accounts.join("localhost".try_into().unwrap()).await.unwrap(); bots.goto(pathfinder::GotoGoal(azalea::BlockCoord(0, 70, 0))).await; diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 5769f289..d8477ea1 100755 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -8,3 +8,4 @@ version = "0.1.0" [dependencies] azalea-auth = {path = "../azalea-auth"} azalea-protocol = {path = "../azalea-protocol"} +tokio = {version = "1.18.0", features = ["sync"]} diff --git a/azalea-client/README.md b/azalea-client/README.md index dd42a6a4..9b51356e 100755 --- a/azalea-client/README.md +++ b/azalea-client/README.md @@ -1,3 +1,3 @@ # Azalea Client -A library that can mimic everything a normal Minecraft client can do. If you want to make a bot, you should use `azalea` instead since it contains utilities for that. +A library that can mimic everything a normal Minecraft client can do. If you want to make a bot with higher-level functions, you should use `azalea` instead. diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index d7345f68..2098d6a3 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -1,6 +1,5 @@ -///! Connect to Minecraft servers. use azalea_protocol::{ - connect::HandshakeConnection, + connect::{GameConnection, HandshakeConnection}, packets::{ game::GamePacket, handshake::client_intention_packet::ClientIntentionPacket, @@ -12,140 +11,219 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; +use futures::FutureExt; +use std::{ + borrow::BorrowMut, + cell::RefCell, + future::Future, + pin::Pin, + sync::{Arc, Mutex, Weak}, +}; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -pub async fn join_server(address: &ServerAddress) -> Result<(), String> { - let username = "bot".to_string(); +///! Connect to Minecraft servers. - let resolved_address = resolver::resolve_address(address).await?; +/// Something that can join Minecraft servers. +pub struct Account { + username: String, +} - let mut conn = HandshakeConnection::new(&resolved_address).await?; +pub struct ClientState { + // placeholder + pub health: u16, +} - // handshake - conn.write( - ClientIntentionPacket { - protocol_version: PROTOCOL_VERSION, - hostname: address.host.clone(), - port: address.port, - intention: ConnectionProtocol::Login, - } - .get(), - ) - .await; - let mut conn = conn.login(); +/// A player that you can control that is currently in a Minecraft server. +pub struct Client { + event_receiver: UnboundedReceiver, + conn: GameConnection, + state: ClientState, +} - // login - conn.write(ServerboundHelloPacket { username }.get()).await; +pub enum Event {} - let mut conn = loop { - let packet_result = conn.read().await; - match packet_result { - Ok(packet) => match packet { - LoginPacket::ClientboundHelloPacket(p) => { - println!("Got encryption request {:?} {:?}", p.nonce, p.public_key); - let e = azalea_auth::encryption::encrypt(&p.public_key, &p.nonce).unwrap(); +impl Client { + async fn join(account: &Account, address: &ServerAddress) -> Result>, String> { + let resolved_address = resolver::resolve_address(address).await?; - // TODO: authenticate with the server here (authenticateServer) - println!("Sending encryption response {:?}", e); + let mut conn = HandshakeConnection::new(&resolved_address).await?; - conn.write( - ServerboundKeyPacket { - nonce: e.encrypted_nonce.into(), - shared_secret: e.encrypted_public_key.into(), - } - .get(), - ) - .await; - conn.set_encryption_key(e.secret_key); - } - LoginPacket::ClientboundLoginCompressionPacket(p) => { - println!("Got compression request {:?}", p.compression_threshold); - conn.set_compression_threshold(p.compression_threshold); - } - LoginPacket::ClientboundGameProfilePacket(p) => { - println!("Got profile {:?}", p.game_profile); - break conn.game(); - } - LoginPacket::ServerboundHelloPacket(p) => { - println!("Got hello {:?}", p); - } - LoginPacket::ClientboundLoginDisconnectPacket(p) => { - println!("Got disconnect {:?}", p); - } - LoginPacket::ClientboundCustomQueryPacket(p) => { - println!("Got custom query {:?}", p); - } - LoginPacket::ServerboundKeyPacket(_) => todo!(), - }, - Err(e) => { - panic!("Error: {:?}", e); + // handshake + conn.write( + ClientIntentionPacket { + protocol_version: PROTOCOL_VERSION, + hostname: address.host.clone(), + port: address.port, + intention: ConnectionProtocol::Login, } - } - }; + .get(), + ) + .await; + let mut conn = conn.login(); - // game - loop { - let packet_result = conn.read().await; - match packet_result { - Ok(packet) => match packet { - GamePacket::ClientboundLoginPacket(p) => { - println!("Got login packet {:?}", p); + // login + conn.write( + ServerboundHelloPacket { + username: account.username.clone(), + } + .get(), + ) + .await; + + let conn = loop { + let packet_result = conn.read().await; + match packet_result { + Ok(packet) => match packet { + LoginPacket::ClientboundHelloPacket(p) => { + println!("Got encryption request {:?} {:?}", p.nonce, p.public_key); + let e = azalea_auth::encryption::encrypt(&p.public_key, &p.nonce).unwrap(); + + // TODO: authenticate with the server here (authenticateServer) + println!("Sending encryption response {:?}", e); + + conn.write( + ServerboundKeyPacket { + nonce: e.encrypted_nonce.into(), + shared_secret: e.encrypted_public_key.into(), + } + .get(), + ) + .await; + conn.set_encryption_key(e.secret_key); + } + LoginPacket::ClientboundLoginCompressionPacket(p) => { + println!("Got compression request {:?}", p.compression_threshold); + conn.set_compression_threshold(p.compression_threshold); + } + LoginPacket::ClientboundGameProfilePacket(p) => { + println!("Got profile {:?}", p.game_profile); + break conn.game(); + } + LoginPacket::ServerboundHelloPacket(p) => { + println!("Got hello {:?}", p); + } + LoginPacket::ClientboundLoginDisconnectPacket(p) => { + println!("Got disconnect {:?}", p); + } + LoginPacket::ClientboundCustomQueryPacket(p) => { + println!("Got custom query {:?}", p); + } + LoginPacket::ServerboundKeyPacket(_) => todo!(), + }, + Err(e) => { + panic!("Error: {:?}", e); } - GamePacket::ClientboundUpdateViewDistancePacket(p) => { - println!("Got view distance packet {:?}", p); - } - GamePacket::ClientboundCustomPayloadPacket(p) => { - println!("Got custom payload packet {:?}", p); - } - GamePacket::ClientboundChangeDifficultyPacket(p) => { - println!("Got difficulty packet {:?}", p); - } - GamePacket::ClientboundDeclareCommandsPacket(p) => { - println!("Got declare commands packet"); - } - GamePacket::ClientboundPlayerAbilitiesPacket(p) => { - println!("Got player abilities packet {:?}", p); - } - GamePacket::ClientboundSetCarriedItemPacket(p) => { - println!("Got set carried item packet {:?}", p); - } - GamePacket::ClientboundUpdateTagsPacket(p) => { - println!("Got update tags packet"); - } - GamePacket::ClientboundDisconnectPacket(p) => { - println!("Got login disconnect packet {:?}", p); - } - GamePacket::ClientboundUpdateRecipesPacket(p) => { - println!("Got update recipes packet"); - } - GamePacket::ClientboundEntityEventPacket(p) => { - println!("Got entity event packet {:?}", p); - } - GamePacket::ClientboundRecipePacket(p) => { - println!("Got recipe packet"); - } - GamePacket::ClientboundPlayerPositionPacket(p) => { - // TODO: reply with teleport confirm - println!("Got player position packet {:?}", p); - } - GamePacket::ClientboundPlayerInfoPacket(p) => { - println!("Got player info packet {:?}", p); - } - GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { - println!("Got chunk cache center packet {:?}", p); - } - GamePacket::ClientboundLevelChunkWithLightPacket(p) => { - println!("Got chunk with light packet"); - } - GamePacket::ClientboundLightUpdatePacket(p) => { - println!("Got light update packet {:?}", p); - } - }, - Err(e) => { - panic!("Error: {:?}", e); + } + }; + + let (tx, rx) = mpsc::unbounded_channel(); + + // we got the GameConnection, so the server is now connected :) + let client = Client { + event_receiver: rx, + conn, + state: ClientState { health: 20 }, + }; + // let client = Arc::new(Mutex::new(client)); + // let weak_client = Arc::<_>::downgrade(&client); + + // just start up the game loop and we're ready! + // tokio::spawn(Self::game_loop(weak_client, tx)); + + Ok(client) + } + + // async fn game_loop(weak_client: Weak>, tx: UnboundedSender) { + // loop { + // let client_option = weak_client.upgrade(); + // match client_option { + // Some(client) => { + // let mut client = client.lock().unwrap(); + + // match client.conn.read().await { + // Ok(packet) => client.handle(&packet, &tx), + // Err(e) => { + // panic!("Error: {:?}", e); + // } + // }; + // } + // // the client was dropped, so we're done + // None => break, + // } + // } + // } + + fn handle(&self, packet: &GamePacket, tx: &UnboundedSender) { + match packet { + GamePacket::ClientboundLoginPacket(p) => { + println!("Got login packet {:?}", p); + } + GamePacket::ClientboundUpdateViewDistancePacket(p) => { + println!("Got view distance packet {:?}", p); + } + GamePacket::ClientboundCustomPayloadPacket(p) => { + println!("Got custom payload packet {:?}", p); + } + GamePacket::ClientboundChangeDifficultyPacket(p) => { + println!("Got difficulty packet {:?}", p); + } + GamePacket::ClientboundDeclareCommandsPacket(p) => { + println!("Got declare commands packet"); + } + GamePacket::ClientboundPlayerAbilitiesPacket(p) => { + println!("Got player abilities packet {:?}", p); + } + GamePacket::ClientboundSetCarriedItemPacket(p) => { + println!("Got set carried item packet {:?}", p); + } + GamePacket::ClientboundUpdateTagsPacket(p) => { + println!("Got update tags packet"); + } + GamePacket::ClientboundDisconnectPacket(p) => { + println!("Got login disconnect packet {:?}", p); + } + GamePacket::ClientboundUpdateRecipesPacket(p) => { + println!("Got update recipes packet"); + } + GamePacket::ClientboundEntityEventPacket(p) => { + println!("Got entity event packet {:?}", p); + } + GamePacket::ClientboundRecipePacket(p) => { + println!("Got recipe packet"); + } + GamePacket::ClientboundPlayerPositionPacket(p) => { + // TODO: reply with teleport confirm + println!("Got player position packet {:?}", p); + } + GamePacket::ClientboundPlayerInfoPacket(p) => { + println!("Got player info packet {:?}", p); + } + GamePacket::ClientboundSetChunkCacheCenterPacket(p) => { + println!("Got chunk cache center packet {:?}", p); + } + GamePacket::ClientboundLevelChunkWithLightPacket(p) => { + println!("Got chunk with light packet"); + } + GamePacket::ClientboundLightUpdatePacket(p) => { + println!("Got light update packet {:?}", p); } } println!(); } - Ok(()) + pub async fn next(&mut self) -> Option { + self.event_receiver.recv().await + } +} + +impl Account { + pub fn offline(username: &str) -> Self { + Self { + username: username.to_string(), + } + } + + pub async fn join(&self, address: &ServerAddress) -> Result>, String> { + Client::join(&self, address).await + } } diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 8c1bcfe9..0e1b8c16 100755 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -1,8 +1,10 @@ //! Significantly abstract azalea-protocol so it's actually useable for bots. -pub mod connect; +mod connect; pub mod ping; +pub use connect::{Account, ServerClient}; + #[cfg(test)] mod tests { #[test] diff --git a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs index 6882d255..b916cb8e 100644 --- a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs @@ -13,6 +13,7 @@ pub struct ClientboundLevelChunkWithLightPacket { #[derive(Clone, Debug, McBufReadable, McBufWritable)] pub struct ClientboundLevelChunkPacketData { heightmaps: azalea_nbt::Tag, + // we can't parse the data in azalea-protocol because it dependso on context from other packets data: Vec, block_entities: Vec, } diff --git a/bot/src/main.rs b/bot/src/main.rs index 14fc9656..8ae4ba1e 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -3,15 +3,17 @@ async fn main() { println!("Hello, world!"); // let address = "95.111.249.143:10000"; - // let address = "localhost:52467"; - let address = "localhost:25566"; + let address = "localhost:53193"; // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // .await // .unwrap(); // println!("{}", response.description.to_ansi(None)); - let _response = azalea_client::connect::join_server(&address.try_into().unwrap()) - .await - .unwrap(); + let account = azalea_client::Account::offline("bot"); + let client = account.join(&address.try_into().unwrap()).await.unwrap(); println!("connected"); + + // loop { + // match client.next().await {} + // } }