Merge pull request #6848 from obsproject/plugin-warning

Add plugin load failure warning on startup
This commit is contained in:
Jim 2022-07-28 17:11:18 -07:00 committed by GitHub
commit ec3ea46516
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 347 additions and 75 deletions

View File

@ -113,6 +113,10 @@ MoveSourceDown="Move Source(s) Down"
SourceProperties="Open Source Properties"
SourceFilters="Open Source Filters"
# warning for plugin load failures
PluginsFailedToLoad.Title="Plugin Load Error"
PluginsFailedToLoad.Text="The following OBS plugins failed to load:\n\n%1\nPlease update or remove these plugins."
# warning if program already open
AlreadyRunning.Title="OBS is already running"
AlreadyRunning.Text="OBS is already running! Unless you meant to do this, please shut down any existing instances of OBS before trying to run a new instance. If you have OBS set to minimize to the system tray, please check to see if it's still running there."

View File

@ -1778,15 +1778,18 @@ void OBSBasic::OBSInit()
LoadLibraryW(L"Qt6Network");
#endif
#endif
struct obs_module_failure_info mfi;
AddExtraModulePaths();
blog(LOG_INFO, "---------------------------------");
obs_load_all_modules();
obs_load_all_modules2(&mfi);
blog(LOG_INFO, "---------------------------------");
obs_log_loaded_modules();
blog(LOG_INFO, "---------------------------------");
obs_post_load_modules();
BPtr<char *> failed_modules = mfi.failed_modules;
#ifdef BROWSER_AVAILABLE
cef = obs_browser_init_panel();
#endif
@ -2071,6 +2074,25 @@ void OBSBasic::OBSInit()
OnFirstLoad();
activateWindow();
/* ------------------------------------------- */
/* display warning message for failed modules */
if (mfi.count) {
QString failed_plugins;
char **plugin = mfi.failed_modules;
while (*plugin) {
failed_plugins += *plugin;
failed_plugins += "\n";
plugin++;
}
QString failed_msg =
QTStr("PluginsFailedToLoad.Text").arg(failed_plugins);
OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"),
failed_msg);
}
}
void OBSBasic::OnFirstLoad()

View File

@ -246,6 +246,36 @@ plugin modules.
---------------------
.. function:: void obs_load_all_modules2(struct obs_module_failure_info *mfi)
Automatically loads all modules from module paths (convenience function).
Additionally gives you information about modules that fail to load.
:param mfi: Provides module failure information. The *failed_modules*
member is a string list via a pointer to pointers of
strings of modules that failed to load. Can be freed
either with :c:func:`obs_module_failure_info_free()` or
by simply calling :c:func:`bfree()` on the
*failed_modules* member variable.
Relevant data types used with this function:
.. code:: cpp
struct obs_module_failure_info {
char **failed_modules;
size_t count;
};
---------------------
.. function:: void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
Frees data allocated data used in the *mfi* parameter (calls
:c:func:`bfree()` on the *failed_modules* member variable).
---------------------
.. function:: void obs_post_load_modules(void)
Notifies modules that all modules have been loaded.
@ -271,6 +301,26 @@ plugin modules.
---------------------
.. function:: void obs_find_modules2(obs_find_module_callback_t callback, void *param)
Finds all modules within the search paths added by
:c:func:`obs_add_module_path()`.
Relevant data types used with this function:
.. code:: cpp
struct obs_module_info2 {
const char *bin_path;
const char *data_path;
const char *name;
};
typedef void (*obs_find_module_callback2_t)(void *param,
const struct obs_module_info2 *info);
---------------------
.. function:: void obs_enum_modules(obs_enum_module_callback_t callback, void *param)
Enumerates all loaded modules.

View File

@ -11,6 +11,12 @@ find_package(
OPTIONAL_COMPONENTS avcodec)
find_package(ZLIB REQUIRED)
if(ENABLE_UI)
find_qt(COMPONENTS Core)
else()
set(_QT_VERSION 0)
endif()
add_library(libobs SHARED)
add_library(OBS::libobs ALIAS libobs)

View File

@ -274,27 +274,55 @@ void obs_add_module_path(const char *bin, const char *data)
da_push_back(obs->module_paths, &omp);
}
static void load_all_callback(void *param, const struct obs_module_info *info)
extern void get_plugin_info(const char *path, bool *is_obs_plugin,
bool *can_load);
struct fail_info {
struct dstr fail_modules;
size_t fail_count;
};
static void load_all_callback(void *param, const struct obs_module_info2 *info)
{
struct fail_info *fail_info = param;
obs_module_t *module;
if (!os_is_obs_plugin(info->bin_path)) {
bool is_obs_plugin;
bool can_load_obs_plugin;
get_plugin_info(info->bin_path, &is_obs_plugin, &can_load_obs_plugin);
if (!is_obs_plugin) {
blog(LOG_WARNING, "Skipping module '%s', not an OBS plugin",
info->bin_path);
return;
}
if (!can_load_obs_plugin) {
blog(LOG_WARNING, "Skipping module '%s' due to possible "
"import conflicts");
goto load_failure;
}
int code = obs_open_module(&module, info->bin_path, info->data_path);
if (code != MODULE_SUCCESS) {
blog(LOG_DEBUG, "Failed to load module file '%s': %d",
info->bin_path, code);
return;
goto load_failure;
}
if (!obs_init_module(module))
free_module(module);
UNUSED_PARAMETER(param);
return;
load_failure:
if (fail_info) {
dstr_cat(&fail_info->fail_modules, info->name);
dstr_cat(&fail_info->fail_modules, ";");
fail_info->fail_count++;
}
}
static const char *obs_load_all_modules_name = "obs_load_all_modules";
@ -305,7 +333,7 @@ static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths";
void obs_load_all_modules(void)
{
profile_start(obs_load_all_modules_name);
obs_find_modules(load_all_callback, NULL);
obs_find_modules2(load_all_callback, NULL);
#ifdef _WIN32
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
@ -314,6 +342,36 @@ void obs_load_all_modules(void)
profile_end(obs_load_all_modules_name);
}
static const char *obs_load_all_modules2_name = "obs_load_all_modules2";
void obs_load_all_modules2(struct obs_module_failure_info *mfi)
{
struct fail_info fail_info = {0};
memset(mfi, 0, sizeof(*mfi));
profile_start(obs_load_all_modules2_name);
obs_find_modules2(load_all_callback, &fail_info);
#ifdef _WIN32
profile_start(reset_win32_symbol_paths_name);
reset_win32_symbol_paths();
profile_end(reset_win32_symbol_paths_name);
#endif
profile_end(obs_load_all_modules2_name);
mfi->count = fail_info.fail_count;
mfi->failed_modules =
strlist_split(fail_info.fail_modules.array, ';', false);
dstr_free(&fail_info.fail_modules);
}
void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
{
if (mfi->failed_modules) {
bfree(mfi->failed_modules);
mfi->failed_modules = NULL;
}
}
void obs_post_load_modules(void)
{
for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
@ -388,10 +446,10 @@ static bool parse_binary_from_directory(struct dstr *parsed_bin_path,
static void process_found_module(struct obs_module_path *omp, const char *path,
bool directory,
obs_find_module_callback_t callback,
obs_find_module_callback2_t callback,
void *param)
{
struct obs_module_info info;
struct obs_module_info2 info;
struct dstr name = {0};
struct dstr parsed_bin_path = {0};
const char *file;
@ -421,6 +479,7 @@ static void process_found_module(struct obs_module_path *omp, const char *path,
if (parsed_data_dir && bin_found) {
info.bin_path = parsed_bin_path.array;
info.data_path = parsed_data_dir;
info.name = name.array;
callback(param, &info);
}
@ -430,7 +489,7 @@ static void process_found_module(struct obs_module_path *omp, const char *path,
}
static void find_modules_in_path(struct obs_module_path *omp,
obs_find_module_callback_t callback,
obs_find_module_callback2_t callback,
void *param)
{
struct dstr search_path = {0};
@ -467,7 +526,7 @@ static void find_modules_in_path(struct obs_module_path *omp,
dstr_free(&search_path);
}
void obs_find_modules(obs_find_module_callback_t callback, void *param)
void obs_find_modules2(obs_find_module_callback2_t callback, void *param)
{
if (!obs)
return;
@ -478,6 +537,12 @@ void obs_find_modules(obs_find_module_callback_t callback, void *param)
}
}
void obs_find_modules(obs_find_module_callback_t callback, void *param)
{
/* the structure is ABI compatible so we can just cast the callback */
obs_find_modules2((obs_find_module_callback2_t)callback, param);
}
void obs_enum_modules(obs_enum_module_callback_t callback, void *param)
{
struct obs_module *module;

View File

@ -506,6 +506,7 @@ EXPORT const char *obs_get_module_binary_path(obs_module_t *module);
/** Returns the module data path */
EXPORT const char *obs_get_module_data_path(obs_module_t *module);
#ifndef SWIG
/**
* Adds a module search path to be used with obs_find_modules. If the search
* path strings contain %module%, that text will be replaced with the module
@ -519,11 +520,18 @@ EXPORT void obs_add_module_path(const char *bin, const char *data);
/** Automatically loads all modules from module paths (convenience function) */
EXPORT void obs_load_all_modules(void);
struct obs_module_failure_info {
char **failed_modules;
size_t count;
};
EXPORT void obs_module_failure_info_free(struct obs_module_failure_info *mfi);
EXPORT void obs_load_all_modules2(struct obs_module_failure_info *mfi);
/** Notifies modules that all modules have been loaded. This function should
* be called after all modules have been loaded. */
EXPORT void obs_post_load_modules(void);
#ifndef SWIG
struct obs_module_info {
const char *bin_path;
const char *data_path;
@ -534,6 +542,19 @@ typedef void (*obs_find_module_callback_t)(void *param,
/** Finds all modules within the search paths added by obs_add_module_path. */
EXPORT void obs_find_modules(obs_find_module_callback_t callback, void *param);
struct obs_module_info2 {
const char *bin_path;
const char *data_path;
const char *name;
};
typedef void (*obs_find_module_callback2_t)(
void *param, const struct obs_module_info2 *info);
/** Finds all modules within the search paths added by obs_add_module_path. */
EXPORT void obs_find_modules2(obs_find_module_callback2_t callback,
void *param);
#endif
typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module);

View File

@ -14,6 +14,7 @@
#define OBS_DATA_PATH "@OBS_DATA_PATH@"
#define OBS_INSTALL_PREFIX "@OBS_INSTALL_PREFIX@"
#define OBS_PLUGIN_DESTINATION "@OBS_PLUGIN_DESTINATION@"
#define OBS_QT_VERSION @_QT_VERSION@
#cmakedefine LINUX_PORTABLE
#cmakedefine GIO_FOUND

View File

@ -96,6 +96,13 @@ void os_dlclose(void *module)
dlclose(module);
}
void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load)
{
*is_obs_plugin = true;
*can_load = true;
UNUSED_PARAMETER(path);
}
bool os_is_obs_plugin(const char *path)
{
UNUSED_PARAMETER(path);

View File

@ -26,6 +26,7 @@
#include "platform.h"
#include "darray.h"
#include "dstr.h"
#include "obsconfig.h"
#include "util_uint64.h"
#include "windows/win-registry.h"
#include "windows/win-version.h"
@ -136,7 +137,145 @@ void os_dlclose(void *module)
FreeLibrary(module);
}
bool os_is_obs_plugin(const char *path)
#if OBS_QT_VERSION == 6
static bool has_qt5_import(VOID *base, PIMAGE_NT_HEADERS nt_headers)
{
__try {
PIMAGE_DATA_DIRECTORY data_dir;
data_dir =
&nt_headers->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (data_dir->Size == 0)
return false;
PIMAGE_SECTION_HEADER section, last_section;
section = IMAGE_FIRST_SECTION(nt_headers);
last_section = section;
/* find the section that contains the export directory */
int i;
for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
if (section->VirtualAddress <=
data_dir->VirtualAddress) {
last_section = section;
section++;
continue;
} else {
break;
}
}
/* double check in case we exited early */
if (last_section->VirtualAddress > data_dir->VirtualAddress ||
section->VirtualAddress <= data_dir->VirtualAddress)
return false;
section = last_section;
/* get a pointer to the import directory */
PIMAGE_IMPORT_DESCRIPTOR import;
import = (PIMAGE_IMPORT_DESCRIPTOR)((byte *)base +
data_dir->VirtualAddress -
section->VirtualAddress +
section->PointerToRawData);
while (import->Name != 0) {
char *name = (char *)((byte *)base + import->Name -
section->VirtualAddress +
section->PointerToRawData);
/* qt5? bingo, reject this library */
if (astrcmpi_n(name, "qt5", 3) == 0) {
return true;
}
import++;
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
/* we failed somehow, for compatibility assume no qt5 import */
return false;
}
return false;
}
#endif
static bool has_obs_export(VOID *base, PIMAGE_NT_HEADERS nt_headers)
{
__try {
PIMAGE_DATA_DIRECTORY data_dir;
data_dir =
&nt_headers->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (data_dir->Size == 0)
return false;
PIMAGE_SECTION_HEADER section, last_section;
section = IMAGE_FIRST_SECTION(nt_headers);
last_section = section;
/* find the section that contains the export directory */
int i;
for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
if (section->VirtualAddress <=
data_dir->VirtualAddress) {
last_section = section;
section++;
continue;
} else {
break;
}
}
/* double check in case we exited early */
if (last_section->VirtualAddress > data_dir->VirtualAddress ||
section->VirtualAddress <= data_dir->VirtualAddress)
return false;
section = last_section;
/* get a pointer to the export directory */
PIMAGE_EXPORT_DIRECTORY export;
export = (PIMAGE_EXPORT_DIRECTORY)((byte *)base +
data_dir->VirtualAddress -
section->VirtualAddress +
section->PointerToRawData);
if (export->NumberOfNames == 0)
return false;
/* get a pointer to the export directory names */
DWORD *names_ptr;
names_ptr = (DWORD *)((byte *)base + export->AddressOfNames -
section->VirtualAddress +
section->PointerToRawData);
/* iterate through each name and see if its an obs plugin */
CHAR *name;
size_t j;
for (j = 0; j < export->NumberOfNames; j++) {
name = (CHAR *)base + names_ptr[j] -
section->VirtualAddress +
section->PointerToRawData;
if (!strcmp(name, "obs_module_load")) {
return true;
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
/* we failed somehow, for compatibility let's assume it
* was a valid plugin and let the loader deal with it */
return true;
}
return false;
}
void get_plugin_info(const char *path, bool *is_obs_plugin, bool *can_load)
{
struct dstr dll_name;
wchar_t *wpath;
@ -147,12 +286,12 @@ bool os_is_obs_plugin(const char *path)
PIMAGE_DOS_HEADER dos_header;
PIMAGE_NT_HEADERS nt_headers;
PIMAGE_SECTION_HEADER section, last_section;
bool ret = false;
*is_obs_plugin = false;
*can_load = false;
if (!path)
return false;
return;
dstr_init_copy(&dll_name, path);
dstr_replace(&dll_name, "\\", "/");
@ -193,72 +332,21 @@ bool os_is_obs_plugin(const char *path)
if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
goto cleanup;
PIMAGE_DATA_DIRECTORY data_dir;
data_dir =
&nt_headers->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
*is_obs_plugin = has_obs_export(base, nt_headers);
if (data_dir->Size == 0)
goto cleanup;
section = IMAGE_FIRST_SECTION(nt_headers);
last_section = section;
/* find the section that contains the export directory */
int i;
for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) {
if (section->VirtualAddress <=
data_dir->VirtualAddress) {
last_section = section;
section++;
continue;
} else {
break;
}
}
/* double check in case we exited early */
if (last_section->VirtualAddress > data_dir->VirtualAddress ||
section->VirtualAddress <= data_dir->VirtualAddress)
goto cleanup;
section = last_section;
/* get a pointer to the export directory */
PIMAGE_EXPORT_DIRECTORY export;
export = (PIMAGE_EXPORT_DIRECTORY)((byte *)base +
data_dir->VirtualAddress -
section->VirtualAddress +
section->PointerToRawData);
if (export->NumberOfNames == 0)
goto cleanup;
/* get a pointer to the export directory names */
DWORD *names_ptr;
names_ptr = (DWORD *)((byte *)base + export->AddressOfNames -
section->VirtualAddress +
section->PointerToRawData);
/* iterate through each name and see if its an obs plugin */
CHAR *name;
size_t j;
for (j = 0; j < export->NumberOfNames; j++) {
name = (CHAR *)base + names_ptr[j] -
section->VirtualAddress +
section->PointerToRawData;
if (!strcmp(name, "obs_module_load")) {
ret = true;
goto cleanup;
}
#if OBS_QT_VERSION == 6
if (*is_obs_plugin) {
*can_load = !has_qt5_import(base, nt_headers);
}
#else
*can_load = true;
#endif
} __except (EXCEPTION_EXECUTE_HANDLER) {
/* we failed somehow, for compatibility let's assume it
* was a valid plugin and let the loader deal with it */
ret = true;
*is_obs_plugin = true;
*can_load = true;
goto cleanup;
}
@ -271,8 +359,16 @@ cleanup:
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
return ret;
bool os_is_obs_plugin(const char *path)
{
bool is_obs_plugin;
bool can_load;
get_plugin_info(path, &is_obs_plugin, &can_load);
return is_obs_plugin && can_load;
}
union time_data {