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