Skip to content

Commit

Permalink
Derive plugin type once
Browse files Browse the repository at this point in the history
Instead of recalculating it every time it's checked. The calculation is fast, but callers could otherwise end up doing it many times.
  • Loading branch information
Ortham committed Oct 7, 2024
1 parent ff36ada commit 3813990
Showing 1 changed file with 146 additions and 99 deletions.
245 changes: 146 additions & 99 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,155 @@ struct PluginData {
record_ids: RecordIds,
}

#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)]
enum PluginScale {
#[default]
Full,
Medium,
Small,
}

impl PluginScale {
fn new(game_id: GameId, path: &Path, header_record: &Record) -> Self {
// If the medium flag is set in a light plugin then the medium flag is ignored.
if is_light_plugin(game_id, path, header_record) {
PluginScale::Small
} else if is_medium_flag_set(game_id, header_record) {
PluginScale::Medium
} else {
PluginScale::Full
}
}
}

pub fn is_light_plugin(game_id: GameId, path: &Path, header_record: &Record) -> bool {
// If the update flag is set, it prevents the .esl extension from
// causing the light flag to be forcibly set on load.
if game_id == GameId::Starfield && is_update_flag_set(game_id, header_record) {
is_light_flag_set(game_id, header_record)
} else if game_id.supports_light_plugins() {
is_light_flag_set(game_id, header_record) || file_extension(path) == FileExtension::Esl
} else {
false
}
}

#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)]
struct DerivedPluginData {
scale: PluginScale,
is_master: bool,
is_update: bool,
is_blueprint: bool
}

impl DerivedPluginData {
fn new(game_id: GameId, path: &Path, header_record: &Record) -> Self {
let scale = PluginScale::new(game_id, path, header_record);
let is_master = is_master_plugin(game_id, path, header_record);
let is_update = is_update_plugin(game_id, header_record);
let is_blueprint = is_blueprint_plugin(game_id, header_record);

DerivedPluginData {
scale,
is_master,
is_update,
is_blueprint
}
}
}

fn is_master_plugin(game_id: GameId, path: &Path, header_record: &Record) -> bool {
match game_id {
GameId::Fallout4 | GameId::SkyrimSE | GameId::Starfield => {
// The .esl extension implies the master flag, but the light and
// medium flags do not.
is_master_flag_set(game_id, header_record)
|| matches!(
file_extension(path),
FileExtension::Esm | FileExtension::Esl
)
}
_ => is_master_flag_set(game_id, header_record),
}
}

fn file_extension(path: &Path) -> FileExtension {
if let Some(p) = path.extension() {
match FileExtension::from(p) {
FileExtension::Ghost => path
.file_stem()
.map(Path::new)
.and_then(Path::extension)
.map(FileExtension::from)
.unwrap_or(FileExtension::Unrecognised),
e => e,
}
} else {
FileExtension::Unrecognised
}
}

pub fn is_update_plugin(game_id: GameId, header_record: &Record) -> bool {
// The update flag is unset by the game if the plugin has no masters or
// if the plugin's light or medium flags are set.
is_update_flag_set(game_id, header_record)
&& !is_light_flag_set(game_id, header_record)
&& !is_medium_flag_set(game_id, header_record)
&& masters(header_record).map(|m| !m.is_empty()).unwrap_or(false)
}

pub fn is_blueprint_plugin(game_id: GameId, header_record: &Record) -> bool {
match game_id {
GameId::Starfield => header_record.header().flags() & 0x800 != 0,
_ => false,
}
}

fn is_master_flag_set(game_id: GameId, header_record: &Record) -> bool {
match game_id {
GameId::Morrowind => header_record
.subrecords()
.iter()
.find(|s| s.subrecord_type() == b"HEDR")
.and_then(|s| s.data().get(4))
.map(|b| b & 0x1 != 0)
.unwrap_or(false),
_ => header_record.header().flags() & 0x1 != 0,
}
}

fn is_light_flag_set(game_id: GameId, header_record: &Record) -> bool {
let flag = match game_id {
GameId::Starfield => 0x100,
GameId::SkyrimSE | GameId::Fallout4 => 0x200,
_ => return false,
};

header_record.header().flags() & flag != 0
}

fn is_medium_flag_set(game_id: GameId, header_record: &Record) -> bool {
let flag = match game_id {
GameId::Starfield => 0x400,
_ => return false,
};

header_record.header().flags() & flag != 0
}

fn is_update_flag_set(game_id: GameId, header_record: &Record) -> bool {
match game_id {
GameId::Starfield => header_record.header().flags() & 0x200 != 0,
_ => false,
}
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct Plugin {
game_id: GameId,
path: PathBuf,
data: PluginData,
derived: DerivedPluginData,
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
Expand All @@ -116,6 +253,7 @@ impl Plugin {
game_id,
path: filepath.to_path_buf(),
data: PluginData::default(),
derived: DerivedPluginData::new(game_id, filepath, &Record::default()),
}
}

Expand All @@ -127,6 +265,7 @@ impl Plugin {
let mut reader = BufReader::new(reader);

self.data = read_plugin(&mut reader, self.game_id, options, self.header_type())?;
self.derived = DerivedPluginData::new(self.game_id, &self.path, &self.data.header_record);

if self.game_id != GameId::Morrowind && self.game_id != GameId::Starfield {
self.resolve_record_ids(&[])?;
Expand Down Expand Up @@ -200,79 +339,28 @@ impl Plugin {
masters(&self.data.header_record)
}

fn file_extension(&self) -> FileExtension {
if let Some(p) = self.path.extension() {
match FileExtension::from(p) {
FileExtension::Ghost => self
.path
.file_stem()
.map(Path::new)
.and_then(Path::extension)
.map(FileExtension::from)
.unwrap_or(FileExtension::Unrecognised),
e => e,
}
} else {
FileExtension::Unrecognised
}
}

pub fn is_master_file(&self) -> bool {
match self.game_id {
GameId::Fallout4 | GameId::SkyrimSE | GameId::Starfield => {
// The .esl extension implies the master flag, but the light and
// medium flags do not.
self.is_master_flag_set()
|| matches!(
self.file_extension(),
FileExtension::Esm | FileExtension::Esl
)
}
_ => self.is_master_flag_set(),
}
self.derived.is_master
}

fn scale(&self) -> PluginScale {
if self.is_light_plugin() {
PluginScale::Small
} else if self.is_medium_plugin() {
PluginScale::Medium
} else {
PluginScale::Full
}
self.derived.scale
}

pub fn is_light_plugin(&self) -> bool {
// If the update flag is set, it prevents the .esl extension from
// causing the light flag to be forcibly set on load.
if self.game_id == GameId::Starfield && self.is_update_flag_set() {
self.is_light_flag_set()
} else if self.game_id.supports_light_plugins() {
self.is_light_flag_set() || self.file_extension() == FileExtension::Esl
} else {
false
}
self.derived.scale == PluginScale::Small
}

pub fn is_medium_plugin(&self) -> bool {
// If the medium flag is set in a light plugin then the medium flag is ignored.
self.is_medium_flag_set() && !self.is_light_plugin()
self.derived.scale == PluginScale::Medium
}

pub fn is_update_plugin(&self) -> bool {
// The update flag is unset by the game if the plugin has no masters or
// if the plugin's light or medium flags are set.
self.is_update_flag_set()
&& !self.is_light_flag_set()
&& !self.is_medium_flag_set()
&& self.masters().map(|m| !m.is_empty()).unwrap_or(false)
self.derived.is_update
}

pub fn is_blueprint_plugin(&self) -> bool {
match self.game_id {
GameId::Starfield => self.data.header_record.header().flags() & 0x800 != 0,
_ => false,
}
self.derived.is_blueprint
}

pub fn is_valid(game_id: GameId, filepath: &Path, options: ParseOptions) -> bool {
Expand Down Expand Up @@ -471,47 +559,6 @@ impl Plugin {
}
}

fn is_master_flag_set(&self) -> bool {
match self.game_id {
GameId::Morrowind => self
.data
.header_record
.subrecords()
.iter()
.find(|s| s.subrecord_type() == b"HEDR")
.and_then(|s| s.data().get(4))
.map(|b| b & 0x1 != 0)
.unwrap_or(false),
_ => self.data.header_record.header().flags() & 0x1 != 0,
}
}

fn is_light_flag_set(&self) -> bool {
let flag = match self.game_id {
GameId::Starfield => 0x100,
GameId::SkyrimSE | GameId::Fallout4 => 0x200,
_ => return false,
};

self.data.header_record.header().flags() & flag != 0
}

fn is_medium_flag_set(&self) -> bool {
let flag = match self.game_id {
GameId::Starfield => 0x400,
_ => return false,
};

self.data.header_record.header().flags() & flag != 0
}

fn is_update_flag_set(&self) -> bool {
match self.game_id {
GameId::Starfield => self.data.header_record.header().flags() & 0x200 != 0,
_ => false,
}
}

fn valid_light_form_id_range(&self) -> RangeInclusive<u32> {
match self.game_id {
GameId::SkyrimSE => match self.header_version() {
Expand Down

0 comments on commit 3813990

Please sign in to comment.