Skip to content

Commit

Permalink
[WIP] Add support for plugins loaded from ini files
Browse files Browse the repository at this point in the history
The sTestFile1 through sTestFile10 ini file properties can be used to
activate plugins in most supported games.

libloadorder treats plugins activated this way as implicitly active, and
because they are not hardcoded to load early, now distinguishes between
plugins that are implicitly active and those that load early.

As part of this, the existing Oblivion.ini parsing that checks
bUseMyGamesDirectory now correctly requires that the property is in the
ini file's [General] section.

This also adds a lo_get_early_loading_plugins() FFI function, as the set
of implicitly active plugins may now be a superset of the set of early
loaders that includes plugins with non-hardcoded positions.
  • Loading branch information
Ortham committed Sep 24, 2023
1 parent ddf6ec2 commit bb30d97
Show file tree
Hide file tree
Showing 10 changed files with 546 additions and 388 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ esplugin = "4.0.0"
regex = "1.0.0"
unicase = "2.0.0"
rayon = "1.0.0"
rust-ini = { version = "0.19.0", features = ["case-insensitive"] }

[target.'cfg(windows)'.dependencies]
dirs = "5.0"
Expand Down
55 changes: 52 additions & 3 deletions ffi/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,60 @@ pub unsafe extern "C" fn lo_fix_plugin_lists(handle: lo_game_handle) -> c_uint {
/// Get the list of implicitly active plugins for the given handle's game.
///
/// The list may be empty if the game has no implicitly active plugins. The list
/// may include plugins that are not installed. Plugins are not necessarily
/// listed in their load order.
///
/// If the list is empty, the `plugins` pointer will be null and `num_plugins`
/// will be `0`.
///
/// Returns `LIBLO_OK` if successful, otherwise a `LIBLO_ERROR_*` code is
/// returned.
#[no_mangle]
pub unsafe extern "C" fn lo_get_implicitly_active_plugins(
handle: lo_game_handle,
plugins: *mut *mut *mut c_char,
num_plugins: *mut size_t,
) -> c_uint {
catch_unwind(|| {
if handle.is_null() || plugins.is_null() || num_plugins.is_null() {
return error(LIBLO_ERROR_INVALID_ARGS, "Null pointer passed");
}

let handle = match (*handle).read() {
Err(e) => return error(LIBLO_ERROR_POISONED_THREAD_LOCK, &e.to_string()),
Ok(h) => h,
};

*plugins = ptr::null_mut();
*num_plugins = 0;

let plugin_names = handle.game_settings().implicitly_active_plugins();

if plugin_names.is_empty() {
return LIBLO_OK;
}

match to_c_string_array(plugin_names) {
Ok((pointer, size)) => {
*plugins = pointer;
*num_plugins = size;
}
Err(x) => return error(x, "A filename contained a null byte"),
}

LIBLO_OK
})
.unwrap_or(LIBLO_ERROR_PANICKED)
}

/// Get the list of implicitly active plugins for the given handle's game.
///
/// The list may be empty if the game has no early loading plugins. The list
/// may include plugins that are not installed. Plugins are listed in their
/// hardcoded load order.
///
/// Note that for the original Skyrim, `Update.esm` is hardcoded to always load,
/// but not in a specific location, unlike all other implicitly active plugins
/// but not in a specific location, unlike all other early loading plugins
/// for all games, which must load in the given order, before any other plugins.
///
/// The order of Creation Club plugins as listed in `Fallout4.ccc` or
Expand All @@ -270,7 +319,7 @@ pub unsafe extern "C" fn lo_fix_plugin_lists(handle: lo_game_handle) -> c_uint {
/// Returns `LIBLO_OK` if successful, otherwise a `LIBLO_ERROR_*` code is
/// returned.
#[no_mangle]
pub unsafe extern "C" fn lo_get_implicitly_active_plugins(
pub unsafe extern "C" fn lo_get_early_loading_plugins(
handle: lo_game_handle,
plugins: *mut *mut *mut c_char,
num_plugins: *mut size_t,
Expand All @@ -288,7 +337,7 @@ pub unsafe extern "C" fn lo_get_implicitly_active_plugins(
*plugins = ptr::null_mut();
*num_plugins = 0;

let plugin_names = handle.game_settings().implicitly_active_plugins();
let plugin_names = handle.game_settings().early_loading_plugins();

if plugin_names.is_empty() {
return LIBLO_OK;
Expand Down
1 change: 1 addition & 0 deletions ffi/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn map_error(err: &Error) -> c_uint {
NoLocalAppData => LIBLO_ERROR_INVALID_ARGS,
UnrepresentedHoist(_, _) => LIBLO_ERROR_INVALID_ARGS,
InstalledPlugin(_) => LIBLO_ERROR_INVALID_ARGS,
IniParsingError { .. } => LIBLO_ERROR_FILE_PARSE_FAIL,
}
}

Expand Down
17 changes: 17 additions & 0 deletions ffi/tests/ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ void test_lo_get_implicitly_active_plugins() {
lo_destroy_handle(handle);
}

void test_lo_get_early_loading_plugins() {
printf("testing lo_get_load_order()...\n");
lo_game_handle handle = create_fo4_handle();

char ** plugins = NULL;
size_t num_plugins = 0;
unsigned int return_code = lo_get_early_loading_plugins(handle, &plugins, &num_plugins);

assert(return_code == 0);
assert(num_plugins == 8);
assert(strcmp(plugins[0], "Fallout4.esm") == 0);
assert(strcmp(plugins[4], "DLCworkshop02.esm") == 0);
lo_free_string_array(plugins, num_plugins);
lo_destroy_handle(handle);
}

void test_lo_get_active_plugins_file_path() {
printf("testing lo_get_active_plugins_file_path()...\n");
lo_game_handle handle = create_handle();
Expand Down Expand Up @@ -315,6 +331,7 @@ int main(void) {
test_lo_is_ambiguous();
test_lo_fix_plugin_lists();
test_lo_get_implicitly_active_plugins();
test_lo_get_early_loading_plugins();
test_lo_get_active_plugins_file_path();
test_lo_set_additional_plugins_directories();

Expand Down
17 changes: 17 additions & 0 deletions ffi/tests/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,22 @@ void test_lo_get_implicitly_active_plugins() {
lo_destroy_handle(handle);
}

void test_lo_get_early_loading_plugins() {
printf("testing lo_get_load_order()...\n");
lo_game_handle handle = create_fo4_handle();

char ** plugins = nullptr;
size_t num_plugins = 0;
unsigned int return_code = lo_get_early_loading_plugins(handle, &plugins, &num_plugins);

assert(return_code == 0);
assert(num_plugins == 8);
assert(strcmp(plugins[0], "Fallout4.esm") == 0);
assert(strcmp(plugins[4], "DLCworkshop02.esm") == 0);
lo_free_string_array(plugins, num_plugins);
lo_destroy_handle(handle);
}

void test_lo_get_active_plugins_file_path() {
printf("testing lo_get_active_plugins_file_path()...\n");
lo_game_handle handle = create_handle();
Expand Down Expand Up @@ -340,6 +356,7 @@ int main(void) {
test_lo_is_ambiguous();
test_lo_fix_plugin_lists();
test_lo_get_implicitly_active_plugins();
test_lo_get_early_loading_plugins();
test_lo_get_active_plugins_file_path();
test_lo_set_additional_plugins_directories();

Expand Down
33 changes: 33 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ pub enum Error {
/// First string is the plugin, second is the master.
UnrepresentedHoist(String, String),
InstalledPlugin(String),
IniParsingError {
line: usize,
column: usize,
message: String,
},
}

impl From<io::Error> for Error {
Expand Down Expand Up @@ -129,6 +134,25 @@ impl From<esplugin::Error> for Error {
}
}

impl From<ini::Error> for Error {
fn from(error: ini::Error) -> Self {
match error {
ini::Error::Io(x) => Error::IoError(x),
ini::Error::Parse(x) => Error::from(x),
}
}
}

impl From<ini::ParseError> for Error {
fn from(error: ini::ParseError) -> Self {
Error::IniParsingError {
line: error.line,
column: error.col,
message: error.msg,
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Expand Down Expand Up @@ -178,6 +202,15 @@ impl fmt::Display for Error {
"The plugin \"{}\" is installed, so cannot be removed from the load order",
plugin
),
Error::IniParsingError {
line,
column,
ref message,
} => write!(
f,
"Failed to parse ini file, error at line {}, column {}: {}",
line, column, message
),
}
}
}
Expand Down
Loading

0 comments on commit bb30d97

Please sign in to comment.