Skip to content

Commit

Permalink
Add lo_get_additional_plugins_directories()
Browse files Browse the repository at this point in the history
  • Loading branch information
Ortham committed Jan 31, 2025
1 parent e47af65 commit f4fd07c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 29 deletions.
4 changes: 3 additions & 1 deletion ffi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
cmake_minimum_required(VERSION 2.8)
project(libloadorder-ffi-tests CXX C)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories("${CMAKE_SOURCE_DIR}/include")

if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set (SYSTEM_LIBS pthread dl m)
endif ()

Expand Down
94 changes: 70 additions & 24 deletions ffi/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,15 +261,12 @@ 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.
/// 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`.
/// 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.
/// 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,
Expand Down Expand Up @@ -310,22 +307,19 @@ pub unsafe extern "C" fn lo_get_implicitly_active_plugins(

/// 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.
/// 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 early loading plugins
/// for all games, which must load in the given order, before any other plugins.
/// Note that for the original Skyrim, `Update.esm` is hardcoded to always load, 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
/// `Skyrim.ccc` is as their hardcoded load order for libloadorder's purposes.
/// The order of Creation Club plugins as listed in `Fallout4.ccc` or `Skyrim.ccc` is as their
/// hardcoded load order for libloadorder's purposes.
///
/// If the list is empty, the `plugins` pointer will be null and `num_plugins`
/// will be `0`.
/// 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.
/// Returns `LIBLO_OK` if successful, otherwise a `LIBLO_ERROR_*` code is returned.
#[no_mangle]
pub unsafe extern "C" fn lo_get_early_loading_plugins(
handle: lo_game_handle,
Expand Down Expand Up @@ -366,9 +360,9 @@ pub unsafe extern "C" fn lo_get_early_loading_plugins(

/// Get the active plugins file path for the given game handle.
///
/// The active plugins file path is often within the game's local path, but its
/// name and location varies by game and game configuration, so this function
/// exposes the path that libloadorder chooses to use.
/// The active plugins file path is often within the game's local path, but its name and location
/// varies by game and game configuration, so this function exposes the path that libloadorder
/// chooses to use.
///
/// Returns `LIBLO_OK` if successful, otherwise a `LIBLO_ERROR_*` code is
/// returned.
Expand Down Expand Up @@ -407,11 +401,63 @@ pub unsafe extern "C" fn lo_get_active_plugins_file_path(
.unwrap_or(LIBLO_ERROR_PANICKED)
}

/// Gets the additional plugins directories that are used when looking up plugin filenames.
///
/// Some games (Fallout 4, Starfield and OpenMW) support loading plugins from outside of the game's
/// main plugins directory, and each game handle is initialised with those additional directories
/// set.
///
/// If the list is empty, the `paths` pointer will be null and `num_paths` will be `0`.
///
/// Returns `LIBLO_OK` if successful, otherwise a `LIBLO_ERROR_*` code is returned.
#[no_mangle]
pub unsafe extern "C" fn lo_get_additional_plugins_directories(
handle: lo_game_handle,
paths: *mut *mut *mut c_char,
num_paths: *mut size_t,
) -> c_uint {
catch_unwind(|| {
if handle.is_null() || paths.is_null() || num_paths.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,
};

*paths = ptr::null_mut();
*num_paths = 0;

let path_strings: Vec<_> = handle
.game_settings()
.additional_plugins_directories()
.iter()
.filter_map(|p| p.to_str())
.collect();

if path_strings.is_empty() {
return LIBLO_OK;
}

match to_c_string_array(&path_strings) {
Ok((pointer, size)) => {
*paths = pointer;
*num_paths = size;
}
Err(x) => return error(x, "A path contained a null byte"),
}

LIBLO_OK
})
.unwrap_or(LIBLO_ERROR_PANICKED)
}

/// Sets the additional plugins directories to be recognised by the given handle.
///
/// If the load order contains plugins that are installed outside of the game's plugins directory,
/// this function can be used to provide the paths to the directories that those plugins are in so that libloadorder is able to
/// find them.
/// this function can be used to provide the paths to the directories that those plugins are in so
/// that libloadorder is able to find them.
///
/// If external plugins exist, this function must be called before performing any operations on
/// the load order to avoid any unexpected behaviour.
Expand Down
29 changes: 25 additions & 4 deletions ffi/tests/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cstdint>
#include <cstring>

#include <string_view>
#include <thread>
#include <vector>

Expand Down Expand Up @@ -82,10 +83,10 @@ lo_game_handle create_handle() {
return handle;
}

lo_game_handle create_fo4_handle() {
lo_game_handle create_handle(unsigned int game_code) {
lo_game_handle handle = nullptr;
unsigned int return_code = lo_create_handle(&handle,
LIBLO_GAME_FO4,
game_code,
"../../testing-plugins/SkyrimSE",
"../../testing-plugins/SkyrimSE");

Expand Down Expand Up @@ -127,7 +128,7 @@ void test_lo_fix_plugin_lists() {

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

char ** plugins = nullptr;
size_t num_plugins = 0;
Expand All @@ -143,7 +144,7 @@ void test_lo_get_implicitly_active_plugins() {

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

char ** plugins = nullptr;
size_t num_plugins = 0;
Expand Down Expand Up @@ -174,6 +175,25 @@ void test_lo_get_active_plugins_file_path() {
lo_destroy_handle(handle);
}

void test_lo_get_additional_plugins_directories() {
printf("testing lo_get_additional_plugins_directories()...\n");
lo_game_handle handle = create_handle(LIBLO_GAME_STARFIELD);

char ** paths = nullptr;
size_t num_paths = 0;
unsigned int return_code = lo_get_additional_plugins_directories(handle, &paths, &num_paths);

assert(return_code == 0);
assert(num_paths == 1);
#ifdef _WIN32
assert(std::string_view(paths[0]).ends_with("Documents\\My Games\\Starfield\\Data"));
#else
assert(std::string_view(paths[0]).ends_with("Documents/My Games/Starfield/Data"));
#endif
lo_free_string_array(paths, num_paths);
lo_destroy_handle(handle);
}

void test_lo_set_additional_plugins_directories() {
printf("testing lo_set_additional_plugins_directories()...\n");
lo_game_handle handle = create_handle();
Expand Down Expand Up @@ -359,6 +379,7 @@ int main(void) {
test_lo_get_implicitly_active_plugins();
test_lo_get_early_loading_plugins();
test_lo_get_active_plugins_file_path();
test_lo_get_additional_plugins_directories();
test_lo_set_additional_plugins_directories();

test_lo_set_active_plugins();
Expand Down

0 comments on commit f4fd07c

Please sign in to comment.