From 02e59e77714a6cf60c6b69911e504d38dc2992fb Mon Sep 17 00:00:00 2001 From: Andrew Brower Date: Sun, 3 Nov 2024 21:22:52 -0500 Subject: [PATCH] feat: (WIP) properly send dimension data --- .cargo/config.toml | 17 ++++ .github/workflows/ci.yml | 17 ++++ Cargo.lock | 27 +++++ Cargo.toml | 1 + src/net/player.rs | 31 ++++-- src/protocol/datatypes/variable.rs | 99 ++++++++++++++++-- src/protocol/mod.rs | 2 + src/protocol/packets/login/registry.rs | 135 +++++++++++++++++++++++++ src/protocol/packets/play/login.rs | 39 ++++--- 9 files changed, 333 insertions(+), 35 deletions(-) create mode 100644 src/protocol/packets/login/registry.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 1c8846d..860f62b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,20 @@ +# Copyright (c) 2024 Andrew Brower. +# This file is part of Crawlspace. +# +# Crawlspace is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public +# License as published by the Free Software Foundation, either +# version 3 of the License, or (at your option) any later version. +# +# Crawlspace is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Crawlspace. If not, see +# . + [target.x86_64-unknown-linux-gnu] rustflags = ["-Ctarget-cpu=native"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc42a0f..c0e1f69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,20 @@ +# Copyright (c) 2024 Andrew Brower. +# This file is part of Crawlspace. +# +# Crawlspace is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public +# License as published by the Free Software Foundation, either +# version 3 of the License, or (at your option) any later version. +# +# Crawlspace is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Crawlspace. If not, see +# . + name: CI on: push: diff --git a/Cargo.lock b/Cargo.lock index 835fc8c..28111e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfb8" version = "0.8.1" @@ -176,6 +182,7 @@ dependencies = [ "bytes", "cfb8", "color-eyre", + "fastnbt", "serde", "serde_json", "sha2", @@ -216,6 +223,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastnbt" +version = "2.5.0" +source = "git+https://github.com/owengage/fastnbt.git#e2a5d8a7001d4f074ae99fd21bb485667934baeb" +dependencies = [ + "byteorder", + "cesu8", + "serde", + "serde_bytes", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -487,6 +505,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.213" diff --git a/Cargo.toml b/Cargo.toml index 5db88f0..fbe9465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ byteorder = "1.5.0" bytes = "1.8.0" cfb8 = { version = "0.8.1", optional = true } color-eyre = "0.6.3" +fastnbt = { git = "https://github.com/owengage/fastnbt.git" } serde = { version = "1.0.213", features = ["derive"] } serde_json = "1.0.132" sha2 = "0.10.8" diff --git a/src/net/player.rs b/src/net/player.rs index f174439..6e3de41 100644 --- a/src/net/player.rs +++ b/src/net/player.rs @@ -32,11 +32,11 @@ use crate::{ datatypes::{Bounded, VarInt}, packets::{ login::{ - FinishConfigurationAckS, FinishConfigurationC, HandshakeS, KnownPacksC, - KnownPacksS, LoginAckS, LoginStartS, LoginSuccessC, Ping, StatusRequestS, - StatusResponseC, + DimensionType, FinishConfigurationAckS, FinishConfigurationC, HandshakeS, + KnownPacksC, KnownPacksS, LoginAckS, LoginStartS, LoginSuccessC, Ping, Registry, + StatusRequestS, StatusResponseC, }, - play::LoginPlayC, + play::{Gamemode, LoginPlayC}, }, PacketState, }, @@ -190,6 +190,11 @@ impl Player { // TODO: maybe(?) actually handle this io.rx::().await?; + let mut world_registry = Registry::::new(); + world_registry.insert("crawlspace:limbo", Some(DimensionType::the_end())); + + io.tx(&world_registry).await?; + io.tx(&FinishConfigurationC).await?; io.rx::().await?; @@ -209,13 +214,27 @@ impl Player { let login = LoginPlayC { entity_id: self.id as i32, is_hardcore: false, - dimension_names: vec![Bounded::<&'static str>("the_end")], + dimension_names: vec![Bounded::<&'static str>("crawlspace:limbo")], max_players: VarInt(max_players), view_distance: VarInt(32), simulation_distance: VarInt(8), - reduced_debug_info: cfg!(debug_assertions), + reduced_debug_info: !cfg!(debug_assertions), + enable_respawn_screen: false, + do_limited_crafting: false, + dimension_type: Bounded::<&'static str>("crawlspace:limbo"), + dimension_name: Bounded::<&'static str>("Limbo"), + hashed_seed: 0, + gamemode: Gamemode::Adventure, + previous_gamemode: None, + is_debug: false, + is_superflat: false, + death_location: None, + portal_cooldown: VarInt(0), + enforces_secure_chat: false, }; + io.tx(&login).await?; + // FIXME: GROSS LOL?????? this should(?) change ownership of the player to the server // thread but realistically who knows burhhhh state.player_send.send(Arc::new(self.clone())).await?; diff --git a/src/protocol/datatypes/variable.rs b/src/protocol/datatypes/variable.rs index b3986aa..c004f79 100644 --- a/src/protocol/datatypes/variable.rs +++ b/src/protocol/datatypes/variable.rs @@ -17,8 +17,8 @@ * . */ +use std::fmt::Display; use std::io::Write; -use std::{arch::x86_64::*, fmt::Display}; use byteorder::ReadBytesExt; use color_eyre::eyre::Result; @@ -119,16 +119,12 @@ impl Encode for VarInt { impl VarLong { // how cute... - #[inline(always)] - #[cfg(target_feature = "neon")] - fn num_to_vector_stage1(self) -> [u8; 16] { - unimplemented!() - } - #[inline(always)] #[cfg(target_feature = "bmi2")] fn num_to_vector_stage1(self) -> [u8; 16] { + use std::arch::x86_64::*; let mut res = [0u64; 2]; + let x = self.0 as u64; res[0] = unsafe { _pdep_u64(x, 0x7f7f7f7f7f7f7f7f) }; @@ -140,6 +136,7 @@ impl VarLong { #[inline(always)] #[cfg(all(target_feature = "avx2", not(all(target_feature = "bmi2"))))] fn num_to_vector_stage1(self) -> [u8; 16] { + use std::arch::x86_64::*; let mut res = [0u64; 2]; let x = self; @@ -175,11 +172,82 @@ impl VarLong { unsafe { core::mem::transmute(res) } } + + // TODO: need to confirm this works. for now it's just a naive translation of avx2, + // but could definitely be improved -- blocking NEON implementation of Encode + // + // #[inline(always)] + // #[cfg(target_feature = "neon")] + // fn num_to_vector_stage1(self) -> [u8; 16] { + // use std::arch::aarch64::*; + // + // let mut res = [0u64; 2]; + // let x = self; + // + // let b = unsafe { vdupq_n_s64(self.0 as i64) }; + // let c = unsafe { + // vorrq_s64( + // vorrq_s64( + // vshlq_s64( + // vandq_s64( + // b, + // vcombine_s64( + // vcreate_s64(0x000003f800000000), + // vcreate_s64(0x00000007f0000000), + // ), + // ), + // vcombine_s64(vcreate_s64(5), vcreate_s64(4)), + // ), + // vshlq_s64( + // vandq_s64( + // b, + // vcombine_s64( + // vcreate_s64(0x00fe000000000000), + // vcreate_s64(0x0001fc0000000000), + // ), + // ), + // vcombine_s64(vcreate_s64(7), vcreate_s64(6)), + // ), + // ), + // vorrq_s64( + // vshlq_s64( + // vandq_s64( + // b, + // vcombine_s64( + // vcreate_s64(0x0000000000003f80), + // vcreate_s64(0x000000000000007f), + // ), + // ), + // vcombine_s64(vcreate_s64(1), vcreate_s64(0)), + // ), + // vshlq_s64( + // vandq_s64( + // b, + // vcombine_s64( + // vcreate_s64(0x000000000fe00000), + // vcreate_s64(0x00000000001fc000), + // ), + // ), + // vcombine_s64(vcreate_s64(3), vcreate_s64(2)), + // ), + // ), + // ) + // }; + // let d = unsafe { vorrq_s64(c, vshrq_n_s64::<8>(c)) }; + // + // res[0] = unsafe { vgetq_lane_s64(d, 0) as u64 }; + // res[1] = + // ((x.0 as u64 & 0x7f00000000000000) >> 56) | ((x.0 as u64 & 0x8000000000000000) >> 55); + // + // unsafe { core::mem::transmute(res) } + // } } impl Encode for VarLong { // ...and here's the second branch ^_^ + #[cfg(any(target_feature = "bmi2", target_feature = "avx2"))] fn encode(&self, mut w: impl Write) -> Result<()> { + use std::arch::x86_64::*; unsafe { // Break the number into 7-bit parts and spread them out into a vector let stage1: __m128i = std::mem::transmute(self.num_to_vector_stage1()); @@ -210,4 +278,21 @@ impl Encode for VarLong { )?) } } + + // TODO: implement this using neon? not likely we'll use arm-based servers but maybe nice for + // local testing? + #[cfg(not(any(target_feature = "bmi2", target_feature = "avx2")))] + fn encode(&self, mut w: impl Write) -> Result<()> { + use byteorder::WriteBytesExt; + + let mut val = self.0 as u64; + loop { + if val & 0b1111111111111111111111111111111111111111111111111111111110000000 == 0 { + w.write_u8(val as u8)?; + return Ok(()); + } + w.write_u8(val as u8 & 0b01111111 | 0b10000000)?; + val >>= 7; + } + } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index babacc8..9d71759 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -34,11 +34,13 @@ pub mod packets { mod config; mod handshake; mod login; + mod registry; mod status; pub use config::*; pub use handshake::*; pub use login::*; + pub use registry::*; pub use status::*; } diff --git a/src/protocol/packets/login/registry.rs b/src/protocol/packets/login/registry.rs new file mode 100644 index 0000000..5d3b505 --- /dev/null +++ b/src/protocol/packets/login/registry.rs @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use std::collections::HashMap; + +use fastnbt::SerOpts; +use serde::Serialize; + +use crate::protocol::{datatypes::VarInt, Encode, Packet}; + +#[derive(Debug)] +pub struct Registry(HashMap>); + +impl Registry +where + T: RegistryItem, +{ + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn insert(&mut self, ident: &str, item: Option) { + self.0.insert(ident.to_owned(), item); + } +} + +impl Packet for Registry +where + T: RegistryItem, +{ + const ID: i32 = 0x07; +} + +impl Encode for Registry +where + T: RegistryItem, +{ + fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + T::ID.encode(&mut w)?; + VarInt(self.0.len() as i32).encode(&mut w)?; + + for (ident, entry) in &self.0 { + ident.encode(&mut w)?; + match entry { + None => false.encode(&mut w)?, + Some(e) => { + true.encode(&mut w)?; + e.encode(&mut w)?; + } + } + } + + Ok(()) + } +} + +// TODO: maybe fill these in? for now we're only using dimensiontype so we can spawn in the end lol +pub trait RegistryItem: Encode { + const ID: &str; +} + +#[derive(Debug, Serialize)] +pub struct DimensionType<'a> { + fixed_time: Option, + has_skylight: bool, + has_ceiling: bool, + ultrawarm: bool, + natural: bool, + coordinate_scale: f64, + bed_works: bool, + respawn_anchor_works: bool, + min_y: i32, + height: i32, + logical_height: i32, + infiniburn: &'a str, + effects: &'a str, + ambient_light: f32, + piglin_safe: bool, + has_raids: bool, + monster_spawn_light_level: i32, + monster_spawn_block_light_limit: i32, +} + +impl DimensionType<'static> { + pub const fn the_end() -> Self { + Self { + fixed_time: Some(6000), + has_skylight: false, + has_ceiling: false, + ultrawarm: false, + natural: false, + coordinate_scale: 1.0, + bed_works: false, + respawn_anchor_works: false, + min_y: 0, + height: 256, + logical_height: 256, + infiniburn: "#minecraft:infiniburn_end", + effects: "minecraft:the_end", + ambient_light: 10.0, + piglin_safe: false, + has_raids: false, + monster_spawn_light_level: 0, + monster_spawn_block_light_limit: 0, + } + } +} + +impl RegistryItem for DimensionType<'_> { + const ID: &'static str = "minecraft:dimension_type"; +} + +impl Encode for DimensionType<'_> { + fn encode(&self, w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + debug!("serializing {:#?}", self); + fastnbt::to_bytes_with_opts(self, SerOpts::new().serialize_root_compound_name(false))? + .encode(w) + } +} diff --git a/src/protocol/packets/play/login.rs b/src/protocol/packets/play/login.rs index 158edca..966a1f2 100644 --- a/src/protocol/packets/play/login.rs +++ b/src/protocol/packets/play/login.rs @@ -19,7 +19,7 @@ use crate::protocol::{ datatypes::{Bounded, Position, VarInt}, - Encode, + Encode, Packet, }; #[derive(Debug)] @@ -42,12 +42,12 @@ pub struct LoginPlayC<'a> { /// Whether players can only craft recipes they have already unlocked. Currently unused by the client. pub do_limited_crafting: bool, /// okay i don't feel like copying these anymore https://wiki.vg/Protocol#Login_.28play.29 - pub dimension_type: VarInt, + pub dimension_type: Bounded<&'a str>, pub dimension_name: Bounded<&'a str>, /// first 8 bytes of seed sha256 - probably unneeded? pub hashed_seed: i64, pub gamemode: Gamemode, - pub previous_gamemode: Gamemode, + pub previous_gamemode: Option, pub is_debug: bool, pub is_superflat: bool, pub death_location: Option>, @@ -56,18 +56,16 @@ pub struct LoginPlayC<'a> { } #[derive(Clone, Copy, Debug)] -enum Gamemode { - None, +pub enum Gamemode { Survival, Creative, Adventure, Spectator, } -impl From> for i8 { - fn from(value: Gamemode) -> Self { +impl From for u8 { + fn from(value: Gamemode) -> Self { match value { - Gamemode::None => -1, Gamemode::Survival => 0, Gamemode::Creative => 1, Gamemode::Adventure => 2, @@ -76,24 +74,16 @@ impl From> for i8 { } } -impl From> for u8 { - fn from(value: Gamemode) -> Self { - match value { - Gamemode::Survival => 0, - Gamemode::Creative => 1, - Gamemode::Adventure => 2, - Gamemode::Spectator => 3, - Gamemode::None => panic!("u8::from(Gamemode::None) called!"), - } - } -} - #[derive(Debug)] -struct DeathLocation<'a> { +pub struct DeathLocation<'a> { dimension_name: Bounded<&'a str>, death_location: Position, } +impl Packet for LoginPlayC<'_> { + const ID: i32 = 0x2B; +} + impl<'a> Encode for LoginPlayC<'a> { fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { self.entity_id.encode(&mut w)?; @@ -110,7 +100,12 @@ impl<'a> Encode for LoginPlayC<'a> { self.dimension_name.encode(&mut w)?; self.hashed_seed.encode(&mut w)?; u8::from(self.gamemode).encode(&mut w)?; - i8::from(self.previous_gamemode).encode(&mut w)?; + + match self.previous_gamemode { + None => (-1).encode(&mut w)?, + Some(g) => u8::from(g).encode(&mut w)?, + } + self.is_debug.encode(&mut w)?; self.is_superflat.encode(&mut w)?; -- 2.51.2