use crate::constants;
use crate::map::parse::{BinaryQuad, BinarySoundSource};
use crate::map::*;
use az::{OverflowingAs, OverflowingCast, UnwrappedAs, WrappingCast};
use image::RgbaImage;
use ndarray::Array2;
use thiserror::Error;
use vek::num_traits::Signed;
use vek::Extent2;
use crate::compression::ZlibDecompressionError;
use std::collections::HashSet;
use std::fmt;
use std::mem;
impl TwMap {
pub fn check(&self) -> Result<(), MapError> {
Info::check(&self.info, self, &mut ())?;
Image::check_all(&self.images, self)?;
Envelope::check_all(&self.envelopes, self)?;
Group::check_all(&self.groups, self)?;
Sound::check_all(&self.sounds, self)?;
self.check_amounts()?;
Ok(())
}
}
#[derive(Debug)]
pub(crate) enum MapItem {
Info,
Image,
Envelope,
Group,
Layer,
Sound,
Quad,
SoundSource,
EnvPoint,
}
#[derive(Error, Debug)]
#[error(transparent)]
pub struct MapError(#[from] pub(crate) MapErr);
#[derive(Error, Debug)]
pub(crate) enum MapErr {
#[error("In {item:?}{}{sub}", index.map(|i| format!(" at index {} -> ", i)).unwrap_or_default())]
Recursive {
item: MapItem,
index: Option<usize>,
sub: Box<MapErr>,
},
#[error(transparent)]
Head(MapErrorKind),
}
impl From<MapErrorKind> for MapErr {
fn from(err: MapErrorKind) -> Self {
Self::Head(err)
}
}
impl MapErr {
pub(crate) fn with_index(mut self, i: usize) -> Self {
if let Self::Recursive { index, .. } = &mut self {
*index = Some(i);
}
self
}
pub(crate) fn with_type(self, item: MapItem) -> Self {
Self::Recursive {
item,
index: None,
sub: Box::new(self),
}
}
}
#[derive(Error, Debug)]
#[error(transparent)]
pub(crate) enum MapErrorKind {
Teeworlds(#[from] TeeworldsError),
DDNet(#[from] DDNetError),
Decompression(#[from] ZlibDecompressionError),
#[error("{amount} items of this type is too many, the maximum is {max}")]
Amount {
amount: usize,
max: usize,
},
#[error("Invalid image index {index} for a map with {len} images")]
ImageIndex {
index: u16,
len: usize,
},
#[error("Invalid sound index {index} for a map with {len} sounds")]
SoundIndex {
index: u16,
len: usize,
},
#[error("Invalid envelope index {index} for a map with {len} envelopes")]
EnvelopeIndex {
index: u16,
len: usize,
},
#[error("Envelope at index {index} referenced as a {expected:?} envelope is instead a {actual:?} envelope")]
EnvelopeKind {
index: u16,
expected: EnvelopeKind,
actual: EnvelopeKind,
},
String(#[from] StringError),
I32Fit(#[from] ValueMaxError),
Negative(#[from] NegativeError),
Image(#[from] ImageError),
Info(#[from] InfoError),
Sound(#[from] opus_headers::ParseError),
Group(#[from] GroupError),
Layer(#[from] LayerError),
Tile(#[from] TileError),
EnvPoint(#[from] EnvPointError),
}
fn check_amount(amount: usize, max: usize, item: MapItem) -> Result<(), MapErr> {
if amount > max {
Err(MapErr::from(MapErrorKind::Amount { amount, max }).with_type(item))
} else {
Ok(())
}
}
#[derive(Error, Debug)]
pub enum DDNetError {
#[error("DDNet does not support bezier curves")]
Bezier,
}
#[derive(Error, Debug)]
pub enum TeeworldsError {
#[error("Teeworlds does not support settings in the map info")]
InfoSettings,
#[error("Teeworlds does not support {0:?} layers")]
DDNetLayer(LayerKind),
#[error("Teeworlds does not support automapper configs")]
TilesAutomapper,
#[error("Teeworlds does not support sounds")]
Sounds,
#[error("Teeworlds does not support sound envelopes")]
SoundEnv,
}
#[derive(Error, Debug)]
pub(crate) enum StringError {
#[error("String is {len} bytes long while its maximum length is {max}")]
Length { len: usize, max: usize },
#[error("Name '{0}' should be sanitized, for example: {}", sanitize_filename::sanitize_with_options(.0, SANITIZE_OPTIONS))]
Sanitization(String),
}
const SANITIZE_OPTIONS: sanitize_filename::Options = sanitize_filename::Options {
windows: true,
truncate: true,
replacement: "",
};
const SANITIZE_CHECK_OPTIONS: sanitize_filename::OptionsForCheck =
sanitize_filename::OptionsForCheck {
windows: true,
truncate: true,
};
#[derive(Error, Debug)]
#[error("Value '{ident}' ({value}) is higher than its maximum value {max}")]
pub(crate) struct ValueMaxError {
ident: &'static str,
value: u64,
max: i32,
}
fn check_max<T>(value: T, max: i32, ident: &'static str) -> Result<(), ValueMaxError>
where
T: PartialOrd + WrappingCast<i64>,
i32: OverflowingCast<T>,
{
let (max, of) = max.overflowing_as::<T>();
if of {
return Ok(());
}
if value > max {
Err(ValueMaxError {
ident,
value: 0,
max: i32::MAX,
})
} else {
Ok(())
}
}
fn check_i32_fit<T>(value: T, ident: &'static str) -> Result<(), ValueMaxError>
where
T: PartialOrd + WrappingCast<i64>,
i32: OverflowingCast<T>,
{
check_max(value, i32::MAX, ident)
}
#[derive(Error, Debug)]
#[error("Value '{ident}' ({value}) must not be negative")]
pub(crate) struct NegativeError {
ident: &'static str,
value: String,
}
fn check_non_negative<T>(value: T, ident: &'static str) -> Result<(), NegativeError>
where
T: Signed + fmt::Display,
{
if value.is_negative() {
Err(NegativeError {
ident,
value: value.to_string(),
})
} else {
Ok(())
}
}
fn check_string(s: &str, max: usize, file_extension: Option<&str>) -> Result<(), StringError> {
if s.len() > max {
return Err(StringError::Length { len: s.len(), max });
}
if let Some(ext) = file_extension {
let mut filename = s.to_owned();
filename.push('.');
filename.push_str(ext);
if !sanitize_filename::is_sanitized_with_options(&filename, SANITIZE_CHECK_OPTIONS) {
return Err(StringError::Sanitization(filename));
}
}
Ok(())
}
impl TwMap {
fn image_index(&self, index: Option<u16>) -> Result<(), MapErrorKind> {
let index = match index {
None => return Ok(()),
Some(i) => i,
};
if index.unwrapped_as::<usize>() >= self.images.len() {
Err(MapErrorKind::ImageIndex {
index,
len: self.images.len(),
})
} else {
Ok(())
}
}
fn sound_index(&self, index: Option<u16>) -> Result<(), MapErrorKind> {
let index = match index {
None => return Ok(()),
Some(i) => i,
};
if index.unwrapped_as::<usize>() >= self.sounds.len() {
Err(MapErrorKind::SoundIndex {
index,
len: self.sounds.len(),
})
} else {
Ok(())
}
}
fn envelope_index(&self, index: Option<u16>, kind: EnvelopeKind) -> Result<(), MapErrorKind> {
let index = match index {
None => return Ok(()),
Some(i) => i,
};
match self.envelopes.get(index.unwrapped_as::<usize>()) {
None => Err(MapErrorKind::EnvelopeIndex {
index,
len: self.images.len(),
}),
Some(env) => {
if env.kind() == kind {
Ok(())
} else {
Err(MapErrorKind::EnvelopeKind {
index,
expected: kind,
actual: env.kind(),
})
}
}
}
}
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
pub enum EnvelopeKind {
Position,
Color,
Sound,
}
impl Envelope {
fn kind(&self) -> EnvelopeKind {
match self {
Envelope::Position(_) => EnvelopeKind::Position,
Envelope::Color(_) => EnvelopeKind::Color,
Envelope::Sound(_) => EnvelopeKind::Sound,
}
}
}
impl TwMap {
fn check_amounts(&self) -> Result<(), MapErr> {
let max = u16::MAX.unwrapped_as::<usize>();
check_amount(self.envelopes.len(), max, Envelope::TYPE)?;
check_amount(self.sounds.len(), max, Sound::TYPE)?;
check_amount(self.groups.len(), max, Group::TYPE)?;
let layers = self.groups.iter().flat_map(|g| &g.layers).count();
check_amount(layers, max, Layer::TYPE)?;
check_amount(self.images.len(), 64, Image::TYPE)?;
Ok(())
}
}
pub(crate) trait InternalMapChecking: Sized {
const TYPE: MapItem;
type State: Default;
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind>;
fn check_state_impl(&self, _: &TwMap, _state: &mut Self::State) -> Result<(), MapErrorKind> {
Ok(())
}
fn check_recursive_impl(&self, _: &TwMap) -> Result<(), MapErr> {
Ok(())
}
fn check(&self, map: &TwMap, state: &mut Self::State) -> Result<(), MapErr> {
self.check_impl(map)
.map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
self.check_recursive_impl(map)
.map_err(|err| err.with_type(Self::TYPE))?;
self.check_state_impl(map, state)
.map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
Ok(())
}
fn check_state(_: Self::State) -> Result<(), MapErrorKind> {
Ok(())
}
fn check_all(items: &[Self], map: &TwMap) -> Result<(), MapErr> {
let mut state = Self::State::default();
for (i, item) in items.iter().enumerate() {
item.check(map, &mut state)
.map_err(|err| err.with_index(i))?;
}
Self::check_state(state).map_err(|err| MapErr::from(err).with_type(Self::TYPE))?;
Ok(())
}
}
pub(crate) trait CheckData {
fn check_data(&self) -> Result<(), MapErrorKind>;
}
#[derive(Error, Debug)]
#[error("Field {field}{} - {err}", .index.map(|i| format!(" with index {i}")).unwrap_or_default())]
pub(crate) struct InfoError {
field: &'static str,
index: Option<usize>,
err: StringError,
}
impl InternalMapChecking for Info {
const TYPE: MapItem = MapItem::Info;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
let items = [
(&self.author, Info::MAX_AUTHOR_LENGTH, "author", None),
(&self.version, Info::MAX_VERSION_LENGTH, "version", None),
(&self.credits, Info::MAX_CREDITS_LENGTH, "credits", None),
(&self.license, Info::MAX_LICENSE_LENGTH, "license", None),
];
for (s, max, field, index) in IntoIterator::into_iter(items).chain(
self.settings
.iter()
.enumerate()
.map(|(i, s)| (s, Info::MAX_SETTING_LENGTH, "setting", Some(i))),
) {
check_string(s, max, None).map_err(|err| InfoError { field, index, err })?;
}
if map.version == Version::Teeworlds07 && !self.settings.is_empty() {
return Err(TeeworldsError::InfoSettings.into());
}
Ok(())
}
}
#[derive(Error, Debug)]
pub(crate) enum ImageError {
#[error("Image '{name}' is not a valid external image for {version:?}")]
InvalidExternal { name: String, version: Version },
#[error("The length ({len}) of the data of this RGBA8 image with size {size} is invalid")]
DataSize { size: Extent2<u32>, len: usize },
#[error("Zero sized ({0})")]
ZeroSized(Extent2<u32>),
}
impl InternalMapChecking for Image {
const TYPE: MapItem = MapItem::Image;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
check_string(self.name(), Image::MAX_NAME_LENGTH, Some("png"))?;
if let Image::External(ex) = self {
if !constants::is_external_name(&ex.name, map.version) {
return Err(MapErrorKind::Image(ImageError::InvalidExternal {
name: ex.name.clone(),
version: map.version,
}));
}
}
if let Some(emb) = self.image() {
emb.check_data()?;
}
Ok(())
}
}
impl CheckData for CompressedData<RgbaImage, ImageLoadInfo> {
fn check_data(&self) -> Result<(), MapErrorKind> {
let size = self.size();
check_i32_fit(size.w, "width")?;
check_i32_fit(size.h, "height")?;
if size.w == 0 || size.h == 0 {
return Err(ImageError::ZeroSized(size).into());
}
let pixel_count = u64::from(size.w) * u64::from(size.h);
check_i32_fit(pixel_count, "pixel count")?;
let data_size = pixel_count * 4; check_i32_fit(data_size, "image data size")?;
match self {
CompressedData::Compressed(_, len, ImageLoadInfo { size }) => {
if *len != data_size.unwrapped_as::<usize>() {
return Err(ImageError::DataSize {
size: *size,
len: *len,
}
.into());
}
}
CompressedData::Loaded(_) => {}
}
Ok(())
}
}
impl InternalMapChecking for Sound {
const TYPE: MapItem = MapItem::Sound;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
check_string(&self.name, Sound::MAX_NAME_LENGTH, Some("opus"))?;
self.data.check_data()?;
if map.version == Version::Teeworlds07 {
return Err(TeeworldsError::Sounds.into());
}
Ok(())
}
}
impl CheckData for CompressedData<Vec<u8>, ()> {
fn check_data(&self) -> Result<(), MapErrorKind> {
let data_size = match self {
CompressedData::Compressed(_, size, _) => *size,
CompressedData::Loaded(buf) => buf.len(),
};
check_i32_fit(data_size, "sound data size")?;
if let CompressedData::Loaded(buf) = self {
opus_headers::parse_from_read(&buf[..])?;
}
Ok(())
}
}
impl InternalMapChecking for Envelope {
const TYPE: MapItem = MapItem::Envelope;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
check_string(self.name(), Envelope::MAX_NAME_LENGTH, None)?;
let envelope_amount = match self {
Envelope::Position(env) => env.points.len(),
Envelope::Color(env) => env.points.len(),
Envelope::Sound(env) => env.points.len(),
};
check_i32_fit(envelope_amount, "env point amount")?;
if map.version == Version::Teeworlds07 && matches!(self, Envelope::Sound(_)) {
return Err(TeeworldsError::SoundEnv.into());
}
Ok(())
}
fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
match self {
Envelope::Position(env) => EnvPoint::check_all(&env.points, map),
Envelope::Color(env) => EnvPoint::check_all(&env.points, map),
Envelope::Sound(env) => EnvPoint::check_all(&env.points, map),
}
}
}
#[derive(Error, Debug)]
pub(crate) enum EnvPointError {
#[error("Invalid curve kind ({0})")]
InvalidCurve(i32),
#[error("Wrong order, the last point was at {last} ms, this on is at {this} ms")]
Order { this: i32, last: i32 },
}
impl<T> InternalMapChecking for EnvPoint<T> {
const TYPE: MapItem = MapItem::EnvPoint;
type State = i32;
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
check_non_negative(self.time, "time stamp")?;
if let CurveKind::Unknown(n) = self.curve {
return Err(EnvPointError::InvalidCurve(n).into());
}
if map.version == Version::DDNet06 && matches!(self.curve, CurveKind::Bezier(_)) {
return Err(DDNetError::Bezier.into());
}
Ok(())
}
fn check_state_impl(&self, _: &TwMap, last_time: &mut i32) -> Result<(), MapErrorKind> {
if self.time < *last_time {
return Err(EnvPointError::Order {
this: self.time,
last: *last_time,
}
.into());
}
*last_time = self.time;
Ok(())
}
}
#[derive(Error, Debug)]
pub(crate) enum GroupError {
#[error("No physics group")]
NoPhysicsGroup,
#[error("There must be only one physics group")]
SecondPhysicsGroup,
#[error("No game layer in physics group")]
NoGameLayer,
#[error("The physics group '{0}' should be called 'Game' instead")]
PhysicsName(String),
#[error("The clipping values of the physics group are changed")]
PhysicsClip,
#[error("The parallax values of the physics group are changed")]
PhysicsParallax,
#[error("The offset values of the physics group are changed")]
PhysicsOffset,
}
impl InternalMapChecking for Group {
const TYPE: MapItem = MapItem::Group;
type State = bool;
fn check_impl(&self, _: &TwMap) -> Result<(), MapErrorKind> {
check_string(&self.name, Group::MAX_NAME_LENGTH, None)?;
check_i32_fit(self.layers.len(), "layers amount")?;
check_non_negative(self.clip.w, "clip width")?;
check_non_negative(self.clip.h, "clip height")?;
if self.is_physics_group() {
if !self.layers.iter().any(|l| matches!(l, Layer::Game(_))) {
return Err(GroupError::NoGameLayer.into());
}
let default = Group::physics();
if self.name != default.name {
return Err(GroupError::PhysicsName(self.name.clone()).into());
}
if self.clipping != default.clipping || self.clip != default.clip {
return Err(GroupError::PhysicsClip.into());
}
if self.offset != default.offset {
return Err(GroupError::PhysicsOffset.into());
}
if self.parallax != default.parallax {
return Err(GroupError::PhysicsParallax.into());
}
}
Ok(())
}
fn check_state_impl(&self, _: &TwMap, has_physics: &mut bool) -> Result<(), MapErrorKind> {
if self.is_physics_group() {
if *has_physics {
return Err(GroupError::SecondPhysicsGroup.into());
}
*has_physics = true;
}
Ok(())
}
fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
Layer::check_all(&self.layers, map)
}
fn check_state(has_physics: bool) -> Result<(), MapErrorKind> {
if !has_physics {
return Err(GroupError::NoPhysicsGroup.into());
}
Ok(())
}
}
#[derive(Error, Debug)]
pub(crate) enum LayerError {
#[error("Invalid layer kind: {0:?}")]
InvalidKind(InvalidLayerKind),
#[error("Width and height must be at least 2")]
TooSmall,
#[error("Images used by tiles layers must have width and height be divisible by 16")]
ImageDimensions,
#[error("Automapper seed ({0}) must be below 1,000,000,000")]
AutomapperSeed(u32),
#[error("Second {0:?} layer")]
DuplicatePhysics(LayerKind),
#[error("The physics layers have different shapes")]
DifferentPhysicsShapes,
#[error("0.7 compressed tile data length must be a multiple of 4")]
CompressedSize,
#[error("The tile data size doesn't match with the layer dimensions")]
TilesDataSize,
#[error("0.7 tilemap decompression failed")]
TeeworldsCompression,
}
impl InternalMapChecking for Layer {
const TYPE: MapItem = MapItem::Layer;
type State = (HashSet<LayerKind>, Option<Extent2<usize>>);
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
use Layer::*;
check_string(self.name(), Layer::MAX_NAME_LENGTH, None)?;
if let Invalid(inv) = self {
return Err(LayerError::InvalidKind(*inv).into());
}
if map.version == Version::Teeworlds07 && !matches!(self, Game(_) | Tiles(_) | Quads(_)) {
return Err(TeeworldsError::DDNetLayer(self.kind()).into());
}
match self {
Game(l) => l.tiles.check_data()?,
Front(l) => l.tiles.check_data()?,
Tele(l) => l.tiles.check_data()?,
Speedup(l) => l.tiles.check_data()?,
Switch(l) => l.tiles.check_data()?,
Tune(l) => l.tiles.check_data()?,
Tiles(l) => {
l.tiles.check_data()?;
map.envelope_index(l.color_env, EnvelopeKind::Color)?;
map.image_index(l.image)?;
if let Some(image) = l.image {
if !map.images[image.unwrapped_as::<usize>()].for_tilemap() {
return Err(LayerError::ImageDimensions.into());
}
}
if map.version == Version::Teeworlds07
&& l.automapper_config != AutomapperConfig::default()
{
return Err(TeeworldsError::TilesAutomapper.into());
}
if l.automapper_config.seed > 1_000_000_000 {
return Err(LayerError::AutomapperSeed(l.automapper_config.seed).into());
}
}
Quads(l) => {
map.image_index(l.image)?;
let element_size = mem::size_of::<BinaryQuad>().unwrapped_as::<i32>();
let max_elements = i32::MAX / element_size;
check_max(l.quads.len(), max_elements, "quads amount")?;
}
Sounds(l) => {
map.sound_index(l.sound)?;
let element_size = mem::size_of::<BinarySoundSource>().unwrapped_as::<i32>();
let max_elements = i32::MAX / element_size;
check_max(l.sources.len(), max_elements, "sound sources amount")?;
}
Invalid(_) => {}
}
Ok(())
}
fn check_state_impl(&self, _: &TwMap, state: &mut Self::State) -> Result<(), MapErrorKind> {
let kind = self.kind();
if !kind.is_physics_layer() {
return Ok(());
}
let (layers, expected_shape) = state;
if layers.replace(kind).is_some() {
return Err(LayerError::DuplicatePhysics(kind).into());
}
let shape = self.shape().unwrap();
match expected_shape {
None => *expected_shape = Some(shape),
Some(expected) => {
if *expected != shape {
return Err(LayerError::DifferentPhysicsShapes.into());
}
}
}
Ok(())
}
fn check_recursive_impl(&self, map: &TwMap) -> Result<(), MapErr> {
match self {
Layer::Quads(l) => Quad::check_all(&l.quads, map),
Layer::Sounds(l) => SoundSource::check_all(&l.sources, map),
_ => Ok(()),
}
}
}
impl InternalMapChecking for Quad {
const TYPE: MapItem = MapItem::Quad;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
map.envelope_index(self.position_env, EnvelopeKind::Position)?;
map.envelope_index(self.color_env, EnvelopeKind::Color)?;
Ok(())
}
}
impl InternalMapChecking for SoundSource {
const TYPE: MapItem = MapItem::SoundSource;
type State = ();
fn check_impl(&self, map: &TwMap) -> Result<(), MapErrorKind> {
check_non_negative(self.delay, "delay")?;
map.envelope_index(self.position_env, EnvelopeKind::Position)?;
map.envelope_index(self.sound_env, EnvelopeKind::Sound)?;
match self.area {
SoundArea::Rectangle(rect) => {
check_non_negative(rect.w, "area width")?;
check_non_negative(rect.h, "area height")?;
}
SoundArea::Circle(disk) => check_non_negative(disk.radius, "area radius")?,
}
Ok(())
}
}
#[derive(Error, Debug)]
#[error("Tile at x: {x}, y: {y} - {err}")]
pub(crate) struct TileError {
x: usize,
y: usize,
err: TileErrorKind,
}
#[derive(Error, Debug)]
pub enum TileErrorKind {
#[error("Skip byte of tile is {0} instead of zero")]
TileSkip(u8),
#[error("Unused byte of tile is {0} instead of zero")]
TileUnused(u8),
#[error("Unknown tile flags used, flags: {:#010b}", .0)]
UnknownTileFlags(u8),
#[error("Opaque tile flag used in physics layer")]
OpaqueTileFlag,
#[error("Unused byte of speedup is {0} instead of zero")]
SpeedupUnused(u8),
#[error("Angle of speedup is {0}, but should be between 0 and (exclusive) 360")]
SpeedupAngle(i16),
}
pub trait TileChecking {
fn check(&self) -> Result<(), TileErrorKind> {
Ok(())
}
}
impl TileFlags {
fn check(self) -> bool {
TileFlags::from_bits(self.bits()).is_some()
}
}
impl TileChecking for Tile {
fn check(&self) -> Result<(), TileErrorKind> {
use TileErrorKind::*;
if self.skip != 0 {
return Err(TileSkip(self.skip));
}
if self.unused != 0 {
return Err(TileUnused(self.unused));
}
if !self.flags.check() {
return Err(UnknownTileFlags(self.flags.bits()));
}
Ok(())
}
}
impl TileChecking for GameTile {
fn check(&self) -> Result<(), TileErrorKind> {
use TileErrorKind::*;
if self.skip != 0 {
return Err(TileSkip(self.skip));
}
if self.unused != 0 {
return Err(TileUnused(self.unused));
}
if !self.flags.check() {
return Err(UnknownTileFlags(self.flags.bits()));
}
if self.flags.contains(TileFlags::OPAQUE) {
return Err(OpaqueTileFlag);
}
Ok(())
}
}
impl TileChecking for Tele {}
impl TileChecking for Switch {
fn check(&self) -> Result<(), TileErrorKind> {
use TileErrorKind::*;
if !self.flags.check() {
return Err(UnknownTileFlags(self.flags.bits()));
}
if self.flags.contains(TileFlags::OPAQUE) {
return Err(OpaqueTileFlag);
}
Ok(())
}
}
impl TileChecking for Speedup {
fn check(&self) -> Result<(), TileErrorKind> {
use TileErrorKind::*;
if self.unused_padding != 0 {
return Err(SpeedupUnused(self.unused_padding));
}
let angle = i16::from(self.angle);
if !(0..360).contains(&angle) {
return Err(SpeedupAngle(angle));
}
Ok(())
}
}
impl TileChecking for Tune {}
impl<T: TileChecking> CheckData for CompressedData<Array2<T>, TilesLoadInfo> {
fn check_data(&self) -> Result<(), MapErrorKind> {
let size = self.shape();
check_i32_fit(size.w, "width")?;
check_i32_fit(size.h, "height")?;
let tile_count = size.w.unwrapped_as::<u64>() * size.h.unwrapped_as::<u64>();
check_i32_fit(tile_count, "tile count")?;
let tile_size = mem::size_of::<T>().unwrapped_as::<u64>();
let tile_data_size = tile_count * tile_size;
check_i32_fit(tile_data_size, "tilemap data size")?;
if size.w < 2 || size.h < 2 {
return Err(LayerError::TooSmall.into());
}
match self {
CompressedData::Loaded(tiles) => {
for ((y, x), tile) in tiles.indexed_iter() {
tile.check().map_err(|err| TileError { x, y, err })?;
}
}
CompressedData::Compressed(_, data_size, info) => {
if info.compression {
if data_size % 4 != 0 {
return Err(LayerError::CompressedSize.into());
}
} else if tile_data_size.unwrapped_as::<usize>() != *data_size {
return Err(LayerError::TilesDataSize.into());
}
}
}
Ok(())
}
}