mirror of
https://github.com/zyllian/classics.git
synced 2025-05-09 19:16:41 -07:00
256 lines
5.8 KiB
Rust
256 lines
5.8 KiB
Rust
use half::f16;
|
|
use safer_bytes::{error::Truncated, SafeBuf};
|
|
|
|
pub mod client;
|
|
pub mod client_extended;
|
|
pub mod server;
|
|
|
|
/// length of classic strings
|
|
pub const STRING_LENGTH: usize = 64;
|
|
/// length of classic level chunk arrays
|
|
pub const ARRAY_LENGTH: usize = 1024;
|
|
/// units in an f16 unit
|
|
pub const F16_UNITS: f32 = 32.0;
|
|
/// the magic number to check whether the client supports extensions
|
|
pub const EXTENSION_MAGIC_NUMBER: u8 = 0x42;
|
|
|
|
/// information about a packet extension
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ExtInfo {
|
|
/// the extension's name
|
|
pub ext_name: String,
|
|
/// the extension's version
|
|
pub version: i32,
|
|
/// the bitmask for the extension
|
|
pub bitmask: ExtBitmask,
|
|
}
|
|
|
|
impl ExtInfo {
|
|
/// creates new extension info
|
|
pub const fn new(ext_name: String, version: i32, bitmask: ExtBitmask) -> Self {
|
|
Self {
|
|
ext_name,
|
|
version,
|
|
bitmask,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// trait extending the `SafeBuf` type
|
|
pub trait SafeBufExtension: SafeBuf {
|
|
/// tries to get the next f16 in the buffer
|
|
fn try_get_f16(&mut self) -> Result<f16, Truncated>;
|
|
/// tries to get the next string in the buffer
|
|
fn try_get_string(&mut self) -> Result<String, Truncated>;
|
|
}
|
|
|
|
impl<T> SafeBufExtension for T
|
|
where
|
|
T: SafeBuf,
|
|
{
|
|
fn try_get_f16(&mut self) -> Result<f16, Truncated> {
|
|
self.try_get_i16()
|
|
.map(|v| f16::from_f32(v as f32 / F16_UNITS))
|
|
}
|
|
|
|
fn try_get_string(&mut self) -> Result<String, Truncated> {
|
|
let mut chars: Vec<char> = Vec::new();
|
|
for _ in 0..STRING_LENGTH {
|
|
chars.push(self.try_get_u8()? as char);
|
|
}
|
|
Ok(String::from_iter(chars).trim().to_string())
|
|
}
|
|
}
|
|
|
|
/// helper for writing a packet
|
|
#[derive(Debug, Default)]
|
|
pub struct PacketWriter {
|
|
raw_packet: Vec<u8>,
|
|
}
|
|
|
|
impl PacketWriter {
|
|
/// gets the actual raw packet data from the writer
|
|
pub fn into_raw_packet(self) -> Vec<u8> {
|
|
self.raw_packet
|
|
}
|
|
|
|
/// writes a u8 to the packet
|
|
pub fn write_u8(mut self, b: u8) -> Self {
|
|
self.raw_packet.push(b);
|
|
self
|
|
}
|
|
|
|
/// writes an i8 to the packet
|
|
fn write_i8(self, b: i8) -> Self {
|
|
self.write_u8(b as u8)
|
|
}
|
|
|
|
/// writes a bool to the packet
|
|
fn write_bool(self, b: bool) -> Self {
|
|
self.write_u8(if b { 1 } else { 0 })
|
|
}
|
|
|
|
/// writes a u16 to the packet
|
|
fn write_u16(self, sh: u16) -> Self {
|
|
let mut s = self;
|
|
for b in sh.to_be_bytes() {
|
|
s = s.write_u8(b);
|
|
}
|
|
s
|
|
}
|
|
|
|
/// writes an i16 to the packet
|
|
fn write_i16(self, sh: i16) -> Self {
|
|
self.write_u16(sh as u16)
|
|
}
|
|
|
|
/// writes an f16 to the packet
|
|
fn write_f16(self, f: f16) -> Self {
|
|
let r = (f.to_f32() * F16_UNITS) as i16;
|
|
self.write_i16(r)
|
|
}
|
|
|
|
/// writes an i32 to the packet
|
|
fn write_i32(self, i: i32) -> Self {
|
|
let mut s = self;
|
|
for b in i.to_be_bytes() {
|
|
s = s.write_u8(b);
|
|
}
|
|
s
|
|
}
|
|
|
|
/// writes a string to the packet
|
|
fn write_string(self, str: &str) -> Self {
|
|
let mut s = self;
|
|
for b in str
|
|
.as_bytes()
|
|
.iter()
|
|
.copied()
|
|
.chain(Some(0x20).into_iter().cycle())
|
|
.take(STRING_LENGTH)
|
|
{
|
|
s = s.write_u8(b);
|
|
}
|
|
s
|
|
}
|
|
|
|
/// writes an array of the given length to the packet
|
|
fn write_array_of_length(self, bytes: &[u8], len: usize) -> Self {
|
|
let mut s = self;
|
|
for i in 0..len {
|
|
s = s.write_u8(bytes.get(i).copied().unwrap_or_default());
|
|
}
|
|
s
|
|
}
|
|
|
|
/// writes an array of default length to the packet
|
|
fn write_array(self, bytes: &[u8]) -> Self {
|
|
self.write_array_of_length(bytes, ARRAY_LENGTH)
|
|
}
|
|
}
|
|
|
|
/// bitmask for enabled extensions
|
|
/// values should not be saved to disk or sent over network! no guarantees on them remaining the same between versions
|
|
#[bitmask_enum::bitmask(u64)]
|
|
pub enum ExtBitmask {
|
|
ClickDistance,
|
|
CustomBlocks,
|
|
HeldBlock,
|
|
EmoteFix,
|
|
TextHotKey,
|
|
ExtPlayerList,
|
|
EnvColors,
|
|
SelectionCuboid,
|
|
BlockPermissions,
|
|
ChangeModel,
|
|
EnvMapAppearance,
|
|
EnvWeatherType,
|
|
HackControl,
|
|
MessageTypes,
|
|
PlayerClick,
|
|
LongerMessages,
|
|
FullCP437,
|
|
BlockDefinitions,
|
|
BlockDefinitionsExt,
|
|
BulkBlockUpdate,
|
|
TextColors,
|
|
EnvMapAspect,
|
|
EntityProperty,
|
|
ExtEntityPositions,
|
|
TwoWayPing,
|
|
InventoryOrder,
|
|
InstantMOTD,
|
|
ExtendedBlocks,
|
|
FastMap,
|
|
ExtendedTextures,
|
|
SetHotbar,
|
|
SetSpawnpoint,
|
|
VelocityControl,
|
|
CustomParticles,
|
|
CustomModels_v2,
|
|
ExtEntityTeleport,
|
|
}
|
|
|
|
impl ExtBitmask {
|
|
/// gets info about a specific extension
|
|
fn info(self) -> Option<ExtInfo> {
|
|
// TODO: add entries as extensions are supported
|
|
Some(match self {
|
|
Self::CustomBlocks => ExtInfo::new("CustomBlocks".to_string(), 1, Self::CustomBlocks),
|
|
// this isn't actually used by the server at all, but it technically sort of implements it
|
|
Self::HeldBlock => ExtInfo::new("HeldBlock".to_string(), 1, Self::HeldBlock),
|
|
Self::EmoteFix => ExtInfo::new("EmoteFix".to_string(), 1, Self::EmoteFix),
|
|
// TODO: render CP437 properly in server output
|
|
Self::FullCP437 => ExtInfo::new("FullCP437".to_string(), 1, Self::FullCP437),
|
|
Self::EnvWeatherType => {
|
|
ExtInfo::new("EnvWeatherType".to_string(), 1, Self::EnvWeatherType)
|
|
}
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// gets info about all extensions
|
|
pub fn all_contained_info(self) -> Vec<ExtInfo> {
|
|
[
|
|
Self::ClickDistance,
|
|
Self::CustomBlocks,
|
|
Self::HeldBlock,
|
|
Self::EmoteFix,
|
|
Self::TextHotKey,
|
|
Self::ExtPlayerList,
|
|
Self::EnvColors,
|
|
Self::SelectionCuboid,
|
|
Self::BlockPermissions,
|
|
Self::ChangeModel,
|
|
Self::EnvMapAppearance,
|
|
Self::EnvWeatherType,
|
|
Self::HackControl,
|
|
Self::MessageTypes,
|
|
Self::PlayerClick,
|
|
Self::LongerMessages,
|
|
Self::FullCP437,
|
|
Self::BlockDefinitions,
|
|
Self::BlockDefinitionsExt,
|
|
Self::BulkBlockUpdate,
|
|
Self::TextColors,
|
|
Self::EnvMapAspect,
|
|
Self::EntityProperty,
|
|
Self::ExtEntityPositions,
|
|
Self::TwoWayPing,
|
|
Self::InventoryOrder,
|
|
Self::InstantMOTD,
|
|
Self::ExtendedBlocks,
|
|
Self::FastMap,
|
|
Self::ExtendedTextures,
|
|
Self::SetHotbar,
|
|
Self::SetSpawnpoint,
|
|
Self::VelocityControl,
|
|
Self::CustomParticles,
|
|
Self::CustomModels_v2,
|
|
Self::ExtEntityTeleport,
|
|
]
|
|
.into_iter()
|
|
.filter_map(|flag| (self & flag).info())
|
|
.collect()
|
|
}
|
|
}
|