twmap/map/
checks.rs

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/// Currently, DDNet maps are a strict superset to Teeworlds maps in regard to their feature set
134#[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
228/// Passing a file extension triggers sanitization checks
229fn 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; // RGBA8 image
447        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    /// Represents if a physics group was already found
572    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}