Store worlds in a HashSet to deduplicate

Came across a valid plugin file that has multiple WRLD records for the same world. I didn't know that was possible, so this change prevents that from outputting duplicate worlds in the worlds array.
This commit is contained in:
Tyler Hallada 2022-09-30 12:26:03 -04:00
parent 7932d98e72
commit 2adcce94ba

View File

@ -1,5 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet;
use std::io::Read; use std::io::Read;
use std::iter::FromIterator;
use std::{convert::TryInto, str}; use std::{convert::TryInto, str};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
@ -78,7 +80,7 @@ struct DecompressedCell {
} }
/// Parsed [WRLD records](https://en.uesp.net/wiki/Skyrim_Mod:Mod_File_Format/WRLD) /// Parsed [WRLD records](https://en.uesp.net/wiki/Skyrim_Mod:Mod_File_Format/WRLD)
#[derive(Debug, Clone, PartialEq, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct World { pub struct World {
/// Note that this `form_id` is relative to the plugin file, not what it would be in-game. /// Note that this `form_id` is relative to the plugin file, not what it would be in-game.
/// The first byte of the `form_id` can be interpreted as an index into the `masters` array of the [`PluginHeader`]. /// The first byte of the `form_id` can be interpreted as an index into the `masters` array of the [`PluginHeader`].
@ -196,7 +198,7 @@ fn decompress_cells(unparsed_cells: Vec<UnparsedCell>) -> Result<Vec<Decompresse
/// Parses the plugin header and finds and extracts the headers and unparsed (and possibly compressed) data sections of every CELL record in the file. /// Parses the plugin header and finds and extracts the headers and unparsed (and possibly compressed) data sections of every CELL record in the file.
fn parse_header_and_cell_bytes( fn parse_header_and_cell_bytes(
input: &[u8], input: &[u8],
) -> IResult<&[u8], (PluginHeader, Vec<World>, Vec<UnparsedCell>)> { ) -> IResult<&[u8], (PluginHeader, HashSet<World>, Vec<UnparsedCell>)> {
let (input, header) = parse_plugin_header(input)?; let (input, header) = parse_plugin_header(input)?;
let (input, (worlds, unparsed_cells)) = parse_group_data(input, input.len() as u32, 0, None)?; let (input, (worlds, unparsed_cells)) = parse_group_data(input, input.len() as u32, 0, None)?;
Ok((input, (header, worlds, unparsed_cells))) Ok((input, (header, worlds, unparsed_cells)))
@ -235,7 +237,7 @@ pub fn parse_plugin(input: &[u8]) -> Result<Plugin> {
Ok(Plugin { Ok(Plugin {
header, header,
worlds, worlds: Vec::from_iter(worlds),
cells, cells,
}) })
} }
@ -245,9 +247,9 @@ fn parse_group_data<'a>(
remaining_bytes: u32, remaining_bytes: u32,
depth: usize, depth: usize,
world_form_id: Option<u32>, world_form_id: Option<u32>,
) -> IResult<&'a [u8], (Vec<World>, Vec<UnparsedCell>)> { ) -> IResult<&'a [u8], (HashSet<World>, Vec<UnparsedCell>)> {
let mut input = input; let mut input = input;
let mut worlds = vec![]; let mut worlds = HashSet::new();
let mut cells = vec![]; let mut cells = vec![];
let mut consumed_bytes = 0; let mut consumed_bytes = 0;
let mut world_form_id = world_form_id; let mut world_form_id = world_form_id;
@ -275,13 +277,13 @@ fn parse_group_data<'a>(
consumed_bytes += group_header.size; consumed_bytes += group_header.size;
continue; continue;
} }
let (remaining, (mut inner_worlds, mut inner_cells)) = parse_group_data( let (remaining, (inner_worlds, mut inner_cells)) = parse_group_data(
remaining, remaining,
group_header.size - RECORD_HEADER_SIZE, group_header.size - RECORD_HEADER_SIZE,
depth + 1, depth + 1,
world_form_id, world_form_id,
)?; )?;
worlds.append(&mut inner_worlds); worlds.extend(inner_worlds.into_iter());
cells.append(&mut inner_cells); cells.append(&mut inner_cells);
input = remaining; input = remaining;
consumed_bytes += group_header.size; consumed_bytes += group_header.size;
@ -302,7 +304,7 @@ fn parse_group_data<'a>(
"WRLD" => { "WRLD" => {
world_form_id = Some(record_header.id); world_form_id = Some(record_header.id);
let (remaining, editor_id) = parse_world_fields(remaining, &record_header)?; let (remaining, editor_id) = parse_world_fields(remaining, &record_header)?;
worlds.push(World { worlds.insert(World {
form_id: record_header.id, form_id: record_header.id,
editor_id, editor_id,
}); });