Skip to content

Instantly share code, notes, and snippets.

@yuriks
Last active February 19, 2020 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuriks/71c4a02526edf83273dae88c2de00a7f to your computer and use it in GitHub Desktop.
Save yuriks/71c4a02526edf83273dae88c2de00a7f to your computer and use it in GitHub Desktop.
La-Mulana MSD parser using nom
mod parser;
// Actual range: 11 bits [0, 2048)
pub type TileIndex = u16;
#[derive(Debug)]
pub struct AnimatedTileFrame {
pub frames_wait: u8,
pub tile_index: TileIndex,
}
#[derive(Debug)]
pub struct AnimatedTile {
pub animate_in_boss: bool,
pub frames: Vec<AnimatedTileFrame>,
}
#[derive(Copy, Clone, Debug)]
pub struct Tile(u16);
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BlendType {
Empty = 0,
Standard = 1,
Add = 2,
Multiply = 3,
}
impl Tile {
pub fn tile_index(self) -> TileIndex {
self.0 & 0x7ff
}
pub fn blend_type(self) -> BlendType {
match self.0 >> 11 & 0x3 {
0 => BlendType::Empty,
1 => BlendType::Standard,
2 => BlendType::Add,
3 => BlendType::Multiply,
_ => unreachable!(),
}
}
pub fn h_flip(self) -> bool {
(self.0 >> 13 & 1) != 0
}
/// Rotation in 90-degree steps
pub fn rotation(self) -> u32 {
(self.0 >> 14 & 0x3) as u32
}
pub fn rotated_90(self) -> bool {
(self.0 >> 14 & 1) != 0
}
pub fn rotated_180(self) -> bool {
(self.0 >> 15 & 1) != 0
}
}
pub type SubLayer = Vec<Tile>;
#[derive(Debug)]
pub struct Layer {
// Dimensions in 20x20 graphics tiles
pub width: usize,
pub height: usize,
pub sublayers: Vec<SubLayer>,
}
#[derive(Debug)]
pub struct Room {
pub use_boss_graphics: bool,
pub gameplay_layer_index: usize,
// Dimensions in 10x10 collision tiles
pub collision_map_width: usize,
pub collision_map_height: usize,
pub collision_map: Vec<u8>,
pub layers: Vec<Layer>,
}
#[derive(Debug)]
pub struct MapMSD {
pub animated_tiles: Vec<AnimatedTile>,
pub graphics_file_id: u8,
pub rooms: Vec<Room>,
}
pub use parser::parse_msd;
use super::{AnimatedTile, AnimatedTileFrame, Layer, MapMSD, Room, SubLayer, Tile};
use nom::bytes::complete::{tag, take};
use nom::combinator::{map, map_res, verify};
use nom::multi::{count, many_till};
use nom::number::complete::{be_u16, be_u8};
use nom::sequence::tuple;
use nom::IResult;
use nom::{bits, do_parse, named, take_bits, tuple};
fn to_bool_strict(x: u8) -> Result<bool, ()> {
match x {
0 => Ok(false),
1 => Ok(true),
_ => Err(()),
}
}
fn animated_tile(i: &[u8]) -> IResult<&[u8], AnimatedTile> {
let (i, header) = be_u16(i)?;
let animate_in_boss = (header >> 15 & 1) != 0;
let frame_count = header & 0x7fff;
let (i, frames) = count(
map(be_u16, |entry| AnimatedTileFrame {
frames_wait: (entry >> 11 & 0x1f) as u8,
tile_index: entry & 0x7ff,
}),
frame_count as usize,
)(i)?;
Ok((
i,
AnimatedTile {
animate_in_boss,
frames,
},
))
}
fn layer(i: &[u8]) -> IResult<&[u8], Layer> {
let (i, layer_width) = map(be_u16, |x| x as usize)(i)?;
let (i, layer_height) = map(be_u16, |x| x as usize)(i)?;
let (i, sublayer_count) = map(be_u8, |x| x as usize)(i)?;
let (i, sublayers) = count(
count(map(be_u16, |x| Tile(x)), layer_width * layer_height),
sublayer_count,
)(i)?;
Ok((
i,
Layer {
width: layer_width,
height: layer_height,
sublayers,
},
))
}
fn room(i: &[u8]) -> IResult<&[u8], Room> {
let (i, use_boss_graphics) = map_res(be_u8, to_bool_strict)(i)?;
let (i, layer_count) = map(be_u8, |x| x as usize)(i)?;
let (i, gameplay_layer_index) = map(be_u8, |x| x as usize)(i)?;
let (i, collision_map_width) = map(be_u16, |x| x as usize)(i)?;
let (i, collision_map_height) = map(be_u16, |x| x as usize)(i)?;
let (i, collision_map) = map(
take(collision_map_width * collision_map_height),
|s: &[u8]| s.to_vec(),
)(i)?;
let (i, layers) = count(layer, layer_count)(i)?;
Ok((
i,
Room {
use_boss_graphics,
gameplay_layer_index,
collision_map_width,
collision_map_height,
collision_map,
layers,
},
))
}
pub fn parse_msd(i: &[u8]) -> IResult<&[u8], MapMSD> {
let (i, (animated_tiles, _)) = many_till(animated_tile, tag([0, 0]))(i)?;
let (i, graphics_file_id) = be_u8(i)?;
let (i, room_count) = be_u16(i)?;
let (i, rooms) = count(room, room_count as usize)(i)?;
Ok((
i,
MapMSD {
animated_tiles,
graphics_file_id,
rooms,
},
))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment