From d68986e9776f7707222ec7842b192b1a8642d5e3 Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Thu, 7 Mar 2024 01:21:54 -0800 Subject: [PATCH] Write start of ZZT preprocessor It's messy, but it can parse worlds now! --- .gitignore | 1 + Cargo.lock | 16 +++++++ Cargo.toml | 9 ++++ src/main.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e6b41aa --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "assembler" +version = "0.1.0" +dependencies = [ + "byteorder", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0d5df31 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "assembler" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +byteorder = "1.5.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..64cc94a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,129 @@ +use byteorder::{ByteOrder, LittleEndian}; +use std::{env, error::Error, fs, process::exit}; + +struct World { + header: Vec, + boards: Vec, +} + +struct Board { + name: Vec, + terrain: Vec<[u8; 2]>, + info: Vec, + stats: Vec, +} + +struct Stat { + info: Vec, + code: Vec, +} + +impl World { + fn from(bytes: &[u8]) -> Result> { + let mut world = World { + header: Vec::from(&bytes[0..512]), + boards: vec![], + }; + // TODO: Remove LittleEndian entirely? + let num_boards = 1 + LittleEndian::read_u16(&world.header[2..4]); + let mut offset = world.header.len(); + for _ in 0..num_boards { + // Ideally, this wouldn't panic if we run out of bytes + let board_len = + u16::from_le_bytes((&bytes[offset..offset + 2]).try_into().unwrap()) as usize; + offset += 2; + world + .boards + .push(Board::from(&bytes[offset..offset + board_len])?); + offset += board_len; + } + Ok(world) + } +} + +impl Board { + fn from(bytes: &[u8]) -> Result> { + // Read board name + let name_len = bytes[0] as usize; + let name = Vec::from(&bytes[1..name_len + 1]); + let mut offset = 51; // skip Pascal string[50] + + // Read terrain + let mut terrain = vec![[0; 2]; 60 * 25]; + let mut i = 0; + while i < terrain.len() { + let times = if bytes[offset] == 0 { + 256 + } else { + bytes[offset] as usize + }; + let tile = (&bytes[offset + 1..offset + 3]).try_into().unwrap(); + offset += 3; + for _ in 0..times { + terrain[i] = tile; + i += 1 + } + } + + // Read board info + let info = Vec::from(&bytes[offset..offset + 86]); + offset += 86; + + // Read stats + let num_stats = + u16::from_le_bytes((&bytes[offset..offset + 2]).try_into().unwrap()) as usize + 1; + offset += 2; + let mut stats = Vec::with_capacity(num_stats); + for _ in 0..num_stats { + let mut stat = Stat { + info: Vec::from(&bytes[offset..offset + 25]), + code: vec![], + }; + offset += 25 + 8; + let code_len = i16::from_le_bytes((&stat.info[23..25]).try_into().unwrap()); + if code_len > 0 { + let code_len = code_len as usize; + stat.code = Vec::from(&bytes[offset..offset + code_len]); + offset += code_len; + } + stats.push(stat); + } + Ok(Board { + name, + terrain, + info, + stats, + }) + } +} + +fn to_latin1(bytes: &[u8]) -> String { + bytes.iter().map(|&x| x as char).collect() +} + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} WORLD_FILE", args[0]); + exit(1); + } + + let bytes = fs::read(&args[1])?; + let world = World::from(&bytes)?; + + println!("num boards: {}", &world.boards.len()); + for board in &world.boards { + println!("board: {}", to_latin1(&board.name)); + for stat in &board.stats { + let (x, y) = (stat.info[0] as usize, stat.info[1] as usize); + println!( + " stat at ({}, {}): {:?}, {:?}", + x, + y, + board.terrain[(x - 1) + (y - 1) * 60], + to_latin1(&stat.code) + ); + } + } + Ok(()) +}