summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs174
1 files changed, 174 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7d34846
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,174 @@
+use futures::future::OptionFuture;
+use mt_net::{CltSender, ReceiverExt, SenderExt, ToCltPkt, ToSrvPkt};
+use rand::RngCore;
+use sha2::Sha256;
+use srp::{client::SrpClient, groups::G_2048};
+use std::{future::Future, time::Duration};
+use tokio::time::{interval, Instant, Interval};
+
+enum AuthState {
+ Init(Interval),
+ Verify(Vec<u8>, SrpClient<'static, Sha256>),
+ Done,
+}
+
+struct Conn {
+ tx: CltSender,
+ auth: AuthState,
+ username: String,
+ password: String,
+}
+
+fn maybe_tick(iv: Option<&mut Interval>) -> OptionFuture<impl Future<Output = Instant> + '_> {
+ OptionFuture::from(iv.map(Interval::tick))
+}
+
+#[tokio::main]
+async fn main() {
+ let (tx, mut rx, worker) = mt_net::connect(&std::env::args().nth(1).expect("missing argument"))
+ .await
+ .unwrap();
+
+ let mut conn = Conn {
+ tx,
+ auth: AuthState::Init(interval(Duration::from_millis(100))),
+ username: "texmodbot".into(),
+ password: "owo".into(),
+ };
+
+ let init_pkt = ToSrvPkt::Init {
+ serialize_version: 29,
+ proto_version: 40..=40,
+ player_name: conn.username.clone(),
+ send_full_item_meta: false,
+ };
+
+ let worker_thread = tokio::spawn(worker.run());
+
+ loop {
+ tokio::select! {
+ pkt = rx.recv() => match pkt {
+ None => break,
+ Some(Err(e)) => eprintln!("{e}"),
+ Some(Ok(v)) => conn.handle_pkt(v).await,
+ },
+ Some(_) = maybe_tick(match &mut conn.auth {
+ AuthState::Init(iv) => Some(iv),
+ _ => None,
+ }) => {
+ conn.tx.send(&init_pkt).await.unwrap();
+ }
+ _ = tokio::signal::ctrl_c() => {
+ conn.tx.close();
+ }
+ }
+ }
+
+ worker_thread.await.unwrap();
+}
+
+impl Conn {
+ async fn handle_pkt(&mut self, pkt: ToCltPkt) {
+ use ToCltPkt::*;
+
+ match pkt {
+ Hello {
+ auth_methods,
+ username: name,
+ ..
+ } => {
+ use mt_net::AuthMethod;
+
+ if !matches!(self.auth, AuthState::Init(_)) {
+ return;
+ }
+
+ let srp = SrpClient::<Sha256>::new(&G_2048);
+
+ let mut rand_bytes = vec![0; 32];
+ rand::thread_rng().fill_bytes(&mut rand_bytes);
+
+ if self.username != name {
+ panic!("username changed");
+ }
+
+ if auth_methods.contains(AuthMethod::FirstSrp) {
+ let verifier = srp.compute_verifier(
+ self.username.to_lowercase().as_bytes(),
+ self.password.as_bytes(),
+ &rand_bytes,
+ );
+
+ self.tx
+ .send(&ToSrvPkt::FirstSrp {
+ salt: rand_bytes,
+ verifier,
+ empty_passwd: self.password.is_empty(),
+ })
+ .await
+ .unwrap();
+
+ self.auth = AuthState::Done;
+ } else if auth_methods.contains(AuthMethod::Srp) {
+ let a = srp.compute_public_ephemeral(&rand_bytes);
+
+ self.tx
+ .send(&ToSrvPkt::SrpBytesA { a, no_sha1: true })
+ .await
+ .unwrap();
+
+ self.auth = AuthState::Verify(rand_bytes, srp);
+ } else {
+ panic!("unsupported auth methods: {auth_methods:?}");
+ }
+ }
+ SrpBytesSaltB { salt, b } => {
+ if let AuthState::Verify(a, srp) = &self.auth {
+ let m = srp
+ .process_reply(
+ a,
+ self.username.to_lowercase().as_bytes(),
+ self.password.as_bytes(),
+ &salt,
+ &b,
+ )
+ .unwrap()
+ .proof()
+ .into();
+
+ self.tx.send(&ToSrvPkt::SrpBytesM { m }).await.unwrap();
+
+ self.auth = AuthState::Done;
+ }
+ }
+ NodeDefs(defs) => {
+ defs.0
+ .values()
+ .flat_map(|def| {
+ std::iter::empty()
+ .chain(&def.tiles)
+ .chain(&def.special_tiles)
+ .chain(&def.overlay_tiles)
+ })
+ .map(|tile| &tile.texture.name)
+ .for_each(|texture| {
+ if !texture.is_empty() {
+ println!("{texture}");
+ }
+ });
+ }
+ Kick(reason) => {
+ eprintln!("kicked: {reason}");
+ }
+ AcceptAuth { .. } => {
+ self.tx
+ .send(&ToSrvPkt::Init2 {
+ lang: "en_US".into(),
+ })
+ .await
+ .unwrap();
+ }
+ _ => {}
+ }
+ }
+}