소스 검색

Merge pull request #6848 from obsproject/plugin-warning

Add plugin load failure warning on startup
Jim 3 년 전
부모
커밋
ec3ea46516
9개의 변경된 파일332개의 추가작업 그리고 60개의 파일을 삭제
  1. 4 0
      UI/data/locale/en-US.ini
  2. 23 1
      UI/window-basic-main.cpp
  3. 50 0
      docs/sphinx/reference-modules.rst
  4. 6 0
      libobs/CMakeLists.txt
  5. 73 8
      libobs/obs-module.c
  6. 22 1
      libobs/obs.h
  7. 1 0
      libobs/obsconfig.h.in
  8. 7 0
      libobs/util/platform-nix.c
  9. 146 50
      libobs/util/platform-windows.c

+ 4 - 0
UI/data/locale/en-US.ini

@@ -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."

+ 23 - 1
UI/window-basic-main.cpp

@@ -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()

+ 50 - 0
docs/sphinx/reference-modules.rst

@@ -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.

+ 6 - 0
libobs/CMakeLists.txt

@@ -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)
 

+ 73 - 8
libobs/obs-module.c

@@ -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;

+ 22 - 1
libobs/obs.h

@@ -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);

+ 1 - 0
libobs/obsconfig.h.in

@@ -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

+ 7 - 0
libobs/util/platform-nix.c

@@ -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);

+ 146 - 50
libobs/util/platform-windows.c

@@ -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,71 +137,83 @@ 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)
 {
-	struct dstr dll_name;
-	wchar_t *wpath;
+	__try {
+		PIMAGE_DATA_DIRECTORY data_dir;
+		data_dir =
+			&nt_headers->OptionalHeader
+				 .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
 
-	HANDLE hFile = INVALID_HANDLE_VALUE;
-	HANDLE hFileMapping = NULL;
-	VOID *base = NULL;
+		if (data_dir->Size == 0)
+			return false;
 
-	PIMAGE_DOS_HEADER dos_header;
-	PIMAGE_NT_HEADERS nt_headers;
-	PIMAGE_SECTION_HEADER section, last_section;
+		PIMAGE_SECTION_HEADER section, last_section;
+		section = IMAGE_FIRST_SECTION(nt_headers);
+		last_section = section;
 
-	bool ret = false;
+		/* 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;
+			}
+		}
 
-	if (!path)
-		return false;
+		/* double check in case we exited early */
+		if (last_section->VirtualAddress > data_dir->VirtualAddress ||
+		    section->VirtualAddress <= data_dir->VirtualAddress)
+			return false;
 
-	dstr_init_copy(&dll_name, path);
-	dstr_replace(&dll_name, "\\", "/");
-	if (!dstr_find(&dll_name, ".dll"))
-		dstr_cat(&dll_name, ".dll");
-	os_utf8_to_wcs_ptr(dll_name.array, 0, &wpath);
+		section = last_section;
 
-	dstr_free(&dll_name);
+		/* get a pointer to the import directory */
+		PIMAGE_IMPORT_DESCRIPTOR import;
+		import = (PIMAGE_IMPORT_DESCRIPTOR)((byte *)base +
+						    data_dir->VirtualAddress -
+						    section->VirtualAddress +
+						    section->PointerToRawData);
 
-	hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL,
-			    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+		while (import->Name != 0) {
+			char *name = (char *)((byte *)base + import->Name -
+					      section->VirtualAddress +
+					      section->PointerToRawData);
 
-	bfree(wpath);
+			/* qt5? bingo, reject this library */
+			if (astrcmpi_n(name, "qt5", 3) == 0) {
+				return true;
+			}
 
-	if (hFile == INVALID_HANDLE_VALUE)
-		goto cleanup;
+			import++;
+		}
 
-	hFileMapping =
-		CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
-	if (hFileMapping == NULL)
-		goto cleanup;
+	} __except (EXCEPTION_EXECUTE_HANDLER) {
+		/* we failed somehow, for compatibility assume no qt5 import */
+		return false;
+	}
 
-	base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
-	if (!base)
-		goto cleanup;
+	return false;
+}
+#endif
 
-	/* all mapped file i/o must be prepared to handle exceptions */
+static bool has_obs_export(VOID *base, PIMAGE_NT_HEADERS nt_headers)
+{
 	__try {
-
-		dos_header = (PIMAGE_DOS_HEADER)base;
-
-		if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
-			goto cleanup;
-
-		nt_headers = (PIMAGE_NT_HEADERS)((byte *)dos_header +
-						 dos_header->e_lfanew);
-
-		if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
-			goto cleanup;
-
 		PIMAGE_DATA_DIRECTORY data_dir;
 		data_dir =
 			&nt_headers->OptionalHeader
 				 .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
 
 		if (data_dir->Size == 0)
-			goto cleanup;
+			return false;
 
+		PIMAGE_SECTION_HEADER section, last_section;
 		section = IMAGE_FIRST_SECTION(nt_headers);
 		last_section = section;
 
@@ -220,7 +233,7 @@ bool os_is_obs_plugin(const char *path)
 		/* double check in case we exited early */
 		if (last_section->VirtualAddress > data_dir->VirtualAddress ||
 		    section->VirtualAddress <= data_dir->VirtualAddress)
-			goto cleanup;
+			return false;
 
 		section = last_section;
 
@@ -232,7 +245,7 @@ bool os_is_obs_plugin(const char *path)
 						   section->PointerToRawData);
 
 		if (export->NumberOfNames == 0)
-			goto cleanup;
+			return false;
 
 		/* get a pointer to the export directory names */
 		DWORD *names_ptr;
@@ -250,15 +263,90 @@ bool os_is_obs_plugin(const char *path)
 			       section->PointerToRawData;
 
 			if (!strcmp(name, "obs_module_load")) {
-				ret = true;
-				goto cleanup;
+				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;
+
+	HANDLE hFile = INVALID_HANDLE_VALUE;
+	HANDLE hFileMapping = NULL;
+	VOID *base = NULL;
+
+	PIMAGE_DOS_HEADER dos_header;
+	PIMAGE_NT_HEADERS nt_headers;
+
+	*is_obs_plugin = false;
+	*can_load = false;
+
+	if (!path)
+		return;
+
+	dstr_init_copy(&dll_name, path);
+	dstr_replace(&dll_name, "\\", "/");
+	if (!dstr_find(&dll_name, ".dll"))
+		dstr_cat(&dll_name, ".dll");
+	os_utf8_to_wcs_ptr(dll_name.array, 0, &wpath);
+
+	dstr_free(&dll_name);
+
+	hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL,
+			    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+
+	bfree(wpath);
+
+	if (hFile == INVALID_HANDLE_VALUE)
+		goto cleanup;
+
+	hFileMapping =
+		CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+	if (hFileMapping == NULL)
+		goto cleanup;
+
+	base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
+	if (!base)
+		goto cleanup;
+
+	/* all mapped file i/o must be prepared to handle exceptions */
+	__try {
+
+		dos_header = (PIMAGE_DOS_HEADER)base;
+
+		if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
+			goto cleanup;
+
+		nt_headers = (PIMAGE_NT_HEADERS)((byte *)dos_header +
+						 dos_header->e_lfanew);
+
+		if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
+			goto cleanup;
+
+		*is_obs_plugin = has_obs_export(base, nt_headers);
+
+#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 {