1use crate::constants;
2use crate::map::parse::{BinaryQuad, BinarySoundSource};
3use crate::map::*;
4
5use az::{OverflowingAs, OverflowingCast, UnwrappedAs, WrappingCast};
6use image::RgbaImage;
7use ndarray::Array2;
8use thiserror::Error;
9use vek::num_traits::Signed;
10use vek::Extent2;
11
12use crate::compression::ZlibDecompressionError;
13use std::collections::HashSet;
14use std::fmt;
15use std::mem;
16
17impl TwMap {
18 pub fn check(&self) -> Result<(), MapError> {
19 Info::check(&self.info, self, &mut ())?;
20 Image::check_all(&self.images, self)?;
21 Envelope::check_all(&self.envelopes, self)?;
22 Group::check_all(&self.groups, self)?;
23 Sound::check_all(&self.sounds, self)?;
24 self.check_amounts()?;
25 Ok(())
26 }
27}
28
29#[derive(Debug)]
30pub(crate) enum MapItem {
31 Info,
32 Image,
33 Envelope,
34 Group,
35 Layer,
36 Sound,
37 Quad,
38 SoundSource,
39 EnvPoint,
40}
41
42#[derive(Error, Debug)]
43#[error(transparent)]
44pub struct MapError(#[from] pub(crate) MapErr);
45
46#[derive(Error, Debug)]
47pub(crate) enum MapErr {
48 #[error("In {item:?}{}{sub}", index.map(|i| format!(" at index {} -> ", i)).unwrap_or_default())]
49 Recursive {
50 item: MapItem,
51 index: Option<usize>,
52 sub: Box<MapErr>,
53 },
54 #[error(transparent)]
55 Head(MapErrorKind),
56}
57
58impl From<MapErrorKind> for MapErr {
59 fn from(err: MapErrorKind) -> Self {
60 Self::Head(err)
61 }
62}
63
64impl MapErr {
65 pub(crate) fn with_index(mut self, i: usize) -> Self {
66 if let Self::Recursive { index, .. } = &mut self {
67 *index = Some(i);
68 }
69 self
70 }
71
72 pub(crate) fn with_type(self, item: MapItem) -> Self {
73 Self::Recursive {
74 item,
75 index: None,
76 sub: Box::new(self),
77 }
78 }
79}
80
81#[derive(Error, Debug)]
82#[error(transparent)]
83pub(crate) enum MapErrorKind {
84 Teeworlds(#[from] TeeworldsError),
85 DDNet(#[from] DDNetError),
86 Decompression(#[from] ZlibDecompressionError),
87 #[error("{amount} items of this type is too many, the maximum is {max}")]
88 Amount {
89 amount: usize,
90 max: usize,
91 },
92 #[error("Invalid image index {index} for a map with {len} images")]
93 ImageIndex {
94 index: u16,
95 len: usize,
96 },
97 #[error("Invalid sound index {index} for a map with {len} sounds")]
98 SoundIndex {
99 index: u16,
100 len: usize,
101 },
102 #[error("Invalid envelope index {index} for a map with {len} envelopes")]
103 EnvelopeIndex {
104 index: u16,
105 len: usize,
106 },
107 #[error("Envelope at index {index} referenced as a {expected:?} envelope is instead a {actual:?} envelope")]
108 EnvelopeKind {
109 index: u16,
110 expected: EnvelopeKind,
111 actual: EnvelopeKind,
112 },
113 String(#[from] StringError),
114 I32Fit(#[from] ValueMaxError),
115 Negative(#[from] NegativeError),
116 Image(#[from] ImageError),
117 Info(#[from] InfoError),
118 Sound(#[from] opus_headers::ParseError),
119 Group(#[from] GroupError),
120 Layer(#[from] LayerError),
121 Tile(#[from] TileError),
122 EnvPoint(#[from] EnvPointError),
123}
124
125fn check_amount(amount: usize, max: usize, item: MapItem) -> Result<(), MapErr> {
126 if amount > max {
127 Err(MapErr::from(MapErrorKind::Amount { amount, max }).with_type(item))
128 } else {
129 Ok(())
130 }
131}
132
133#[derive(Error, Debug)]
135pub enum DDNetError {}
136
137#[derive(Error, Debug)]
138pub enum TeeworldsError {
139 #[error("Teeworlds does not support settings in the map info")]
140 InfoSettings,
141 #[error("Teeworlds does not support {0:?} layers")]
142 DDNetLayer(LayerKind),
143 #[error("Teeworlds does not support automapper configs")]
144 TilesAutomapper,
145 #[error("Teeworlds does not support sounds")]
146 Sounds,
147 #[error("Teeworlds does not support sound envelopes")]
148 SoundEnv,
149}
150
151#[derive(Error, Debug)]
152pub(crate) enum StringError {
153 #[error("String is {len} bytes long while its maximum length is {max}")]
154 Length { len: usize, max: usize },
155 #[error("Name '{0}' should be sanitized, for example: {}", sanitize_filename::sanitize_with_options(.0, SANITIZE_OPTIONS))]
156 Sanitization(String),
157}
158
159const SANITIZE_OPTIONS: sanitize_filename::Options = sanitize_filename::Options {
160 windows: true,
161 truncate: true,
162 replacement: "",
163};
164
165const SANITIZE_CHECK_OPTIONS: sanitize_filename::OptionsForCheck =
166 sanitize_filename::OptionsForCheck {
167 windows: true,
168 truncate: true,
169 };
170
171#[derive(Error, Debug)]
172#[error("Value '{ident}' ({value}) is higher than its maximum value {max}")]
173pub(crate) struct ValueMaxError {
174 ident: &'static str,
175 value: u64,
176 max: i32,
177}
178
179fn check_max<T>(value: T, max: i32, ident: &'static str) -> Result<(), ValueMaxError>
180where
181 T: PartialOrd + WrappingCast<i64>,
182 i32: OverflowingCast<T>,
183{
184 let (max, of) = max.overflowing_as::<T>();
185 if of {
186 return Ok(());
187 }
188 if value > max {
189 Err(ValueMaxError {
190 ident,
191 value: 0,
192 max: i32::MAX,
193 })
194 } else {
195 Ok(())
196 }
197}
198
199fn check_i32_fit<T>(value: T, ident: &'static str) -> Result<(), ValueMaxError>
200where
201 T: PartialOrd + WrappingCast<i64>,
202 i32: OverflowingCast<T>,
203{
204 check_max(value, i32::MAX, ident)
205}
206
207#[derive(Error, Debug)]
208#[error("Value '{ident}' ({value}) must not be negative")]
209pub(crate) struct NegativeError {
210 ident: &'static str,
211 value: String,
212}
213
214fn check_non_negative<T>(value: T, ident: &'static str) -> Result<(), NegativeError>
215where
216 T: Signed + fmt::Display,
217{
218 if value.is_negative() {
219 Err(NegativeError {
220 ident,
221 value: value.to_string(),
222 })
223 } else {
224 Ok(())
225 }
226}
227
228fn check_string(s: &str, max: usize, file_extension: Option<&str>) -> Result<(), StringError> {
230 if s.len() > max {
231 return Err(StringError::Length { len: s.len(), max });
232 }
233 if let Some(ext) = file_extension {
234 let mut filename = s.to_owned();
235 filename.push('.');
236 filename.push_str(ext);
237 if !sanitize_filename::is_sanitized_with_options(&filename, SANITIZE_CHECK_OPTIONS) {
238 return Err(StringError::Sanitization(filename));
239 }
240 }
241 Ok(())
242}
243
244impl TwMap {
245 fn image_index(&self, index: Option<u16>) -> Result<(), MapErrorKind> {
246 let index = match index {
247 None => return Ok(()),
248 Some(i) => i,
249 };
250 if index.unwrapped_as::<usize>() >= self.images.len() {
251 Err(MapErrorKind::ImageIndex {
252 index,
253 len: self.images.len(),
254 })
255 } else {
256 Ok(())
257 }
258 }
259 fn sound_index(&self, index: Option<u16>) -> Result<(), MapErrorKind> {
260 let index = match index {
261 None => return Ok(()),
262 Some(i) => i,
263 };
264 if index.unwrapped_as::<usize>() >= self.sounds.len() {
265 Err(MapErrorKind::SoundIndex {
266 index,
267 len: self.sounds.len(),
268 })
269 } else {
270 Ok(())
271 }
272 }
273 fn envelope_index(&self, index: Option<u16>, kind: EnvelopeKind) -> Result<(), MapErrorKind> {
274 let index = match index {
275 None => return Ok(()),
276 Some(i) => i,
277 };
278 match self.envelopes.get(index.unwrapped_as::<usize>()) {
279 None => Err(MapErrorKind::EnvelopeIndex {
280 index,
281 len: self.images.len(),
282 }),
283 Some(env) => {
284 if env.kind() == kind {
285 Ok(())
286 } else {
287 Err(MapErrorKind::EnvelopeKind {
288 index,
289 expected: kind,
290 actual: env.kind(),
291 })
292 }
293 }
294 }
295 }
296}
297
298#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
299pub enum EnvelopeKind {
300 Position,
301 Color,
302 Sound,
303}
304
305impl Envelope {
306 fn kind(&self) -> EnvelopeKind {
307 match self {
308 Envelope::Position(_) => EnvelopeKind::Position,
309 Envelope::Color(_) => EnvelopeKind::Color,
310 Envelope::Sound(_) => EnvelopeKind::Sound,
311 }
312 }
313}
314
315impl TwMap {
316 fn check_amounts(&self) -> Result<(), MapErr> {
317 let max = u16::MAX.unwrapped_as::<usize>();
318 check_amount(self.envelopes.len(), max, Envelope::TYPE)?;
319 check_amount(self.sounds.len(), max, Sound::TYPE)?;
320 check_amount(self.groups.len(), max, Group::TYPE)?;
321 let layers = self.groups.iter().flat_map(|g| &g.layers).count();
322 check_amount(layers, max, Layer::TYPE)?;
323 check_amount(self.images.len(), 64, Image::TYPE)?;
324 Ok(())
325 }
326}
327
328pub(crate) trait InternalMapChecking: Sized {
329 const TYPE: MapItem;
330 type State: Default;
331
332 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind>;
333
334 fn check_state_impl(&self, _: &TwMap, _state: &mut Self::State) -> Result<(), MapErrorKind> {
335 Ok(())
336 }
337
338 fn check_recursive_impl(&self, _: &TwMap) -> Result<(), MapErr> {
339 Ok(())
340 }
341
342 fn check(&self, map: &TwMap, state: &mut Self::State) -> Result<(), MapErr> {
343 self.check_impl(map)
344 .map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
345 self.check_recursive_impl(map)
346 .map_err(|err| err.with_type(Self::TYPE))?;
347 self.check_state_impl(map, state)
348 .map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
349 Ok(())
350 }
351
352 fn check_state(_: Self::State) -> Result<(), MapErrorKind> {
353 Ok(())
354 }
355
356 fn check_all(items: &[Self], map: &TwMap) -> Result<(), MapErr> {
357 let mut state = Self::State::default();
358 for (i, item) in items.iter().enumerate() {
359 item.check(map, &mut state)
360 .map_err(|err| err.with_index(i))?;
361 }
362 Self::check_state(state).map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
363 Ok(())
364 }
365}
366
367pub(crate) trait CheckData {
368 fn check_data(&self) -> Result<(), MapErrorKind>;
369}
370
371#[derive(Error, Debug)]
372#[error("Field {field}{} - {err}", .index.map(|i| format!(" with index {i}")).unwrap_or_default())]
373pub(crate) struct InfoError {
374 field: &'static str,
375 index: Option<usize>,
376 err: StringError,
377}
378
379impl InternalMapChecking for Info {
380 const TYPE: MapItem = MapItem::Info;
381 type State = ();
382
383 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
384 let items = [
385 (&self.author, Info::MAX_AUTHOR_LENGTH, "author", None),
386 (&self.version, Info::MAX_VERSION_LENGTH, "version", None),
387 (&self.credits, Info::MAX_CREDITS_LENGTH, "credits", None),
388 (&self.license, Info::MAX_LICENSE_LENGTH, "license", None),
389 ];
390 for (s, max, field, index) in IntoIterator::into_iter(items).chain(
391 self.settings
392 .iter()
393 .enumerate()
394 .map(|(i, s)| (s, Info::MAX_SETTING_LENGTH, "setting", Some(i))),
395 ) {
396 check_string(s, max, None).map_err(|err| InfoError { field, index, err })?;
397 }
398 if map.version == Version::Teeworlds07 && !self.settings.is_empty() {
399 return Err(TeeworldsError::InfoSettings.into());
400 }
401 Ok(())
402 }
403}
404
405#[derive(Error, Debug)]
406pub(crate) enum ImageError {
407 #[error("Image '{name}' is not a valid external image for {version:?}")]
408 InvalidExternal { name: String, version: Version },
409 #[error("The length ({len}) of the data of this RGBA8 image with size {size} is invalid")]
410 DataSize { size: Extent2<u32>, len: usize },
411 #[error("Zero sized ({0})")]
412 ZeroSized(Extent2<u32>),
413}
414
415impl InternalMapChecking for Image {
416 const TYPE: MapItem = MapItem::Image;
417 type State = ();
418
419 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
420 check_string(self.name(), Image::MAX_NAME_LENGTH, Some("png"))?;
421 if let Image::External(ex) = self {
422 if !constants::is_external_name(&ex.name, map.version) {
423 return Err(MapErrorKind::Image(ImageError::InvalidExternal {
424 name: ex.name.clone(),
425 version: map.version,
426 }));
427 }
428 }
429 if let Some(emb) = self.image() {
430 emb.check_data()?;
431 }
432 Ok(())
433 }
434}
435
436impl CheckData for CompressedData<RgbaImage, ImageLoadInfo> {
437 fn check_data(&self) -> Result<(), MapErrorKind> {
438 let size = self.size();
439 check_i32_fit(size.w, "width")?;
440 check_i32_fit(size.h, "height")?;
441 if size.w == 0 || size.h == 0 {
442 return Err(ImageError::ZeroSized(size).into());
443 }
444 let pixel_count = u64::from(size.w) * u64::from(size.h);
445 check_i32_fit(pixel_count, "pixel count")?;
446 let data_size = pixel_count * 4; check_i32_fit(data_size, "image data size")?;
448 match self {
449 CompressedData::Compressed(_, len, ImageLoadInfo { size }) => {
450 if *len != data_size.unwrapped_as::<usize>() {
451 return Err(ImageError::DataSize {
452 size: *size,
453 len: *len,
454 }
455 .into());
456 }
457 }
458 CompressedData::Loaded(_) => {}
459 }
460 Ok(())
461 }
462}
463
464impl InternalMapChecking for Sound {
465 const TYPE: MapItem = MapItem::Sound;
466 type State = ();
467
468 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
469 check_string(&self.name, Sound::MAX_NAME_LENGTH, Some("opus"))?;
470 self.data.check_data()?;
471 if map.version == Version::Teeworlds07 {
472 return Err(TeeworldsError::Sounds.into());
473 }
474 Ok(())
475 }
476}
477impl CheckData for CompressedData<Vec<u8>, ()> {
478 fn check_data(&self) -> Result<(), MapErrorKind> {
479 let data_size = match self {
480 CompressedData::Compressed(_, size, _) => *size,
481 CompressedData::Loaded(buf) => buf.len(),
482 };
483 check_i32_fit(data_size, "sound data size")?;
484 if let CompressedData::Loaded(buf) = self {
485 opus_headers::parse_from_read(&buf[..])?;
486 }
487 Ok(())
488 }
489}
490
491impl InternalMapChecking for Envelope {
492 const TYPE: MapItem = MapItem::Envelope;
493 type State = ();
494
495 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
496 check_string(self.name(), Envelope::MAX_NAME_LENGTH, None)?;
497 let envelope_amount = match self {
498 Envelope::Position(env) => env.points.len(),
499 Envelope::Color(env) => env.points.len(),
500 Envelope::Sound(env) => env.points.len(),
501 };
502 check_i32_fit(envelope_amount, "env point amount")?;
503 if map.version == Version::Teeworlds07 && matches!(self, Envelope::Sound(_)) {
504 return Err(TeeworldsError::SoundEnv.into());
505 }
506 Ok(())
507 }
508
509 fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
510 match self {
511 Envelope::Position(env) => EnvPoint::check_all(&env.points, map),
512 Envelope::Color(env) => EnvPoint::check_all(&env.points, map),
513 Envelope::Sound(env) => EnvPoint::check_all(&env.points, map),
514 }
515 }
516}
517
518#[derive(Error, Debug)]
519pub(crate) enum EnvPointError {
520 #[error("Invalid curve kind ({0})")]
521 InvalidCurve(i32),
522 #[error("Wrong order, the last point was at {last} ms, this on is at {this} ms")]
523 Order { this: i32, last: i32 },
524}
525
526impl<T> InternalMapChecking for EnvPoint<T> {
527 const TYPE: MapItem = MapItem::EnvPoint;
528 type State = i32;
529
530 fn check_impl(&self, _: &TwMap) -> Result<(), MapErrorKind> {
531 check_non_negative(self.time, "time stamp")?;
532 if let CurveKind::Unknown(n) = self.curve {
533 return Err(EnvPointError::InvalidCurve(n).into());
534 }
535 Ok(())
536 }
537
538 fn check_state_impl(&self, _: &TwMap, last_time: &mut i32) -> Result<(), MapErrorKind> {
539 if self.time < *last_time {
540 return Err(EnvPointError::Order {
541 this: self.time,
542 last: *last_time,
543 }
544 .into());
545 }
546 *last_time = self.time;
547 Ok(())
548 }
549}
550
551#[derive(Error, Debug)]
552pub(crate) enum GroupError {
553 #[error("No physics group")]
554 NoPhysicsGroup,
555 #[error("There must be only one physics group")]
556 SecondPhysicsGroup,
557 #[error("No game layer in physics group")]
558 NoGameLayer,
559 #[error("The physics group '{0}' should be called 'Game' instead")]
560 PhysicsName(String),
561 #[error("The clipping values of the physics group are changed")]
562 PhysicsClip,
563 #[error("The parallax values of the physics group are changed")]
564 PhysicsParallax,
565 #[error("The offset values of the physics group are changed")]
566 PhysicsOffset,
567}
568
569impl InternalMapChecking for Group {
570 const TYPE: MapItem = MapItem::Group;
571 type State = bool;
573
574 fn check_impl(&self, _: &TwMap) -> Result<(), MapErrorKind> {
575 check_string(&self.name, Group::MAX_NAME_LENGTH, None)?;
576 check_i32_fit(self.layers.len(), "layers amount")?;
577 check_non_negative(self.clip.w, "clip width")?;
578 check_non_negative(self.clip.h, "clip height")?;
579 if self.is_physics_group() {
580 if !self.layers.iter().any(|l| matches!(l, Layer::Game(_))) {
581 return Err(GroupError::NoGameLayer.into());
582 }
583 let default = Group::physics();
584 if self.name != default.name {
585 return Err(GroupError::PhysicsName(self.name.clone()).into());
586 }
587 if self.clipping != default.clipping || self.clip != default.clip {
588 return Err(GroupError::PhysicsClip.into());
589 }
590 if self.offset != default.offset {
591 return Err(GroupError::PhysicsOffset.into());
592 }
593 if self.parallax != default.parallax {
594 return Err(GroupError::PhysicsParallax.into());
595 }
596 }
597 Ok(())
598 }
599
600 fn check_state_impl(&self, _: &TwMap, has_physics: &mut bool) -> Result<(), MapErrorKind> {
601 if self.is_physics_group() {
602 if *has_physics {
603 return Err(GroupError::SecondPhysicsGroup.into());
604 }
605 *has_physics = true;
606 }
607 Ok(())
608 }
609
610 fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
611 Layer::check_all(&self.layers, map)
612 }
613
614 fn check_state(has_physics: bool) -> Result<(), MapErrorKind> {
615 if !has_physics {
616 return Err(GroupError::NoPhysicsGroup.into());
617 }
618 Ok(())
619 }
620}
621
622#[derive(Error, Debug)]
623pub(crate) enum LayerError {
624 #[error("Invalid layer kind: {0:?}")]
625 InvalidKind(InvalidLayerKind),
626 #[error("Width and height must be at least 2")]
627 TooSmall,
628 #[error("Images used by tiles layers must have width and height be divisible by 16")]
629 ImageDimensions,
630 #[error("Automapper seed ({0}) must be below 1,000,000,000")]
631 AutomapperSeed(u32),
632 #[error("Second {0:?} layer")]
633 DuplicatePhysics(LayerKind),
634 #[error("The physics layers have different shapes")]
635 DifferentPhysicsShapes,
636 #[error("0.7 compressed tile data length must be a multiple of 4")]
637 CompressedSize,
638 #[error("The tile data size doesn't match with the layer dimensions")]
639 TilesDataSize,
640 #[error("0.7 tilemap decompression failed")]
641 TeeworldsCompression,
642}
643
644impl InternalMapChecking for Layer {
645 const TYPE: MapItem = MapItem::Layer;
646 type State = (HashSet<LayerKind>, Option<Extent2<usize>>);
647
648 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
649 use Layer::*;
650 check_string(self.name(), Layer::MAX_NAME_LENGTH, None)?;
651 if let Invalid(inv) = self {
652 return Err(LayerError::InvalidKind(*inv).into());
653 }
654 if map.version == Version::Teeworlds07 && !matches!(self, Game(_) | Tiles(_) | Quads(_)) {
655 return Err(TeeworldsError::DDNetLayer(self.kind()).into());
656 }
657 match self {
658 Game(l) => l.tiles.check_data()?,
659 Front(l) => l.tiles.check_data()?,
660 Tele(l) => l.tiles.check_data()?,
661 Speedup(l) => l.tiles.check_data()?,
662 Switch(l) => l.tiles.check_data()?,
663 Tune(l) => l.tiles.check_data()?,
664 Tiles(l) => {
665 l.tiles.check_data()?;
666 map.envelope_index(l.color_env, EnvelopeKind::Color)?;
667 map.image_index(l.image)?;
668 if let Some(image) = l.image {
669 if !map.images[image.unwrapped_as::<usize>()].for_tilemap() {
670 return Err(LayerError::ImageDimensions.into());
671 }
672 }
673 if map.version == Version::Teeworlds07
674 && l.automapper_config != AutomapperConfig::default()
675 {
676 return Err(TeeworldsError::TilesAutomapper.into());
677 }
678 if l.automapper_config.seed > 1_000_000_000 {
679 return Err(LayerError::AutomapperSeed(l.automapper_config.seed).into());
680 }
681 }
682 Quads(l) => {
683 map.image_index(l.image)?;
684 let element_size = mem::size_of::<BinaryQuad>().unwrapped_as::<i32>();
685 let max_elements = i32::MAX / element_size;
686 check_max(l.quads.len(), max_elements, "quads amount")?;
687 }
688 Sounds(l) => {
689 map.sound_index(l.sound)?;
690 let element_size = mem::size_of::<BinarySoundSource>().unwrapped_as::<i32>();
691 let max_elements = i32::MAX / element_size;
692 check_max(l.sources.len(), max_elements, "sound sources amount")?;
693 }
694 Invalid(_) => {}
695 }
696 Ok(())
697 }
698
699 fn check_state_impl(&self, _: &TwMap, state: &mut Self::State) -> Result<(), MapErrorKind> {
700 let kind = self.kind();
701 if !kind.is_physics_layer() {
702 return Ok(());
703 }
704 let (layers, expected_shape) = state;
705 if layers.replace(kind).is_some() {
706 return Err(LayerError::DuplicatePhysics(kind).into());
707 }
708 let shape = self.shape().unwrap();
709 match expected_shape {
710 None => *expected_shape = Some(shape),
711 Some(expected) => {
712 if *expected != shape {
713 return Err(LayerError::DifferentPhysicsShapes.into());
714 }
715 }
716 }
717 Ok(())
718 }
719
720 fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
721 match self {
722 Layer::Quads(l) => Quad::check_all(&l.quads, map),
723 Layer::Sounds(l) => SoundSource::check_all(&l.sources, map),
724 _ => Ok(()),
725 }
726 }
727}
728
729impl InternalMapChecking for Quad {
730 const TYPE: MapItem = MapItem::Quad;
731 type State = ();
732
733 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
734 map.envelope_index(self.position_env, EnvelopeKind::Position)?;
735 map.envelope_index(self.color_env, EnvelopeKind::Color)?;
736 Ok(())
737 }
738}
739
740impl InternalMapChecking for SoundSource {
741 const TYPE: MapItem = MapItem::SoundSource;
742 type State = ();
743
744 fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
745 check_non_negative(self.delay, "delay")?;
746 map.envelope_index(self.position_env, EnvelopeKind::Position)?;
747 map.envelope_index(self.sound_env, EnvelopeKind::Sound)?;
748 match self.area {
749 SoundArea::Rectangle(rect) => {
750 check_non_negative(rect.w, "area width")?;
751 check_non_negative(rect.h, "area height")?;
752 }
753 SoundArea::Circle(disk) => check_non_negative(disk.radius, "area radius")?,
754 }
755 Ok(())
756 }
757}
758
759#[derive(Error, Debug)]
760#[error("Tile at x: {x}, y: {y} - {err}")]
761pub(crate) struct TileError {
762 x: usize,
763 y: usize,
764 err: TileErrorKind,
765}
766
767#[derive(Error, Debug)]
768pub enum TileErrorKind {
769 #[error("Skip byte of tile is {0} instead of zero")]
770 TileSkip(u8),
771 #[error("Unused byte of tile is {0} instead of zero")]
772 TileUnused(u8),
773 #[error("Unknown tile flags used, flags: {:#010b}", .0)]
774 UnknownTileFlags(u8),
775 #[error("Opaque tile flag used in physics layer")]
776 OpaqueTileFlag,
777 #[error("Unused byte of speedup is {0} instead of zero")]
778 SpeedupUnused(u8),
779 #[error("Angle of speedup is {0}, but should be between 0 and (exclusive) 360")]
780 SpeedupAngle(i16),
781}
782
783pub trait TileChecking {
784 fn check(&self) -> Result<(), TileErrorKind> {
785 Ok(())
786 }
787}
788
789impl TileFlags {
790 fn check(self) -> bool {
791 TileFlags::from_bits(self.bits()).is_some()
792 }
793}
794
795impl TileChecking for Tile {
796 fn check(&self) -> Result<(), TileErrorKind> {
797 use TileErrorKind::*;
798 if self.skip != 0 {
799 return Err(TileSkip(self.skip));
800 }
801 if self.unused != 0 {
802 return Err(TileUnused(self.unused));
803 }
804 if !self.flags.check() {
805 return Err(UnknownTileFlags(self.flags.bits()));
806 }
807 Ok(())
808 }
809}
810
811impl TileChecking for GameTile {
812 fn check(&self) -> Result<(), TileErrorKind> {
813 use TileErrorKind::*;
814 if self.skip != 0 {
815 return Err(TileSkip(self.skip));
816 }
817 if self.unused != 0 {
818 return Err(TileUnused(self.unused));
819 }
820 if !self.flags.check() {
821 return Err(UnknownTileFlags(self.flags.bits()));
822 }
823 if self.flags.contains(TileFlags::OPAQUE) {
824 return Err(OpaqueTileFlag);
825 }
826 Ok(())
827 }
828}
829
830impl TileChecking for Tele {}
831
832impl TileChecking for Switch {
833 fn check(&self) -> Result<(), TileErrorKind> {
834 use TileErrorKind::*;
835 if !self.flags.check() {
836 return Err(UnknownTileFlags(self.flags.bits()));
837 }
838 if self.flags.contains(TileFlags::OPAQUE) {
839 return Err(OpaqueTileFlag);
840 }
841 Ok(())
842 }
843}
844
845impl TileChecking for Speedup {
846 fn check(&self) -> Result<(), TileErrorKind> {
847 use TileErrorKind::*;
848 if self.unused_padding != 0 {
849 return Err(SpeedupUnused(self.unused_padding));
850 }
851 let angle = i16::from(self.angle);
852 if !(0..360).contains(&angle) {
853 return Err(SpeedupAngle(angle));
854 }
855 Ok(())
856 }
857}
858
859impl TileChecking for Tune {}
860
861impl<T: TileChecking> CheckData for CompressedData<Array2<T>, TilesLoadInfo> {
862 fn check_data(&self) -> Result<(), MapErrorKind> {
863 let size = self.shape();
864 check_i32_fit(size.w, "width")?;
865 check_i32_fit(size.h, "height")?;
866 let tile_count = size.w.unwrapped_as::<u64>() * size.h.unwrapped_as::<u64>();
867 check_i32_fit(tile_count, "tile count")?;
868 let tile_size = mem::size_of::<T>().unwrapped_as::<u64>();
869 let tile_data_size = tile_count * tile_size;
870 check_i32_fit(tile_data_size, "tilemap data size")?;
871 if size.w < 2 || size.h < 2 {
872 return Err(LayerError::TooSmall.into());
873 }
874 match self {
875 CompressedData::Loaded(tiles) => {
876 for ((y, x), tile) in tiles.indexed_iter() {
877 tile.check().map_err(|err| TileError { x, y, err })?;
878 }
879 }
880 CompressedData::Compressed(_, data_size, info) => {
881 if info.compression {
882 if data_size % 4 != 0 {
883 return Err(LayerError::CompressedSize.into());
884 }
885 } else if tile_data_size.unwrapped_as::<usize>() != *data_size {
886 return Err(LayerError::TilesDataSize.into());
887 }
888 }
889 }
890 Ok(())
891 }
892}