vintage_schematics/entity/
mod.rs1use eyre::{Context, OptionExt, bail, eyre};
5use itertools::Itertools;
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
7use serde_repr::{Deserialize_repr, Serialize_repr};
8
9use crate::{Map, formats::internal::EntityMap};
10
11#[cfg(feature = "miniserde")]
12mod miniserde;
13
14const MAX_PREALLOC: usize = 1024;
20
21pub type BlockEntityMap = Map<u32, EntityMap>;
23
24#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
28pub enum Value {
29 Integer(i32),
31 Long(i64),
32 Double(f64),
33 Float(f32),
34 String(String),
35 Tree(Map<String, Self>),
36 ItemStack(Option<ItemStack>),
37 ByteArray(Vec<u8>),
38 Boolean(bool),
39 StringArray(Vec<String>),
40 IntArray(Vec<i32>),
41 FloatArray(Vec<f32>),
42 DoubleArray(Vec<f64>),
43 TreeArray(Vec<Map<String, Self>>),
44 LongArray(Vec<i64>),
45 BoolArray(Vec<bool>),
46}
47
48#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
50pub struct ItemStack {
51 pub class: ItemClass,
53
54 pub id: i32,
56
57 pub stack_size: i32,
59
60 pub stack_attributes: EntityMap,
62}
63
64impl ItemStack {
65 fn encode(&self) -> Vec<u8> {
67 let mut output = Vec::with_capacity(16);
68 output.push(0);
69 output.extend((self.class as i32).to_le_bytes());
70 output.extend(self.id.to_le_bytes());
71 output.extend(self.stack_size.to_le_bytes());
72 output.extend(encode_entities(&self.stack_attributes));
73
74 output
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize_repr, Serialize_repr)]
80#[repr(i32)]
81pub enum ItemClass {
82 Block = 0,
84
85 Item = 1,
88}
89
90impl TryFrom<i32> for ItemClass {
91 type Error = eyre::Error;
92
93 fn try_from(value: i32) -> Result<Self, Self::Error> {
94 match value {
95 0 => Ok(Self::Block),
96 1 => Ok(Self::Item),
97 _ => Err(eyre!("unknown item class: {value}")),
98 }
99 }
100}
101
102impl Value {
103 #[must_use]
114 pub const fn expected_length(&self) -> Option<usize> {
115 match self {
116 Self::Integer(_) | Self::Float(_) => Some(4),
117 Self::Long(_) | Self::Double(_) => Some(8),
118 Self::Boolean(_) => Some(1),
119 _ => None,
120 }
121 }
122
123 #[must_use]
125 pub const fn tag_byte(&self) -> u8 {
126 match self {
127 Self::Integer(_) => 1,
128 Self::Long(_) => 2,
129 Self::Double(_) => 3,
130 Self::Float(_) => 4,
131 Self::String(_) => 5,
132 Self::Tree(_) => 6,
133 Self::ItemStack(_) => 7,
134 Self::ByteArray(_) => 8,
135 Self::Boolean(_) => 9,
136 Self::StringArray(_) => 10,
137 Self::IntArray(_) => 11,
138 Self::FloatArray(_) => 12,
139 Self::DoubleArray(_) => 13,
140 Self::TreeArray(_) => 14,
141 Self::LongArray(_) => 15,
142 Self::BoolArray(_) => 16,
143 }
144 }
145
146 pub fn encode(&self) -> Vec<u8> {
154 match self {
155 Self::Integer(i) => i.to_le_bytes().to_vec(),
156 Self::Long(l) => l.to_le_bytes().to_vec(),
157 Self::Double(d) => d.to_le_bytes().to_vec(),
158 Self::Float(f) => f.to_le_bytes().to_vec(),
159 Self::String(s) => {
160 let mut output = encode_seven_bit_int(i32::try_from(s.len()).expect("string is too long"));
161 output.extend(s.as_bytes());
162 output
163 }
164 Self::Tree(t) => encode_entities(t),
165 Self::ByteArray(b) => {
166 let length = u16::try_from(b.len()).expect("byte arrays should contain less than 65,535 bytes");
167 let mut output = Vec::with_capacity(MAX_PREALLOC.min(b.len() + 2));
168 output.extend(length.to_le_bytes().iter());
169 output.extend(b);
170 output
171 }
172 Self::ItemStack(i) => i.as_ref().map_or_else(|| vec![1], ItemStack::encode),
173 Self::Boolean(b) => vec![u8::from(*b)],
174 Self::StringArray(s) => {
175 let mut output = Vec::with_capacity(MAX_PREALLOC.min(s.iter().map(|s| s.len() + 1).sum::<usize>() + 1));
176
177 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
178 output.extend(encode_seven_bit_int(s.len() as i32));
179
180 for s in s {
181 output.extend(Self::String(s.clone()).encode());
182 }
183
184 output
185 }
186 Self::IntArray(i) => encode_array(i),
187 Self::FloatArray(f) => encode_array(f),
188 Self::DoubleArray(d) => encode_array(d),
189 Self::TreeArray(_t) => unimplemented!("tree arrays are unsupported"),
190 Self::LongArray(l) => encode_array(l),
191 Self::BoolArray(b) => {
192 let bytes = b.iter().map(|b| u8::from(*b)).collect::<Box<_>>();
193 encode_array(&bytes)
194 }
195 }
196 }
197
198 const fn is_string(&self) -> bool { matches!(self, Self::String(_)) }
200
201 const fn is_tree(&self) -> bool { matches!(self, Self::Tree(_)) }
203
204 const fn is_string_array(&self) -> bool { matches!(self, Self::StringArray(_)) }
206
207 const fn is_item_stack(&self) -> bool { matches!(self, Self::ItemStack(_)) }
209
210 const fn is_byte_array(&self) -> bool { matches!(self, Self::ByteArray(_)) }
212}
213
214impl TryFrom<u8> for Value {
215 type Error = eyre::Error;
216
217 fn try_from(value: u8) -> Result<Self, Self::Error> {
218 match value {
219 1 => Ok(Self::Integer(0)),
220 2 => Ok(Self::Long(0)),
221 3 => Ok(Self::Double(0.0)),
222 4 => Ok(Self::Float(0.0)),
223 5 => Ok(Self::String(String::new())),
224 6 => Ok(Self::Tree(Map::new())),
225 7 => Ok(Self::ItemStack(None)),
226 8 => Ok(Self::ByteArray(Vec::new())),
227 9 => Ok(Self::Boolean(false)),
228 10 => Ok(Self::StringArray(Vec::new())),
229 11 => Ok(Self::IntArray(Vec::new())),
230 12 => Ok(Self::FloatArray(Vec::new())),
231 13 => Ok(Self::DoubleArray(Vec::new())),
232 14 => Ok(Self::TreeArray(Vec::new())),
233 15 => Ok(Self::LongArray(Vec::new())),
234 16 => Ok(Self::BoolArray(Vec::new())),
235 _ => Err(eyre!("unknown kind (not in range 1..=16): {value}")),
236 }
237 }
238}
239
240impl Default for Value {
241 fn default() -> Self { Self::Integer(0) }
242}
243
244#[derive(Debug, Clone, Default)]
245pub struct BlockEntities(pub BlockEntityMap);
247
248impl<'de> Deserialize<'de> for BlockEntities {
249 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
250 where
251 D: Deserializer<'de>,
252 {
253 let map: Map<u32, String> = Deserialize::deserialize(deserializer)?;
254 Ok(Self(
255 map
256 .into_iter()
257 .map(|(id, value)| parse_encoded_entities(&value).map(|v| (id, v)))
258 .collect::<Result<_, _>>()
259 .map_err(D::Error::custom)?,
260 ))
261 }
262}
263
264impl Serialize for BlockEntities {
265 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
266 where
267 S: Serializer,
268 {
269 let map: Map<u32, String> =
270 self.0.iter().map(|(&id, entities)| (id, btoa::encode(&encode_entities(entities), false))).collect();
271 map.serialize(serializer)
272 }
273}
274
275pub fn parse_encoded_entities(input: &str) -> eyre::Result<EntityMap> {
282 let mut input = btoa::decode(input)?.into_iter();
283 let input = &mut input;
284
285 parse_entities(input)
286}
287
288pub fn parse_entities(input: &mut impl Iterator<Item = u8>) -> eyre::Result<EntityMap> {
294 let mut map = Map::new();
295
296 while let Some(&byte) = input.next().as_ref() {
303 if byte == 0 {
306 break;
307 }
308
309 let name = parse_string(input)?;
311
312 let mut value = Value::try_from(byte)?;
314
315 value = if value.is_tree() {
316 Value::Tree(parse_entities(input)?)
317 } else if value.is_string_array() {
318 let count = i32::from_le_bytes(input.next_array().ok_or_eyre("EOF while parsing array length")?);
319
320 #[allow(clippy::cast_sign_loss)]
321 let mut strings = Vec::with_capacity(MAX_PREALLOC.min(count as usize));
322 for _ in 0..count {
323 strings.push(parse_string(input)?);
324 }
325
326 Value::StringArray(strings)
327 } else if value.is_item_stack() {
328 Value::ItemStack(parse_item_stack(input)?)
329 } else if value.is_byte_array() {
330 let length = u16::from_le_bytes(input.next_array().ok_or_eyre("byte array length should be two bytes")?) as usize;
331 Value::ByteArray(input.take(length).collect())
332 } else {
333 let value_length = value
334 .expected_length()
335 .map_or_else(
336 || {
337 if value.is_string() {
338 usize::try_from(parse_seven_bit_int(input)?)
339 } else {
340 usize::try_from(i32::from_le_bytes(input.next_array().ok_or_eyre("EOF while parsing array length")?))
341 }
342 .context("invalid value for string/array length")
343 },
344 Ok,
345 )
346 .context("lengths should be positive")?;
347
348 let data: Vec<u8> = input.take(value_length).collect();
349 match value {
350 Value::Integer(_) => {
353 Value::Integer(i32::from_le_bytes(data.try_into().map_err(|_| eyre!("EOF while parsing integer"))?))
354 }
355 Value::Long(_) => {
356 Value::Long(i64::from_le_bytes(data.try_into().map_err(|_| eyre!("EOF while parsing long"))?))
357 }
358 Value::Double(_) => {
359 Value::Double(f64::from_le_bytes(data.try_into().map_err(|_| eyre!("EOF while parsing double"))?))
360 }
361 Value::Float(_) => {
362 Value::Float(f32::from_le_bytes(data.try_into().map_err(|_| eyre!("EOF while parsing float"))?))
363 }
364 Value::Boolean(_) => Value::Boolean(*data.first().ok_or_eyre("EOF while parsing bool")? != 0),
365 Value::String(_) => Value::String(String::from_utf8(data).context("invalid string value")?),
366 Value::IntArray(_) => Value::IntArray(parse_array(&mut data.into_iter(), value_length)?),
367 Value::FloatArray(_) => Value::FloatArray(parse_array(&mut data.into_iter(), value_length)?),
368 Value::DoubleArray(_) => Value::DoubleArray(parse_array(&mut data.into_iter(), value_length)?),
369 Value::TreeArray(_) => bail!("tree arrays are unsupported"),
370 Value::LongArray(_) => Value::LongArray(parse_array(&mut data.into_iter(), value_length)?),
371 Value::BoolArray(_) => {
372 let array = parse_array::<u8>(&mut data.into_iter(), value_length)?;
373 Value::BoolArray(array.iter().map(|b| *b != 0).collect::<Vec<bool>>())
374 }
375
376 Value::Tree(_) | Value::StringArray(_) | Value::ItemStack(_) | Value::ByteArray(_) => {
377 unreachable!("should have been parsed already")
378 }
379 }
380 };
381
382 map.insert(name, value);
383 }
384
385 Ok(map)
386}
387
388pub fn parse_array<T>(input: &mut dyn Iterator<Item = u8>, length: usize) -> eyre::Result<Vec<T>>
395where
396 T: num_traits::FromBytes,
397 for<'a> &'a [u8]: TryInto<&'a T::Bytes>,
398{
399 let mut output = Vec::with_capacity(MAX_PREALLOC.min(length));
400 for _ in 0..length {
401 let bytes = input.take(size_of::<T>()).collect::<Vec<u8>>();
402
403 #[allow(
404 clippy::chunks_exact_to_as_chunks,
405 reason = "false positive: https://github.com/rust-lang/rust-clippy/issues/17597"
406 )]
407 let bytes = bytes
408 .chunks_exact(size_of::<T>())
409 .next()
410 .ok_or_else(|| eyre!("unexpected EOF while parsing {}", std::any::type_name::<T>()))?;
411
412 let bytes =
413 TryInto::<&T::Bytes>::try_into(bytes).map_err(|_| eyre!("couldn't convert slice to num_traits::Bytes"))?;
414 output.push(T::from_le_bytes(bytes));
415 }
416
417 Ok(output)
418}
419
420pub fn encode_array<T>(array: &[T]) -> Vec<u8>
427where
428 T: num_traits::ToBytes,
429{
430 let mut output = Vec::with_capacity(size_of_val(array) + 4);
431
432 output.extend_from_slice(
433 &i32::try_from(array.len()).expect("arrays should contain less than i32::MAX items").to_le_bytes(),
434 );
435
436 for item in array {
437 output.extend_from_slice(item.to_le_bytes().as_ref());
438 }
439
440 output
441}
442
443pub fn parse_item_stack(input: &mut impl Iterator<Item = u8>) -> eyre::Result<Option<ItemStack>> {
449 if input.next().ok_or_eyre("EOF while parsing item stack")? == 1 {
450 Ok(None)
452 } else {
453 Ok(Some(ItemStack {
454 class: ItemClass::try_from(i32::from_le_bytes(input.next_array().ok_or_eyre("EOF while parsing item class")?))?,
455 id: i32::from_le_bytes(input.next_array().ok_or_eyre("EOF while parsing item id")?),
456 stack_size: i32::from_le_bytes(input.next_array().ok_or_eyre("EOF while parsing item stack size")?),
457 stack_attributes: parse_entities(input)?,
458 }))
459 }
460}
461
462pub fn parse_string(input: &mut dyn Iterator<Item = u8>) -> eyre::Result<String> {
469 let length = usize::try_from(parse_seven_bit_int(input)?).context("string length should be positive")?;
470 if length == 0 {
471 bail!("string length should be non-zero")
472 }
473 String::from_utf8(input.take(length).collect()).context("invalid string")
474}
475
476pub fn parse_seven_bit_int(input: &mut dyn Iterator<Item = u8>) -> eyre::Result<i32> {
483 let shifts = (0..=u32::MAX).step_by(7);
493
494 input
495 .take_while_inclusive(|&byte| byte >= 0b1000_0000)
497 .zip(shifts)
498 .try_fold(0i32, |acc, (byte, shift)| {
499 i32::from(byte & 0b0111_1111) .checked_shl(shift)
501 .and_then(|value| acc.checked_add(value))
502 .ok_or_eyre("integer value too large: overflow")
503 })
504}
505
506#[must_use]
509pub fn encode_seven_bit_int(value: i32) -> Vec<u8> {
510 let mut output = Vec::with_capacity(4);
511
512 #[allow(clippy::cast_sign_loss)]
513 let mut value = value as u32;
514
515 let mask = 0b1000_0000;
517
518 while value >= mask {
519 #[allow(clippy::cast_possible_truncation)]
520 output.push((value | mask) as u8);
521 value >>= 7;
522 }
523
524 #[allow(clippy::cast_possible_truncation)]
526 output.push(value as u8);
527
528 output
529}
530
531#[must_use]
533pub fn encode_entities(entities: &EntityMap) -> Vec<u8> {
534 let mut output = Vec::new();
535 for (key, value) in entities {
536 output.push(value.tag_byte());
537 let key = Value::String(key.clone()).encode();
538 output.extend(key);
539 output.extend(value.encode());
540 }
541 output.push(0);
543 output
544}