chore(net): change get_string to get_str

changed return value from `String` to `Box<str>` which is smaller and makes more sense since we don't mutate packet data

String — 24 bytes (ptr + len + capacity)
Box<str> — 16 bytes (ptr + len)
This commit is contained in:
Alexander Medvedev 2026-05-23 11:21:53 +02:00
parent 8e7195c39e
commit 3303bb7b97
30 changed files with 139 additions and 113 deletions

10
Cargo.lock generated
View File

@ -2149,6 +2149,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.6" version = "0.4.6"
@ -3848,6 +3857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [ dependencies = [
"matchers", "matchers",
"nu-ansi-term",
"once_cell", "once_cell",
"regex-automata", "regex-automata",
"sharded-slab", "sharded-slab",

View File

@ -171,6 +171,7 @@ tracing = { version = "0.1.44", default-features = false, features = ["std", "at
tracing-subscriber = { version = "0.3.23", default-features = false, features = [ tracing-subscriber = { version = "0.3.23", default-features = false, features = [
"env-filter", "env-filter",
"fmt", "fmt",
"ansi",
"time", "time",
"std", "std",
] } ] }

View File

@ -87,7 +87,7 @@ impl UDPNetworkDecoder {
pub async fn get_packet_payload( pub async fn get_packet_payload(
&mut self, &mut self,
full_packet: Vec<u8>, mut full_packet: Vec<u8>,
) -> Result<Vec<u8>, PacketDecodeError> { ) -> Result<Vec<u8>, PacketDecodeError> {
if full_packet.is_empty() { if full_packet.is_empty() {
return Err(PacketDecodeError::MalformedLength("Empty packet".into())); return Err(PacketDecodeError::MalformedLength("Empty packet".into()));
@ -104,7 +104,7 @@ impl UDPNetworkDecoder {
// If compression is NOT enabled yet, the payload starts at index 1. // If compression is NOT enabled yet, the payload starts at index 1.
if self.compression.is_none() { if self.compression.is_none() {
return Ok(full_packet[1..].to_vec()); return Ok(full_packet.split_off(1));
} }
// If compression IS enabled, Bedrock expects a compression method byte at index 1. // If compression IS enabled, Bedrock expects a compression method byte at index 1.
@ -125,7 +125,7 @@ impl UDPNetworkDecoder {
} }
0xff => { 0xff => {
// None (Compression enabled but this specific packet is raw) // None (Compression enabled but this specific packet is raw)
Ok(full_packet[data_start..].to_vec()) Ok(full_packet.split_off(2))
} }
_ => Err(PacketDecodeError::FailedDecompression(format!( _ => Err(PacketDecodeError::FailedDecompression(format!(
"Unsupported compression method: 0x{compression_method:02x}" "Unsupported compression method: 0x{compression_method:02x}"

View File

@ -27,7 +27,7 @@ pub struct CPlayerChatMessage {
/// The RSA signature (256 bytes) verifying the message's authenticity. /// The RSA signature (256 bytes) verifying the message's authenticity.
pub message_signature: Option<Box<[u8]>>, pub message_signature: Option<Box<[u8]>>,
/// The raw plain-text content of the message. /// The raw plain-text content of the message.
pub message: String, pub message: Box<str>,
/// Epoch timestamp (milliseconds) when the message was sent. /// Epoch timestamp (milliseconds) when the message was sent.
pub timestamp: i64, pub timestamp: i64,
/// A random 64-bit value used to ensure signature uniqueness. /// A random 64-bit value used to ensure signature uniqueness.
@ -58,7 +58,7 @@ impl CPlayerChatMessage {
sender: uuid::Uuid, sender: uuid::Uuid,
index: VarInt, index: VarInt,
message_signature: Option<Box<[u8]>>, message_signature: Option<Box<[u8]>>,
message: String, message: Box<str>,
timestamp: i64, timestamp: i64,
salt: i64, salt: i64,
previous_messages: Box<[PreviousMessage]>, previous_messages: Box<[PreviousMessage]>,

View File

@ -2,7 +2,7 @@ use std::io::Read;
use pumpkin_data::packet::serverbound::CONFIG_COOKIE_RESPONSE; use pumpkin_data::packet::serverbound::CONFIG_COOKIE_RESPONSE;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion}; use pumpkin_util::version::JavaMinecraftVersion;
use crate::{ReadingError, ServerPacket, ser::NetworkReadExt}; use crate::{ReadingError, ServerPacket, ser::NetworkReadExt};
@ -16,7 +16,7 @@ const MAX_COOKIE_LENGTH: usize = 5120;
#[java_packet(CONFIG_COOKIE_RESPONSE)] #[java_packet(CONFIG_COOKIE_RESPONSE)]
pub struct SConfigCookieResponse { pub struct SConfigCookieResponse {
/// The unique identifier for the cookie being returned /// The unique identifier for the cookie being returned
pub key: ResourceLocation, pub key: Box<str>,
/// Indicates whether a payload is attached to this response /// Indicates whether a payload is attached to this response
pub has_payload: bool, pub has_payload: bool,
/// The actual data stored in the cookie. Limited to 5120 bytes /// The actual data stored in the cookie. Limited to 5120 bytes
@ -26,7 +26,7 @@ pub struct SConfigCookieResponse {
impl ServerPacket for SConfigCookieResponse { impl ServerPacket for SConfigCookieResponse {
fn read(read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
let mut read = read; let mut read = read;
let key = read.get_string()?; let key = read.get_str()?;
let has_payload = read.get_bool()?; let has_payload = read.get_bool()?;
if !has_payload { if !has_payload {

View File

@ -2,7 +2,7 @@ use std::io::Read;
use pumpkin_data::packet::serverbound::CONFIG_CUSTOM_PAYLOAD; use pumpkin_data::packet::serverbound::CONFIG_CUSTOM_PAYLOAD;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion}; use pumpkin_util::version::JavaMinecraftVersion;
use crate::{ReadingError, ServerPacket, ser::NetworkReadExt}; use crate::{ReadingError, ServerPacket, ser::NetworkReadExt};
@ -17,7 +17,7 @@ const MAX_PAYLOAD_SIZE: usize = 1_048_576;
pub struct SPluginMessage { pub struct SPluginMessage {
/// The name of the channel used to distinguish different types of messages. /// The name of the channel used to distinguish different types of messages.
/// Example: `minecraft:brand` or `velocity:main`. /// Example: `minecraft:brand` or `velocity:main`.
pub channel: ResourceLocation, pub channel: Box<str>,
/// The payload sent by the client. /// The payload sent by the client.
pub data: Box<[u8]>, pub data: Box<[u8]>,
} }
@ -25,7 +25,7 @@ pub struct SPluginMessage {
impl ServerPacket for SPluginMessage { impl ServerPacket for SPluginMessage {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
Ok(Self { Ok(Self {
channel: read.get_string()?, channel: read.get_str()?,
data: read.read_remaining_to_boxed_slice(MAX_PAYLOAD_SIZE)?, data: read.read_remaining_to_boxed_slice(MAX_PAYLOAD_SIZE)?,
}) })
} }

View File

@ -17,7 +17,7 @@ pub struct SHandShake {
/// The protocol version of the client (e.g., 767 for 1.21). /// The protocol version of the client (e.g., 767 for 1.21).
pub protocol_version: VarInt, pub protocol_version: VarInt,
/// The hostname or IP used by the client to connect /// The hostname or IP used by the client to connect
pub server_address: String, pub server_address: Box<str>,
/// The port number used by the client to connect /// The port number used by the client to connect
pub server_port: u16, pub server_port: u16,
/// The state the client wants to transition to (1 for Status, 2 for Login) /// The state the client wants to transition to (1 for Status, 2 for Login)
@ -28,7 +28,7 @@ impl ServerPacket for SHandShake {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
Ok(Self { Ok(Self {
protocol_version: read.get_var_int()?, protocol_version: read.get_var_int()?,
server_address: read.get_string_bounded(255)?, server_address: read.get_str_bounded(255)?,
server_port: read.get_u16_be()?, server_port: read.get_u16_be()?,
next_state: read next_state: read
.get_var_int()? .get_var_int()?

View File

@ -1,6 +1,6 @@
use pumpkin_data::packet::serverbound::LOGIN_COOKIE_RESPONSE; use pumpkin_data::packet::serverbound::LOGIN_COOKIE_RESPONSE;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion}; use pumpkin_util::version::JavaMinecraftVersion;
use std::io::Read; use std::io::Read;
use crate::{ use crate::{
@ -12,7 +12,7 @@ use crate::{
/// Response to a `CCookieRequest` (login) from the server. /// Response to a `CCookieRequest` (login) from the server.
/// The Notchian server only accepts responses of up to 5 kiB in size. /// The Notchian server only accepts responses of up to 5 kiB in size.
pub struct SLoginCookieResponse { pub struct SLoginCookieResponse {
pub key: ResourceLocation, pub key: Box<str>,
pub payload: Option<Box<[u8]>>, // 5120, pub payload: Option<Box<[u8]>>, // 5120,
} }
@ -20,7 +20,7 @@ const MAX_COOKIE_LENGTH: usize = 5120;
impl ServerPacket for SLoginCookieResponse { impl ServerPacket for SLoginCookieResponse {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
let key = read.get_string()?; let key = read.get_str()?;
let has_payload = read.get_bool()?; let has_payload = read.get_bool()?;
if !has_payload { if !has_payload {

View File

@ -13,14 +13,14 @@ use crate::{
#[java_packet(LOGIN_HELLO)] #[java_packet(LOGIN_HELLO)]
#[derive(Serialize)] #[derive(Serialize)]
pub struct SLoginStart { pub struct SLoginStart {
pub name: String, // 16 pub name: Box<str>, // 16
pub uuid: uuid::Uuid, pub uuid: uuid::Uuid,
} }
impl ServerPacket for SLoginStart { impl ServerPacket for SLoginStart {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
Ok(Self { Ok(Self {
name: read.get_string_bounded(16)?, name: read.get_str_bounded(16)?,
uuid: read.get_uuid()?, uuid: read.get_uuid()?,
}) })
} }

View File

@ -13,7 +13,7 @@ use crate::{
#[java_packet(PLAY_CHAT)] #[java_packet(PLAY_CHAT)]
pub struct SChatMessage { pub struct SChatMessage {
pub message: String, pub message: Box<str>,
pub timestamp: i64, pub timestamp: i64,
pub salt: i64, pub salt: i64,
pub signature: Option<Box<[u8]>>, pub signature: Option<Box<[u8]>>,
@ -25,7 +25,7 @@ pub struct SChatMessage {
impl ServerPacket for SChatMessage { impl ServerPacket for SChatMessage {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
Ok(Self { Ok(Self {
message: read.get_string_bounded(256)?, message: read.get_str_bounded(256)?,
timestamp: read.get_i64_be()?, timestamp: read.get_i64_be()?,
salt: read.get_i64_be()?, salt: read.get_i64_be()?,
signature: read.get_option(|v| v.read_boxed_slice(256))?, signature: read.get_option(|v| v.read_boxed_slice(256))?,

View File

@ -2,7 +2,7 @@ use std::io::Read;
use pumpkin_data::packet::serverbound::PLAY_COOKIE_RESPONSE; use pumpkin_data::packet::serverbound::PLAY_COOKIE_RESPONSE;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion}; use pumpkin_util::version::JavaMinecraftVersion;
use crate::{ use crate::{
ServerPacket, ServerPacket,
@ -13,7 +13,7 @@ use crate::{
/// Response to a `CCookieRequest` (play) from the server. /// Response to a `CCookieRequest` (play) from the server.
/// The Notchian (vanilla) server only accepts responses of up to 5 KiB in size. /// The Notchian (vanilla) server only accepts responses of up to 5 KiB in size.
pub struct SCookieResponse { pub struct SCookieResponse {
pub key: ResourceLocation, pub key: Box<str>,
pub payload: Option<Box<[u8]>>, // 5120, pub payload: Option<Box<[u8]>>, // 5120,
} }
@ -21,7 +21,7 @@ const MAX_COOKIE_LENGTH: usize = 5120;
impl ServerPacket for SCookieResponse { impl ServerPacket for SCookieResponse {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
let key = read.get_string()?; let key = read.get_str()?;
let has_payload = read.get_bool()?; let has_payload = read.get_bool()?;
if !has_payload { if !has_payload {

View File

@ -2,7 +2,7 @@ use std::io::Read;
use pumpkin_data::packet::serverbound::PLAY_CUSTOM_PAYLOAD; use pumpkin_data::packet::serverbound::PLAY_CUSTOM_PAYLOAD;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion}; use pumpkin_util::version::JavaMinecraftVersion;
use crate::{ReadingError, ServerPacket, ser::NetworkReadExt}; use crate::{ReadingError, ServerPacket, ser::NetworkReadExt};
@ -17,7 +17,7 @@ const MAX_PAYLOAD_SIZE: usize = 32_767;
pub struct SCustomPayload { pub struct SCustomPayload {
/// The name of the channel used to distinguish different types of messages. /// The name of the channel used to distinguish different types of messages.
/// Example: `minecraft:brand` or `voicechat:request_secret`. /// Example: `minecraft:brand` or `voicechat:request_secret`.
pub channel: ResourceLocation, pub channel: Box<str>,
/// The payload sent by the client. /// The payload sent by the client.
pub data: Box<[u8]>, pub data: Box<[u8]>,
} }
@ -25,7 +25,7 @@ pub struct SCustomPayload {
impl ServerPacket for SCustomPayload { impl ServerPacket for SCustomPayload {
fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, _version: &JavaMinecraftVersion) -> Result<Self, ReadingError> {
Ok(Self { Ok(Self {
channel: read.get_string()?, channel: read.get_str()?,
data: read.read_remaining_to_boxed_slice(MAX_PAYLOAD_SIZE)?, data: read.read_remaining_to_boxed_slice(MAX_PAYLOAD_SIZE)?,
}) })
} }

View File

@ -13,10 +13,10 @@ use crate::{
pub struct SUpdateSign { pub struct SUpdateSign {
pub location: BlockPos, pub location: BlockPos,
pub is_front_text: bool, pub is_front_text: bool,
pub line_1: String, pub line_1: Box<str>,
pub line_2: String, pub line_2: Box<str>,
pub line_3: String, pub line_3: Box<str>,
pub line_4: String, pub line_4: Box<str>,
} }
const MAX_LINE_LENGTH: usize = 386; const MAX_LINE_LENGTH: usize = 386;
@ -26,10 +26,10 @@ impl ServerPacket for SUpdateSign {
Ok(Self { Ok(Self {
location: BlockPos::from_i64(read.get_i64_be()?), location: BlockPos::from_i64(read.get_i64_be()?),
is_front_text: read.get_bool()?, is_front_text: read.get_bool()?,
line_1: read.get_string_bounded(MAX_LINE_LENGTH)?, line_1: read.get_str_bounded(MAX_LINE_LENGTH)?,
line_2: read.get_string_bounded(MAX_LINE_LENGTH)?, line_2: read.get_str_bounded(MAX_LINE_LENGTH)?,
line_3: read.get_string_bounded(MAX_LINE_LENGTH)?, line_3: read.get_str_bounded(MAX_LINE_LENGTH)?,
line_4: read.get_string_bounded(MAX_LINE_LENGTH)?, line_4: read.get_str_bounded(MAX_LINE_LENGTH)?,
}) })
} }
} }

View File

@ -418,11 +418,11 @@ pub struct Sample {
// basically game profile // basically game profile
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Property { pub struct Property {
pub name: String, pub name: Box<str>,
// base 64 // base 64
pub value: String, pub value: Box<str>,
// base 64 // base 64
pub signature: Option<String>, pub signature: Option<Box<str>>,
} }
#[derive(Serialize)] #[derive(Serialize)]

View File

@ -101,7 +101,7 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer<R> {
} }
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_str(&self.inner.get_string()?) visitor.visit_str(&self.inner.get_str()?)
} }
fn deserialize_bytes<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> { fn deserialize_bytes<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> {

View File

@ -60,8 +60,8 @@ pub trait NetworkReadExt {
fn get_var_uint(&mut self) -> Result<VarUInt, ReadingError>; fn get_var_uint(&mut self) -> Result<VarUInt, ReadingError>;
fn get_var_long(&mut self) -> Result<VarLong, ReadingError>; fn get_var_long(&mut self) -> Result<VarLong, ReadingError>;
fn get_var_ulong(&mut self) -> Result<VarULong, ReadingError>; fn get_var_ulong(&mut self) -> Result<VarULong, ReadingError>;
fn get_string_bounded(&mut self, bound: usize) -> Result<String, ReadingError>; fn get_str_bounded(&mut self, bound: usize) -> Result<Box<str>, ReadingError>;
fn get_string(&mut self) -> Result<String, ReadingError>; fn get_str(&mut self) -> Result<Box<str>, ReadingError>;
fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError>; fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError>;
fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError>; fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError>;
@ -154,7 +154,7 @@ impl<R: Read> NetworkReadExt for R {
VarULong::decode(self) VarULong::decode(self)
} }
fn get_string_bounded(&mut self, bound: usize) -> Result<String, ReadingError> { fn get_str_bounded(&mut self, bound: usize) -> Result<Box<str>, ReadingError> {
let bytes_len = self.get_var_uint()?.0 as usize; let bytes_len = self.get_var_uint()?.0 as usize;
// We treat `bound` as the maximum number of Java `char`s allowed. // We treat `bound` as the maximum number of Java `char`s allowed.
@ -170,7 +170,7 @@ impl<R: Read> NetworkReadExt for R {
let data = self.read_boxed_slice(bytes_len)?; let data = self.read_boxed_slice(bytes_len)?;
let string = let string =
String::from_utf8(data.into()).map_err(|e| ReadingError::Message(e.to_string()))?; std::str::from_utf8(&data).map_err(|e| ReadingError::Message(e.to_string()))?;
// Next, if we're able to find the (bound + 1)th UTF-16 character, the string is too big. // Next, if we're able to find the (bound + 1)th UTF-16 character, the string is too big.
if string.encode_utf16().nth(bound).is_some() { if string.encode_utf16().nth(bound).is_some() {
@ -179,11 +179,11 @@ impl<R: Read> NetworkReadExt for R {
))); )));
} }
Ok(string) Ok(string.into())
} }
fn get_string(&mut self) -> Result<String, ReadingError> { fn get_str(&mut self) -> Result<Box<str>, ReadingError> {
self.get_string_bounded(i32::MAX as usize) self.get_str_bounded(i32::MAX as usize)
} }
fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError> { fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError> {

View File

@ -15,6 +15,7 @@ use std::{
use crate::text::{TextComponentBase, TextContent, style::Style}; use crate::text::{TextComponentBase, TextContent, style::Style};
static VANILLA_EN_US_JSON: &str = include_str!("../../assets/en_us_java.json"); static VANILLA_EN_US_JSON: &str = include_str!("../../assets/en_us_java.json");
static PUMPKIN_EN_US_JSON: &str = include_str!("../../assets/translations/en_us.json");
static PUMPKIN_BRB_JSON: &str = include_str!("../../assets/translations/brb.json"); static PUMPKIN_BRB_JSON: &str = include_str!("../../assets/translations/brb.json");
static PUMPKIN_DE_DE_JSON: &str = include_str!("../../assets/translations/de_de.json"); static PUMPKIN_DE_DE_JSON: &str = include_str!("../../assets/translations/de_de.json");
static PUMPKIN_ES_ES_JSON: &str = include_str!("../../assets/translations/es_es.json"); static PUMPKIN_ES_ES_JSON: &str = include_str!("../../assets/translations/es_es.json");
@ -272,6 +273,8 @@ pub static TRANSLATIONS: LazyLock<Mutex<[HashMap<String, String>; Locale::COUNT]
std::array::from_fn(|_| HashMap::new()); std::array::from_fn(|_| HashMap::new());
let vanilla_en_us: HashMap<String, String> = let vanilla_en_us: HashMap<String, String> =
serde_json::from_str(VANILLA_EN_US_JSON).expect("Could not parse en_us_java.json."); serde_json::from_str(VANILLA_EN_US_JSON).expect("Could not parse en_us_java.json.");
let pumpkin_en_us: HashMap<String, String> =
serde_json::from_str(PUMPKIN_EN_US_JSON).expect("Could not parse en_us.json.");
let pumpkin_brb: HashMap<String, String> = let pumpkin_brb: HashMap<String, String> =
serde_json::from_str(PUMPKIN_BRB_JSON).expect("Could not parse brb.json."); serde_json::from_str(PUMPKIN_BRB_JSON).expect("Could not parse brb.json.");
let pumpkin_de_de: HashMap<String, String> = let pumpkin_de_de: HashMap<String, String> =
@ -322,6 +325,9 @@ pub static TRANSLATIONS: LazyLock<Mutex<[HashMap<String, String>; Locale::COUNT]
for (key, value) in vanilla_en_us { for (key, value) in vanilla_en_us {
array[Locale::EnUs as usize].insert(format!("minecraft:{key}"), value); array[Locale::EnUs as usize].insert(format!("minecraft:{key}"), value);
} }
for (key, value) in pumpkin_en_us {
array[Locale::EnUs as usize].insert(format!("pumpkin:{key}"), value);
}
for (key, value) in pumpkin_brb { for (key, value) in pumpkin_brb {
array[Locale::Brb as usize].insert(format!("pumpkin:{key}"), value); array[Locale::Brb as usize].insert(format!("pumpkin:{key}"), value);
} }

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use alter_ground::AlterGroundTreeDecorator; use alter_ground::AlterGroundTreeDecorator;
use attached_to_leaves::AttachedToLeavesTreeDecorator; use attached_to_leaves::AttachedToLeavesTreeDecorator;
@ -63,22 +65,24 @@ impl TreeDecorator {
} }
} }
pub(super) fn get_leaf_litter_positions( pub(super) fn get_leaf_litter_positions<'a>(
root_positions: &[BlockPos], root_positions: &'a [BlockPos],
log_positions: &[BlockPos], log_positions: &'a [BlockPos],
) -> Vec<BlockPos> { ) -> Cow<'a, [BlockPos]> {
if root_positions.is_empty() { if root_positions.is_empty() {
return log_positions.to_vec(); return Cow::Borrowed(log_positions);
} }
if let (Some(root), Some(log)) = (root_positions.first(), log_positions.first()) if log_positions
&& root.0.y == log.0.y .first()
.is_some_and(|log| root_positions[0].0.y == log.0.y)
{ {
let mut list = Vec::with_capacity(root_positions.len() + log_positions.len()); let mut list = Vec::with_capacity(root_positions.len() + log_positions.len());
list.extend_from_slice(log_positions); list.extend_from_slice(log_positions);
list.extend_from_slice(root_positions); list.extend_from_slice(root_positions);
return list; return Cow::Owned(list);
} }
root_positions.to_vec()
Cow::Borrowed(root_positions)
} }
} }

View File

@ -34,7 +34,7 @@ impl PlaceOnGroundTreeDecorator {
let mut l = pos.0.z; let mut l = pos.0.z;
let mut m = pos.0.z; let mut m = pos.0.z;
for block_pos_2 in list { for block_pos_2 in list.iter() {
if block_pos_2.0.y != i { if block_pos_2.0.y != i {
continue; continue;
} }

View File

@ -121,7 +121,7 @@ pub struct SignBlockEntity {
pub struct Text { pub struct Text {
pub has_glowing_text: AtomicBool, pub has_glowing_text: AtomicBool,
color: AtomicI8, color: AtomicI8,
pub messages: Arc<std::sync::Mutex<[String; 4]>>, pub messages: Arc<std::sync::Mutex<[Box<str>; 4]>>,
} }
impl Clone for Text { impl Clone for Text {
@ -160,7 +160,7 @@ impl From<Text> for NbtTag {
.lock() .lock()
.unwrap() .unwrap()
.iter() .iter()
.map(|s| Self::String(s.clone().into())) .map(|s| Self::String(s.clone()))
.collect(), .collect(),
); );
Self::Compound(nbt) Self::Compound(nbt)
@ -173,28 +173,30 @@ impl From<NbtTag> for Text {
let nbt = tag.extract_compound().unwrap(); let nbt = tag.extract_compound().unwrap();
let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false); let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false);
let color = nbt.get_string("color").unwrap(); let color = nbt.get_string("color").unwrap();
let messages: Vec<String> = nbt let messages: Vec<Box<str>> = nbt
.get_list("messages") .get_list("messages")
.unwrap() .unwrap()
.iter() .iter()
.filter_map(|tag| tag.extract_string().map(std::string::ToString::to_string)) .filter_map(|tag| tag.extract_string().map(Box::from))
.collect(); .collect();
let get_message =
|i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) };
Self { Self {
has_glowing_text: AtomicBool::new(has_glowing_text), has_glowing_text: AtomicBool::new(has_glowing_text),
color: AtomicI8::new(DyeColor::from(color) as i8), color: AtomicI8::new(DyeColor::from(color) as i8),
messages: Arc::new(std::sync::Mutex::new([ messages: Arc::new(std::sync::Mutex::new([
// its important that we use unwrap_or since otherwise we may crash on older versions get_message(0),
messages.first().unwrap_or(&String::new()).clone(), get_message(1),
messages.get(1).unwrap_or(&String::new()).clone(), get_message(2),
messages.get(2).unwrap_or(&String::new()).clone(), get_message(3),
messages.get(3).unwrap_or(&String::new()).clone(),
])), ])),
} }
} }
} }
impl Text { impl Text {
fn new(messages: [String; 4]) -> Self { fn new(messages: [Box<str>; 4]) -> Self {
Self { Self {
has_glowing_text: AtomicBool::new(false), has_glowing_text: AtomicBool::new(false),
color: AtomicI8::new(DyeColor::default() as i8), color: AtomicI8::new(DyeColor::default() as i8),
@ -263,7 +265,7 @@ impl BlockEntity for SignBlockEntity {
impl SignBlockEntity { impl SignBlockEntity {
pub const ID: &'static str = "minecraft:sign"; pub const ID: &'static str = "minecraft:sign";
#[must_use] #[must_use]
pub fn new(position: BlockPos, is_front: bool, messages: [String; 4]) -> Self { pub fn new(position: BlockPos, is_front: bool, messages: [Box<str>; 4]) -> Self {
Self { Self {
position, position,
is_waxed: AtomicBool::new(false), is_waxed: AtomicBool::new(false),

View File

@ -9,6 +9,7 @@ use crate::net::java::{JavaClient, PacketHandlerResult};
use crate::net::{ClientPlatform, DisconnectReason}; use crate::net::{ClientPlatform, DisconnectReason};
use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer}; use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
use crate::server::{Server, ticker::Ticker}; use crate::server::{Server, ticker::Ticker};
use bytes::BytesMut;
use plugin::server::server_command::ServerCommandEvent; use plugin::server::server_command::ServerCommandEvent;
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; use pumpkin_config::{AdvancedConfiguration, BasicConfiguration};
use pumpkin_macros::send_cancellable; use pumpkin_macros::send_cancellable;
@ -18,7 +19,7 @@ use rustyline::Editor;
use rustyline::history::FileHistory; use rustyline::history::FileHistory;
use rustyline::{Config, error::ReadlineError}; use rustyline::{Config, error::ReadlineError};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{Cursor, ErrorKind, IsTerminal, stdin}; use std::io::{ErrorKind, IsTerminal, stdin};
use std::process::exit; use std::process::exit;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
@ -418,14 +419,13 @@ impl PumpkinServer {
} }
} }
#[expect(clippy::too_many_lines)]
pub async fn unified_listener_task( pub async fn unified_listener_task(
&self, &self,
master_client_id_counter: &mut u64, master_client_id_counter: &mut u64,
tasks: &Arc<TaskTracker>, tasks: &Arc<TaskTracker>,
bedrock_clients: &Arc<Mutex<HashMap<SocketAddr, Arc<BedrockClient>>>>, bedrock_clients: &Arc<Mutex<HashMap<SocketAddr, Arc<BedrockClient>>>>,
) -> bool { ) -> bool {
let mut udp_buf = [0; 1496]; // Buffer for UDP receive let mut udp_buf = BytesMut::with_capacity(1496);
select! { select! {
// Branch for TCP connections (Java Edition) // Branch for TCP connections (Java Edition)
@ -491,7 +491,10 @@ impl PumpkinServer {
}, },
// Branch for UDP packets (Bedrock Edition) // Branch for UDP packets (Bedrock Edition)
udp_result = resolve_some(self.udp_socket.as_ref(), |sock: &Arc<UdpSocket>| sock.recv_from(&mut udp_buf)) => { udp_result = resolve_some(self.udp_socket.as_ref(), |sock: &Arc<UdpSocket>| {
unsafe { udp_buf.set_len(1496) };
sock.recv_from(&mut udp_buf[..])
}) => {
match udp_result { match udp_result {
Ok((len, client_addr)) => { Ok((len, client_addr)) => {
if len > 0 { if len > 0 {
@ -515,24 +518,19 @@ impl PumpkinServer {
new_client new_client
}).clone(); }).clone();
let reader = udp_buf[..len].to_vec(); let packet_bytes = udp_buf.split_to(len).freeze();
udp_buf.clear();
let server = self.server.clone(); let server = self.server.clone();
tasks.spawn(async move {
client.process_packet(&server, reader).await;
});
} else if let Some(sock) = self.udp_socket.as_ref() { tasks.spawn(async move {
let _ = BedrockClient::handle_offline_packet( client.process_packet(&server, packet_bytes).await;
&self.server, });
id,
&mut Cursor::new(&udp_buf[1..len]),
client_addr,
sock
).await;
} }
} }
} }
Err(e) => error!("UDP socket error: {e}"), Err(e) => {
error!("Failed to receive UDP packet: {e}");
}
} }
}, },

View File

@ -106,7 +106,7 @@ pub fn authenticate(
pub fn validate_textures(property: &Property, config: &TextureConfig) -> Result<(), TextureError> { pub fn validate_textures(property: &Property, config: &TextureConfig) -> Result<(), TextureError> {
let from64 = general_purpose::STANDARD let from64 = general_purpose::STANDARD
.decode(&property.value) .decode(property.value.as_bytes())
.map_err(|e| TextureError::DecodeError(e.to_string()))?; .map_err(|e| TextureError::DecodeError(e.to_string()))?;
let textures: ProfileTextures = let textures: ProfileTextures =
serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?; serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?;

View File

@ -229,7 +229,7 @@ impl BedrockClient {
}); });
} }
pub async fn process_packet(self: &Arc<Self>, server: &Arc<Server>, packet: Vec<u8>) { pub async fn process_packet(self: &Arc<Self>, server: &Arc<Server>, packet: Bytes) {
self.last_seen.store(std::time::Instant::now()); self.last_seen.store(std::time::Instant::now());
if let Err(error) = self.handle_packet_payload(server, packet).await { if let Err(error) = self.handle_packet_payload(server, packet).await {
error!( error!(
@ -501,7 +501,7 @@ impl BedrockClient {
pub async fn handle_packet_payload( pub async fn handle_packet_payload(
self: &Arc<Self>, self: &Arc<Self>,
server: &Arc<Server>, server: &Arc<Server>,
packet: Vec<u8>, packet: Bytes,
) -> Result<(), Error> { ) -> Result<(), Error> {
let reader = &mut Cursor::new(packet); let reader = &mut Cursor::new(packet);

View File

@ -61,7 +61,7 @@ impl JavaClient {
match bungeecord::bungeecord_login( match bungeecord::bungeecord_login(
&self.address, &self.address,
&self.server_address.lock().await, &self.server_address.lock().await,
login_start.name, login_start.name.into_string(),
) )
.await .await
{ {
@ -82,7 +82,7 @@ impl JavaClient {
let profile = GameProfile { let profile = GameProfile {
id, id,
name: login_start.name, name: login_start.name.into_string(),
properties: ArcSwap::new(Arc::new(vec![])), properties: ArcSwap::new(Arc::new(vec![])),
profile_actions: None, profile_actions: None,
}; };

View File

@ -76,7 +76,7 @@ pub struct JavaClient {
/// The client's configuration settings, Optional /// The client's configuration settings, Optional
pub config: Mutex<Option<PlayerConfig>>, pub config: Mutex<Option<PlayerConfig>>,
/// The Address used to connect to the Server, Send in the Handshake /// The Address used to connect to the Server, Send in the Handshake
pub server_address: Mutex<String>, pub server_address: Mutex<Box<str>>,
/// The current connection state of the client (e.g., Handshaking, Status, Play). /// The current connection state of the client (e.g., Handshaking, Status, Play).
pub connection_state: AtomicCell<ConnectionState>, pub connection_state: AtomicCell<ConnectionState>,
/// The client's IP address. /// The client's IP address.
@ -138,7 +138,7 @@ impl JavaClient {
id, id,
gameprofile: Mutex::new(None), gameprofile: Mutex::new(None),
config: Mutex::new(None), config: Mutex::new(None),
server_address: Mutex::new(String::new()), server_address: Mutex::new("".into()),
address: Mutex::new(address), address: Mutex::new(address),
connection_state: AtomicCell::new(ConnectionState::HandShake), connection_state: AtomicCell::new(ConnectionState::HandShake),
close_token: CancellationToken::new(), close_token: CancellationToken::new(),
@ -921,7 +921,7 @@ impl JavaClient {
let payload = SCustomPayload::read(payload, &version)?; let payload = SCustomPayload::read(payload, &version)?;
let event = PlayerCustomPayloadEvent::new( let event = PlayerCustomPayloadEvent::new(
player.clone(), player.clone(),
payload.channel, payload.channel.to_string(),
Bytes::from(payload.data), Bytes::from(payload.data),
); );
server.plugin_manager.fire(event).await; server.plugin_manager.fire(event).await;

View File

@ -1211,7 +1211,7 @@ impl JavaClient {
send_cancellable! {{ send_cancellable! {{
server; server;
PlayerChatEvent::new(player.clone(), chat_message.message.clone(), vec![]); PlayerChatEvent::new(player.clone(), chat_message.message.to_string(), vec![]);
'after: { 'after: {
info!("<chat> {}: {}", gameprofile.name, event.message); info!("<chat> {}: {}", gameprofile.name, event.message);

View File

@ -80,14 +80,14 @@ fn read_game_profile(read: impl Read) -> Result<GameProfile, VelocityError> {
.map_err(|_| VelocityError::FailedReadProfileUUID)?; .map_err(|_| VelocityError::FailedReadProfileUUID)?;
let name = read let name = read
.get_string() .get_str()
.map_err(|_| VelocityError::FailedReadProfileName)?; .map_err(|_| VelocityError::FailedReadProfileName)?;
let properties = read let properties = read
.get_list(|data| { .get_list(|data| {
let name = data.get_string()?; let name = data.get_str()?;
let value = data.get_string()?; let value = data.get_str()?;
let signature = data.get_option(NetworkReadExt::get_string)?; let signature = data.get_option(NetworkReadExt::get_str)?;
Ok(Property { Ok(Property {
name, name,
@ -99,7 +99,7 @@ fn read_game_profile(read: impl Read) -> Result<GameProfile, VelocityError> {
Ok(GameProfile { Ok(GameProfile {
id, id,
name, name: name.into_string(),
properties: ArcSwap::new(Arc::from(properties)), properties: ArcSwap::new(Arc::from(properties)),
profile_actions: None, profile_actions: None,
}) })
@ -131,7 +131,7 @@ pub fn receive_velocity_plugin_response(
)); ));
} }
let addr = data_without_signature let addr = data_without_signature
.get_string() .get_str()
.map_err(|_| VelocityError::FailedReadAddress)?; .map_err(|_| VelocityError::FailedReadAddress)?;
let socket_addr: SocketAddr = SocketAddr::new( let socket_addr: SocketAddr = SocketAddr::new(

View File

@ -1,5 +1,3 @@
use std::string::FromUtf8Error;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use thiserror::Error; use thiserror::Error;
@ -60,7 +58,7 @@ pub enum PacketError {
#[error("Missing packet lull terminator")] #[error("Missing packet lull terminator")]
MissingNullTerminator, MissingNullTerminator,
#[error("Invalid packet string body")] #[error("Invalid packet string body")]
InvalidBody(FromUtf8Error), InvalidBody(std::str::Utf8Error),
} }
#[derive(Debug)] #[derive(Debug)]
@ -68,7 +66,7 @@ pub enum PacketError {
pub struct Packet { pub struct Packet {
id: i32, id: i32,
ptype: ServerboundPacket, ptype: ServerboundPacket,
body: String, body: Box<str>,
} }
impl Packet { impl Packet {
@ -110,8 +108,9 @@ impl Packet {
} }
let payload = &incoming[body_start..body_end]; let payload = &incoming[body_start..body_end];
let body = String::from_utf8(payload.to_vec()).map_err(PacketError::InvalidBody)?; let body = std::str::from_utf8(payload)
.map_err(PacketError::InvalidBody)?
.into();
incoming.drain(0..total_packet_len); incoming.drain(0..total_packet_len);
Ok(Some(Self { Ok(Some(Self {

View File

@ -82,7 +82,13 @@ const fn to_wasm_dye_color(color: InternalDyeColor) -> DyeColor {
fn to_wasm_sign_text(text: &InternalText) -> SignText { fn to_wasm_sign_text(text: &InternalText) -> SignText {
SignText { SignText {
messages: text.messages.lock().unwrap().to_vec(), messages: text
.messages
.lock()
.unwrap()
.clone()
.map(str::into_string)
.to_vec(),
color: to_wasm_dye_color(text.get_color()), color: to_wasm_dye_color(text.get_color()),
has_glowing_text: text has_glowing_text: text
.has_glowing_text .has_glowing_text

View File

@ -1326,10 +1326,10 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
.properties .properties
.load() .load()
.iter() .iter()
.find(|p| p.name == "textures") .find(|p| p.name.as_ref() == "textures")
.map(|p| PlayerSkin { .map(|p| PlayerSkin {
value: p.value.clone(), value: p.value.to_string(),
signature: p.signature.clone(), signature: p.signature.as_ref().map(std::string::ToString::to_string),
})) }))
} }
@ -1341,11 +1341,11 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
let player = player_from_resource(self, &player)?; let player = player_from_resource(self, &player)?;
let mut properties = (**player.gameprofile.properties.load()).clone(); let mut properties = (**player.gameprofile.properties.load()).clone();
properties.retain(|p| p.name != "textures"); properties.retain(|p| p.name.as_ref() != "textures");
properties.push(Property { properties.push(Property {
name: "textures".into(), name: "textures".into(),
value: skin.value, value: skin.value.into(),
signature: skin.signature, signature: skin.signature.map(std::convert::Into::into),
}); });
player.gameprofile.properties.store(Arc::new(properties)); player.gameprofile.properties.store(Arc::new(properties));
@ -1509,7 +1509,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
.client .client
.java() .java()
.ok_or_else(|| wasmtime::Error::msg("Not a java player"))?; .ok_or_else(|| wasmtime::Error::msg("Not a java player"))?;
Ok(client.server_address.lock().await.clone()) Ok(client.server_address.lock().await.to_string())
} }
async fn get_settings( async fn get_settings(