1use std::io::{Read, Write};
11
12use mcproto_codec::{
13 error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason},
14 varint::{VarIntRead, VarIntWrite},
15};
16
17use crate::TypeCodec;
18
19#[path = "slot/components.rs"]
20pub mod components;
21#[path = "slot/display.rs"]
22pub mod display;
23#[path = "slot/hashed.rs"]
24pub mod hashed;
25#[path = "slot/types.rs"]
26pub mod types;
27
28pub use components::*;
29pub use display::*;
30pub use hashed::*;
31pub use types::*;
32
33pub use crate::profile::{PartialProfile, ResolvableProfile, ResolvableProfileData, SkinModel};
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct ItemStack {
40 pub count: u32,
42 pub item_id: u32,
44 pub components_to_add: Vec<DataComponent>,
46 pub components_to_remove: Vec<DataComponentType>,
48}
49
50impl ItemStack {
51 pub fn new(count: u32, item_id: u32) -> Result<Self, InvalidItemStack> {
54 if count == 0 || count > i32::MAX as u32 {
55 return Err(InvalidItemStack::Count(count));
56 }
57 if item_id > i32::MAX as u32 {
58 return Err(InvalidItemStack::ItemId(item_id));
59 }
60 Ok(Self {
61 count,
62 item_id,
63 components_to_add: Vec::new(),
64 components_to_remove: Vec::new(),
65 })
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Default)]
95pub enum Slot {
96 #[default]
98 Empty,
99 Item(ItemStack),
101}
102
103impl TypeCodec for Slot {
104 fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
105 let Self::Item(item) = self else {
106 return writer
107 .write_varint(0)
108 .map_err(|error| error.with_context(CodecKind::Slot));
109 };
110 validate_item(item)?;
111 writer
112 .write_varint(item.count as i32)
113 .map_err(|error| error.with_context(CodecKind::Slot))?;
114 writer
115 .write_varint(item.item_id as i32)
116 .map_err(|error| error.with_context(CodecKind::Slot))?;
117 write_length(writer, item.components_to_add.len(), CodecKind::Slot)?;
118 write_length(writer, item.components_to_remove.len(), CodecKind::Slot)?;
119 for component in &item.components_to_add {
120 component
121 .encode(writer)
122 .map_err(|error| error.with_context(CodecKind::Slot))?;
123 }
124 for component_type in &item.components_to_remove {
125 component_type
126 .encode(writer)
127 .map_err(|error| error.with_context(CodecKind::Slot))?;
128 }
129 Ok(())
130 }
131
132 fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
133 let count = reader
134 .read_varint()
135 .map_err(|error| error.with_context(CodecKind::Slot))?;
136 if count == 0 {
137 return Ok(Self::Empty);
138 }
139 let count = decode_item_count(count, CodecKind::Slot)?;
140 let item_id = reader
141 .read_varint()
142 .map_err(|error| error.with_context(CodecKind::Slot))?;
143 let item_id = decode_item_id(item_id, CodecKind::Slot)?;
144 let add_count = read_length(reader, CodecKind::Slot)?;
145 let remove_count = read_length(reader, CodecKind::Slot)?;
146 let mut components_to_add = Vec::with_capacity(add_count.min(1024));
147 for _ in 0..add_count {
148 components_to_add.push(
149 DataComponent::decode(reader)
150 .map_err(|error| error.with_context(CodecKind::Slot))?,
151 );
152 }
153 let mut components_to_remove = Vec::with_capacity(remove_count.min(1024));
154 for _ in 0..remove_count {
155 components_to_remove.push(
156 DataComponentType::decode(reader)
157 .map_err(|error| error.with_context(CodecKind::Slot))?,
158 );
159 }
160 Ok(Self::Item(ItemStack {
161 count,
162 item_id,
163 components_to_add,
164 components_to_remove,
165 }))
166 }
167}
168
169fn validate_item(item: &ItemStack) -> Result<(), CodecError> {
170 validate_item_fields(item.count, item.item_id, CodecKind::Slot)
171}
172
173pub(super) fn validate_item_fields(
174 count: u32,
175 item_id: u32,
176 kind: CodecKind,
177) -> Result<(), CodecError> {
178 if count == 0 || count > i32::MAX as u32 {
179 return Err(CodecError::invalid_encoding_for_operation(
180 kind,
181 CodecOperation::Write,
182 0,
183 InvalidEncodingReason::InvalidSlotCount {
184 value: i64::from(count),
185 },
186 ));
187 }
188 if item_id > i32::MAX as u32 {
189 return Err(CodecError::invalid_encoding_for_operation(
190 kind,
191 CodecOperation::Write,
192 0,
193 InvalidEncodingReason::InvalidRegistryId {
194 value: item_id as i32,
195 max: i32::MAX,
196 },
197 ));
198 }
199 Ok(())
200}
201
202pub(super) fn write_length(
203 writer: &mut impl Write,
204 length: usize,
205 kind: CodecKind,
206) -> Result<(), CodecError> {
207 let length = i32::try_from(length).map_err(|_| {
208 CodecError::invalid_encoding_for_operation(
209 kind,
210 CodecOperation::Write,
211 0,
212 InvalidEncodingReason::LengthOutOfRange {
213 max: i32::MAX as usize,
214 actual: length,
215 },
216 )
217 })?;
218 writer
219 .write_varint(length)
220 .map_err(|error| error.with_context(kind))
221}
222
223pub(super) fn read_length(reader: &mut impl Read, kind: CodecKind) -> Result<usize, CodecError> {
224 let value = reader
225 .read_varint()
226 .map_err(|error| error.with_context(kind))?;
227 usize::try_from(value).map_err(|_| {
228 CodecError::invalid_encoding(kind, 0, InvalidEncodingReason::NegativeLength { value })
229 })
230}
231
232pub(super) fn decode_item_count(value: i32, kind: CodecKind) -> Result<u32, CodecError> {
233 if value <= 0 {
234 return Err(CodecError::invalid_encoding(
235 kind,
236 0,
237 InvalidEncodingReason::InvalidSlotCount {
238 value: i64::from(value),
239 },
240 ));
241 }
242 Ok(value as u32)
243}
244
245pub(super) fn decode_item_id(value: i32, kind: CodecKind) -> Result<u32, CodecError> {
246 if value < 0 {
247 return Err(CodecError::invalid_encoding(
248 kind,
249 0,
250 InvalidEncodingReason::InvalidRegistryId {
251 value,
252 max: i32::MAX,
253 },
254 ));
255 }
256 Ok(value as u32)
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum InvalidItemStack {
262 Count(u32),
264 ItemId(u32),
266}
267
268impl std::fmt::Display for InvalidItemStack {
269 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 Self::Count(value) => write!(formatter, "invalid item count: {value}"),
272 Self::ItemId(value) => write!(formatter, "invalid item registry ID: {value}"),
273 }
274 }
275}
276
277impl std::error::Error for InvalidItemStack {}