Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / common / src / protocol / packet.rs

//! The packets of protocol 14 and their wire format, from the `Packet` subclasses.

use std::io::{self, Read, Write};
use std::sync::Arc;

use flate2::{Compression, read::ZlibDecoder, write::ZlibEncoder};

use crate::item::{Item, ItemStack};
use crate::nbt;

#[derive(Debug, thiserror::Error)]
pub enum ProtocolError {
    #[error(transparent)]
    Io(#[from] io::Error),
    #[error("bad packet id {0}")]
    BadId(u8),
    #[error("string length {length} outside 0..={max}")]
    StringLength { length: i16, max: usize },
    #[error("invalid string")]
    BadString,
    #[error("bad compressed chunk data")]
    BadChunkData,
}

/// A value of `DataWatcher`, sent as entity metadata.
#[derive(Clone, Debug, PartialEq)]
pub enum MetadataValue {
    Byte(i8),
    Short(i16),
    Int(i32),
    Float(f32),
    String(String),
    Item(ItemStack),
    Position([i32; 3]),
}

/// Entity metadata entries: index (0 to 31) and value.
pub type Metadata = Vec<(u8, MetadataValue)>;

/// A value with a fixed big-endian wire format.
trait Field: Sized {
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError>;
    fn write(&self, w: &mut impl Write) -> io::Result<()>;
}

fn read<T: Field>(r: &mut impl Read) -> Result<T, ProtocolError> {
    T::read(r)
}

fn invalid(message: &'static str) -> ProtocolError {
    io::Error::new(io::ErrorKind::InvalidData, message).into()
}

macro_rules! numbers {
    ($($t:ty),*) => {$(
        impl Field for $t {
            fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
                let mut bytes = [0; size_of::<$t>()];
                r.read_exact(&mut bytes)?;
                Ok(Self::from_be_bytes(bytes))
            }

            fn write(&self, w: &mut impl Write) -> io::Result<()> {
                w.write_all(&self.to_be_bytes())
            }
        }
    )*};
}

numbers!(u8, i8, u16, i16, i32, i64, f32, f64);

impl Field for bool {
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
        Ok(read::<u8>(r)? != 0)
    }

    fn write(&self, w: &mut impl Write) -> io::Result<()> {
        u8::from(*self).write(w)
    }
}

impl<T: Field + Default + Copy, const N: usize> Field for [T; N] {
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
        let mut values = [T::default(); N];
        for value in &mut values {
            *value = read(r)?;
        }
        Ok(values)
    }

    fn write(&self, w: &mut impl Write) -> io::Result<()> {
        self.iter().try_for_each(|value| value.write(w))
    }
}

fn read_stack(id: i16, r: &mut impl Read) -> Result<ItemStack, ProtocolError> {
    let count = i32::from(read::<i8>(r)?);
    Ok(ItemStack::new(Item(id), count, i32::from(read::<i16>(r)?)))
}

impl Field for ItemStack {
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
        read_stack(read(r)?, r)
    }

    fn write(&self, w: &mut impl Write) -> io::Result<()> {
        self.item.0.write(w)?;
        (self.count as i8).write(w)?;
        (self.damage as i16).write(w)
    }
}

impl Field for Option<ItemStack> {
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
        let id: i16 = read(r)?;
        if id < 0 {
            return Ok(None);
        }
        read_stack(id, r).map(Some)
    }

    fn write(&self, w: &mut impl Write) -> io::Result<()> {
        match self {
            Some(stack) => stack.write(w),
            None => (-1i16).write(w),
        }
    }
}

impl Field for Metadata {
    /// `DataWatcher.readWatchableObjects`.
    fn read(r: &mut impl Read) -> Result<Self, ProtocolError> {
        let mut entries = Vec::new();
        loop {
            let header: u8 = read(r)?;
            if header == 127 {
                return Ok(entries);
            }
            if entries.len() == 32 {
                return Err(invalid("Too many entity metadata entries"));
            }
            let value = match header >> 5 {
                0 => MetadataValue::Byte(read(r)?),
                1 => MetadataValue::Short(read(r)?),
                2 => MetadataValue::Int(read(r)?),
                3 => MetadataValue::Float(read(r)?),
                4 => MetadataValue::String(read_string(r, 64)?),
                5 => MetadataValue::Item(read(r)?),
                _ => MetadataValue::Position(read(r)?),
            };
            entries.push((header & 31, value));
        }
    }

    fn write(&self, w: &mut impl Write) -> io::Result<()> {
        for (index, value) in self {
            let kind: u8 = match value {
                MetadataValue::Byte(_) => 0,
                MetadataValue::Short(_) => 1,
                MetadataValue::Int(_) => 2,
                MetadataValue::Float(_) => 3,
                MetadataValue::String(_) => 4,
                MetadataValue::Item(_) => 5,
                MetadataValue::Position(_) => 6,
            };
            (kind << 5 | index & 31).write(w)?;
            match value {
                MetadataValue::Byte(v) => v.write(w)?,
                MetadataValue::Short(v) => v.write(w)?,
                MetadataValue::Int(v) => v.write(w)?,
                MetadataValue::Float(v) => v.write(w)?,
                MetadataValue::String(v) => write_string(w, v)?,
                MetadataValue::Item(stack) => stack.write(w)?,
                MetadataValue::Position(p) => p.write(w)?,
            }
        }
        127u8.write(w)
    }
}

/// `Packet.readString`: UTF-16 code units with a 16-bit length.
fn read_string(r: &mut impl Read, max: usize) -> Result<String, ProtocolError> {
    let length: i16 = read(r)?;
    if length < 0 || length as usize > max {
        return Err(ProtocolError::StringLength { length, max });
    }
    let units = (0..length)
        .map(|_| read(r))
        .collect::<Result<Vec<u16>, _>>()?;
    Ok(String::from_utf16_lossy(&units))
}

/// `Packet.writeString`.
fn write_string(w: &mut impl Write, text: &str) -> io::Result<()> {
    let units: Vec<u16> = text.encode_utf16().collect();
    i16::try_from(units.len())
        .map_err(|_| io::Error::other("String too big"))?
        .write(w)?;
    units.iter().try_for_each(|unit| unit.write(w))
}

fn read_bytes(r: &mut impl Read, len: usize) -> Result<Vec<u8>, ProtocolError> {
    if len > 64 << 20 {
        return Err(invalid("Packet data is too large"));
    }
    let mut bytes = vec![0; len];
    r.read_exact(&mut bytes)?;
    Ok(bytes)
}

macro_rules! put {
    ($w:ident, $($value:expr),*) => {
        $(Field::write($value, $w)?;)*
    };
}

macro_rules! read_field {
    ($r:ident) => {
        read($r)?
    };
    ($r:ident, $max:literal) => {
        read_string($r, $max)?
    };
}

macro_rules! write_field {
    ($w:ident, $value:ident) => {
        $value.write($w)?
    };
    ($w:ident, $value:ident, $max:literal) => {
        write_string($w, $value)?
    };
}

/// Declares `Packet` with its ids. Plain packets are read and written field by field, a
/// `String[max]` field with the length limit of `Packet.readString`; `custom` packets have
/// hand-written formats and `local` packets never leave the process.
macro_rules! packets {
    (
        $($(#[$doc:meta])* $id:literal $name:ident
            $({ $($field:ident: $ty:ty $([$max:literal])?),* })?)*
        custom:
        $($(#[$cdoc:meta])* $cid:literal $cname:ident
            { $($(#[$cfdoc:meta])* $cfield:ident: $cty:ty),* })*
        local:
        $($(#[$ldoc:meta])* $lname:ident { $($lfield:ident: $lty:ty),* })*
    ) => {
        #[derive(Clone, Debug, PartialEq)]
        pub enum Packet {
            $($(#[$doc])* $name $({ $($field: $ty),* })?,)*
            $($(#[$cdoc])* $cname { $($(#[$cfdoc])* $cfield: $cty),* },)*
            $($(#[$ldoc])* $lname { $($lfield: $lty),* },)*
        }

        impl Packet {
            /// The id of `Packet.addIdClassMapping`, or `None` for packets that never leave
            /// the process.
            pub fn id(&self) -> Option<u8> {
                match self {
                    $(Self::$name { .. } => Some($id),)*
                    $(Self::$cname { .. } => Some($cid),)*
                    $(Self::$lname { .. })|* => None,
                }
            }

            fn read_body(id: u8, r: &mut impl Read) -> Result<Self, ProtocolError> {
                Ok(match id {
                    $($id => Self::$name $({ $($field: read_field!(r $(, $max)?)),* })?,)*
                    _ => Self::read_custom(id, r)?,
                })
            }

            /// `Packet.writePacket`. Chunk data is compressed here; local-only packets write
            /// nothing.
            pub fn write(&self, w: &mut impl Write) -> io::Result<()> {
                let Some(id) = self.id() else {
                    return Ok(());
                };
                id.write(w)?;
                match self {
                    $(Self::$name $({ $($field),* })? => {
                        $($(write_field!(w, $field $(, $max)?);)*)?
                    })*
                    _ => self.write_custom(w)?,
                }
                Ok(())
            }
        }
    };
}

packets! {
    0 KeepAlive
    /// Client to server: protocol version and username. Server to client: entity id in place
    /// of the version, with the world seed and dimension.
    1 Login { version_or_entity: i32, username: String[16], seed: i64, dimension: i8 }
    2 Handshake { username: String[32] }
    3 Chat { message: String[119] }
    4 UpdateTime { time: i64 }
    5 PlayerInventory { entity: i32, slot: i16, item: i16, damage: i16 }
    6 SpawnPosition { x: i32, y: i32, z: i32 }
    7 UseEntity { player: i32, target: i32, left_click: bool }
    8 UpdateHealth { health: i16 }
    9 Respawn { dimension: i8 }
    10 Flying { on_ground: bool }
    11 PlayerPosition { x: f64, y: f64, stance: f64, z: f64, on_ground: bool }
    12 PlayerLook { yaw: f32, pitch: f32, on_ground: bool }
    /// Client to server `y` is the feet and `stance` the eye height; server to client the reverse.
    13 PlayerPositionLook {
        x: f64, y: f64, stance: f64, z: f64, yaw: f32, pitch: f32, on_ground: bool
    }
    14 BlockDig { status: u8, x: i32, y: u8, z: i32, face: u8 }
    15 Place { x: i32, y: u8, z: i32, direction: u8, item: Option<ItemStack> }
    16 BlockItemSwitch { slot: i16 }
    17 Sleep { entity: i32, status: i8, x: i32, y: i8, z: i32 }
    18 Animation { entity: i32, animation: i8 }
    19 EntityAction { entity: i32, action: i8 }
    20 NamedEntitySpawn {
        entity: i32, name: String[16], x: i32, y: i32, z: i32, yaw: i8, pitch: i8, item: i16
    }
    21 PickupSpawn {
        entity: i32, item: ItemStack, x: i32, y: i32, z: i32, rotation: i8, pitch: i8, roll: i8
    }
    22 Collect { collected: i32, collector: i32 }
    24 MobSpawn {
        entity: i32, kind: i8, x: i32, y: i32, z: i32, yaw: i8, pitch: i8, metadata: Metadata
    }
    25 Painting { entity: i32, title: String[13], x: i32, y: i32, z: i32, direction: i32 }
    /// `Packet27Position`, unused by the original client.
    27 VehicleInput {
        strafe: f32, forward: f32, pitch: f32, yaw: f32, sneaking: bool, jumping: bool
    }
    28 EntityVelocity { entity: i32, velocity: [i16; 3] }
    29 DestroyEntity { entity: i32 }
    30 Entity { entity: i32 }
    31 RelEntityMove { entity: i32, delta: [i8; 3] }
    32 EntityLook { entity: i32, yaw: i8, pitch: i8 }
    33 RelEntityMoveLook { entity: i32, delta: [i8; 3], yaw: i8, pitch: i8 }
    34 EntityTeleport { entity: i32, x: i32, y: i32, z: i32, yaw: i8, pitch: i8 }
    38 EntityStatus { entity: i32, status: i8 }
    39 AttachEntity { entity: i32, vehicle: i32 }
    40 EntityMetadata { entity: i32, metadata: Metadata }
    50 PreChunk { x: i32, z: i32, load: bool }
    53 BlockChange { x: i32, y: u8, z: i32, block: u8, metadata: u8 }
    54 PlayNoteBlock { x: i32, y: i16, z: i32, instrument: u8, pitch: u8 }
    /// `Packet61DoorChange`: an auxiliary effect such as a sound or particles.
    61 AuxEffect { effect: i32, x: i32, y: i8, z: i32, data: i32 }
    /// `Packet70Bed`: 0 invalid bed, 1 rain starts, 2 rain stops.
    70 GameState { state: i8 }
    71 Weather { entity: i32, lightning: bool, x: i32, y: i32, z: i32 }
    101 CloseWindow { window: i8 }
    102 WindowClick {
        window: i8, slot: i16, button: i8, action: i16, shift: bool, item: Option<ItemStack>
    }
    103 SetSlot { window: i8, slot: i16, item: Option<ItemStack> }
    105 UpdateProgressBar { window: i8, bar: i16, value: i16 }
    106 Transaction { window: i8, action: i16, accepted: bool }
    200 Statistic { id: i32, amount: i8 }
    255 Disconnect { reason: String[100] }

    custom:
    23 VehicleSpawn {
        entity: i32, kind: i8, x: i32, y: i32, z: i32,
        /// Thrower entity id, with the velocity sent only when positive.
        thrower: i32,
        velocity: [i16; 3]
    }
    /// A box of blocks, uncompressed in the layout of `World.getChunkData`: per chunk column
    /// run, ids, then metadata, block light and sky light nibbles.
    51 MapChunk { x: i32, y: i16, z: i32, size: [u8; 3], data: Arc<[u8]> }
    52 MultiBlockChange {
        chunk_x: i32,
        chunk_z: i32,
        /// `x << 12 | z << 8 | y` within the chunk, block id, metadata.
        changes: Vec<(i16, u8, u8)>
    }
    60 Explosion {
        x: f64, y: f64, z: f64, size: f32,
        /// Destroyed blocks relative to the truncated explosion position.
        blocks: Vec<[i8; 3]>
    }
    100 OpenWindow { window: i8, kind: i8, title: String, slots: i8 }
    104 WindowItems { window: i8, items: Vec<Option<ItemStack>> }
    130 UpdateSign { x: i32, y: i16, z: i32, lines: [String; 4] }
    131 MapData { item: i16, map: i16, data: Vec<u8> }

    local:
    /// Local only: tile entity state the original only had in singleplayer, such as the mob
    /// shown in a spawner.
    TileEntity { x: i32, y: i32, z: i32, data: Box<crate::world::TileEntity> }
    /// Local only: a world sound, which the original server never sends.
    Sound { name: String, pos: [f64; 3], volume: f32, pitch: f32 }
    /// Local only: a particle effect.
    Particle { name: String, pos: [f64; 3], velocity: [f64; 3] }
    /// Local only: the weather flags singleplayer clients fade towards, and the strengths
    /// to start from.
    WeatherState { raining: bool, thundering: bool, rain: f32, thunder: f32 }
    /// Local only: `EntityLiving.attackedAtYaw` of a player, for its hurt camera.
    HurtDirection { entity: i32, yaw: f32 }
    /// Local simulation state without multiplayer position rounding
    EntityState {
        entity: i32,
        pos: [f64; 3],
        rotation: [f32; 3],
        walk: [f32; 2],
        hurt: i32,
        death: i32,
        fuse: i32,
        special: crate::entity::mob::SpecialPose,
        vehicle: crate::entity::vehicle::VehiclePose
    }
}

impl Packet {
    /// `Packet3Chat(String)`: cut to the 119 UTF-16 units the reader accepts.
    pub fn chat(message: impl Into<String>) -> Self {
        let mut message = message.into();
        let mut units = 0;
        if let Some((end, _)) = message.char_indices().find(|&(_, ch)| {
            units += ch.len_utf16();
            units > 119
        }) {
            message.truncate(end);
        }
        Self::Chat { message }
    }

    /// `Packet.readPacket` on the server: client-bound ids are refused before their payload.
    pub fn read_from_client(input: &mut impl Read) -> Result<Self, ProtocolError> {
        const SERVER_BOUND: &[u8] = &[
            0, 1, 2, 3, 7, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 27, 101, 102, 106, 130, 255,
        ];
        Self::read_if(input, |id| SERVER_BOUND.contains(&id))
    }

    /// `Packet.readPacket` on the client: server-bound ids are refused before their payload.
    pub fn read_from_server(input: &mut impl Read) -> Result<Self, ProtocolError> {
        Self::read_if(input, |id| ![7, 14, 15, 16, 19, 27, 102].contains(&id))
    }

    fn read_if(input: &mut impl Read, accept: impl Fn(u8) -> bool) -> Result<Self, ProtocolError> {
        let id = read(input)?;
        if !accept(id) {
            return Err(ProtocolError::BadId(id));
        }
        Self::read_body(id, input)
    }

    fn read_custom(id: u8, r: &mut impl Read) -> Result<Self, ProtocolError> {
        Ok(match id {
            23 => {
                let (entity, kind, [x, y, z]) = (read(r)?, read(r)?, read(r)?);
                let thrower: i32 = read(r)?;
                let velocity = if thrower > 0 { read(r)? } else { [0; 3] };
                Self::VehicleSpawn {
                    entity,
                    kind,
                    x,
                    y,
                    z,
                    thrower,
                    velocity,
                }
            }
            51 => {
                let (x, y, z, size) = (read(r)?, read(r)?, read(r)?, read::<[u8; 3]>(r)?);
                let length =
                    usize::try_from(read::<i32>(r)?).map_err(|_| ProtocolError::BadChunkData)?;
                let volume = size.iter().map(|&s| usize::from(s) + 1).product::<usize>();
                let mut data = vec![0; volume * 5 / 2];
                ZlibDecoder::new(&read_bytes(r, length)?[..])
                    .read_exact(&mut data)
                    .map_err(|_| ProtocolError::BadChunkData)?;
                Self::MapChunk {
                    x,
                    y,
                    z,
                    size,
                    data: data.into(),
                }
            }
            52 => {
                let (chunk_x, chunk_z) = (read(r)?, read(r)?);
                let count = usize::from(read::<u16>(r)?);
                let coordinates = (0..count)
                    .map(|_| read(r))
                    .collect::<Result<Vec<i16>, _>>()?;
                let (blocks, metadata) = (read_bytes(r, count)?, read_bytes(r, count)?);
                let changes = (0..count)
                    .map(|i| (coordinates[i], blocks[i], metadata[i]))
                    .collect();
                Self::MultiBlockChange {
                    chunk_x,
                    chunk_z,
                    changes,
                }
            }
            60 => {
                let (x, y, z, size) = (read(r)?, read(r)?, read(r)?, read(r)?);
                let count: i32 = read(r)?;
                if !(0..=1 << 20).contains(&count) {
                    return Err(invalid("Invalid explosion size"));
                }
                let blocks = (0..count).map(|_| read(r)).collect::<Result<_, _>>()?;
                Self::Explosion {
                    x,
                    y,
                    z,
                    size,
                    blocks,
                }
            }
            100 => Self::OpenWindow {
                window: read(r)?,
                kind: read(r)?,
                title: nbt::read_utf(r).map_err(|_| ProtocolError::BadString)?,
                slots: read(r)?,
            },
            104 => {
                let window = read(r)?;
                let count = read::<i16>(r)?.max(0);
                let items = (0..count).map(|_| read(r)).collect::<Result<_, _>>()?;
                Self::WindowItems { window, items }
            }
            130 => Self::UpdateSign {
                x: read(r)?,
                y: read(r)?,
                z: read(r)?,
                lines: [
                    read_string(r, 15)?,
                    read_string(r, 15)?,
                    read_string(r, 15)?,
                    read_string(r, 15)?,
                ],
            },
            131 => {
                let (item, map, length) = (read(r)?, read(r)?, read::<u8>(r)?);
                let data = read_bytes(r, length.into())?;
                Self::MapData { item, map, data }
            }
            id => return Err(ProtocolError::BadId(id)),
        })
    }

    fn write_custom(&self, w: &mut impl Write) -> io::Result<()> {
        match self {
            Self::VehicleSpawn {
                entity,
                kind,
                x,
                y,
                z,
                thrower,
                velocity,
            } => {
                put!(w, entity, kind, x, y, z, thrower);
                if *thrower > 0 {
                    velocity.write(w)?;
                }
            }
            Self::MapChunk {
                x,
                y,
                z,
                size,
                data,
            } => {
                let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
                encoder.write_all(data)?;
                let compressed = encoder.finish()?;
                put!(w, x, y, z, size, &(compressed.len() as i32));
                w.write_all(&compressed)?;
            }
            Self::MultiBlockChange {
                chunk_x,
                chunk_z,
                changes,
            } => {
                put!(w, chunk_x, chunk_z, &(changes.len() as i16));
                changes.iter().try_for_each(|c| c.0.write(w))?;
                changes.iter().try_for_each(|c| c.1.write(w))?;
                changes.iter().try_for_each(|c| c.2.write(w))?;
            }
            Self::Explosion {
                x,
                y,
                z,
                size,
                blocks,
            } => {
                put!(w, x, y, z, size, &(blocks.len() as i32));
                blocks.iter().try_for_each(|block| block.write(w))?;
            }
            Self::OpenWindow {
                window,
                kind,
                title,
                slots,
            } => {
                put!(w, window, kind);
                nbt::write_utf(w, title)?;
                slots.write(w)?;
            }
            Self::WindowItems { window, items } => {
                put!(w, window, &(items.len() as i16));
                items.iter().try_for_each(|item| item.write(w))?;
            }
            Self::UpdateSign { x, y, z, lines } => {
                put!(w, x, y, z);
                lines.iter().try_for_each(|line| write_string(w, line))?;
            }
            Self::MapData { item, map, data } => {
                put!(w, item, map, &(data.len() as u8));
                w.write_all(data)?;
            }
            _ => unreachable!("plain and local packets are written by `write`"),
        }
        Ok(())
    }
}