Skip to main content

steel_utils/serial/
write.rs

1#![expect(
2    clippy::disallowed_types,
3    reason = "HashMap is used directly to implement WriteTo; project types are not available here"
4)]
5use std::{
6    collections::HashMap,
7    hash::BuildHasher,
8    io::{Error, Result, Write},
9};
10
11use simdnbt::owned::{NbtCompound, NbtTag};
12use text_components::TextComponent;
13use uuid::Uuid;
14
15use crate::{
16    BlockPos, Identifier, PackedBlockPos,
17    codec::VarInt,
18    serial::{DEFAULT_BOUND, PrefixedWrite, WriteTo},
19};
20
21impl WriteTo for bool {
22    fn write(&self, writer: &mut impl Write) -> Result<()> {
23        u8::from(*self).write(writer)?;
24        Ok(())
25    }
26}
27
28impl WriteTo for u8 {
29    fn write(&self, writer: &mut impl Write) -> Result<()> {
30        writer.write_all(&self.to_be_bytes())
31    }
32
33    fn write_slice(values: &[Self], writer: &mut impl Write) -> Result<()> {
34        writer.write_all(values)
35    }
36}
37
38impl WriteTo for u16 {
39    fn write(&self, writer: &mut impl Write) -> Result<()> {
40        writer.write_all(&self.to_be_bytes())
41    }
42}
43
44impl WriteTo for u32 {
45    fn write(&self, writer: &mut impl Write) -> Result<()> {
46        writer.write_all(&self.to_be_bytes())
47    }
48}
49
50impl WriteTo for u64 {
51    fn write(&self, writer: &mut impl Write) -> Result<()> {
52        writer.write_all(&self.to_be_bytes())
53    }
54}
55
56impl WriteTo for i8 {
57    fn write(&self, writer: &mut impl Write) -> Result<()> {
58        writer.write_all(&self.to_be_bytes())
59    }
60}
61
62impl WriteTo for i16 {
63    fn write(&self, writer: &mut impl Write) -> Result<()> {
64        writer.write_all(&self.to_be_bytes())
65    }
66}
67
68impl WriteTo for i32 {
69    fn write(&self, writer: &mut impl Write) -> Result<()> {
70        writer.write_all(&self.to_be_bytes())
71    }
72}
73
74impl WriteTo for i64 {
75    fn write(&self, writer: &mut impl Write) -> Result<()> {
76        writer.write_all(&self.to_be_bytes())
77    }
78}
79
80impl WriteTo for f32 {
81    fn write(&self, writer: &mut impl Write) -> Result<()> {
82        writer.write_all(&self.to_be_bytes())
83    }
84}
85
86impl WriteTo for f64 {
87    fn write(&self, writer: &mut impl Write) -> Result<()> {
88        writer.write_all(&self.to_be_bytes())
89    }
90}
91
92impl<T: WriteTo> WriteTo for Option<T> {
93    fn write(&self, writer: &mut impl Write) -> Result<()> {
94        if let Some(value) = self {
95            true.write(writer)?;
96            value.write(writer)
97        } else {
98            false.write(writer)
99        }
100    }
101}
102
103impl<T: WriteTo, const N: usize> WriteTo for [T; N] {
104    fn write(&self, writer: &mut impl Write) -> Result<()> {
105        T::write_slice(self, writer)
106    }
107}
108
109impl<T: WriteTo, Z: WriteTo> WriteTo for (T, Z) {
110    fn write(&self, writer: &mut impl Write) -> Result<()> {
111        self.0.write(writer)?;
112        self.1.write(writer)
113    }
114}
115
116impl<K: WriteTo, V: WriteTo, S: BuildHasher> WriteTo for HashMap<K, V, S> {
117    fn write(&self, writer: &mut impl Write) -> Result<()> {
118        VarInt(self.len() as i32).write(writer)?;
119        for (key, value) in self {
120            key.write(writer)?;
121            value.write(writer)?;
122        }
123        Ok(())
124    }
125}
126
127impl<T: WriteTo> WriteTo for Vec<T> {
128    fn write(&self, writer: &mut impl Write) -> Result<()> {
129        self.write_prefixed::<VarInt>(writer)
130    }
131}
132
133impl WriteTo for BlockPos {
134    fn write(&self, writer: &mut impl Write) -> Result<()> {
135        PackedBlockPos::from(*self).write(writer)
136    }
137}
138
139impl WriteTo for TextComponent {
140    fn write(&self, writer: &mut impl Write) -> Result<()> {
141        WriteTo::write(&self.to_codec_nbt(), writer)
142    }
143}
144
145impl WriteTo for Uuid {
146    fn write(&self, writer: &mut impl Write) -> Result<()> {
147        let (most_significant_bits, least_significant_bits) = self.as_u64_pair();
148        most_significant_bits.write(writer)?;
149        least_significant_bits.write(writer)?;
150        Ok(())
151    }
152}
153
154impl WriteTo for Identifier {
155    fn write(&self, writer: &mut impl Write) -> Result<()> {
156        let len = self.namespace.len() + 1 + self.path.len();
157        if len > DEFAULT_BOUND {
158            return Err(Error::other("Too long"));
159        }
160
161        VarInt(len as i32).write(writer)?;
162        writer.write_all(self.namespace.as_bytes())?;
163        writer.write_all(b":")?;
164        writer.write_all(self.path.as_bytes())
165    }
166}
167
168impl WriteTo for NbtTag {
169    fn write(&self, writer: &mut impl Write) -> Result<()> {
170        let mut buf = Vec::new();
171        self.write(&mut buf);
172        writer.write_all(&buf)?;
173        Ok(())
174    }
175}
176
177impl WriteTo for NbtCompound {
178    fn write(&self, writer: &mut impl Write) -> Result<()> {
179        let mut buf = Vec::new();
180        self.write(&mut buf);
181        writer.write_all(&buf)?;
182        Ok(())
183    }
184}
185
186/// Wrapper for optional NBT that uses the protocol format (END tag for None).
187///
188/// This is different from `Option<NbtCompound>` which writes a boolean prefix.
189/// In the Minecraft protocol, nullable NBT is represented as:
190/// - Present: the compound tag bytes
191/// - Absent: a single END tag byte (0x00)
192#[derive(Debug, Clone)]
193pub struct OptionalNbt(pub Option<NbtCompound>);
194
195impl WriteTo for OptionalNbt {
196    fn write(&self, writer: &mut impl Write) -> Result<()> {
197        match &self.0 {
198            Some(compound) => {
199                // Write compound tag type (0x0A) first, then the compound contents
200                // This matches vanilla's writeAnyTag format
201                writer.write_all(&[0x0A])?;
202                let mut buf = Vec::new();
203                compound.write(&mut buf);
204                writer.write_all(&buf)?;
205            }
206            None => {
207                // Write END tag (0x00) for null/absent NBT
208                writer.write_all(&[0x00])?;
209            }
210        }
211        Ok(())
212    }
213}
214
215impl From<Option<NbtCompound>> for OptionalNbt {
216    fn from(opt: Option<NbtCompound>) -> Self {
217        Self(opt)
218    }
219}