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)?;