From 9c8fdc9b5f40a254cfbb4089e86e128797182d68 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 21 Aug 2019 13:22:18 -0400 Subject: [PATCH 1/9] Initial support for reading entire Morrowind plugins --- src/plugin.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++++- src/record.rs | 34 +++++++++++++++-- src/subrecord.rs | 12 ++++++ 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index 82be8ff..1801147 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -39,6 +39,12 @@ enum FileExtension { ESL, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PluginEntry { + Record(Record), + Group(Group), +} + impl PartialEq<&std::borrow::Cow<'_, str>> for FileExtension { fn eq(&self, other: &&std::borrow::Cow<'_, str>) -> bool { const ESM: &str = "esm"; @@ -92,6 +98,7 @@ impl Default for RecordIds { struct PluginData { header_record: Record, record_ids: RecordIds, + entries: Vec, } #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -365,6 +372,10 @@ impl Plugin { fn is_light_flag_set(&self) -> bool { self.data.header_record.header().flags() & 0x200 != 0 } + + pub fn get_entries(&self) -> &Vec { + &self.data.entries + } } fn sorted_slices_intersect(left: &[T], right: &[T]) -> bool { @@ -467,6 +478,19 @@ fn parse_morrowind_record_ids<'a>(input: &'a [u8]) -> IResult<&'a [u8], RecordId Ok((remaining_input, record_ids.into())) } +fn parse_morrowind_records<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec> { + let mut records = Vec::new(); + let mut remaining_input = input; + + while !remaining_input.is_empty() { + let (input, record) = Record::parse(remaining_input, GameId::Morrowind, false)?; + remaining_input = input; + records.push(PluginEntry::Record(record)); + } + + Ok((remaining_input, records)) +} + fn read_morrowind_record_ids(reader: &mut R) -> Result { let mut record_ids = Vec::new(); let mut header_buf = [0; 16]; // Morrowind record headers are 16 bytes long. @@ -485,6 +509,35 @@ fn read_morrowind_record_ids(reader: &mut R) -> Result(reader: &mut R) -> Result, Error> { + let mut records = Vec::new(); + + while !reader.fill_buf()?.is_empty() { + let record = Record::read(reader, GameId::Morrowind, false)?; + + records.push(PluginEntry::Record(record)); + } + + Ok(records) +} + +fn parse_record_ids_from_entries(game_id: GameId, entries: &[PluginEntry]) -> RecordIds { + let mut record_ids = Vec::new(); + for entry in entries { + match entry { + PluginEntry::Record(record) => { + let record_id = record.parse_record_id_from_self(game_id); + + if let Some(RecordId::NamespacedId(record_id)) = record_id { + record_ids.push(record_id); + } + } + _ => unimplemented!(), + } + } + record_ids.into() +} + fn parse_record_ids<'a>( input: &'a [u8], game_id: GameId, @@ -507,6 +560,17 @@ fn parse_record_ids<'a>( } } +fn parse_entries<'a>( + input: &'a [u8], + game_id: GameId, +) -> IResult<&'a [u8], Vec> { + if game_id == GameId::Morrowind { + parse_morrowind_records(input) + } else { + unimplemented!() + } +} + fn read_record_ids( reader: &mut R, game_id: GameId, @@ -525,6 +589,17 @@ fn read_record_ids( } } +fn read_entries( + reader: &mut R, + game_id: GameId, +) -> Result, Error> { + if game_id == GameId::Morrowind { + read_morrowind_records(reader) + } else { + unimplemented!() + } +} + fn parse_plugin<'a>( input: &'a [u8], game_id: GameId, @@ -539,17 +614,26 @@ fn parse_plugin<'a>( PluginData { header_record, record_ids: RecordIds::None, + entries: vec![], }, )); } - let (input2, record_ids) = parse_record_ids(input1, game_id, &header_record, filename)?; + let (input2, entries, record_ids) = if game_id == GameId::Morrowind { + let (input2, entries) = parse_entries(input1, game_id)?; + let record_ids = parse_record_ids_from_entries(game_id, &entries); + (input2, entries, record_ids) + } else { + let (input2, record_ids) = parse_record_ids(input1, game_id, &header_record, filename)?; + (input2, vec![], record_ids) + }; Ok(( input2, PluginData { header_record, record_ids, + entries, }, )) } @@ -574,14 +658,23 @@ fn read_plugin( return Ok(PluginData { header_record, record_ids: RecordIds::None, + entries: vec![], }); } - let record_ids = read_record_ids(reader, game_id, &header_record, filename)?; + let (entries, record_ids) = if game_id == GameId::Morrowind { + let entries = read_entries(reader, game_id)?; + let record_ids = parse_record_ids_from_entries(game_id, &entries); + (entries, record_ids) + } else { + let record_ids = read_record_ids(reader, game_id, &header_record, filename)?; + (vec![], record_ids) + }; Ok(PluginData { header_record, record_ids, + entries, }) } diff --git a/src/record.rs b/src/record.rs index 16ad225..96d3cab 100644 --- a/src/record.rs +++ b/src/record.rs @@ -19,6 +19,7 @@ use std::convert::TryInto; use std::io; use std::num::NonZeroU32; +use std::string::FromUtf8Error; use nom::bytes::complete::take; use nom::combinator::{cond, map}; @@ -50,6 +51,10 @@ impl RecordHeader { pub fn flags(&self) -> u32 { self.flags } + + pub fn record_type(&self) -> Result { + String::from_utf8(self.record_type.to_vec()) + } } #[derive(Clone, PartialEq, Eq, Debug, Hash, Default)] @@ -140,7 +145,7 @@ impl Record { let bytes_read = header_length_read as u32 + header.size_of_subrecords; - let (_, record_id) = parse_morrowind_record_id(&subrecords_data, &header)?; + let (_, record_id) = parse_morrowind_record_id_from_input(&subrecords_data, &header)?; Ok((bytes_read, record_id)) } else { // Seeking discards the current buffer, so only do so if the data @@ -165,7 +170,7 @@ impl Record { let (remaining_input, subrecords_data) = take(header.size_of_subrecords)(remaining_input)?; - let (_, record_id) = parse_morrowind_record_id(subrecords_data, &header)?; + let (_, record_id) = parse_morrowind_record_id_from_input(subrecords_data, &header)?; Ok((remaining_input, record_id)) } else { let mut parser = tuple(( @@ -189,6 +194,29 @@ impl Record { } } + pub fn parse_record_id_from_self(&self, game_id: GameId) -> Option { + if game_id == GameId::Morrowind { + let types = record_id_subrecord_types(self.header.record_type); + if !types.is_empty() { + let subrecordrefs: Vec = self + .subrecords + .iter() + .filter(|x| types.contains(&x.subrecord_type())) + .map(|x| SubrecordRef::from_subrecord(x)) + .collect::>(); + let data = record_id_subrecord_mapper(self.header.record_type, &subrecordrefs); + + data.map(|data| { + RecordId::NamespacedId(NamespacedId::new(self.header.record_type, data)) + }) + } else { + None + } + } else { + unimplemented!() + } + } + pub fn header(&self) -> &RecordHeader { &self.header } @@ -202,7 +230,7 @@ impl Record { } } -fn parse_morrowind_record_id<'a>( +fn parse_morrowind_record_id_from_input<'a>( subrecords_data: &'a [u8], header: &RecordHeader, ) -> IResult<&'a [u8], Option> { diff --git a/src/subrecord.rs b/src/subrecord.rs index 579a6fc..5b9a534 100644 --- a/src/subrecord.rs +++ b/src/subrecord.rs @@ -24,6 +24,7 @@ use std::convert::TryInto; use std::io; #[cfg(feature = "compressed-fields")] use std::io::Read; +use std::string::FromUtf8Error; #[cfg(feature = "compressed-fields")] use flate2::read::DeflateDecoder; @@ -93,6 +94,10 @@ impl Subrecord { pub fn data(&self) -> &[u8] { &self.data } + + pub fn typ(&self) -> Result { + String::from_utf8(self.subrecord_type.to_vec()) + } } pub struct SubrecordRef<'a> { @@ -118,6 +123,13 @@ impl<'a> SubrecordRef<'a> { )) } + pub fn from_subrecord(subrecord: &'a Subrecord) -> SubrecordRef<'a> { + SubrecordRef { + subrecord_type: *subrecord.subrecord_type(), + data: &subrecord.data, + } + } + pub fn subrecord_type(&'a self) -> &SubrecordType { &self.subrecord_type } From 3ec2c0eab22e478761e6f5e96f5e3c56adfdfd34 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 4 Sep 2019 10:16:51 -0400 Subject: [PATCH 2/9] Added option to store all records Currently only does anything with the Morrowind GameId --- benches/parsing.rs | 12 +++- src/plugin.rs | 164 ++++++++++++++++++++++++++++----------------- 2 files changed, 111 insertions(+), 65 deletions(-) diff --git a/benches/parsing.rs b/benches/parsing.rs index f63f785..4e1c623 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -19,7 +19,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); }); }); @@ -27,7 +27,15 @@ fn criterion_benchmark(c: &mut Criterion) { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); + }); + }); + + c.bench_function("Plugin.parse_file() full with records", |b| { + let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); + + b.iter(|| { + assert!(plugin.parse_file(false, true).is_ok()); }); }); diff --git a/src/plugin.rs b/src/plugin.rs index 1801147..81a55e6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -117,11 +117,23 @@ impl Plugin { } } - pub fn parse(&mut self, input: &[u8], load_header_only: bool) -> Result<(), Error> { + pub fn parse( + &mut self, + input: &[u8], + load_header_only: bool, + store_all_records: bool, + ) -> Result<(), Error> { match self.filename() { None => Err(Error::NoFilename), Some(filename) => { - self.data = parse_plugin(input, self.game_id, &filename, load_header_only)?.1; + self.data = parse_plugin( + input, + self.game_id, + &filename, + load_header_only, + store_all_records, + )? + .1; Ok(()) } @@ -133,6 +145,7 @@ impl Plugin { mut reader: R, load_header_only: bool, expected_header_type: &'static [u8], + store_all_records: bool, ) -> Result<(), Error> { match self.filename() { None => Err(Error::NoFilename), @@ -143,6 +156,7 @@ impl Plugin { &filename, load_header_only, expected_header_type, + store_all_records, )?; Ok(()) @@ -150,21 +164,35 @@ impl Plugin { } } - pub fn parse_open_file(&mut self, file: File, load_header_only: bool) -> Result<(), Error> { + pub fn parse_open_file( + &mut self, + file: File, + load_header_only: bool, + store_all_records: bool, + ) -> Result<(), Error> { let mut reader = BufReader::new(&file); if load_header_only { let content = Record::read_and_validate(&mut reader, self.game_id, self.header_type())?; - self.parse(&content, load_header_only) + self.parse(&content, load_header_only, store_all_records) } else { - self.read(reader, load_header_only, self.header_type()) + self.read( + reader, + load_header_only, + self.header_type(), + store_all_records, + ) } } - pub fn parse_file(&mut self, load_header_only: bool) -> Result<(), Error> { + pub fn parse_file( + &mut self, + load_header_only: bool, + store_all_records: bool, + ) -> Result<(), Error> { let file = File::open(&self.path)?; - self.parse_open_file(file, load_header_only) + self.parse_open_file(file, load_header_only, store_all_records) } pub fn game_id(&self) -> &GameId { @@ -233,10 +261,17 @@ impl Plugin { } } - pub fn is_valid(game_id: GameId, filepath: &Path, load_header_only: bool) -> bool { + pub fn is_valid( + game_id: GameId, + filepath: &Path, + load_header_only: bool, + store_all_records: bool, + ) -> bool { let mut plugin = Plugin::new(game_id, &filepath.to_path_buf()); - plugin.parse_file(load_header_only).is_ok() + plugin + .parse_file(load_header_only, store_all_records) + .is_ok() } pub fn description(&self) -> Result, Error> { @@ -605,6 +640,7 @@ fn parse_plugin<'a>( game_id: GameId, filename: &str, load_header_only: bool, + store_all_records: bool, ) -> IResult<&'a [u8], PluginData> { let (input1, header_record) = Record::parse(input, game_id, false)?; @@ -619,7 +655,7 @@ fn parse_plugin<'a>( )); } - let (input2, entries, record_ids) = if game_id == GameId::Morrowind { + let (input2, entries, record_ids) = if game_id == GameId::Morrowind && store_all_records { let (input2, entries) = parse_entries(input1, game_id)?; let record_ids = parse_record_ids_from_entries(game_id, &entries); (input2, entries, record_ids) @@ -644,6 +680,7 @@ fn read_plugin( filename: &str, load_header_only: bool, expected_header_type: &'static [u8], + store_all_records: bool, ) -> Result { let header_record = Record::read(reader, game_id, false)?; @@ -662,7 +699,7 @@ fn read_plugin( }); } - let (entries, record_ids) = if game_id == GameId::Morrowind { + let (entries, record_ids) = if game_id == GameId::Morrowind && store_all_records { let entries = read_entries(reader, game_id)?; let record_ids = parse_record_ids_from_entries(game_id, &entries); (entries, record_ids) @@ -695,7 +732,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), @@ -710,7 +747,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => { @@ -728,7 +765,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(RecordIds::None, plugin.data.record_ids); } @@ -750,7 +787,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_master_file()); } @@ -781,7 +818,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(!plugin.is_master_file()); } @@ -802,7 +839,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); let expected_description = format!("{:\0<218}{:\0<38}\n\0\0", "v5.0", "\r"); assert_eq!(expected_description, plugin.description().unwrap().unwrap()); @@ -815,7 +852,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(1.2, plugin.header_version().unwrap()); } @@ -828,7 +865,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(10, plugin.record_and_group_count().unwrap()); } @@ -840,7 +877,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert_eq!(10, plugin.record_and_group_count().unwrap()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), @@ -855,7 +892,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert_eq!(0, plugin.count_override_records()); } @@ -870,8 +907,8 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Different.esm"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(false, false).is_ok()); + assert!(plugin2.parse_file(false, false).is_ok()); assert!(plugin1.overlaps_with(&plugin1)); assert!(!plugin1.overlaps_with(&plugin2)); @@ -962,7 +999,7 @@ mod tests { GameId::Morrowind, Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -987,7 +1024,7 @@ mod tests { Path::new("testing-plugins/Oblivion/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(0.8, plugin.header_version().unwrap()); } @@ -998,7 +1035,7 @@ mod tests { GameId::Oblivion, Path::new("testing-plugins/Oblivion/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1013,7 +1050,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); match plugin.data.record_ids { RecordIds::FormIds(ids) => assert_eq!(10, ids.len()), @@ -1028,7 +1065,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(RecordIds::None, plugin.data.record_ids); } @@ -1047,7 +1084,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_master_file()); } @@ -1058,7 +1095,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(!plugin.is_master_file()); } @@ -1079,7 +1116,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!("v5.0", plugin.description().unwrap().unwrap()); let mut plugin = Plugin::new( @@ -1087,7 +1124,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!("€ƒŠ", plugin.description().unwrap().unwrap()); let mut plugin = Plugin::new( @@ -1098,7 +1135,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!("", plugin.description().unwrap().unwrap()); } @@ -1109,7 +1146,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(0.94, plugin.header_version().unwrap()); } @@ -1122,7 +1159,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(15, plugin.record_and_group_count().unwrap()); } @@ -1133,7 +1170,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Different Master Dependent.esp"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert_eq!(2, plugin.count_override_records()); } @@ -1148,8 +1185,8 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Different.esm"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(false, false).is_ok()); + assert!(plugin2.parse_file(false, false).is_ok()); assert!(plugin1.overlaps_with(&plugin1)); assert!(!plugin1.overlaps_with(&plugin2)); @@ -1240,7 +1277,7 @@ mod tests { GameId::Skyrim, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1272,14 +1309,14 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(!plugin.is_master_file()); let mut plugin = Plugin::new( GameId::SkyrimSE, Path::new("testing-plugins/Skyrim/Data/Blank.esm.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_master_file()); } @@ -1312,7 +1349,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank.esl.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_light_plugin()); assert!(!plugin.is_master_file()); } @@ -1330,7 +1367,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank.esl.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_light_plugin()); assert!(plugin.is_master_file()); } @@ -1342,7 +1379,7 @@ mod tests { Path::new("testing-plugins/SkyrimSE/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(0.94, plugin.header_version().unwrap()); } @@ -1354,7 +1391,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1373,7 +1410,7 @@ mod tests { bytes[0x7A] = 0xFF; bytes[0x7B] = 0x07; - assert!(plugin.parse(&bytes, false).is_ok()); + assert!(plugin.parse(&bytes, false, false).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1392,7 +1429,7 @@ mod tests { bytes[0x386] = 0xFF; bytes[0x387] = 0x07; - assert!(plugin.parse(&bytes, false).is_ok()); + assert!(plugin.parse(&bytes, false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } @@ -1411,7 +1448,7 @@ mod tests { bytes[0x386] = 0x00; bytes[0x387] = 0x10; - assert!(plugin.parse(&bytes, false).is_ok()); + assert!(plugin.parse(&bytes, false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } @@ -1436,7 +1473,7 @@ mod tests { GameId::Fallout3, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1460,7 +1497,7 @@ mod tests { GameId::FalloutNV, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1490,14 +1527,14 @@ mod tests { GameId::Fallout4, Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(!plugin.is_master_file()); let mut plugin = Plugin::new( GameId::Fallout4, Path::new("testing-plugins/Skyrim/Data/Blank.esm.esp"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert!(plugin.is_master_file()); } @@ -1524,7 +1561,7 @@ mod tests { GameId::Fallout4, Path::new("testing-plugins/SkyrimSE/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(false, false).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1541,14 +1578,14 @@ mod tests { fn parse_file_should_error_if_plugin_does_not_exist() { let mut plugin = Plugin::new(GameId::Skyrim, Path::new("Blank.esm")); - assert!(plugin.parse_file(false).is_err()); + assert!(plugin.parse_file(false, false).is_err()); } #[test] fn parse_file_should_error_if_plugin_is_not_valid() { let mut plugin = Plugin::new(GameId::Skyrim, Path::new("README.md")); - let result = plugin.parse_file(false); + let result = plugin.parse_file(false, false); assert!(result.is_err()); assert_eq!( "failed to fill whole buffer", @@ -1565,7 +1602,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Invalid.esm"), ); - let result = plugin.parse_file(true); + let result = plugin.parse_file(true, false); assert!(result.is_err()); assert_eq!("An error was encountered while parsing the plugin content [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]: Expected record type [54, 45, 53, 34]", result.unwrap_err().to_string()); } @@ -1579,7 +1616,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Invalid.esm"), ); - let result = plugin.parse_file(false); + let result = plugin.parse_file(false, false); assert!(result.is_err()); assert_eq!("An error was encountered while parsing the plugin content [00, 00, 00, 00]: Expected record type [54, 45, 53, 34]", result.unwrap_err().to_string()); } @@ -1590,6 +1627,7 @@ mod tests { GameId::Skyrim, Path::new("testing-plugins/Skyrim/Data/Blank.esm"), true, + false, ); assert!(is_valid); @@ -1597,7 +1635,7 @@ mod tests { #[test] fn is_valid_should_return_false_for_an_invalid_plugin() { - let is_valid = Plugin::is_valid(GameId::Skyrim, Path::new("README.md"), true); + let is_valid = Plugin::is_valid(GameId::Skyrim, Path::new("README.md"), true, false); assert!(!is_valid); } @@ -1635,7 +1673,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); assert_eq!(0, plugin.masters().unwrap().len()); } @@ -1649,7 +1687,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true).is_ok()); + assert!(plugin.parse_file(true, false).is_ok()); let masters = plugin.masters().unwrap(); assert_eq!(1, masters.len()); @@ -1670,7 +1708,7 @@ mod tests { data[0x14] = 8; data[0x15] = 0; - assert!(plugin.parse(&data, true).is_ok()); + assert!(plugin.parse(&data, true, false).is_ok()); let result = plugin.description(); assert!(result.is_err()); @@ -1691,7 +1729,7 @@ mod tests { data[0x14] = 3; data[0x15] = 0; - assert!(plugin.parse(&data, true).is_ok()); + assert!(plugin.parse(&data, true, false).is_ok()); assert!(plugin.header_version().is_none()); } @@ -1707,7 +1745,7 @@ mod tests { data[0x04] = 0x30; data[0x14] = 0x28; - assert!(plugin.parse(&data, true).is_ok()); + assert!(plugin.parse(&data, true, false).is_ok()); assert!(plugin.record_and_group_count().is_none()); } From b588ce79a3a5e6d68820ddab5dcd056b4671db03 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 28 Aug 2019 16:01:27 -0400 Subject: [PATCH 3/9] Added public methods to allow inspection of records --- src/lib.rs | 4 +++- src/plugin.rs | 4 ++++ src/record.rs | 5 ----- src/subrecord.rs | 5 ----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 20679f8..cfdeba3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,9 @@ use std::convert::TryInto; pub use crate::error::Error; pub use crate::game_id::GameId; -pub use crate::plugin::Plugin; +pub use crate::plugin::{Plugin, PluginEntry}; +pub use crate::record::{Record, RecordHeader}; +pub use crate::subrecord::Subrecord; mod error; mod form_id; diff --git a/src/plugin.rs b/src/plugin.rs index 81a55e6..aab8a22 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -408,6 +408,10 @@ impl Plugin { self.data.header_record.header().flags() & 0x200 != 0 } + pub fn get_header_record(&self) -> &Record { + &self.data.header_record + } + pub fn get_entries(&self) -> &Vec { &self.data.entries } diff --git a/src/record.rs b/src/record.rs index 96d3cab..52d3ce7 100644 --- a/src/record.rs +++ b/src/record.rs @@ -19,7 +19,6 @@ use std::convert::TryInto; use std::io; use std::num::NonZeroU32; -use std::string::FromUtf8Error; use nom::bytes::complete::take; use nom::combinator::{cond, map}; @@ -51,10 +50,6 @@ impl RecordHeader { pub fn flags(&self) -> u32 { self.flags } - - pub fn record_type(&self) -> Result { - String::from_utf8(self.record_type.to_vec()) - } } #[derive(Clone, PartialEq, Eq, Debug, Hash, Default)] diff --git a/src/subrecord.rs b/src/subrecord.rs index 5b9a534..900feec 100644 --- a/src/subrecord.rs +++ b/src/subrecord.rs @@ -24,7 +24,6 @@ use std::convert::TryInto; use std::io; #[cfg(feature = "compressed-fields")] use std::io::Read; -use std::string::FromUtf8Error; #[cfg(feature = "compressed-fields")] use flate2::read::DeflateDecoder; @@ -94,10 +93,6 @@ impl Subrecord { pub fn data(&self) -> &[u8] { &self.data } - - pub fn typ(&self) -> Result { - String::from_utf8(self.subrecord_type.to_vec()) - } } pub struct SubrecordRef<'a> { From 5236dba574547e8cb52d29d8cae5392854260d77 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Thu, 23 Apr 2020 10:25:11 -0400 Subject: [PATCH 4/9] Replaced boolean flags with a ParseMode enum --- benches/parsing.rs | 12 +-- src/lib.rs | 2 +- src/plugin.rs | 196 +++++++++++++++++++-------------------------- 3 files changed, 90 insertions(+), 120 deletions(-) diff --git a/benches/parsing.rs b/benches/parsing.rs index 4e1c623..9524ad8 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -5,7 +5,7 @@ extern crate esplugin; use std::path::Path; use criterion::Criterion; -use esplugin::{GameId, Plugin}; +use esplugin::{GameId, ParseMode, Plugin}; // HearthFires.esm is a 3.8 MB file, so it's got plenty of content without being // large enough to slow down benchmarking much. @@ -19,7 +19,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); }); }); @@ -27,7 +27,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); }); }); @@ -35,14 +35,14 @@ fn criterion_benchmark(c: &mut Criterion) { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { - assert!(plugin.parse_file(false, true).is_ok()); + assert!(plugin.parse_file(ParseMode::All).is_ok()); }); }); c.bench_function("Plugin.overlaps_with()", |b| { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); b.iter(|| { assert!(plugin.overlaps_with(&plugin)); @@ -52,7 +52,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("Plugin.count_override_records()", |b| { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); - assert!(plugin.parse_file(false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); b.iter(|| { assert_eq!(plugin.count_override_records(), 1272); diff --git a/src/lib.rs b/src/lib.rs index cfdeba3..fcf63e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ use std::convert::TryInto; pub use crate::error::Error; pub use crate::game_id::GameId; -pub use crate::plugin::{Plugin, PluginEntry}; +pub use crate::plugin::{ParseMode, Plugin, PluginEntry}; pub use crate::record::{Record, RecordHeader}; pub use crate::subrecord::Subrecord; diff --git a/src/plugin.rs b/src/plugin.rs index aab8a22..8ccad31 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -33,6 +33,13 @@ use crate::group::Group; use crate::record::Record; use crate::record_id::{NamespacedId, RecordId}; +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ParseMode { + HeaderOnly, + RecordIds, + All, +} + #[derive(Copy, Clone, PartialEq, Eq)] enum FileExtension { ESM, @@ -117,23 +124,11 @@ impl Plugin { } } - pub fn parse( - &mut self, - input: &[u8], - load_header_only: bool, - store_all_records: bool, - ) -> Result<(), Error> { + pub fn parse(&mut self, input: &[u8], mode: ParseMode) -> Result<(), Error> { match self.filename() { None => Err(Error::NoFilename), Some(filename) => { - self.data = parse_plugin( - input, - self.game_id, - &filename, - load_header_only, - store_all_records, - )? - .1; + self.data = parse_plugin(input, self.game_id, &filename, mode)?.1; Ok(()) } @@ -143,9 +138,8 @@ impl Plugin { fn read( &mut self, mut reader: R, - load_header_only: bool, + mode: ParseMode, expected_header_type: &'static [u8], - store_all_records: bool, ) -> Result<(), Error> { match self.filename() { None => Err(Error::NoFilename), @@ -154,9 +148,8 @@ impl Plugin { &mut reader, self.game_id, &filename, - load_header_only, + mode, expected_header_type, - store_all_records, )?; Ok(()) @@ -164,35 +157,21 @@ impl Plugin { } } - pub fn parse_open_file( - &mut self, - file: File, - load_header_only: bool, - store_all_records: bool, - ) -> Result<(), Error> { + pub fn parse_open_file(&mut self, file: File, mode: ParseMode) -> Result<(), Error> { let mut reader = BufReader::new(&file); - if load_header_only { + if mode == ParseMode::HeaderOnly { let content = Record::read_and_validate(&mut reader, self.game_id, self.header_type())?; - self.parse(&content, load_header_only, store_all_records) + self.parse(&content, mode) } else { - self.read( - reader, - load_header_only, - self.header_type(), - store_all_records, - ) + self.read(reader, mode, self.header_type()) } } - pub fn parse_file( - &mut self, - load_header_only: bool, - store_all_records: bool, - ) -> Result<(), Error> { + pub fn parse_file(&mut self, mode: ParseMode) -> Result<(), Error> { let file = File::open(&self.path)?; - self.parse_open_file(file, load_header_only, store_all_records) + self.parse_open_file(file, mode) } pub fn game_id(&self) -> &GameId { @@ -261,17 +240,10 @@ impl Plugin { } } - pub fn is_valid( - game_id: GameId, - filepath: &Path, - load_header_only: bool, - store_all_records: bool, - ) -> bool { + pub fn is_valid(game_id: GameId, filepath: &Path, mode: ParseMode) -> bool { let mut plugin = Plugin::new(game_id, &filepath.to_path_buf()); - plugin - .parse_file(load_header_only, store_all_records) - .is_ok() + plugin.parse_file(mode).is_ok() } pub fn description(&self) -> Result, Error> { @@ -599,10 +571,7 @@ fn parse_record_ids<'a>( } } -fn parse_entries<'a>( - input: &'a [u8], - game_id: GameId, -) -> IResult<&'a [u8], Vec> { +fn parse_entries<'a>(input: &'a [u8], game_id: GameId) -> IResult<&'a [u8], Vec> { if game_id == GameId::Morrowind { parse_morrowind_records(input) } else { @@ -643,12 +612,11 @@ fn parse_plugin<'a>( input: &'a [u8], game_id: GameId, filename: &str, - load_header_only: bool, - store_all_records: bool, + mode: ParseMode, ) -> IResult<&'a [u8], PluginData> { let (input1, header_record) = Record::parse(input, game_id, false)?; - if load_header_only { + if mode == ParseMode::HeaderOnly { return Ok(( input1, PluginData { @@ -659,7 +627,7 @@ fn parse_plugin<'a>( )); } - let (input2, entries, record_ids) = if game_id == GameId::Morrowind && store_all_records { + let (input2, entries, record_ids) = if game_id == GameId::Morrowind && mode == ParseMode::All { let (input2, entries) = parse_entries(input1, game_id)?; let record_ids = parse_record_ids_from_entries(game_id, &entries); (input2, entries, record_ids) @@ -682,9 +650,8 @@ fn read_plugin( reader: &mut R, game_id: GameId, filename: &str, - load_header_only: bool, + mode: ParseMode, expected_header_type: &'static [u8], - store_all_records: bool, ) -> Result { let header_record = Record::read(reader, game_id, false)?; @@ -695,7 +662,7 @@ fn read_plugin( )); } - if load_header_only { + if mode == ParseMode::HeaderOnly { return Ok(PluginData { header_record, record_ids: RecordIds::None, @@ -703,7 +670,7 @@ fn read_plugin( }); } - let (entries, record_ids) = if game_id == GameId::Morrowind && store_all_records { + let (entries, record_ids) = if game_id == GameId::Morrowind && mode == ParseMode::All { let entries = read_entries(reader, game_id)?; let record_ids = parse_record_ids_from_entries(game_id, &entries); (entries, record_ids) @@ -736,7 +703,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), @@ -751,7 +718,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => { @@ -769,7 +736,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(RecordIds::None, plugin.data.record_ids); } @@ -791,7 +758,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_master_file()); } @@ -822,7 +789,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(!plugin.is_master_file()); } @@ -843,7 +810,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); let expected_description = format!("{:\0<218}{:\0<38}\n\0\0", "v5.0", "\r"); assert_eq!(expected_description, plugin.description().unwrap().unwrap()); @@ -856,7 +823,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(1.2, plugin.header_version().unwrap()); } @@ -869,7 +836,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(10, plugin.record_and_group_count().unwrap()); } @@ -881,7 +848,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(10, plugin.record_and_group_count().unwrap()); match plugin.data.record_ids { RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), @@ -896,7 +863,7 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(0, plugin.count_override_records()); } @@ -911,8 +878,8 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Different.esm"), ); - assert!(plugin1.parse_file(false, false).is_ok()); - assert!(plugin2.parse_file(false, false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert!(plugin1.overlaps_with(&plugin1)); assert!(!plugin1.overlaps_with(&plugin2)); @@ -1003,7 +970,7 @@ mod tests { GameId::Morrowind, Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1028,7 +995,7 @@ mod tests { Path::new("testing-plugins/Oblivion/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(0.8, plugin.header_version().unwrap()); } @@ -1039,7 +1006,7 @@ mod tests { GameId::Oblivion, Path::new("testing-plugins/Oblivion/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1054,7 +1021,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); match plugin.data.record_ids { RecordIds::FormIds(ids) => assert_eq!(10, ids.len()), @@ -1069,7 +1036,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(RecordIds::None, plugin.data.record_ids); } @@ -1088,7 +1055,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_master_file()); } @@ -1099,7 +1066,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(!plugin.is_master_file()); } @@ -1120,7 +1087,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!("v5.0", plugin.description().unwrap().unwrap()); let mut plugin = Plugin::new( @@ -1128,7 +1095,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!("€ƒŠ", plugin.description().unwrap().unwrap()); let mut plugin = Plugin::new( @@ -1139,7 +1106,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!("", plugin.description().unwrap().unwrap()); } @@ -1150,7 +1117,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(0.94, plugin.header_version().unwrap()); } @@ -1163,7 +1130,7 @@ mod tests { ); assert!(plugin.record_and_group_count().is_none()); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(15, plugin.record_and_group_count().unwrap()); } @@ -1174,7 +1141,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Different Master Dependent.esp"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(2, plugin.count_override_records()); } @@ -1189,8 +1156,8 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Different.esm"), ); - assert!(plugin1.parse_file(false, false).is_ok()); - assert!(plugin2.parse_file(false, false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert!(plugin1.overlaps_with(&plugin1)); assert!(!plugin1.overlaps_with(&plugin2)); @@ -1281,7 +1248,7 @@ mod tests { GameId::Skyrim, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1313,14 +1280,14 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(!plugin.is_master_file()); let mut plugin = Plugin::new( GameId::SkyrimSE, Path::new("testing-plugins/Skyrim/Data/Blank.esm.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_master_file()); } @@ -1353,7 +1320,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank.esl.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_light_plugin()); assert!(!plugin.is_master_file()); } @@ -1371,7 +1338,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank.esl.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_light_plugin()); assert!(plugin.is_master_file()); } @@ -1383,7 +1350,7 @@ mod tests { Path::new("testing-plugins/SkyrimSE/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(0.94, plugin.header_version().unwrap()); } @@ -1395,7 +1362,7 @@ mod tests { GameId::SkyrimSE, Path::new("testing-plugins/SkyrimSE/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1414,7 +1381,7 @@ mod tests { bytes[0x7A] = 0xFF; bytes[0x7B] = 0x07; - assert!(plugin.parse(&bytes, false, false).is_ok()); + assert!(plugin.parse(&bytes, ParseMode::RecordIds).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1433,7 +1400,7 @@ mod tests { bytes[0x386] = 0xFF; bytes[0x387] = 0x07; - assert!(plugin.parse(&bytes, false, false).is_ok()); + assert!(plugin.parse(&bytes, ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } @@ -1452,7 +1419,7 @@ mod tests { bytes[0x386] = 0x00; bytes[0x387] = 0x10; - assert!(plugin.parse(&bytes, false, false).is_ok()); + assert!(plugin.parse(&bytes, ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } @@ -1477,7 +1444,7 @@ mod tests { GameId::Fallout3, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1501,7 +1468,7 @@ mod tests { GameId::FalloutNV, Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin.is_valid_as_light_plugin()); } } @@ -1531,14 +1498,14 @@ mod tests { GameId::Fallout4, Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(!plugin.is_master_file()); let mut plugin = Plugin::new( GameId::Fallout4, Path::new("testing-plugins/Skyrim/Data/Blank.esm.esp"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert!(plugin.is_master_file()); } @@ -1565,7 +1532,7 @@ mod tests { GameId::Fallout4, Path::new("testing-plugins/SkyrimSE/Data/Blank - Master Dependent.esm"), ); - assert!(plugin.parse_file(false, false).is_ok()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_ok()); assert!(plugin.is_valid_as_light_plugin()); } @@ -1582,14 +1549,14 @@ mod tests { fn parse_file_should_error_if_plugin_does_not_exist() { let mut plugin = Plugin::new(GameId::Skyrim, Path::new("Blank.esm")); - assert!(plugin.parse_file(false, false).is_err()); + assert!(plugin.parse_file(ParseMode::RecordIds).is_err()); } #[test] fn parse_file_should_error_if_plugin_is_not_valid() { let mut plugin = Plugin::new(GameId::Skyrim, Path::new("README.md")); - let result = plugin.parse_file(false, false); + let result = plugin.parse_file(ParseMode::RecordIds); assert!(result.is_err()); assert_eq!( "failed to fill whole buffer", @@ -1606,7 +1573,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Invalid.esm"), ); - let result = plugin.parse_file(true, false); + let result = plugin.parse_file(ParseMode::HeaderOnly); assert!(result.is_err()); assert_eq!("An error was encountered while parsing the plugin content [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]: Expected record type [54, 45, 53, 34]", result.unwrap_err().to_string()); } @@ -1620,7 +1587,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Invalid.esm"), ); - let result = plugin.parse_file(false, false); + let result = plugin.parse_file(ParseMode::RecordIds); assert!(result.is_err()); assert_eq!("An error was encountered while parsing the plugin content [00, 00, 00, 00]: Expected record type [54, 45, 53, 34]", result.unwrap_err().to_string()); } @@ -1630,8 +1597,7 @@ mod tests { let is_valid = Plugin::is_valid( GameId::Skyrim, Path::new("testing-plugins/Skyrim/Data/Blank.esm"), - true, - false, + ParseMode::HeaderOnly, ); assert!(is_valid); @@ -1639,7 +1605,11 @@ mod tests { #[test] fn is_valid_should_return_false_for_an_invalid_plugin() { - let is_valid = Plugin::is_valid(GameId::Skyrim, Path::new("README.md"), true, false); + let is_valid = Plugin::is_valid( + GameId::Skyrim, + Path::new("README.md"), + ParseMode::HeaderOnly, + ); assert!(!is_valid); } @@ -1677,7 +1647,7 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esm"), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); assert_eq!(0, plugin.masters().unwrap().len()); } @@ -1691,7 +1661,7 @@ mod tests { ), ); - assert!(plugin.parse_file(true, false).is_ok()); + assert!(plugin.parse_file(ParseMode::HeaderOnly).is_ok()); let masters = plugin.masters().unwrap(); assert_eq!(1, masters.len()); @@ -1712,7 +1682,7 @@ mod tests { data[0x14] = 8; data[0x15] = 0; - assert!(plugin.parse(&data, true, false).is_ok()); + assert!(plugin.parse(&data, ParseMode::HeaderOnly).is_ok()); let result = plugin.description(); assert!(result.is_err()); @@ -1733,7 +1703,7 @@ mod tests { data[0x14] = 3; data[0x15] = 0; - assert!(plugin.parse(&data, true, false).is_ok()); + assert!(plugin.parse(&data, ParseMode::HeaderOnly).is_ok()); assert!(plugin.header_version().is_none()); } @@ -1749,7 +1719,7 @@ mod tests { data[0x04] = 0x30; data[0x14] = 0x28; - assert!(plugin.parse(&data, true, false).is_ok()); + assert!(plugin.parse(&data, ParseMode::HeaderOnly).is_ok()); assert!(plugin.record_and_group_count().is_none()); } From f1477bf5dddf17e0afeccef651a6d8f2e186facb Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Thu, 23 Apr 2020 11:03:31 -0400 Subject: [PATCH 5/9] Updated name of full parsing benchmark to better reflect mode --- benches/parsing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/parsing.rs b/benches/parsing.rs index 9524ad8..70d9683 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -23,7 +23,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); }); - c.bench_function("Plugin.parse_file() full", |b| { + c.bench_function("Plugin.parse_file() record ids", |b| { let mut plugin = Plugin::new(GAME_ID, Path::new(PLUGIN_TO_PARSE)); b.iter(|| { From ec1b8af37c8d6b38d7834179ec23917b18230ad4 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Thu, 23 Apr 2020 11:57:19 -0400 Subject: [PATCH 6/9] Updated arguments to new tests --- src/plugin.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index 8ccad31..7e3727e 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -896,8 +896,8 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(4, plugin1.overlap_size(&[&plugin2, &plugin2])); } @@ -917,9 +917,9 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); - assert!(plugin3.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin3.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(4, plugin1.overlap_size(&[&plugin2, &plugin3])); } @@ -937,11 +937,11 @@ mod tests { assert_eq!(0, plugin1.overlap_size(&[&plugin2])); - assert!(plugin1.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(0, plugin1.overlap_size(&[&plugin2])); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert_ne!(0, plugin1.overlap_size(&[&plugin2])); } @@ -957,8 +957,8 @@ mod tests { Path::new("testing-plugins/Morrowind/Data Files/Blank.esp"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin1.overlaps_with(&plugin2)); assert_eq!(0, plugin1.overlap_size(&[&plugin2])); @@ -1174,8 +1174,8 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esm"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(4, plugin1.overlap_size(&[&plugin2, &plugin2])); } @@ -1195,9 +1195,9 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank - Master Dependent.esp"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); - assert!(plugin3.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin3.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(2, plugin1.overlap_size(&[&plugin2, &plugin3])); } @@ -1215,11 +1215,11 @@ mod tests { assert_eq!(0, plugin1.overlap_size(&[&plugin2])); - assert!(plugin1.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); assert_eq!(0, plugin1.overlap_size(&[&plugin2])); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert_ne!(0, plugin1.overlap_size(&[&plugin2])); } @@ -1235,8 +1235,8 @@ mod tests { Path::new("testing-plugins/Skyrim/Data/Blank.esp"), ); - assert!(plugin1.parse_file(false).is_ok()); - assert!(plugin2.parse_file(false).is_ok()); + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::RecordIds).is_ok()); assert!(!plugin1.overlaps_with(&plugin2)); assert_eq!(0, plugin1.overlap_size(&[&plugin2])); From 459318dac7bcca1f29caaf4daf9407e91af5ed6b Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Thu, 23 Apr 2020 13:41:39 -0400 Subject: [PATCH 7/9] Updated ffi interface to use ParseModes --- ffi/src/plugin.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ffi/src/plugin.rs b/ffi/src/plugin.rs index 69cde94..e2e02e6 100644 --- a/ffi/src/plugin.rs +++ b/ffi/src/plugin.rs @@ -4,7 +4,7 @@ use std::{f32, mem, panic, ptr}; use libc::{c_char, c_float, size_t}; use constants::*; -use esplugin::Plugin; +use esplugin::{ParseMode, Plugin}; use helpers::*; #[no_mangle] @@ -45,8 +45,13 @@ pub unsafe extern "C" fn esp_plugin_parse(plugin_ptr: *mut Plugin, load_header_o if plugin_ptr.is_null() { ESP_ERROR_NULL_POINTER } else { + let mode = if load_header_only { + ParseMode::HeaderOnly + } else { + ParseMode::RecordIds + }; let plugin = &mut *plugin_ptr; - match plugin.parse_file(load_header_only) { + match plugin.parse_file(mode) { Ok(_) => ESP_OK, Err(_) => ESP_ERROR_PARSE_ERROR, } @@ -177,7 +182,12 @@ pub unsafe extern "C" fn esp_plugin_is_valid( Err(x) => return x, }; - *is_valid = Plugin::is_valid(mapped_game_id, rust_path, load_header_only); + let mode = if load_header_only { + ParseMode::HeaderOnly + } else { + ParseMode::RecordIds + }; + *is_valid = Plugin::is_valid(mapped_game_id, rust_path, mode); ESP_OK } From 225777556cc48706f0b2f243370af6b2688c26f5 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Thu, 23 Apr 2020 14:54:41 -0400 Subject: [PATCH 8/9] Sort record ids in parse_record_ids_from_entries --- src/plugin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin.rs b/src/plugin.rs index 7e3727e..37917c4 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -546,6 +546,7 @@ fn parse_record_ids_from_entries(game_id: GameId, entries: &[PluginEntry]) -> Re _ => unimplemented!(), } } + record_ids.sort(); record_ids.into() } From d57652b3e7181742cc110b0fe9a3ec786d3a367e Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 29 Apr 2020 11:38:35 -0400 Subject: [PATCH 9/9] Added tests for parsing plugin contents --- src/plugin.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/record.rs | 22 +++++++++ 2 files changed, 142 insertions(+) diff --git a/src/plugin.rs b/src/plugin.rs index 37917c4..c93e3a6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -712,6 +712,21 @@ mod tests { } } + #[test] + fn parse_file_should_succeed_mode_all() { + let mut plugin = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + + assert!(plugin.parse_file(ParseMode::All).is_ok()); + + match plugin.data.record_ids { + RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), + _ => panic!("Expected namespaced record IDs"), + } + } + #[test] fn plugin_parse_should_read_a_unique_id_for_each_record() { let mut plugin = Plugin::new( @@ -730,6 +745,45 @@ mod tests { } } + #[test] + fn plugin_parse_should_read_a_unique_id_for_each_record_mode_all() { + let mut plugin = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + + assert!(plugin.parse_file(ParseMode::All).is_ok()); + + match plugin.data.record_ids { + RecordIds::NamespacedIds(ids) => { + let set: HashSet = HashSet::from_iter(ids.iter().cloned()); + assert_eq!(set.len(), ids.len()); + } + _ => panic!("Expected namespaced record IDs"), + } + } + + #[test] + fn plugin_parse_should_read_all_records_mode_all() { + let mut plugin = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + + assert!(plugin.parse_file(ParseMode::All).is_ok()); + assert_eq!(10, plugin.get_entries().len()); + + for entry in plugin.get_entries() { + match entry { + PluginEntry::Record(record) => { + assert_eq!(record.header_type(), *b"GMST"); + assert_eq!(record.subrecords().len(), 2); + } + PluginEntry::Group(_) => panic!("Unexpected group in morrowind record"), + } + } + } + #[test] fn parse_file_header_only_should_not_store_record_ids() { let mut plugin = Plugin::new( @@ -857,6 +911,23 @@ mod tests { } } + #[test] + fn record_and_group_count_should_match_record_ids_and_entries_length_mode_all() { + let mut plugin = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + + assert!(plugin.record_and_group_count().is_none()); + assert!(plugin.parse_file(ParseMode::All).is_ok()); + assert_eq!(10, plugin.record_and_group_count().unwrap()); + assert_eq!(10, plugin.get_entries().len()); + match plugin.data.record_ids { + RecordIds::NamespacedIds(ids) => assert_eq!(10, ids.len()), + _ => panic!("Expected namespaced record IDs"), + } + } + #[test] fn count_override_records_should_return_0_even_when_override_records_are_present() { let mut plugin = Plugin::new( @@ -868,6 +939,18 @@ mod tests { assert_eq!(0, plugin.count_override_records()); } + #[test] + fn count_override_records_should_return_0_even_when_override_records_are_present_mode_all() + { + let mut plugin = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank - Master Dependent.esm"), + ); + + assert!(plugin.parse_file(ParseMode::All).is_ok()); + assert_eq!(0, plugin.count_override_records()); + } + #[test] fn overlaps_with_should_detect_when_two_plugins_have_a_record_with_the_same_id() { let mut plugin1 = Plugin::new( @@ -886,6 +969,43 @@ mod tests { assert!(!plugin1.overlaps_with(&plugin2)); } + #[test] + fn overlaps_with_should_detect_when_two_plugins_have_a_record_with_the_same_id_mode_all() { + let mut plugin1 = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + let mut plugin2 = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank - Different.esm"), + ); + + assert!(plugin1.parse_file(ParseMode::All).is_ok()); + assert!(plugin2.parse_file(ParseMode::All).is_ok()); + + assert!(plugin1.overlaps_with(&plugin1)); + assert!(!plugin1.overlaps_with(&plugin2)); + } + + #[test] + fn overlaps_with_should_detect_when_two_plugins_have_a_record_with_the_same_id_mode_mixed() + { + let mut plugin1 = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank.esm"), + ); + let mut plugin2 = Plugin::new( + GameId::Morrowind, + Path::new("testing-plugins/Morrowind/Data Files/Blank - Different.esm"), + ); + + assert!(plugin1.parse_file(ParseMode::RecordIds).is_ok()); + assert!(plugin2.parse_file(ParseMode::All).is_ok()); + + assert!(plugin1.overlaps_with(&plugin1)); + assert!(!plugin1.overlaps_with(&plugin2)); + } + #[test] fn overlap_size_should_only_count_each_record_once() { let mut plugin1 = Plugin::new( diff --git a/src/record.rs b/src/record.rs index 52d3ce7..fca5f9b 100644 --- a/src/record.rs +++ b/src/record.rs @@ -483,6 +483,28 @@ mod tests { assert!(record.header.form_id.is_none()); } + #[test] + fn parse_record_id_from_self_should_return_none_for_tes3_header() { + let data = + &include_bytes!("../testing-plugins/Morrowind/Data Files/Blank.esm")[..0x144]; + + let record = Record::parse(data, GameId::Morrowind, false).unwrap().1; + let form_id = record.parse_record_id_from_self(GameId::Morrowind); + + assert!(form_id.is_none()); + } + + #[test] + fn parse_record_id_from_self_should_return_some_namespaced_id_for_gmst() { + let data = + &include_bytes!("../testing-plugins/Morrowind/Data Files/Blank.esm")[0x144..0x16F]; + + let record = Record::parse(data, GameId::Morrowind, false).unwrap().1; + let form_id = record.parse_record_id_from_self(GameId::Morrowind); + + assert!(form_id.unwrap().namespaced_id().is_some()); + } + #[test] fn parse_record_id_should_return_none_for_tes3_header() { let data =