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