Parse world header into individual fields

This commit is contained in:
2024-04-07 02:23:03 -07:00
parent 9e2eb5a07b
commit 4363fd04ac
2 changed files with 89 additions and 23 deletions
+1
View File
@@ -37,6 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// Try to write a modified world file // Try to write a modified world file
world.boards.reverse(); world.boards.reverse();
world.starting_board = (world.boards.len() as i16 - 1) - world.starting_board;
fs::write("tmp.zzt", world.to_bytes()?)?; fs::write("tmp.zzt", world.to_bytes()?)?;
Ok(()) Ok(())
+88 -23
View File
@@ -1,13 +1,13 @@
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use nom::{ use nom::{
bytes::complete::take, bytes::complete::{tag, take},
combinator::fail, combinator::fail,
error::{ErrorKind, ParseError}, error::{ErrorKind, ParseError},
multi::count, multi::count,
number::complete::{le_i16, le_u8}, number::complete::{le_i16, le_u16, le_u8},
sequence::tuple, sequence::tuple,
Err, IResult, Err, IResult, Parser,
}; };
#[derive(Debug)] #[derive(Debug)]
@@ -69,7 +69,20 @@ impl Error for LoadError {
} }
pub struct World { pub struct World {
pub header: Vec<u8>, pub ammo: i16,
pub gems: i16,
pub keys: [bool; 7],
pub health: i16,
pub starting_board: i16,
pub torches: i16,
pub torch_cycles: i16,
pub energizer_cycles: i16,
pub score: i16,
pub world_name: Vec<u8>,
pub flags: [Vec<u8>; 10],
pub time: i16,
pub time_ticks: i16,
pub locked: bool,
pub boards: Vec<Board>, pub boards: Vec<Board>,
} }
@@ -110,27 +123,71 @@ pub struct Stat {
impl World { impl World {
pub fn from_bytes(bytes: &[u8]) -> Result<World, LoadError> { pub fn from_bytes(bytes: &[u8]) -> Result<World, LoadError> {
let mut world = World { let (input, (_, num_boards)) = tuple((tag([0xff, 0xff]), le_i16)).parse(bytes)?;
header: Vec::from(&bytes[0..512]), let (input, (ammo, gems, keys)) =
boards: vec![], tuple((le_i16, le_i16, count(bool_u8, 7))).parse(input)?;
}; let (input, (health, starting_board, torches, torch_cycles, energizer_cycles)) =
let num_boards = 1 + u16::from_le_bytes((&world.header[2..4]).try_into().unwrap()); tuple((le_i16, le_i16, le_i16, le_i16, le_i16)).parse(input)?;
let mut offset = world.header.len(); let (input, (_, score, world_name)) = tuple((take(2usize), le_i16, pstring(20)))(input)?;
for _ in 0..num_boards { let (input, flags) = count(pstring(20), 10).parse(input)?;
// Ideally, this wouldn't panic if we run out of bytes let (_input, (time, time_ticks, locked)) = tuple((le_i16, le_i16, bool_u8)).parse(input)?;
let board_len =
u16::from_le_bytes((&bytes[offset..offset + 2]).try_into().unwrap()) as usize; // Rest of header is padding; fast-forward starting from original input
offset += 2; let (input, _) = take(512usize).parse(bytes)?;
world
.boards // Load boards
.push(Board::from_bytes(&bytes[offset..offset + board_len])?); let num_boards = num_boards as usize + 1;
offset += board_len; let (_input, chunks) = count(board_slice, num_boards).parse(input)?;
} let boards: Result<Vec<Board>, LoadError> = chunks
Ok(world) .iter()
.map(|bytes: &&[u8]| Board::from_bytes(bytes))
.collect();
let boards = boards?;
Ok(World {
ammo,
gems,
keys: keys.try_into().unwrap(),
health,
starting_board,
torches,
torch_cycles,
energizer_cycles,
score,
world_name,
flags: flags.try_into().unwrap(),
time,
time_ticks,
locked,
boards,
})
} }
pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> { pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> {
let mut result = self.header.clone(); let mut result = Vec::with_capacity(512);
result.push_i16(-1); // file magic: ZZT world
result.push_i16(self.boards.len() as i16 - 1);
result.push_i16(self.ammo);
result.push_i16(self.gems);
for key in self.keys {
result.push_bool(key);
}
result.push_i16(self.health);
result.push_i16(self.starting_board);
result.push_i16(self.torches);
result.push_i16(self.torch_cycles);
result.push_i16(self.energizer_cycles);
result.push_padding(2);
result.push_i16(self.score);
result.push_string(20, &self.world_name)?;
for flag in &self.flags {
result.push_string(20, flag)?;
}
result.push_i16(self.time);
result.push_i16(self.time_ticks);
result.push_bool(self.locked);
result.push_padding(512 - result.len());
for board in &self.boards { for board in &self.boards {
result.extend_from_slice(&board.to_bytes()?); result.extend_from_slice(&board.to_bytes()?);
} }
@@ -140,8 +197,11 @@ impl World {
impl Board { impl Board {
pub fn from_bytes(bytes: &[u8]) -> Result<Board, LoadError> { pub fn from_bytes(bytes: &[u8]) -> Result<Board, LoadError> {
// Ignore length bytes
let (input, _) = le_u16.parse(bytes)?;
// Read board name // Read board name
let (input, name) = pstring(50)(bytes)?; let (input, name) = pstring(50)(input)?;
// Read terrain // Read terrain
const NUM_TILES: usize = 60 * 25; const NUM_TILES: usize = 60 * 25;
@@ -326,6 +386,11 @@ fn pstring(cap: u8) -> impl Fn(&[u8]) -> IResult<&[u8], Vec<u8>, LoadError> {
} }
} }
fn board_slice(bytes: &[u8]) -> IResult<&[u8], &[u8], LoadError> {
let (_, size) = le_u16.parse(bytes)?;
take(size + 2).parse(bytes)
}
trait SerializationHelpers { trait SerializationHelpers {
fn push_bool(&mut self, value: bool); fn push_bool(&mut self, value: bool);
fn push_i16(&mut self, value: i16); fn push_i16(&mut self, value: i16);