From fe1dd1ab24e12a70b436b2f685aaad3ea6cf06f7 Mon Sep 17 00:00:00 2001 From: "Jakob L. Kreuze" Date: Wed, 23 May 2018 18:06:26 -0400 Subject: Better GRP parser in terms of API and implementation. --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/defs.rs | 95 ++++++++++++++++++ src/fmt.rs | 114 +++++++++------------ src/main.rs | 329 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 453 insertions(+), 93 deletions(-) create mode 100644 src/defs.rs diff --git a/Cargo.lock b/Cargo.lock index 300988d..47c9b8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,14 @@ name = "rebuild" version = "0.1.0" dependencies = [ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "simple-error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" +"checksum simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "7779d1977a9e1e50bebb430a57114acc64bc4c40d6d8efb3e57893531d5fd895" diff --git a/Cargo.toml b/Cargo.toml index 7c2ba50..c3fdc4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,4 @@ authors = ["Jakob L. Kreuze "] [dependencies] byteorder = "1" +simple-error = "0.1" diff --git a/src/defs.rs b/src/defs.rs new file mode 100644 index 0000000..37dd5a1 --- /dev/null +++ b/src/defs.rs @@ -0,0 +1,95 @@ +// Straight ripped from DUKE3D.H + +pub struct UserDefs { + god: bool, + warp_on: bool, + cashman: bool, + eog: bool, + showallmap: bool, + show_help: bool, + scrollmode: bool, + clipping: bool, + overhead_on: bool, + last_overhead: bool, + showweapons: bool, + + // Vec is MAXPLAYERS in size, names are 32 characters. + user_name: Vec, + + // Vec is 10 in size, strings are 40 characters. + ridecule: Vec, + + // Vec is 10 in size, strings are 22 characters. + savegame: Vec, + + // Constrained to 128 characters. + pwlockout: String, + + // Constrained to 128 characters. + rtsname: String, + + pause_on: i16, // Maybe a bool? + from_bonus: i16, // ?? + camerasprite: i16, //?? + last_camsprite: i16, // ?? + last_level: i16, // ?? + secretlevel: i16, + + // These names are pretty shitty. + const_visibility: i32, + uw_framerate: i32, + camera_time: i32, + folfvel: i32, + folavel: i32, + folx: i32, + foly: i32, + fola: i32, + reccnt: i32, + + entered_name: i32, + screen_tilting: i32, + shadows: i32, + fta_on: i32, + executions: i32, + auto_run: i32, + + coords: i32, + tickrate: i32, + m_coop: i32, + coop: i32, + screen_size: i32, + lockout: i32, + crosshair: i32, + + // [MAXPLAYERS][MAX_WEAPONS] + wchoice: Vec>, + playerai: i32, + + respawn_monsters: i32, + respawn_items: i32, + respawn_inventory: i32, + recstat: i32, + monsters_off: i32, + brightness: i32, + + m_respawn_items: i32, + m_respawn_monsters: i32, + m_respawn_inventory: i32, + m_recstat: i32, + m_monsters_off: i32, + detail: i32, + + m_ffire: i32, + ffire: i32, + m_player_skill: i32, + m_level_number: i32, + m_volume_number: i32, + multimode: i32, + + player_skill: i32, + level_number: i32, + volume_number: i32, + m_marker: i32, + marker: i32, + mouseflip: i32, +} diff --git a/src/fmt.rs b/src/fmt.rs index 4a747a7..61f53eb 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,33 +1,13 @@ extern crate byteorder; +extern crate simple_error; -use std::fmt; +use std::collections::HashMap; use std::error::Error; +use std::fs::File; +use std::io::Read; use self::byteorder::{ByteOrder, LittleEndian}; -#[derive(Debug)] -pub struct FormatError { - details: &'static str, -} - -impl FormatError { - fn new(details: &'static str) -> FormatError { - FormatError { details } - } -} - -impl fmt::Display for FormatError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.details) - } -} - -impl Error for FormatError { - fn description(&self) -> &str { - self.details - } -} - // The ".grp" file format is just a collection of a lot of files stored into 1 big // one. I tried to make the format as simple as possible: The first 12 bytes @@ -38,75 +18,77 @@ impl Error for FormatError { // after the other in the same order as the list of files. #[derive(Debug)] -pub struct GroupEntry { - pub name: String, - pub data: Vec, +pub struct GroupManager { + files: HashMap>, } -#[derive(Debug)] -pub struct Group { - pub file_count: usize, - - data: Vec, - - iter_index: usize, - data_off: usize, -} +impl GroupManager { + pub fn new() -> GroupManager { + GroupManager { files: HashMap::new() } + } -impl Group { - pub fn new(data: &[u8]) -> Result> { + pub fn load_from_slice(&mut self, data: &[u8]) -> Result<(), Box> { let len = data.len(); if len < 16 { - let details = "'data' is too small to contain the GRP header."; - return Err(Box::new(FormatError::new(details))); + bail!("'data' is too small to contain the GRP header."); } let header = String::from_utf8(data[..12].to_vec())?; if header.as_str() != "KenSilverman" { - let details = "Invalid GRP header."; - return Err(Box::new(FormatError::new(details))); + bail!("Invalid GRP header."); } let file_count = LittleEndian::read_u32(&data[12..16]) as usize; - // 16 bytes for the header, and 16 bytes for each file entry. The raw + // 16 bytes for the header, and 16 bytes for each table entry. The raw // data will follow. - let data_off = 16 * (file_count + 1) as usize; + let data_start = 16 * (file_count + 1) as usize; - if data_off >= len { - let details = "Invalid number of files."; - return Err(Box::new(FormatError::new(details))); + if data_start >= len { + bail!("Invalid number of files."); } - Ok(Group { file_count, data: data.clone().to_vec(), iter_index: 0, data_off }) - } -} + let mut data_off = data_start; + + for i in 0..file_count { + // Similar to how 'data_start' was calculated - 16 bytes for the + // header, and 16 bytes for each table entry. + let table_off = 16 * (i + 1); + + let name = &data[table_off..table_off+12]; + let name = String::from_utf8(name.to_vec())?; + let name = if let Some(j) = name.find('\x00') { + String::from(&name[..j]) + } else { + name + }; -impl Iterator for Group { - type Item = GroupEntry; + let size = &data[table_off+12..table_off+16]; + let size = LittleEndian::read_u32(size) as usize; - fn next(&mut self) -> Option { - if self.iter_index >= self.file_count { - return None; + let data = data[data_off..data_off+size].to_vec(); + data_off += size; + + self.files.insert(name, data); } - let table_off = 16 * (1 + self.iter_index); + Ok(()) + } - // Raising an error would be more ideal than a sentinel filename. - let size = LittleEndian::read_u32(&self.data[table_off+12..table_off+16]) as usize; - let name = match String::from_utf8(self.data[table_off..table_off+12].to_vec()) { - Ok(name) => name, - Err(_) => String::from("ERRORFNAMEAA"), - }; + pub fn load_from_file(&mut self, filename: &str) -> Result<(), Box> { + let mut file = File::open(filename)?; + let mut bytes: Vec = Vec::new(); - let result = Some(GroupEntry { name, data: self.data[self.data_off..self.data_off+size].to_vec() }); + file.read_to_end(&mut bytes)?; + self.load_from_slice(&bytes)?; - self.iter_index += 1; - self.data_off += size; + Ok(()) + } - result + pub fn get(&self, filename: &str) -> Option<&[u8]> { + Some(&self.files.get(filename)?) } } diff --git a/src/main.rs b/src/main.rs index 29c3e1e..0c48e53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,314 @@ +#[macro_use] +extern crate simple_error; + +use std::process; + mod fmt; -use std::fs::File; -use std::io::Read; -use std::io::Write; // Temporary +// initgroupfile(char *filename) +// { +// char buf[16]; +// long i, j, k; + +// if (numgroupfiles >= MAXGROUPFILES) return(-1); + +// groupfil[numgroupfiles] = open(filename,O_BINARY|O_RDWR,S_IREAD); +// if (groupfil[numgroupfiles] != -1) +// { +// groupfilpos[numgroupfiles] = 0; +// read(groupfil[numgroupfiles],buf,16); +// if ((buf[0] != 'K') || (buf[1] != 'e') || (buf[2] != 'n') || +// (buf[3] != 'S') || (buf[4] != 'i') || (buf[5] != 'l') || +// (buf[6] != 'v') || (buf[7] != 'e') || (buf[8] != 'r') || +// (buf[9] != 'm') || (buf[10] != 'a') || (buf[11] != 'n')) +// { +// close(groupfil[numgroupfiles]); +// groupfil[numgroupfiles] = -1; +// return(-1); +// } +// gnumfiles[numgroupfiles] = *((long *)&buf[12]); + +// if ((gfilelist[numgroupfiles] = (char *)kmalloc(gnumfiles[numgroupfiles]<<4)) == 0) +// { printf("Not enough memory for file grouping system\n"); exit(0); } +// if ((gfileoffs[numgroupfiles] = (long *)kmalloc((gnumfiles[numgroupfiles]+1)<<2)) == 0) +// { printf("Not enough memory for file grouping system\n"); exit(0); } + +// read(groupfil[numgroupfiles],gfilelist[numgroupfiles],gnumfiles[numgroupfiles]<<4); + +// j = 0; +// for(i=0;i>1),0,HEAD2,79); + + // ud.multimode = 1; + // printstr(0,1," Copyright (c) 1996 3D Realms Entertainment ",79); - let mut file = match File::open(filename) { - Ok(file) => file, - Err(err) => { - panic!("Couldn't open {}", filename); - } - }; + // initgroupfile("duke3d.grp"); - let mut bytes: Vec = Vec::new(); + let filename = "DUKE3D.GRP"; + let mut group_manager = fmt::GroupManager::new(); - match file.read_to_end(&mut bytes) { - Ok(count) => println!("Read {} bytes", count), - Err(err) => panic!("Couldn't read."), + if let Err(e) = group_manager.load_from_file(filename) { + println!("Couldn't open {}: {}", filename, e); + process::exit(1); } - let grp = fmt::Group::new(&bytes).unwrap(); + println!("DOGWHINE.VOC: {} bytes", group_manager.get("DOGWHINE.VOC").unwrap().len()); - println!("Files: {}", grp.file_count); + // checkcommandline(argc,argv); - for entry in grp { - println!("{}", entry.name); - if entry.name.contains("MID") { - let mut filename = String::from("/tmp/"); - filename.push_str(&entry.name); - filename = filename[..filename.find('\x00').unwrap()].to_string(); - - let mut file = File::create(filename).unwrap(); - file.write_all(&entry.data).unwrap(); - } - } + println!("You don't have enough free memory to run Duke Nukem 3D."); + println!("The DOS \"mem\" command should report 6,800K (or 6.8 megs)"); + println!("of \"total memory free\"."); + println!(""); + println!("Duke Nukem 3D requires {} more bytes to run.", 3162000 - 350000); + process::exit(1); + + // Considering that most of this can be implemented with Drop, I + // don't think it's necessary. + + // RegisterShutdownFunction( ShutDown ); + // void ShutDown( void ) + // { + // SoundShutdown(); + // MusicShutdown(); + // uninittimer(); + // uninitengine(); + // CONTROL_Shutdown(); + // CONFIG_WriteSetup(); + // KB_Shutdown(); + // } + + // Startup(); + + + // if(numplayers > 1) + // { + // ud.multimode = numplayers; + // sendlogon(); + // } + // else if(boardfilename[0] != 0) + // { + // ud.m_level_number = 7; + // ud.m_volume_number = 0; + // ud.warp_on = 1; + // } + + // getnames(); + + // if(ud.multimode > 1) + // { + // playerswhenstarted = ud.multimode; + + // if(ud.warp_on == 0) + // { + // ud.m_monsters_off = 1; + // ud.m_player_skill = 0; + // } + // } + + // ud.last_level = -1; + + // RTS_Init(ud.rtsname); + // if(numlumps) printf("Using .RTS file:%s\n",ud.rtsname); + + // if( setgamemode(ScreenMode,ScreenWidth,ScreenHeight) < 0 ) + // { + // printf("\nVESA driver for ( %i * %i ) not found/supported!\n",xdim,ydim); + // ScreenMode = 2; + // ScreenWidth = 320; + // ScreenHeight = 200; + // setgamemode(ScreenMode,ScreenWidth,ScreenHeight); + // } + + // // CTW END - MODIFICATION + + // genspriteremaps(); + +// #ifdef VOLUMEONE +// if(numplayers > 4 || ud.multimode > 4) +// gameexit(" The full version of Duke Nukem 3D supports 5 or more players."); +// #endif + + // setbrightness(ud.brightness>>2,&ps[myconnectindex].palette[0]); + + // ESCESCAPE; + + // FX_StopAllSounds(); + // clearsoundlocks(); + + // if(ud.warp_on > 1 && ud.multimode < 2) + // { + // clearview(0L); + // ps[myconnectindex].palette = palette; + // palto(0,0,0,0); + // rotatesprite(320<<15,200<<15,65536L,0,LOADSCREEN,0,0,2+8+64,0,0,xdim-1,ydim-1); + // menutext(160,105,0,0,"LOADING SAVED GAME..."); + // nextpage(); + + // j = loadplayer(ud.warp_on-2); + // if(j) + // ud.warp_on = 0; + // } + + // // getpackets(); + +// MAIN_LOOP_RESTART: + +// if(ud.warp_on == 0) +// Logo(); +// else if(ud.warp_on == 1) +// { +// newgame(ud.m_volume_number,ud.m_level_number,ud.m_player_skill); +// enterlevel(MODE_GAME); +// } +// else vscrn(); + +// tempautorun = ud.auto_run; + +// if( ud.warp_on == 0 && playback() ) +// { +// FX_StopAllSounds(); +// clearsoundlocks(); +// nomorelogohack = 1; +// goto MAIN_LOOP_RESTART; +// } + +// ud.auto_run = tempautorun; + +// ud.warp_on = 0; + +// while ( !(ps[myconnectindex].gm&MODE_END) ) //The whole loop!!!!!!!!!!!!!!!!!! +// { +// if( ud.recstat == 2 || ud.multimode > 1 || ( ud.show_help == 0 && (ps[myconnectindex].gm&MODE_MENU) != MODE_MENU ) ) +// if( ps[myconnectindex].gm&MODE_GAME ) +// if( moveloop() ) continue; + +// if( ps[myconnectindex].gm&MODE_EOL || ps[myconnectindex].gm&MODE_RESTART ) +// { +// if( ps[myconnectindex].gm&MODE_EOL ) +// { +// #ifdef ONELEVELDEMO +// gameexit(" "); +// #endif +// closedemowrite(); + +// ready2send = 0; + +// i = ud.screen_size; +// ud.screen_size = 0; +// vscrn(); +// ud.screen_size = i; +// dobonus(0); + +// if(ud.eog) +// { +// ud.eog = 0; +// if(ud.multimode < 2) +// { +// #ifndef VOLUMEALL +// doorders(); +// #endif +// ps[myconnectindex].gm = MODE_MENU; +// cmenu(0); +// probey = 0; +// goto MAIN_LOOP_RESTART; +// } +// else +// { +// ud.m_level_number = 0; +// ud.level_number = 0; +// } +// } +// } + +// ready2send = 0; +// if(numplayers > 1) ps[myconnectindex].gm = MODE_GAME; +// enterlevel(ps[myconnectindex].gm); +// continue; +// } + +// cheats(); +// nonsharedkeys(); + +// if( (ud.show_help == 0 && ud.multimode < 2 && !(ps[myconnectindex].gm&MODE_MENU) ) || ud.multimode > 1 || ud.recstat == 2) +// i = min(max((totalclock-ototalclock)*(65536L/TICSPERFRAME),0),65536); +// else +// i = 65536; + +// displayrooms(screenpeek,i); +// displayrest(i); + +// // if( KB_KeyPressed(sc_F) ) +// // { +// // KB_ClearKeyDown(sc_F); +// // addplayer(); +// // } + +// if(ps[myconnectindex].gm&MODE_DEMO) +// goto MAIN_LOOP_RESTART; + +// if(debug_on) caches(); + +// checksync(); + +// #ifdef VOLUMEONE +// if(ud.show_help == 0 && show_shareware > 0 && (ps[myconnectindex].gm&MODE_MENU) == 0 ) +// rotatesprite((320-50)<<16,9<<16,65536L,0,BETAVERSION,0,0,2+8+16+128,0,0,xdim-1,ydim-1); +// #endif +// nextpage(); +// } + +// gameexit(" "); } -- cgit v1.3