diff --git a/cmake/common/helpers_common.cmake b/cmake/common/helpers_common.cmake index 4ce77230a09a4b..9e37012ef556f6 100644 --- a/cmake/common/helpers_common.cmake +++ b/cmake/common/helpers_common.cmake @@ -40,7 +40,7 @@ function(message_configuration) endforeach() endif() - if(ENABLE_PLUGINS) + if(ENABLE_CORE_MODULES) get_property(OBS_MODULES_ENABLED GLOBAL PROPERTY OBS_MODULES_ENABLED) list(SORT OBS_MODULES_ENABLED COMPARE NATURAL CASE SENSITIVE ORDER ASCENDING) @@ -82,6 +82,11 @@ function(target_disable_feature target feature_description) endif() endfunction() +# target_enable: Adds target to list of enabled modules +function(target_enable target) + set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) +endfunction() + # target_disable: Adds target to list of disabled modules function(target_disable target) set_property(GLOBAL APPEND PROPERTY OBS_MODULES_DISABLED ${target}) @@ -451,13 +456,17 @@ function(check_uuid uuid_string return_value) return(PROPAGATE ${return_value}) endfunction() -# add_obs_plugin: Add plugin subdirectory if host platform is in specified list of supported platforms and architectures -function(add_obs_plugin target) +# add_core_module: Add module subdirectory if host platform is in specified list of supported platforms and architectures +function(add_core_module target) set(options WITH_MESSAGE) set(oneValueArgs "") set(multiValueArgs PLATFORMS ARCHITECTURES) cmake_parse_arguments(PARSE_ARGV 0 _AOP "${options}" "${oneValueArgs}" "${multiValueArgs}") + if(NOT TARGET libobs) + message(FATAL_ERROR "Cannot add libobs core module without existing 'libobs' target") + endif() + set(found_platform FALSE) list(LENGTH _AOP_PLATFORMS _AOP_NUM_PLATFORMS) @@ -499,8 +508,37 @@ function(add_obs_plugin target) if(found_platform AND found_architecture) add_subdirectory(${target}) - elseif(_AOP_WITH_MESSAGE) - add_custom_target(${target} COMMENT "Dummy target for unavailable module ${target}") + endif() + + if(TARGET ${target}) + target_enable(${target}) + else() + if(_AOP_WITH_MESSAGE) + add_custom_target(${target} COMMENT "Dummy target for unavailable module ${target}") + endif() target_disable(${target}) endif() endfunction() + +function(set_obs_core_modules) + if(NOT TARGET libobs OR NOT TARGET libobs-core-modules) + message(FATAL_ERROR "Unable to set up OBS Core Modules without 'libobs' target") + endif() + + # get_target_property(core_modules_list libobs CORE_MODULE_TARGETS) + get_property(OBS_MODULES_ENABLED GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(libobs_source_directory libobs SOURCE_DIR) + get_target_property(libobs_binary_directory libobs BINARY_DIR) + + list(LENGTH OBS_MODULES_ENABLED core_modules_count) + string(REPLACE ";" "\",\n\t\"" core_modules_array_content "${OBS_MODULES_ENABLED}") + + set(OBS_CORE_MODULE_COUNT "${core_modules_count}") + set(OBS_CORE_MODULE_LIST "\t\"${core_modules_array_content}\"") + + configure_file( + "${libobs_source_directory}/obs-core-modules.c.in" + "${libobs_binary_directory}/obs-core-modules.c" + @ONLY + ) +endfunction() diff --git a/cmake/linux/defaults.cmake b/cmake/linux/defaults.cmake index b6a0061ff30cb8..5ffe864e6b77f2 100644 --- a/cmake/linux/defaults.cmake +++ b/cmake/linux/defaults.cmake @@ -25,13 +25,13 @@ set(OBS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/rundir") set(OBS_EXECUTABLE_DESTINATION "${CMAKE_INSTALL_BINDIR}") set(OBS_INCLUDE_DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/obs") set(OBS_LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") -set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins") +set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-modules") set(OBS_SCRIPT_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-scripting") set(OBS_DATA_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/obs") set(OBS_CMAKE_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake") # Set additional paths used by OBS for self-discovery -set(OBS_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-plugins") +set(OBS_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-modules/core") set(OBS_SCRIPT_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/obs-scripting") set(OBS_DATA_PATH "${OBS_DATA_DESTINATION}") set(OBS_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") diff --git a/cmake/linux/helpers.cmake b/cmake/linux/helpers.cmake index 494586de920808..c8b78097fe3618 100644 --- a/cmake/linux/helpers.cmake +++ b/cmake/linux/helpers.cmake @@ -22,6 +22,10 @@ function(set_target_properties_obs target) set(OBS_SOVERSION 30) if(target_type STREQUAL EXECUTABLE) + if(target STREQUAL browser-helper) + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/core") + endif() + install(TARGETS ${target} RUNTIME DESTINATION "${OBS_EXECUTABLE_DESTINATION}" COMPONENT Runtime) add_custom_command( @@ -124,7 +128,7 @@ function(set_target_properties_obs target) set(plugin_destination "${OBS_SCRIPT_PLUGIN_DESTINATION}") set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/;$ORIGIN/..") else() - set(plugin_destination "${OBS_PLUGIN_DESTINATION}") + set(plugin_destination "${OBS_PLUGIN_DESTINATION}/core") endif() install( @@ -170,20 +174,21 @@ function(set_target_properties_obs target) add_custom_command( TARGET ${target} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${imported_location}" "${cef_location}/chrome-sandbox" "${cef_location}/libEGL.so" "${cef_location}/libGLESv2.so" "${cef_location}/libvk_swiftshader.so" "${cef_location}/libvulkan.so.1" "${cef_location}/v8_context_snapshot.bin" - "${cef_location}/vk_swiftshader_icd.json" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + "${cef_location}/vk_swiftshader_icd.json" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${cef_root_location}/Resources/chrome_100_percent.pak" "${cef_root_location}/Resources/chrome_200_percent.pak" "${cef_root_location}/Resources/icudtl.dat" - "${cef_root_location}/Resources/resources.pak" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/" + "${cef_root_location}/Resources/resources.pak" + "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${cef_root_location}/Resources/locales" - "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/locales" - COMMENT "Add Chromium Embedded Framework to library directory" + "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/core/locales" + COMMENT "Add Chromium Embedded Framwork to library directory" ) install( @@ -200,21 +205,19 @@ function(set_target_properties_obs target) "${cef_root_location}/Resources/chrome_200_percent.pak" "${cef_root_location}/Resources/icudtl.dat" "${cef_root_location}/Resources/resources.pak" - DESTINATION "${OBS_PLUGIN_DESTINATION}" + DESTINATION "${OBS_PLUGIN_DESTINATION}/core" COMPONENT Runtime ) install( DIRECTORY "${cef_root_location}/Resources/locales" - DESTINATION "${OBS_PLUGIN_DESTINATION}" + DESTINATION "${OBS_PLUGIN_DESTINATION}/core" USE_SOURCE_PERMISSIONS COMPONENT Runtime ) endif() endif() endif() - - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) endif() target_install_resources(${target}) @@ -236,9 +239,9 @@ function(target_install_resources target) source_group("Resources/${relative_path}" FILES "${data_file}") endforeach() - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) - if(target IN_LIST obs_module_list) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL MODULE_LIBRARY) + set(target_destination "${OBS_DATA_DESTINATION}/obs-modules/core/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -267,11 +270,11 @@ endfunction() # Helper function to add a specific resource to a bundle function(target_add_resource target resource) - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(target_type ${target} TYPE) if(ARGN) set(target_destination "${ARGN}") - elseif(${target} IN_LIST obs_module_list) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + elseif(target_type STREQUAL MODULE_LIBRARY) + set(target_destination "${OBS_DATA_DESTINATION}/obs-modules/core/${target}") elseif(target STREQUAL obs) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() diff --git a/cmake/macos/helpers.cmake b/cmake/macos/helpers.cmake index 8913b2b24f9d96..ec4c901708eac2 100644 --- a/cmake/macos/helpers.cmake +++ b/cmake/macos/helpers.cmake @@ -303,7 +303,6 @@ function(set_target_properties_obs target) endif() endif() - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) set_property(GLOBAL APPEND PROPERTY _OBS_DEPENDENCIES ${target}) endif() diff --git a/cmake/windows/defaults.cmake b/cmake/windows/defaults.cmake index c2e141105ce6bf..e212ac66e4ec2b 100644 --- a/cmake/windows/defaults.cmake +++ b/cmake/windows/defaults.cmake @@ -5,7 +5,7 @@ include_guard(GLOBAL) set(OBS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(OBS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/rundir") -set(OBS_PLUGIN_DESTINATION obs-plugins/64bit) +set(OBS_PLUGIN_DESTINATION core) set(OBS_DATA_DESTINATION data) set(OBS_CMAKE_DESTINATION cmake) set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting/64bit") @@ -14,7 +14,7 @@ set(OBS_EXECUTABLE_DESTINATION bin/64bit) set(OBS_LIBRARY_DESTINATION lib) set(OBS_INCLUDE_DESTINATION include) # Set relative paths used by OBS for self-discovery -set(OBS_PLUGIN_PATH "../../${CMAKE_INSTALL_LIBDIR}/obs-plugins/64bit") +set(OBS_PLUGIN_PATH "../../${CMAKE_INSTALL_LIBDIR}/core") set(OBS_SCRIPT_PLUGIN_PATH "../../${OBS_DATA_DESTINATION}/obs-scripting/64bit") set(OBS_DATA_PATH "../../${OBS_DATA_DESTINATION}") diff --git a/cmake/windows/helpers.cmake b/cmake/windows/helpers.cmake index 9a443f1cf75736..5e597666f9a67d 100644 --- a/cmake/windows/helpers.cmake +++ b/cmake/windows/helpers.cmake @@ -22,9 +22,9 @@ function(set_target_properties_obs target) if(target_type STREQUAL EXECUTABLE) if(target STREQUAL obs-browser-helper) - set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}") + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/obs-browser") elseif(target STREQUAL inject-helper OR target STREQUAL get-graphics-offsets) - set(OBS_EXECUTABLE_DESTINATION "${OBS_DATA_DESTINATION}/obs-plugins/win-capture") + set(OBS_EXECUTABLE_DESTINATION "${OBS_PLUGIN_DESTINATION}/win-capture/data") _target_install_obs(${target} DESTINATION ${OBS_EXECUTABLE_DESTINATION} x86) @@ -66,7 +66,7 @@ function(set_target_properties_obs target) elseif(target STREQUAL "obspython" OR target STREQUAL "obslua") set(target_destination "${OBS_SCRIPT_PLUGIN_DESTINATION}") elseif(target STREQUAL graphics-hook) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/win-capture") + set(target_destination "${OBS_PLUGIN_DESTINATION}/win-capture/data") target_add_resource(graphics-hook "${CMAKE_CURRENT_SOURCE_DIR}/obs-vulkan64.json" "${target_destination}") target_add_resource(graphics-hook "${CMAKE_CURRENT_SOURCE_DIR}/obs-vulkan32.json" "${target_destination}") @@ -76,7 +76,7 @@ function(set_target_properties_obs target) _target_install_obs(${target} DESTINATION ${target_destination} x64) endif() elseif(target STREQUAL obs-virtualcam-module) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow") + set(target_destination "${OBS_PLUGIN_DESTINATION}/win-dshow/data") _target_install_obs(${target} DESTINATION ${target_destination} x86) @@ -84,7 +84,7 @@ function(set_target_properties_obs target) _target_install_obs(${target} DESTINATION ${target_destination} x64) endif() else() - set(target_destination "${OBS_PLUGIN_DESTINATION}") + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}") endif() _target_install_obs(${target} DESTINATION ${target_destination}) @@ -157,8 +157,6 @@ function(set_target_properties_obs target) endif() endif() endif() - - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_ENABLED ${target}) endif() target_link_options(${target} PRIVATE "/PDBALTPATH:$") @@ -314,9 +312,9 @@ function(target_install_resources target) source_group("Resources/${relative_path}" FILES "${data_file}") endforeach() - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) - if(target IN_LIST obs_module_list) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL MODULE_LIBRARY) + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}/data") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -346,11 +344,11 @@ endfunction() # Helper function to add a specific resource to a bundle function(target_add_resource target resource) - get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) + get_target_property(target_type ${target} TYPE) if(ARGN) set(target_destination "${ARGN}") - elseif(${target} IN_LIST obs_module_list) - set(target_destination "${OBS_DATA_DESTINATION}/obs-plugins/${target}") + elseif(target_type STREQUAL MODULE_LIBRARY) + set(target_destination "${OBS_PLUGIN_DESTINATION}/${target}/data") elseif(target STREQUAL obs-studio) set(target_destination "${OBS_DATA_DESTINATION}/obs-studio") else() @@ -397,7 +395,7 @@ function(_bundle_dependencies target) set(plugins_list) foreach(library IN LISTS found_dependencies) - # CEF needs to be placed in obs-plugins directory on Windows, which is handled already + # CEF needs to be placed in core directory on Windows, which is handled already if(${library} STREQUAL CEF::Library) continue() endif() diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index f0cde6b47b855e..f74c7a7b704c58 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -94,10 +94,6 @@ foreach(graphics_library IN ITEMS opengl metal d3d11) endif() endforeach() -get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) -list(JOIN obs_module_list "|" SAFE_MODULES) -target_compile_definitions(obs-studio PRIVATE "SAFE_MODULES=\"${SAFE_MODULES}\"") - get_target_property(target_sources obs-studio SOURCES) set(target_cpp_sources ${target_sources}) set(target_hpp_sources ${target_sources}) diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index 0d6d0e28e35fbd..ebd30ca6795958 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -88,6 +88,35 @@ typedef struct UncleanLaunchAction { bool sendCrashReport = false; } UncleanLaunchAction; +enum class PluginFailureAction { Continue, OpenPluginManager }; + +PluginFailureAction handlePluginFailure() +{ + QMessageBox pluginWarning; + + pluginWarning.setIcon(QMessageBox::Warning); + + pluginWarning.setWindowTitle(QTStr("PluginFailure.Dialog.Title")); + pluginWarning.setText(QTStr("PluginFailure.Labels.Text")); + + QPushButton *continueButton = + pluginWarning.addButton(QTStr("PluginFailure.Dialog.Continue"), QMessageBox::RejectRole); + QPushButton *handleButton = + pluginWarning.addButton(QTStr("PluginFailure.Dialog.Open"), QMessageBox::AcceptRole); + + pluginWarning.setDefaultButton(continueButton); + + pluginWarning.exec(); + + bool openPluginManager = pluginWarning.clickedButton() == handleButton; + + if (openPluginManager) { + return PluginFailureAction::OpenPluginManager; + } else { + return PluginFailureAction::Continue; + } +} + UncleanLaunchAction handleUncleanShutdown(bool enableCrashUpload) { UncleanLaunchAction launchAction; @@ -2037,16 +2066,27 @@ void OBSApp::addLogLine(int logLevel, const QString &message) emit logLineAdded(logLevel, message); } -void OBSApp::loadAppModules(struct obs_module_failure_info &mfi) +void OBSApp::loadAppModules() +{ + using PluginMode = OBS::PluginManager::Mode; + PluginMode mode = (disable_3p_plugins || safe_mode) ? PluginMode::CoreOnly : PluginMode::Full; + pluginManager_->setPluginMode(mode); + + pluginManager_->loadAllPlugins(portable_mode); +} + +void OBSApp::handlePluginLoadState() { - pluginManager_->preLoad(); - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - pluginManager_->postLoad(); + using PluginState = OBS::PluginManager::State; + PluginState loadState = pluginManager_->loadState(); + + if (loadState != PluginState::Success) { + PluginFailureAction action = handlePluginFailure(); + + if (action == PluginFailureAction::OpenPluginManager) { + pluginManagerOpenDialog(); + } + } } void OBSApp::pluginManagerOpenDialog() diff --git a/frontend/OBSApp.hpp b/frontend/OBSApp.hpp index fc5774e047398e..e3e26fb6b5f9cd 100644 --- a/frontend/OBSApp.hpp +++ b/frontend/OBSApp.hpp @@ -236,12 +236,13 @@ private slots: static void sigQuitSignalHandler(int); #endif - void loadAppModules(struct obs_module_failure_info &mfi); + void loadAppModules(); ThumbnailManager *thumbnails() const { return thumbnailManager; } // Plugin Manager Accessors void pluginManagerOpenDialog(); + void handlePluginLoadState(); public slots: void Exec(VoidFunc func); diff --git a/frontend/cmake/feature-plugin-manager.cmake b/frontend/cmake/feature-plugin-manager.cmake index faf9cf2a0f0879..949c3b02082f6d 100644 --- a/frontend/cmake/feature-plugin-manager.cmake +++ b/frontend/cmake/feature-plugin-manager.cmake @@ -1,5 +1,13 @@ find_package(nlohmann_json 3.11 REQUIRED) +set(OBS_PLATFORM_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/") +set(OBS_PLATFORM_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}/") +set(OBS_PLATFORM_DATA_PATH "${CMAKE_INSTALL_DATAROOTDIR}/") +set(OBS_PLATFORM_CORE_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/obs-modules/core") +set(OBS_PLATFORM_CORE_DATA_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-modules/core") +set(OBS_PLATFORM_PLUGIN_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/obs-modules/plugins") +set(OBS_PLATFORM_PLUGIN_DATA_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-modules/plugins") + target_sources( obs-studio PRIVATE @@ -7,6 +15,19 @@ target_sources( plugin-manager/PluginManager.hpp plugin-manager/PluginManagerWindow.cpp plugin-manager/PluginManagerWindow.hpp + plugin-manager/PluginModuleLoader.hpp ) +if(OS_WINDOWS) + target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_Windows.cpp) +elseif(OS_MACOS) + target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_MacOS.mm) +elseif(OS_LINUX) + configure_file(plugin-manager/LoaderPaths_Linux.hpp.in LoaderPaths_Linux.hpp @ONLY) + target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_Linux.cpp LoaderPaths_Linux.hpp) +elseif(OS_FREEBSD OR OS_OPENBSD) + configure_file(plugin-manager/LoaderPaths_BSD.hpp.in LoaderPaths_BSD.hpp @ONLY) + target_sources(obs-studio PRIVATE plugin-manager/PluginModuleLoader_BSD.cpp LoaderPaths_BSD.hpp) +endif() + target_link_libraries(obs-studio PRIVATE nlohmann_json::nlohmann_json) diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index 51ea30b4aa1186..e92bd5ba09a398 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -141,6 +141,12 @@ CrashHandling.Buttons.LaunchNormal="Run in Normal Mode" CrashHandling.Errors.UploadJSONError="An error occurred while trying to upload the most recent crash log. Please try again later." CrashHandling.Errors.Title="Crash Log Upload Error" +# Plugin Manager Load Failure +PluginFailure.Dialog.Title="OBS Studio Plugin Loading Failure" +PluginFailure.Labels.Text="OBS Studio was not able to load all 3rd party plugins.\n\nOpen Plugin Manager to check the state of 3rd party plugins?" +PluginFailure.Dialog.Continue="Continue" +PluginFailure.Dialog.Open="Open Pugin Manager" + # Safe Mode Restart Option SafeMode.Restart="Do you want to restart OBS in Safe Mode (third-party plugins, scripting, and WebSockets disabled)?" SafeMode.RestartNormal="Do you want to restart OBS in Normal Mode?" diff --git a/frontend/plugin-manager/LoaderPaths_BSD.hpp.in b/frontend/plugin-manager/LoaderPaths_BSD.hpp.in new file mode 100644 index 00000000000000..018409af15bdbb --- /dev/null +++ b/frontend/plugin-manager/LoaderPaths_BSD.hpp.in @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace OBS { + namespace Constants { + inline constexpr std::string_view kPlatformInstallPath{"@OBS_PLATFORM_INSTALL_PATH@"}; + inline constexpr std::string_view kPlatformLibraryPath{"@OBS_PLATFORM_LIBRARY_PATH@"}; + inline constexpr std::string_view kPlatformDataPath{"@OBS_PLATFORM_DATA_PATH@"}; + inline constexpr std::string_view kPlatformPluginModulePath{"@OBS_PLATFORM_PLUGIN_MODULE_PATH@"}; + inline constexpr std::string_view kPlatformPluginDataPath{"@OBS_PLATFORM_PLUGIN_DATA_PATH@"}; + } +} diff --git a/frontend/plugin-manager/LoaderPaths_Linux.hpp.in b/frontend/plugin-manager/LoaderPaths_Linux.hpp.in new file mode 100644 index 00000000000000..018409af15bdbb --- /dev/null +++ b/frontend/plugin-manager/LoaderPaths_Linux.hpp.in @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace OBS { + namespace Constants { + inline constexpr std::string_view kPlatformInstallPath{"@OBS_PLATFORM_INSTALL_PATH@"}; + inline constexpr std::string_view kPlatformLibraryPath{"@OBS_PLATFORM_LIBRARY_PATH@"}; + inline constexpr std::string_view kPlatformDataPath{"@OBS_PLATFORM_DATA_PATH@"}; + inline constexpr std::string_view kPlatformPluginModulePath{"@OBS_PLATFORM_PLUGIN_MODULE_PATH@"}; + inline constexpr std::string_view kPlatformPluginDataPath{"@OBS_PLATFORM_PLUGIN_DATA_PATH@"}; + } +} diff --git a/frontend/plugin-manager/PluginManager.cpp b/frontend/plugin-manager/PluginManager.cpp index 5cd351d453bdc2..8e6a51b57e2103 100644 --- a/frontend/plugin-manager/PluginManager.cpp +++ b/frontend/plugin-manager/PluginManager.cpp @@ -39,7 +39,7 @@ void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule) std::string moduleName = obs_get_module_file_name(newModule); moduleName = moduleName.substr(0, moduleName.rfind(".")); - if (!obs_get_module_allow_disable(moduleName.c_str())) { + if (!obs_is_core_module(newModule)) { return; } @@ -67,7 +67,7 @@ constexpr std::string_view OBSPluginManagerModulesFile = "modules.json"; void PluginManager::preLoad() { - loadModules_(); + loadModuleConfiguration_(); disableModules_(); } @@ -82,6 +82,33 @@ void PluginManager::postLoad() linkUnloadedModules_(); } +void PluginManager::loadAllPlugins(bool usePortableMode) +{ + preLoad(); + bool coreModuleLoadSuccess = obs_load_core_modules(); + + if (!coreModuleLoadSuccess) { + loadState_ = State::Failure; + //TODO: Replace with structured exception type - https://github.com/obsproject/obs-studio/issues/13394 + throw "Failed to load core OBS modules. OBS cannot run without these modules. Please try reinstalling OBS."; + return; + } + + if (loadMode_ == Mode::Full) { + blog(LOG_INFO, "---------------------------------"); + State pluginState = loadPlugins(usePortableMode); + State legacyPluginState = loadLegacyPlugins(usePortableMode); + + loadState_ = (pluginState && legacyPluginState) ? State::Success : State::PartialFailure; + } + + blog(LOG_INFO, "---------------------------------"); + obs_log_loaded_modules(); + blog(LOG_INFO, "---------------------------------"); + obs_post_load_modules(); + postLoad(); +} + std::filesystem::path PluginManager::getConfigFilePath_() { std::filesystem::path path = App()->userPluginManagerSettingsLocation / @@ -90,7 +117,7 @@ std::filesystem::path PluginManager::getConfigFilePath_() return path; } -void PluginManager::loadModules_() +void PluginManager::loadModuleConfiguration_() { auto modulesFile = getConfigFilePath_(); if (std::filesystem::exists(modulesFile)) { @@ -282,7 +309,12 @@ void PluginManager::disableModules_() void PluginManager::open() { auto main = OBSBasic::Get(); - PluginManagerWindow pluginManagerWindow(modules_, main); + PluginManagerWindow pluginManagerWindow(modules_, failedModules_, main); + + if (loadState_ == State::PartialFailure) { + pluginManagerWindow.setPage(PluginManagerWindow::Page::Failure); + } + auto result = pluginManagerWindow.exec(); if (result == QDialog::Accepted) { modules_ = pluginManagerWindow.result(); @@ -310,3 +342,45 @@ void PluginManager::open() } }; // namespace OBS + +bool operator&&(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs) +{ + using State = OBS::PluginManager::State; + + if (lhs == State::Success && rhs == State::Success) { + return true; + } + + return false; +} + +bool operator&&(const OBS::PluginManager::State &lhs, bool rhs) +{ + return (lhs == OBS::PluginManager::State::Success && rhs); +} + +bool operator&&(bool lhs, const OBS::PluginManager::State &rhs) +{ + return (lhs && rhs == OBS::PluginManager::State::Success); +} + +bool operator||(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs) +{ + using State = OBS::PluginManager::State; + + if (lhs == State::Success || rhs == State::Success) { + return true; + } + + return false; +} + +bool operator||(const OBS::PluginManager::State &lhs, bool rhs) +{ + return (lhs == OBS::PluginManager::State::Success || rhs); +} + +bool operator||(bool lhs, const OBS::PluginManager::State &rhs) +{ + return (lhs || rhs == OBS::PluginManager::State::Success); +} diff --git a/frontend/plugin-manager/PluginManager.hpp b/frontend/plugin-manager/PluginManager.hpp index 2f6d5808ae9f7d..c07cf54c3ce274 100644 --- a/frontend/plugin-manager/PluginManager.hpp +++ b/frontend/plugin-manager/PluginManager.hpp @@ -43,22 +43,41 @@ struct ModuleInfo { }; class PluginManager { +public: + using ModuleList = std::vector; + enum class Mode { CoreOnly, Full }; + enum class State { Failure, PartialFailure, Success }; + private: + Mode loadMode_{Mode::Full}; + State loadState_{State::Failure}; + std::vector modules_ = {}; - std::vector disabledSources_ = {}; - std::vector disabledOutputs_ = {}; - std::vector disabledServices_ = {}; - std::vector disabledEncoders_ = {}; + ModuleList failedModules_{}; + ModuleList disabledSources_ = {}; + ModuleList disabledOutputs_ = {}; + ModuleList disabledServices_ = {}; + ModuleList disabledEncoders_ = {}; std::filesystem::path getConfigFilePath_(); - void loadModules_(); + void loadModuleConfiguration_(); void saveModules_(); void disableModules_(); void addModuleTypes_(); void linkUnloadedModules_(); + State loadPlugins(bool usePortableMode); + State loadLegacyPlugins(bool usePortableMode); + public: + Mode pluginMode() { return loadMode_; } + void setPluginMode(Mode mode) { loadMode_ = mode; } + + State loadState() { return loadState_; } + + void disablePlugins(); void preLoad(); void postLoad(); + void loadAllPlugins(bool usePortableMode); void open(); friend void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule); @@ -68,6 +87,14 @@ void addModuleToPluginManagerImpl(void *param, obs_module_t *newModule); }; // namespace OBS +bool operator&&(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs); +bool operator&&(const OBS::PluginManager::State &lhs, bool rhs); +bool operator&&(bool lhs, const OBS::PluginManager::State &rhs); + +bool operator||(const OBS::PluginManager::State &lhs, const OBS::PluginManager::State &rhs); +bool operator||(const OBS::PluginManager::State &lhs, bool rhs); +bool operator||(bool lhs, const OBS::PluginManager::State &rhs); + // Anonymous namespace function to add module to plugin manager // via libobs's module enumeration. namespace { diff --git a/frontend/plugin-manager/PluginManagerWindow.cpp b/frontend/plugin-manager/PluginManagerWindow.cpp index b29cb12c89f72f..0cb90a8c215958 100644 --- a/frontend/plugin-manager/PluginManagerWindow.cpp +++ b/frontend/plugin-manager/PluginManagerWindow.cpp @@ -33,7 +33,9 @@ extern bool safe_mode; namespace OBS { -PluginManagerWindow::PluginManagerWindow(std::vector const &modules, QWidget *parent) +PluginManagerWindow::PluginManagerWindow(std::vector const &modules, + std::vector const &failedModules, QWidget *parent) + : QDialog(parent), modules_(modules), ui(new Ui::PluginManagerWindow) @@ -61,6 +63,9 @@ PluginManagerWindow::PluginManagerWindow(std::vector const &modules, QListWidgetItem *installed = new QListWidgetItem(QTStr("PluginManager.Section.Manage")); ui->sectionList->addItem(installed); + QListWidgetItem *failed = new QListWidgetItem("Failed"); + ui->sectionList->addItem(failed); + QListWidgetItem *updates = new QListWidgetItem(QTStr("PluginManager.Section.Updates")); updates->setFlags(updates->flags() & ~Qt::ItemIsEnabled); updates->setFlags(updates->flags() & ~Qt::ItemIsSelectable); @@ -87,22 +92,35 @@ PluginManagerWindow::PluginManagerWindow(std::vector const &modules, int row = 0; int missingIndex = -1; for (auto &metadata : modules_) { - std::string id = metadata.module_name; + std::string_view id{metadata.module_name}; // Check if the module is missing: - bool missing = !obs_get_module(id.c_str()) && !obs_get_disabled_module(id.c_str()); + obs_module_t *moduleData = nullptr; + + moduleData = obs_get_module(id.data()); + + if (!moduleData) { + moduleData = obs_get_disabled_module(id.data()); + } + + bool isMissingModule = !moduleData; + bool isLegacyModule = !isMissingModule && obs_is_legacy_module(moduleData); QString name = !metadata.display_name.empty() ? metadata.display_name.c_str() : metadata.module_name.c_str(); - if (missing && missingIndex == -1) { + if (isMissingModule && missingIndex == -1) { missingIndex = row; } + if (isLegacyModule) { + name += " LEGACY"; + } + auto item = new QCheckBox(name); item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); item->setChecked(metadata.enabled); - if (!metadata.enabledAtLaunch || missing) { + if (!metadata.enabledAtLaunch || isMissingModule) { item->setProperty("class", "text-muted"); } @@ -116,6 +134,19 @@ PluginManagerWindow::PluginManagerWindow(std::vector const &modules, row++; } + QLabel *item = new QLabel("FAILED ITEMS"); + item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + ui->modulesList->layout()->addWidget(item); + + for (std::string const &moduleName : failedModules) { + QString name = QString::fromStdString(moduleName); + + QLabel *item = new QLabel(name); + item->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + item->setProperty("class", "text-muted"); + ui->modulesList->layout()->addWidget(item); + } + QVBoxLayout *layout = qobject_cast(ui->modulesList->layout()); if (safe_mode) { QLabel *safeModeLabel = new QLabel(ui->modulesList); @@ -175,4 +206,18 @@ bool PluginManagerWindow::isEnabledPluginsChanged() return result; } +void PluginManagerWindow::setPage(Page page) +{ + switch (page) { + case Page::Installed: + ui->sectionList->setCurrentRow(1); + break; + case Page::Failure: + ui->sectionList->setCurrentRow(2); + break; + default: + break; + } +} + }; // namespace OBS diff --git a/frontend/plugin-manager/PluginManagerWindow.hpp b/frontend/plugin-manager/PluginManagerWindow.hpp index 4c851067b4b7a6..54e19e4a9670e3 100644 --- a/frontend/plugin-manager/PluginManagerWindow.hpp +++ b/frontend/plugin-manager/PluginManagerWindow.hpp @@ -30,9 +30,14 @@ class PluginManagerWindow : public QDialog { std::unique_ptr ui; public: - explicit PluginManagerWindow(std::vector const &modules, QWidget *parent = nullptr); + enum class Page { Installed, Failure }; + + explicit PluginManagerWindow(std::vector const &modules, + std::vector const &failedModules, QWidget *parent = nullptr); inline std::vector const result() { return modules_; } + void setPage(Page page); + private: std::vector modules_; diff --git a/frontend/plugin-manager/PluginModuleLoader.hpp b/frontend/plugin-manager/PluginModuleLoader.hpp new file mode 100644 index 00000000000000..3865912940e3c9 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader.hpp @@ -0,0 +1,66 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include + +using FailureInfo = obs_module_failure_info; +using ModuleLoadInfo = obs_runtime_module_info; +using ModuleList = std::vector; + +inline constexpr std::string_view kPathVariable{"OBS_PLUGINS_PATH"}; +inline constexpr std::string_view kLegacyBinaryPathVariable{"OBS_LEGACY_PLUGINS_PATH"}; +inline constexpr std::string_view kLegacyDataPathVariable{"OBS_LEGACY_PLUGINS_DATA_PATH"}; + +inline std::string getEnvironmentVariable(std::string_view variableName) +{ + std::unique_ptr variablePointer{}; + variablePointer.reset(getenv(variableName.data())); + + if (!variablePointer) { + return {}; + } + + std::string result{variablePointer.release()}; + + return result; +} + +inline int loadPluginsByInfo(const ModuleLoadInfo &info, ModuleList &failedModules) +{ + FailureInfo result = {0}; + + obs_load_plugins(const_cast(std::addressof(info)), std::addressof(result)); + + for (size_t i = 0; i < result.count; ++i) { + const char *failedPluginName = result.failed_modules[i]; + + if (failedPluginName && *failedPluginName) { + failedModules.emplace_back(failedPluginName); + } + } + + obs_module_failure_info_free(std::addressof(result)); + + return static_cast(result.count); +} diff --git a/frontend/plugin-manager/PluginModuleLoader_BSD.cpp b/frontend/plugin-manager/PluginModuleLoader_BSD.cpp new file mode 100644 index 00000000000000..94a1c6f6e73272 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_BSD.cpp @@ -0,0 +1,219 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" +#include + +#include +#include +#include + +#include +#include +#include +#include + +// BSD 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.so +// * Data: //data +// +// * System Root Path: /usr +// * Binary: //obs-modules/plugins/.so +// * Data: /share/obs/obs-modules/plugins/ +// +// * User Root Path: /home//.config +// * Binary: /obs-studio/plugins//.so +// * Data: /obs-studio/plugins//data +// +// * Legacy User Path: + +// * Binary: /.so +// * Data: / +// +// * Legacy System Root Path: /usr +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy User Root Path: /home//.config +// * Binary: /obs-studio/plugins//bin/64bit/.so +// * Data: /obs-studio/plugins//data +// +// * Legacy Fallback Path: +// * Binary: /../../obs-plugins/64bit/.so +// * Data: /share/obs/obs-plugins/ +// + +using State = OBS::PluginManager::State; +using ModuleType = obs_runtime_module_type; + +namespace Constants = OBS::Constants; + +constexpr std::string_view kModulePathSuffix{"/%module%/"}; +constexpr std::string_view kModuleDataPathSuffix{"/%module%/data/"}; +constexpr std::string_view kConfigBinaryPath{"/obs-studio/plugins/%module%/"}; +constexpr std::string_view kConfigDataPath{"/obs-studio/plugins/%module%/data/"}; + +constexpr std::string_view kUserBinaryPath{"/%module%"}; +constexpr std::string_view kUserDataPath{"/%module%/data"}; +constexpr std::string_view kLegacyConfigBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"}; +constexpr std::string_view kLegacyConfigDataPath{"/obs-studio/plugins/%module%/data"}; + +namespace { +State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules) +{ + int failedModuleCount = loadPluginsByInfo(info, failedModules); + + State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + + return result; +} +} // namespace + +namespace OBS { +State PluginManager::loadPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + std::string userPluginPath = getEnvironmentVariable(kPathVariable); + + if (!userPluginPath.empty()) { + std::string binaryPath{userPluginPath}; + std::string dataPath{userPluginPath}; + binaryPath.append(kModulePathSuffix); + dataPath.append(kModuleDataPathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + if (Constants::kPlatformPluginModulePath.size() && Constants::kPlatformPluginDataPath.size()) { + std::string binaryPath{Constants::kPlatformPluginModulePath}; + std::string dataPath{Constants::kPlatformPluginDataPath}; + + dataPath.append(kModulePathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + systemPluginState = pluginLoadHelper(info, failedModules_); + } else { + systemPluginState = State::Success; + } + + State configPluginState = State::Failure; + + if (!usePortableMode) { + std::string userConfigPath{}; + userConfigPath.resize(PATH_MAX); + int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr); + + if (bufferSize > 0) { + userConfigPath.resize(bufferSize); + std::string binaryPath{userConfigPath}; + std::string dataPath{userConfigPath}; + + binaryPath.append(kConfigBinaryPath); + dataPath.append(kConfigDataPath); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + configPluginState = pluginLoadHelper(info, failedModules_); + } + } else { + configPluginState = State::Success; + } + + return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure; +} + +State PluginManager::loadLegacyPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + + std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable); + std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable); + + if (!userPluginPath.empty() && !userDataPath.empty()) { + std::string binaryPath{userPluginPath}; + std::string dataPath{userDataPath}; + + dataPath.append(kModulePathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + { + std::string binaryPath{Constants::kPlatformInstallPath}; + std::string dataPath{Constants::kPlatformInstallPath}; + binaryPath.append(Constants::kPlatformLibraryPath); + binaryPath.append("obs-plugins/"); + dataPath.append(Constants::kPlatformDataPath); + dataPath.append("obs/obs-plugins/%module%"); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + systemPluginState = pluginLoadHelper(info, failedModules_); + } + + State configPluginState = State::Failure; + + if (!usePortableMode) { + std::string userConfigPath{}; + userConfigPath.resize(PATH_MAX); + int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr); + + if (bufferSize > 0) { + userConfigPath.resize(bufferSize); + std::string binaryPath{userConfigPath}; + std::string dataPath{userConfigPath}; + + binaryPath.append(kLegacyConfigBinaryPath); + dataPath.append(kLegacyConfigDataPath); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + configPluginState = pluginLoadHelper(info, failedModules_); + } else { + configPluginState = State::Success; + } + } + + return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_Linux.cpp b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp new file mode 100644 index 00000000000000..9f214ffdca4a9b --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_Linux.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" +#include + +#include +#include +#include + +#include +#include +#include +#include + +// Linux 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.so +// * Data: //data +// +// * System Root Path: /usr +// * Binary: //obs-modules/plugins/.so +// * Data: /share/obs/obs-modules/plugins/ +// +// * User Root Path: /home//.config +// * Binary: /obs-studio/plugins//.so +// * Data: /obs-studio/plugins//data +// +// * Legacy User Path: + +// * Binary: /.so +// * Data: / +// +// * Legacy System Root Path: /usr +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy User Root Path: /home//.config +// * Binary: /obs-studio/plugins//bin/64bit/.so +// * Data: /obs-studio/plugins//data +// +// * Legacy Fallback Path: +// * Binary: /../../obs-plugins/64bit/.so +// * Data: /share/obs/obs-plugins/ +// +// * Legacy Flatpak Root Path: /app/plugins +// * Binary: //obs-plugins/.so +// * Data: /share/obs/obs-plugins/ +// + +using State = OBS::PluginManager::State; +using ModuleType = obs_runtime_module_type; + +namespace Constants = OBS::Constants; + +constexpr std::string_view kModulePathSuffix{"/%module%/"}; +constexpr std::string_view kModuleDataPathSuffix{"/%module%/data/"}; +constexpr std::string_view kConfigBinaryPath{"/obs-studio/plugins/%module%/"}; +constexpr std::string_view kConfigDataPath{"/obs-studio/plugins/%module%/data/"}; + +constexpr std::string_view kUserBinaryPath{"/%module%"}; +constexpr std::string_view kUserDataPath{"/%module%/data"}; +constexpr std::string_view kLegacyConfigBinaryPath{"/obs-studio/plugins/%module%/bin/64bit"}; +constexpr std::string_view kLegacyConfigDataPath{"/obs-studio/plugins/%module%/data"}; +constexpr std::string_view kLegacyFlatpakPath{"/app/plugins/"}; + +namespace { +State pluginLoadHelper(const ModuleLoadInfo &info, ModuleList &failedModules) +{ + int failedModuleCount = loadPluginsByInfo(info, failedModules); + + State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + + return result; +} +} // namespace + +namespace OBS { +State PluginManager::loadPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + std::string userPluginPath = getEnvironmentVariable(kPathVariable); + + if (!userPluginPath.empty()) { + std::string binaryPath{userPluginPath}; + std::string dataPath{userPluginPath}; + binaryPath.append(kModulePathSuffix); + dataPath.append(kModuleDataPathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + if (Constants::kPlatformPluginModulePath.size() && Constants::kPlatformPluginDataPath.size()) { + std::string binaryPath{Constants::kPlatformPluginModulePath}; + std::string dataPath{Constants::kPlatformPluginDataPath}; + + dataPath.append(kModulePathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + + systemPluginState = pluginLoadHelper(info, failedModules_); + } else { + systemPluginState = State::Success; + } + + State configPluginState = State::Failure; + + if (!usePortableMode) { + std::string userConfigPath{}; + userConfigPath.resize(PATH_MAX); + int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr); + + if (bufferSize > 0) { + userConfigPath.resize(bufferSize); + std::string binaryPath{userConfigPath}; + std::string dataPath{userConfigPath}; + + binaryPath.append(kConfigBinaryPath); + dataPath.append(kConfigDataPath); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_PLUGIN, + .name = nullptr}; + configPluginState = pluginLoadHelper(info, failedModules_); + } + } else { + configPluginState = State::Success; + } + + return (userPluginState && systemPluginState && configPluginState) ? State::Success : State::PartialFailure; +} + +State PluginManager::loadLegacyPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + + std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable); + std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable); + + if (!userPluginPath.empty() && !userDataPath.empty()) { + std::string binaryPath{userPluginPath}; + std::string dataPath{userDataPath}; + + dataPath.append(kModulePathSuffix); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + userPluginState = pluginLoadHelper(info, failedModules_); + } else { + userPluginState = State::Success; + } + + State systemPluginState = State::Failure; + + { + std::string binaryPath{Constants::kPlatformInstallPath}; + std::string dataPath{Constants::kPlatformInstallPath}; + binaryPath.append(Constants::kPlatformLibraryPath); + binaryPath.append("obs-plugins/"); + dataPath.append(Constants::kPlatformDataPath); + dataPath.append("obs/obs-plugins/%module%"); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + systemPluginState = pluginLoadHelper(info, failedModules_); + } + + State configPluginState = State::Failure; + + if (!usePortableMode) { + std::string userConfigPath{}; + userConfigPath.resize(PATH_MAX); + int bufferSize = os_get_config_path(userConfigPath.data(), userConfigPath.size(), nullptr); + + if (bufferSize > 0) { + userConfigPath.resize(bufferSize); + std::string binaryPath{userConfigPath}; + std::string dataPath{userConfigPath}; + + binaryPath.append(kLegacyConfigBinaryPath); + dataPath.append(kLegacyConfigDataPath); + + ModuleLoadInfo info = {.path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + configPluginState = pluginLoadHelper(info, failedModules_); + } else { + configPluginState = State::Success; + } + } + + State flatpakPluginState = State::Failure; + + { + std::string binaryPath{kLegacyFlatpakPath}; + std::string dataPath{kLegacyFlatpakPath}; + binaryPath.append(Constants::kPlatformLibraryPath); + binaryPath.append("obs-plugins/"); + dataPath.append(Constants::kPlatformDataPath); + dataPath.append("obs/obs-plugins/%module%"); + + ModuleLoadInfo info = {.path_info = + { + .binary = binaryPath.c_str(), + .data = dataPath.c_str(), + }, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr}; + + flatpakPluginState = pluginLoadHelper(info, failedModules_); + } + + return (userPluginState && systemPluginState && configPluginState && flatpakPluginState) + ? State::Success + : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_MacOS.mm b/frontend/plugin-manager/PluginModuleLoader_MacOS.mm new file mode 100644 index 00000000000000..3b0c514ed78267 --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_MacOS.mm @@ -0,0 +1,195 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" + +#include +#include +#include + +#include +#include +#include + +// macOS 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: /.plugin/Contents/MacOS +// * Data: /.plugin/Contents/Resources +// +// * Root Path: /Library/Application Support +// * Binary: /obs-studio/plugins/.plugin/Contents/MacOS/ +// * Data: /obs-studio/plugins/.plugin/Contents/Resources +// +// * Legacy User Path: + +// * Binary: /.plugin/Contents/MacOS/ +// * Data: /.plugin/Contents/Resources +// +// * Legacy System Root Path: /Library/Application Support +// * Legacy binary: /obs-studio/plugins//bin/.so +// * Legacy data: /obs-studio/plugins//data +// +// * Legacy User Root Path: /Library/Application Support +// * Legacy binary: /obs-studio/plugins//bin/.so +// * Legacy data: /obs-studio/plugins//data +// + +using State = OBS::PluginManager::State; +using ModuleType = obs_runtime_module_type; + +constexpr std::string_view kPluginPathSuffix {"obs-studio/plugins/%module%.plugin"}; +constexpr std::string_view kUserPluginPathSuffix {"/%module%.plugin"}; +constexpr std::string_view kLegacyPluginPathSuffix {"obs-studio/plugins/%module%"}; + +#ifdef __aarch64__ +constexpr bool kIsAppleSilicon = true; +#else +constexpr bool kIsAppleSilicon = false; +#endif + +namespace { + State loadPluginsFromPath(const std::string &pathString, ModuleType type, ModuleList &failedModules) + { + std::string binaryPath {pathString}; + std::string dataPath {pathString}; + + switch (type) { + case MODULE_TYPE_LEGACY_PLUGIN: { + binaryPath.append("/bin"); + dataPath.append("/data"); + break; + } + default: { + binaryPath.append("/Contents/MacOS"); + dataPath.append("/Contents/Resources"); + break; + } + } + + ModuleLoadInfo info = { + .path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = type, + .name = nullptr + }; + + int failedModuleCount = loadPluginsByInfo(info, failedModules); + + State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + + return result; + } + + State loadPluginsFromLegacyPaths(const std::string &binaryPathString, const std::string &dataPathString, + ModuleList &failedModules) + { + std::string binaryPath {binaryPathString}; + std::string dataPath {dataPathString}; + + binaryPath.append("/%module%.plugin/Contents/MacOS"); + dataPath.append("/%module%.plugin/Contents/Resources"); + + ModuleLoadInfo info = { + .path_info = {.binary = binaryPath.c_str(), .data = dataPath.c_str()}, + .type = MODULE_TYPE_LEGACY_PLUGIN, + .name = nullptr + }; + + int failedModuleCount = loadPluginsByInfo(info, failedModules); + + State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + + return result; + } + +} // namespace + +namespace OBS { + State PluginManager::loadPlugins(bool usePortableMode __unused) + { + State userPluginState = State::Failure; + std::string userPluginPath = getEnvironmentVariable(kPathVariable); + + if (!userPluginPath.empty()) { + userPluginPath.append(kUserPluginPathSuffix); + + userPluginState = loadPluginsFromPath(userPluginPath, MODULE_TYPE_PLUGIN, failedModules_); + } else { + userPluginState = State::Success; + } + + State libraryPluginState = State::Failure; + + std::string appLibraryPath {}; + appLibraryPath.resize(PATH_MAX); + int bufferSize = os_get_config_path(appLibraryPath.data(), appLibraryPath.size(), kPluginPathSuffix.data()); + if (bufferSize > 0) { + appLibraryPath.resize(bufferSize); + + libraryPluginState = loadPluginsFromPath(appLibraryPath, MODULE_TYPE_PLUGIN, failedModules_); + } + + return (userPluginState && libraryPluginState) ? State::Success : State::PartialFailure; + } + + State PluginManager::loadLegacyPlugins(bool usePortableMode __unused) + { + State userPluginState = State::Failure; + + std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable); + std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable); + + if (!userPluginPath.empty() && !userDataPath.empty()) { + userPluginState = loadPluginsFromLegacyPaths(userPluginPath, userDataPath, failedModules_); + } else { + userPluginState = State::Success; + } + + State legacyPluginState = State::Failure; + + if constexpr (!kIsAppleSilicon) { + State systemLibraryState = State::Failure; + + std::string systemLibraryPath {}; + systemLibraryPath.resize(PATH_MAX); + int bufferSize = os_get_program_data_path(systemLibraryPath.data(), systemLibraryPath.size(), + kLegacyPluginPathSuffix.data()); + + if (bufferSize > 0) { + systemLibraryPath.resize(bufferSize); + systemLibraryState = loadPluginsFromPath(systemLibraryPath, MODULE_TYPE_LEGACY_PLUGIN, failedModules_); + } + + State appLibrayState = State::Failure; + std::string appLibraryPath {}; + appLibraryPath.resize(PATH_MAX); + bufferSize = + os_get_config_path(appLibraryPath.data(), appLibraryPath.size(), kLegacyPluginPathSuffix.data()); + + if (bufferSize > 0) { + appLibraryPath.resize(bufferSize); + appLibrayState = loadPluginsFromPath(appLibraryPath, MODULE_TYPE_LEGACY_PLUGIN, failedModules_); + } + + legacyPluginState = (systemLibraryState && appLibrayState) ? State::Success : State::PartialFailure; + } else { + legacyPluginState = State::Success; + } + + return (userPluginState && legacyPluginState) ? State::Success : State::PartialFailure; + } +} // namespace OBS diff --git a/frontend/plugin-manager/PluginModuleLoader_Windows.cpp b/frontend/plugin-manager/PluginModuleLoader_Windows.cpp new file mode 100644 index 00000000000000..e78f15527053df --- /dev/null +++ b/frontend/plugin-manager/PluginModuleLoader_Windows.cpp @@ -0,0 +1,193 @@ +/****************************************************************************** + Copyright (C) 2026 by FiniteSingularity + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "PluginModuleLoader.hpp" +#include "PluginManager.hpp" + +#include +#include +#include + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// Windows 3rd-Party Plugin Locations +// +// * User Path: +// * Binary: //.dll +// * Data: //data +// +// * Root Path: C:/ProgramData +// * Binary: /obs-studio/plugins//.dll +// * Data: /obs-studio/plugins//data +// +// * Portable Root Path: +// * Binary: /../../plugins//.dll +// * Data: /../../plugins//data +// +// * Legacy User Path: + +// * Binary: /.dll +// * Data: //data +// +// * Legacy Root Path: +// * Binary: /../../obs-plugins/.dll +// * Data: /../../data/obs-plugins/ +// +// * Legacy Portable Path: +// * Binary: /../../obs-plugins/.dll +// * Data: /../../data/obs-plugins/ +// +// * Legacy System Path: C:/ProgramData +// * Binary: /obs-studio/plugins//bin/.dll +// * Data: /obs-studio/plugins//data +// + +using State = OBS::PluginManager::State; +using ModuleType = obs_runtime_module_type; + +constexpr std::string_view kPluginPathSuffix{"obs-studio/plugins/%module%"}; +constexpr std::string_view kUserPluginPathSuffix{"/%module%"}; +constexpr std::string_view kPortablePluginPath{"../../plugins/%module%"}; + +constexpr std::string_view kLegacyPortableBinaryPath{"../../obs-plugins/"}; +constexpr std::string_view kLegacyPortableDataPath{"../../data/obs-plugins/%module%"}; + +namespace { +State loadPluginsFromPath(const std::string &pathString, ModuleType type, ModuleList &failedModules) +{ + std::string binaryPath{pathString}; + std::string dataPath{pathString}; + + switch (type) { + case MODULE_TYPE_LEGACY_PLUGIN: { + binaryPath.append("/bin/64bit"); + dataPath.append("/data"); + break; + } + default: { + dataPath.append("/data"); + break; + } + } + + ModuleLoadInfo info = {0}; + info.path_info.binary = binaryPath.c_str(); + info.path_info.data = dataPath.c_str(); + info.type = type; + info.name = nullptr; + + int failedModuleCount = loadPluginsByInfo(info, failedModules); + + State result = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + + return result; +} +} // namespace + +namespace OBS { +State PluginManager::loadPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + std::string userPluginPath = getEnvironmentVariable(kPathVariable); + + if (!userPluginPath.empty()) { + userPluginPath.append(kUserPluginPathSuffix); + + userPluginState = loadPluginsFromPath(userPluginPath, MODULE_TYPE_PLUGIN, failedModules_); + } else { + userPluginState = State::Success; + } + + State libraryPluginState = State::Failure; + std::string appLibraryPath{}; + + if (!usePortableMode) { + appLibraryPath.resize(MAX_PATH); + int bufferSize = os_get_program_data_path(appLibraryPath.data(), appLibraryPath.size(), + kPluginPathSuffix.data()); + if (bufferSize > 0) { + appLibraryPath.resize(bufferSize); + } + } else { + appLibraryPath = kPortablePluginPath; + } + + libraryPluginState = loadPluginsFromPath(appLibraryPath, MODULE_TYPE_PLUGIN, failedModules_); + + return (userPluginState && libraryPluginState) ? State::Success : State::PartialFailure; +} + +State PluginManager::loadLegacyPlugins(bool usePortableMode) +{ + State userPluginState = State::Failure; + + std::string userPluginPath = getEnvironmentVariable(kLegacyBinaryPathVariable); + std::string userDataPath = getEnvironmentVariable(kLegacyDataPathVariable); + + if (!userPluginPath.empty() && !userDataPath.empty()) { + userDataPath.append(kUserPluginPathSuffix); + + ModuleLoadInfo info = {0}; + info.path_info.binary = userPluginPath.c_str(); + info.path_info.data = userDataPath.c_str(); + info.type = MODULE_TYPE_LEGACY_PLUGIN; + info.name = nullptr; + + int failedModuleCount = loadPluginsByInfo(info, failedModules_); + + userPluginState = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + } else { + userPluginState = State::Success; + } + + State portablePluginState = State::Failure; + + { + ModuleLoadInfo info = {0}; + info.path_info.binary = kLegacyPortableBinaryPath.data(); + info.path_info.data = kLegacyPortableDataPath.data(); + info.type = MODULE_TYPE_LEGACY_PLUGIN; + info.name = nullptr; + + int failedModuleCount = loadPluginsByInfo(info, failedModules_); + + portablePluginState = (failedModuleCount > 0) ? State::PartialFailure : State::Success; + } + + State libraryPluginState = State::Failure; + + if (!usePortableMode) { + std::string appLibraryPath{}; + appLibraryPath.resize(MAX_PATH); + int bufferSize = os_get_program_data_path(appLibraryPath.data(), appLibraryPath.size(), + kPluginPathSuffix.data()); + if (bufferSize > 0) { + appLibraryPath.resize(bufferSize); + libraryPluginState = + loadPluginsFromPath(appLibraryPath, MODULE_TYPE_LEGACY_PLUGIN, failedModules_); + } + } else { + libraryPluginState = State::Success; + } + + return (userPluginState && portablePluginState && libraryPluginState) ? State::Success : State::PartialFailure; +} +} // namespace OBS diff --git a/frontend/updater/init-hook-files.c b/frontend/updater/init-hook-files.c index a5df3c767bf67a..6f2e867a0a357d 100644 --- a/frontend/updater/init-hook-files.c +++ b/frontend/updater/init-hook-files.c @@ -106,7 +106,7 @@ static LSTATUS get_reg(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name, bool b64) } while (false) #define IMPLICIT_LAYERS L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers" -#define HOOK_LOCATION L"\\data\\obs-plugins\\win-capture\\" +#define HOOK_LOCATION L"\\core\\win-capture\\data\\" static bool update_hook_file(bool b64) { diff --git a/frontend/updater/updater.cpp b/frontend/updater/updater.cpp index fcb1248b2ddd6f..bbe9f7f63a136f 100644 --- a/frontend/updater/updater.cpp +++ b/frontend/updater/updater.cpp @@ -741,7 +741,7 @@ static inline bool FileExists(const wchar_t *path) static bool NonCorePackageInstalled(const char *name) { if (strcmp(name, "obs-browser") == 0) { - return FileExists(L"obs-plugins\\64bit\\obs-browser.dll"); + return FileExists(L"core\\obs-browser\\obs-browser.dll"); } return false; @@ -1744,7 +1744,7 @@ static bool Update(wchar_t *cmdLine) StringCbCat(regsvr, sizeof(regsvr), L"\\regsvr32.exe"); StringCbCopy(src, sizeof(src), obs_base_directory); - StringCbCat(src, sizeof(src), L"\\data\\obs-plugins\\win-dshow\\"); + StringCbCat(src, sizeof(src), L"\\core\\win-dshow\\data\\"); StringCbCopy(tmp, sizeof(tmp), L"\""); StringCbCat(tmp, sizeof(tmp), regsvr); diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index b6f5445305f301..ce02584cbebf7b 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -112,78 +112,6 @@ namespace { std::once_flag saveOnceFlag; } -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) { - plugins_path = s; - } - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) { - plugins_data_path = s; - } - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) { - return; - } - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) { - return; - } - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - /* First-party modules considered to be potentially unsafe to load in Safe Mode * due to them allowing external code (e.g. scripts) to modify OBS's state. */ static const unordered_set unsafe_modules = { @@ -191,42 +119,6 @@ static const unordered_set unsafe_modules = { "obs-websocket", // Allows outside modifications }; -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules_(SAFE_MODULES); - - while (getline(modules_, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) { - obs_add_safe_module(module.c_str()); - } - } -#endif -} - -static void SetCoreModuleNames() -{ -#ifndef SAFE_MODULES - throw "SAFE_MODULES not defined"; -#else - std::string safeModules = SAFE_MODULES; - if (safeModules.empty()) { - throw "SAFE_MODULES is empty"; - } - string module; - stringstream modules_(SAFE_MODULES); - - while (getline(modules_, module, '|')) { - obs_add_core_module(module.c_str()); - } -#endif -} - extern void setupDockAction(QDockWidget *dock); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) @@ -1035,27 +927,15 @@ void OBSBasic::OBSInit() #if defined(_WIN32) && !defined(_DEBUG) LoadLibraryW(L"Qt6Network"); #endif - struct obs_module_failure_info mfi; - - // Safe Mode disables third-party plugins so we don't need to add each path outside the OBS bundle/installation. - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - // Core modules are not allowed to be disabled by the user via plugin manager. - SetCoreModuleNames(); - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after initializing global settings) to ensure legacy code gets valid path information. - */ + // Modules can access frontend information (i.e. profile and scene collection data) during their initialization, + // and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their + // own code. + // + // Thus the profile and scene collection discovery needs to happen before any access to that information happens + // (but after initializing global settings) to ensure legacy code gets valid path information. RefreshSceneCollections(true); - App()->loadAppModules(mfi); - - BPtr failed_modules = mfi.failed_modules; + App()->loadAppModules(); #ifdef BROWSER_AVAILABLE cef = obs_browser_init_panel(); @@ -1374,22 +1254,7 @@ void OBSBasic::OBSInit() 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); - } + App()->handlePluginLoadState(); } void OBSBasic::OnFirstLoad() diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index b20c2fa476ce5c..acceaba87d4c39 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -7,6 +7,8 @@ if(OS_WINDOWS AND NOT OBS_PARENT_ARCHITECTURE STREQUAL CMAKE_VS_PLATFORM_NAME) return() endif() +include(cmake/obs-core-modules.cmake) + find_package(SIMDe REQUIRED) find_package(Threads REQUIRED) @@ -37,6 +39,10 @@ target_sources( obs-avc.h obs-canvas.c obs-config.h + $<$:obs-core-modules-loader-mac.m> + $<$:obs-core-modules-loader-win.c> + $<$:obs-core-modules-loader-nix.c> + obs-core-modules-loader.h obs-data.c obs-data.h obs-defs.h @@ -241,6 +247,7 @@ target_link_libraries( PRIVATE OBS::caption OBS::libobs-version + OBS::libobs-core-modules FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil diff --git a/libobs/cmake/obs-core-modules.cmake b/libobs/cmake/obs-core-modules.cmake new file mode 100644 index 00000000000000..b9b600c249c71d --- /dev/null +++ b/libobs/cmake/obs-core-modules.cmake @@ -0,0 +1,8 @@ +add_library(libobs-core-modules OBJECT) +add_library(OBS::libobs-core-modules ALIAS libobs-core-modules) + +target_sources(libobs-core-modules PRIVATE obs-core-modules.c obs-core-modules.h) + +target_include_directories(libobs-core-modules PUBLIC "$") + +set_property(TARGET libobs-core-modules PROPERTY FOLDER core) diff --git a/libobs/obs-cocoa.m b/libobs/obs-cocoa.m index 08b64ef1b0aa01..6acb1f1629dad4 100644 --- a/libobs/obs-cocoa.m +++ b/libobs/obs-cocoa.m @@ -35,15 +35,6 @@ return ""; } -void add_default_module_paths(void) -{ - NSURL *pluginURL = [[NSBundle mainBundle] builtInPlugInsURL]; - NSString *pluginModulePath = [[pluginURL path] stringByAppendingString:@"/%module%.plugin/Contents/MacOS/"]; - NSString *pluginDataPath = [[pluginURL path] stringByAppendingString:@"/%module%.plugin/Contents/Resources/"]; - - obs_add_module_path(pluginModulePath.UTF8String, pluginDataPath.UTF8String); -} - char *find_libobs_data_file(const char *file) { NSBundle *frameworkBundle = [NSBundle bundleWithIdentifier:@"com.obsproject.libobs"]; diff --git a/libobs/obs-core-modules-loader-mac.m b/libobs/obs-core-modules-loader-mac.m new file mode 100644 index 00000000000000..3813c264bd6acc --- /dev/null +++ b/libobs/obs-core-modules-loader-mac.m @@ -0,0 +1,43 @@ +#import "obs-core-modules.h" + +#import +#import +#import +#import + +#import + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + NSURL *pluginURL = [[NSBundle mainBundle] builtInPlugInsURL]; + NSString *pluginBasePath = [pluginURL path]; + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + NSString *moduleName = [NSString stringWithUTF8String:name]; + + NSString *binPath = + [NSString stringWithFormat:@"%@/%@.plugin/Contents/MacOS/%@", pluginBasePath, moduleName, moduleName]; + + NSString *dataPath = + [NSString stringWithFormat:@"%@/%@.plugin/Contents/Resources/", pluginBasePath, moduleName]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:binPath]) { + blog(LOG_ERROR, "Core Module %s required but missing!", name); + return; + } + + struct obs_runtime_module_info module_info = { + .path_info = {.binary = [binPath UTF8String], .data = [dataPath UTF8String]}, + .type = MODULE_TYPE_CORE, + .name = name + }; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + } +} diff --git a/libobs/obs-core-modules-loader-nix.c b/libobs/obs-core-modules-loader-nix.c new file mode 100644 index 00000000000000..5352be4fc429f4 --- /dev/null +++ b/libobs/obs-core-modules-loader-nix.c @@ -0,0 +1,43 @@ +#include "obs-core-modules.h" + +#include +#include +#include +#include + +const char *core_module_bin = "../" OBS_PLUGIN_PATH "/%module%"; +const char *core_module_data = "../" OBS_DATA_PATH "/obs-modules/core/%module%"; + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + char *core_bin_path = os_get_executable_path_ptr(core_module_bin); + char *core_data_path = os_get_executable_path_ptr(core_module_data); + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + struct dstr bin_path = {0}; + struct dstr data_path = {0}; + dstr_init_copy(&bin_path, core_bin_path); + dstr_init_copy(&data_path, core_data_path); + dstr_replace(&bin_path, "%module%", name); + dstr_replace(&data_path, "%module%", name); + + struct obs_runtime_module_info module_info = {.path_info = {.binary = bin_path.array, + .data = data_path.array}, + .type = MODULE_TYPE_CORE, + .name = name}; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + + dstr_free(&bin_path); + dstr_free(&data_path); + } + + bfree(core_bin_path); + bfree(core_data_path); +} diff --git a/libobs/obs-core-modules-loader-win.c b/libobs/obs-core-modules-loader-win.c new file mode 100644 index 00000000000000..302aed8fe18754 --- /dev/null +++ b/libobs/obs-core-modules-loader-win.c @@ -0,0 +1,47 @@ +#include "obs-core-modules.h" + +#include +#include +#include +#include + +const char *core_module_bin = "../../core/%module%/%module%"; +const char *core_module_data = "../../core/%module%/data"; + +extern bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + +void load_core_modules(obs_find_module_callback2_t callback, void *data) +{ + char *core_bin_path = os_get_abs_path_ptr(core_module_bin); + char *core_data_path = os_get_abs_path_ptr(core_module_data); + + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + const char *name = obs_core_modules[i]; + struct dstr bin_path = {0}; + struct dstr data_path = {0}; + dstr_init_copy(&bin_path, core_bin_path); + dstr_init_copy(&data_path, core_data_path); + dstr_replace(&bin_path, "%module%", name); + dstr_replace(&data_path, "%module%", name); + + // Convert windows backslash to forward slash + dstr_replace(&bin_path, "\\", "/"); + dstr_replace(&data_path, "\\", "/"); + + struct obs_runtime_module_info module_info = {.path_info = {.binary = bin_path.array, + .data = data_path.array}, + .type = MODULE_TYPE_CORE, + .name = name}; + + if (!find_core_module(&module_info, callback, data)) { + blog(LOG_ERROR, "Failed to load core module %s", name); + return; + } + + dstr_free(&bin_path); + dstr_free(&data_path); + } + + bfree(core_bin_path); + bfree(core_data_path); +} diff --git a/libobs/obs-core-modules-loader.h b/libobs/obs-core-modules-loader.h new file mode 100644 index 00000000000000..66a231269fc7e9 --- /dev/null +++ b/libobs/obs-core-modules-loader.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern void load_core_modules(obs_find_module_callback2_t callback, void *data); diff --git a/libobs/obs-core-modules.c.in b/libobs/obs-core-modules.c.in new file mode 100644 index 00000000000000..8ee5221525c4f2 --- /dev/null +++ b/libobs/obs-core-modules.c.in @@ -0,0 +1,17 @@ +#include +#include + +const char *obs_core_modules[] = { +@OBS_CORE_MODULE_LIST@ +}; + +const unsigned int obs_core_modules_count = @OBS_CORE_MODULE_COUNT@; + +bool obs_in_core_module_list(const char *name) { + for (unsigned int i = 0; i < obs_core_modules_count; i++) { + if(strcmp(obs_core_modules[i], name) == 0) { + return true; + } + } + return false; +} diff --git a/libobs/obs-core-modules.h b/libobs/obs-core-modules.h new file mode 100644 index 00000000000000..297a6eaf6f6fb6 --- /dev/null +++ b/libobs/obs-core-modules.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +extern const char *obs_core_modules[]; +extern const unsigned int obs_core_modules_count; + +extern bool obs_in_core_module_list(const char *name); diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index ca5bdac88afdf5..924ff04bd4dbe7 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -135,6 +135,8 @@ struct obs_module { DARRAY(char *) outputs; DARRAY(char *) encoders; DARRAY(char *) services; + + enum obs_runtime_module_type module_type; }; struct obs_disabled_module { @@ -576,6 +578,8 @@ struct obs_core { os_task_queue_t *destruction_task_thread; obs_task_handler_t ui_task_handler; + + bool core_modules_loaded; }; extern struct obs_core *obs; diff --git a/libobs/obs-module.c b/libobs/obs-module.c index d623607be67d16..b46ad4060143c8 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -15,12 +15,13 @@ along with this program. If not, see . ******************************************************************************/ -#include "util/platform.h" -#include "util/dstr.h" - +#include "obs-core-modules-loader.h" +#include "obs-core-modules.h" #include "obs-defs.h" #include "obs-internal.h" #include "obs-module.h" +#include "util/dstr.h" +#include "util/platform.h" extern const char *get_module_extension(void); @@ -556,42 +557,47 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info) } } -static const char *obs_load_all_modules_name = "obs_load_all_modules"; -#ifdef _WIN32 -static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths"; -#endif - void obs_load_all_modules(void) { - profile_start(obs_load_all_modules_name); - obs_find_modules2(load_all_callback, NULL); -#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_modules_name); + return; } -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)); + UNUSED_PARAMETER(mfi); + return; +} - 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); +bool obs_load_core_modules() +{ + if (obs->core_modules_loaded) { + return true; + } + + struct fail_info error = {0}; + + load_core_modules(load_all_callback, &error); + + bool has_core_module_failure = error.fail_count > 0; + + dstr_free(&error.fail_modules); + + return !has_core_module_failure; +} + +static void find_modules_in_path(struct obs_module_path *omp, obs_find_module_callback2_t callback, void *param); + +void obs_load_plugins(struct obs_runtime_module_info *info, struct obs_module_failure_info *error) +{ + struct obs_module_path omp = {.bin = (char *)info->path_info.binary, .data = (char *)info->path_info.data}; + + struct fail_info failure = {0}; + + find_modules_in_path(&omp, load_all_callback, &failure); - mfi->count = fail_info.fail_count; - mfi->failed_modules = strlist_split(fail_info.fail_modules.array, ';', false); - dstr_free(&fail_info.fail_modules); + error->count = failure.fail_count; + error->failed_modules = strlist_split(failure.fail_modules.array, ';', false); + dstr_free(&failure.fail_modules); } void obs_module_failure_info_free(struct obs_module_failure_info *mfi) @@ -602,8 +608,17 @@ void obs_module_failure_info_free(struct obs_module_failure_info *mfi) } } +#ifdef _WIN32 +static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths"; +#endif + void obs_post_load_modules(void) { +#ifdef _WIN32 + profile_start(reset_win32_symbol_paths_name); + reset_win32_symbol_paths(); + profile_end(reset_win32_symbol_paths_name); +#endif for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next) if (mod->post_load) mod->post_load(); @@ -632,6 +647,61 @@ static char *make_data_directory(const char *module_name, const char *data_dir) return parsed_data_dir.array; } +/* Function to find a specifically defined module. Used for loading core modules by name. + * omp->bin should be path to the module binary, without its file extension. + * omp->data should be path to the module data directory (including the module name) + */ +bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data) +{ + const char *file_name = strrchr(info->path_info.binary, '/'); + const char *name = file_name ? (file_name + 1) : info->path_info.binary; + info->name = name; + + if (info->type != MODULE_TYPE_CORE) { + blog(LOG_WARNING, "Module %s is not a core module, and must be loaded with the plugin loader.", name); + return false; + } + + bool found = false; + + struct dstr module_path = {0}; + dstr_copy(&module_path, info->path_info.binary); + dstr_cat(&module_path, get_module_extension()); + + char *parsed_data_directory; + parsed_data_directory = make_data_directory(name, info->path_info.data); + + if (os_file_exists(module_path.array)) { + struct obs_module_info2 callback_info = {.bin_path = module_path.array, + .data_path = parsed_data_directory, + .name = name}; + + callback(data, &callback_info); + found = true; + } + + bfree(parsed_data_directory); + dstr_free(&module_path); + + return found; +} + +bool obs_is_core_module(obs_module_t *module) +{ + if (!module) + return false; + return module->module_type == MODULE_TYPE_CORE; +} + +bool obs_is_legacy_module(obs_module_t *module) +{ + if (!module) { + return false; + } + + return module->module_type == MODULE_TYPE_LEGACY_PLUGIN; +} + static bool parse_binary_from_directory(struct dstr *parsed_bin_path, const char *bin_path, const char *file) { struct dstr directory = {0}; diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 65cd1251dcfe06..6c38516821e4c9 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -47,49 +47,8 @@ const char *get_module_extension(void) return ".so"; } -#define FLATPAK_PLUGIN_PATH "/app/plugins" - -static const char *module_bin[] = { - "../../obs-plugins/64bit", - OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION, - FLATPAK_PLUGIN_PATH "/" OBS_PLUGIN_DESTINATION, -}; - -static const char *module_data[] = { - OBS_DATA_PATH "/obs-plugins/%module%", - OBS_INSTALL_DATA_PATH "/obs-plugins/%module%", - FLATPAK_PLUGIN_PATH "/share/obs/obs-plugins/%module%", -}; - -static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); - static const struct obs_nix_hotkeys_vtable *hotkeys_vtable = NULL; -void add_default_module_paths(void) -{ - char *module_bin_path = os_get_executable_path_ptr("../" OBS_PLUGIN_PATH); - char *module_data_path = os_get_executable_path_ptr("../" OBS_DATA_PATH "/obs-plugins/%module%"); - - if (module_bin_path && module_data_path) { - char *abs_module_bin_path = os_get_abs_path_ptr(module_bin_path); - char *abs_module_install_path = os_get_abs_path_ptr(OBS_INSTALL_PREFIX "/" OBS_PLUGIN_DESTINATION); - - if (abs_module_bin_path && - (!abs_module_install_path || strcmp(abs_module_bin_path, abs_module_install_path) != 0)) { - obs_add_module_path(module_bin_path, module_data_path); - } - bfree(abs_module_install_path); - bfree(abs_module_bin_path); - } - - bfree(module_bin_path); - bfree(module_data_path); - - for (int i = 0; i < module_patterns_size; i++) { - obs_add_module_path(module_bin[i], module_data[i]); - } -} - /* * /usr/local/share/libobs * /usr/share/libobs diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 90fb343052eea2..f74c5c62cacd4d 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -34,18 +34,6 @@ const char *get_module_extension(void) return ".dll"; } -static const char *module_bin[] = {"../../obs-plugins/64bit"}; - -static const char *module_data[] = {"../../data/obs-plugins/%module%"}; - -static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); - -void add_default_module_paths(void) -{ - for (int i = 0; i < module_patterns_size; i++) - obs_add_module_path(module_bin[i], module_data[i]); -} - /* on windows, points to [base directory]/data/libobs */ char *find_libobs_data_file(const char *file) { diff --git a/libobs/obs.c b/libobs/obs.c index ba53eecef92836..fc8a0967b121c8 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -27,7 +27,6 @@ struct obs_core *obs = NULL; static THREAD_LOCAL bool is_ui_thread = false; -extern void add_default_module_paths(void); extern char *find_libobs_data_file(const char *file); static inline void make_video_info(struct video_output_info *vi, struct obs_video_info *ovi) @@ -1261,7 +1260,8 @@ static bool obs_init(const char *locale, const char *module_config_path, profile obs_register_source(&scene_info); obs_register_source(&group_info); obs_register_source(&audio_line_info); - add_default_module_paths(); + + obs->core_modules_loaded = false; return true; } diff --git a/libobs/obs.h b/libobs/obs.h index 4e51eb4488a0ca..5d59e22d7d83c5 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -173,6 +173,19 @@ enum obs_module_load_state { OBS_MODULE_FAILED_TO_INITIALIZE, }; +enum obs_runtime_module_type { MODULE_TYPE_UNDEFINED, MODULE_TYPE_CORE, MODULE_TYPE_PLUGIN, MODULE_TYPE_LEGACY_PLUGIN }; + +struct obs_runtime_module_path { + const char *binary; + const char *data; +}; + +struct obs_runtime_module_info { + struct obs_runtime_module_path path_info; + enum obs_runtime_module_type type; + const char *name; +}; + struct obs_transform_info { struct vec2 pos; float rot; @@ -575,7 +588,7 @@ EXPORT void obs_add_safe_module(const char *name); EXPORT void obs_add_core_module(const char *name); /** Automatically loads all modules from module paths (convenience function) */ -EXPORT void obs_load_all_modules(void); +OBS_DEPRECATED EXPORT void obs_load_all_modules(void); struct obs_module_failure_info { char **failed_modules; @@ -583,7 +596,7 @@ struct obs_module_failure_info { }; 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); +OBS_DEPRECATED 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. */ @@ -609,6 +622,22 @@ typedef void (*obs_find_module_callback2_t)(void *param, const struct obs_module /** 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); + +/** Loads all registered core modules. */ +EXPORT bool obs_load_core_modules(); + +/** Loads plugins at a given path. omp defines if modern or legacy plugins at path */ +EXPORT void obs_load_plugins(struct obs_runtime_module_info *info, struct obs_module_failure_info *error); + +/** Returns true if a module is a core module. */ +EXPORT bool obs_is_core_module(obs_module_t *module); + +EXPORT bool obs_is_legacy_module(obs_module_t *module); + +/** Finds and loads a particular core module. + * Returns false if module cant be found. */ +bool find_core_module(struct obs_runtime_module_info *info, obs_find_module_callback2_t callback, void *data); + #endif typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1bea2e4ff92d02..6bc682accd5c3c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,20 +1,25 @@ cmake_minimum_required(VERSION 3.28...3.30) -option(ENABLE_PLUGINS "Enable building OBS plugins" ON) +if(NOT TARGET libobs) + message(FATAL_ERROR "OBS Core modules require 'libobs' target.") +endif() + +option(ENABLE_CORE_MODULES "Enable building OBS Core Modules" ON) +set_property(GLOBAL PROPERTY ENABLE_PLUGINS ${ENABLE_CORE_MODULES}) -if(NOT ENABLE_PLUGINS) - set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "Plugin Support") +if(NOT ENABLE_CORE_MODULES) + set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "Core Modules") return() endif() -set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_ENABLED "Plugin Support") +set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_ENABLED "Core Modules") macro(check_obs_browser) if((OS_WINDOWS AND CMAKE_VS_PLATFORM_NAME MATCHES "(ARM64|x64)") OR OS_MACOS OR OS_LINUX) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/obs-browser/CMakeLists.txt") message(FATAL_ERROR "Required submodule 'obs-browser' not available.") else() - add_subdirectory(obs-browser) + add_core_module(obs-browser) endif() else() add_custom_target(obs-browser) @@ -26,81 +31,83 @@ macro(check_obs_websocket) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/obs-websocket/CMakeLists.txt") message(FATAL_ERROR "Required submodule 'obs-websocket' not available.") else() - add_subdirectory(obs-websocket) + add_core_module(obs-websocket) endif() endmacro() # Add plugins in alphabetical order to retain order in IDE projects -add_obs_plugin( +add_core_module( aja PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(aja-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE) -add_obs_plugin(coreaudio-encoder PLATFORMS WINDOWS MACOS) -add_obs_plugin( +add_core_module(aja-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE) +add_core_module(coreaudio-encoder PLATFORMS WINDOWS MACOS) +add_core_module( decklink PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( decklink-captions PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( decklink-output-ui PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin( +add_core_module( frontend-tools PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(image-source) -add_obs_plugin(linux-alsa PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-capture PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-jack PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-pipewire PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-pulseaudio PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(linux-v4l2 PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(mac-avcapture PLATFORMS MACOS) -add_obs_plugin(mac-capture PLATFORMS MACOS) -add_obs_plugin(mac-syphon PLATFORMS MACOS) -add_obs_plugin(mac-videotoolbox PLATFORMS MACOS) -add_obs_plugin(mac-virtualcam PLATFORMS MACOS) -add_obs_plugin(nv-filters PLATFORMS WINDOWS) +add_core_module(image-source) +add_core_module(linux-alsa PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-capture PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-jack PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-pipewire PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-pulseaudio PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(linux-v4l2 PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(mac-avcapture PLATFORMS MACOS) +add_core_module(mac-capture PLATFORMS MACOS) +add_core_module(mac-syphon PLATFORMS MACOS) +add_core_module(mac-videotoolbox PLATFORMS MACOS) +add_core_module(mac-virtualcam PLATFORMS MACOS) +add_core_module(nv-filters PLATFORMS WINDOWS) check_obs_browser() -add_obs_plugin(obs-ffmpeg) -add_obs_plugin(obs-filters) -add_obs_plugin(obs-libfdk) -add_obs_plugin(obs-nvenc PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64) -add_obs_plugin(obs-outputs) -add_obs_plugin( +add_core_module(obs-ffmpeg) +add_core_module(obs-filters) +add_core_module(obs-libfdk) +add_core_module(obs-nvenc PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64) +add_core_module(obs-outputs) +add_core_module( obs-qsv11 PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64 ) -add_obs_plugin(obs-text PLATFORMS WINDOWS) -add_obs_plugin(obs-transitions) -add_obs_plugin( +add_core_module(obs-text PLATFORMS WINDOWS) +add_core_module(obs-transitions) +add_core_module( obs-vst PLATFORMS WINDOWS MACOS LINUX WITH_MESSAGE ) -add_obs_plugin(obs-webrtc) +add_core_module(obs-webrtc) check_obs_websocket() -add_obs_plugin(obs-x264) -add_obs_plugin(oss-audio PLATFORMS FREEBSD OPENBSD) -add_obs_plugin(rtmp-services) -add_obs_plugin(sndio PLATFORMS LINUX FREEBSD OPENBSD) -add_obs_plugin(text-freetype2) -add_obs_plugin(vlc-video WITH_MESSAGE) -add_obs_plugin(win-capture PLATFORMS WINDOWS) -add_obs_plugin(win-dshow PLATFORMS WINDOWS) -add_obs_plugin(win-wasapi PLATFORMS WINDOWS) +add_core_module(obs-x264) +add_core_module(oss-audio PLATFORMS FREEBSD OPENBSD) +add_core_module(rtmp-services) +add_core_module(sndio PLATFORMS LINUX FREEBSD OPENBSD) +add_core_module(text-freetype2) +add_core_module(vlc-video WITH_MESSAGE) +add_core_module(win-capture PLATFORMS WINDOWS) +add_core_module(win-dshow PLATFORMS WINDOWS) +add_core_module(win-wasapi PLATFORMS WINDOWS) + +set_obs_core_modules() diff --git a/plugins/aja-output-ui/CMakeLists.txt b/plugins/aja-output-ui/CMakeLists.txt index 003490d012364b..56dedbd117f7c6 100644 --- a/plugins/aja-output-ui/CMakeLists.txt +++ b/plugins/aja-output-ui/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_AJA) - target_disable(aja-output-ui) return() endif() diff --git a/plugins/aja/CMakeLists.txt b/plugins/aja/CMakeLists.txt index 88ef18cad3bd43..6b04c1a0b1b9c5 100644 --- a/plugins/aja/CMakeLists.txt +++ b/plugins/aja/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_AJA "Build OBS with aja support" ON) if(NOT ENABLE_AJA) - set_property(GLOBAL APPEND PROPERTY OBS_MODULES_DISABLED aja) return() endif() diff --git a/plugins/coreaudio-encoder/CMakeLists.txt b/plugins/coreaudio-encoder/CMakeLists.txt index 099995d8919306..907c55348aa629 100644 --- a/plugins/coreaudio-encoder/CMakeLists.txt +++ b/plugins/coreaudio-encoder/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(OS_WINDOWS) option(ENABLE_COREAUDIO_ENCODER "Enable building with CoreAudio encoder (Windows)" ON) if(NOT ENABLE_COREAUDIO_ENCODER) - target_disable(coreaudio-encoder) return() endif() endif() diff --git a/plugins/decklink-captions/CMakeLists.txt b/plugins/decklink-captions/CMakeLists.txt index 53dab0da14d743..13ab83b7308427 100644 --- a/plugins/decklink-captions/CMakeLists.txt +++ b/plugins/decklink-captions/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_DECKLINK) - target_disable(decklink-captions) return() endif() diff --git a/plugins/decklink-output-ui/CMakeLists.txt b/plugins/decklink-output-ui/CMakeLists.txt index b0cca166cc3fa1..4695c89513a174 100644 --- a/plugins/decklink-output-ui/CMakeLists.txt +++ b/plugins/decklink-output-ui/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_DECKLINK) - target_disable(decklink-output-ui) return() endif() diff --git a/plugins/decklink/CMakeLists.txt b/plugins/decklink/CMakeLists.txt index 18b9b6bc5b6204..bd08406560b1d2 100644 --- a/plugins/decklink/CMakeLists.txt +++ b/plugins/decklink/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_DECKLINK "Build OBS with Decklink support" ON) if(NOT ENABLE_DECKLINK) - target_disable(decklink) return() endif() diff --git a/plugins/linux-alsa/CMakeLists.txt b/plugins/linux-alsa/CMakeLists.txt index 7e7b2f5246ff17..c8d516213eeaa4 100644 --- a/plugins/linux-alsa/CMakeLists.txt +++ b/plugins/linux-alsa/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_ALSA "Build OBS with ALSA support" ON) if(NOT ENABLE_ALSA) - target_disable(linux-alsa) return() endif() diff --git a/plugins/linux-jack/CMakeLists.txt b/plugins/linux-jack/CMakeLists.txt index 4e58530a78b479..1a3e61f616fab7 100644 --- a/plugins/linux-jack/CMakeLists.txt +++ b/plugins/linux-jack/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_JACK "Build OBS with JACK support" OFF) if(NOT ENABLE_JACK) - target_disable(linux-jack) return() endif() diff --git a/plugins/linux-pipewire/CMakeLists.txt b/plugins/linux-pipewire/CMakeLists.txt index e81bdfe2f2c262..baf367df2c880b 100644 --- a/plugins/linux-pipewire/CMakeLists.txt +++ b/plugins/linux-pipewire/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_PIPEWIRE "Enable PipeWire support" ON) if(NOT ENABLE_PIPEWIRE) - target_disable(linux-pipewire) return() endif() diff --git a/plugins/linux-pulseaudio/CMakeLists.txt b/plugins/linux-pulseaudio/CMakeLists.txt index 5ab582c163854a..d45cc5a070c2a7 100644 --- a/plugins/linux-pulseaudio/CMakeLists.txt +++ b/plugins/linux-pulseaudio/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28...3.30) if(NOT ENABLE_PULSEAUDIO) - target_disable(linux-pulseaudio) return() endif() diff --git a/plugins/linux-v4l2/CMakeLists.txt b/plugins/linux-v4l2/CMakeLists.txt index aafbc0d28b0e36..43dbd2eb2b8a38 100644 --- a/plugins/linux-v4l2/CMakeLists.txt +++ b/plugins/linux-v4l2/CMakeLists.txt @@ -4,7 +4,6 @@ option(ENABLE_V4L2 "Build OBS with v4l2 support" ON) option(ENABLE_UDEV "Build linux-v4l2 with UDEV support" ON) if(NOT ENABLE_V4L2) - target_disable(linux-v4l2) return() endif() diff --git a/plugins/mac-syphon/CMakeLists.txt b/plugins/mac-syphon/CMakeLists.txt index a0d48604216d05..8d411c87b30d87 100644 --- a/plugins/mac-syphon/CMakeLists.txt +++ b/plugins/mac-syphon/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_SYPHON "Enable Syphon sharing support" ON) if(NOT ENABLE_SYPHON) - target_disable(mac-syphon) target_disable_feature(mac-syphon "Syphon sharing support") return() else() diff --git a/plugins/obs-libfdk/CMakeLists.txt b/plugins/obs-libfdk/CMakeLists.txt index c785a08db18c55..c0b722aa653d0d 100644 --- a/plugins/obs-libfdk/CMakeLists.txt +++ b/plugins/obs-libfdk/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_LIBFDK "Enable FDK AAC support" OFF) if(NOT ENABLE_LIBFDK) - target_disable(obs-libfdk) return() endif() diff --git a/plugins/obs-nvenc/CMakeLists.txt b/plugins/obs-nvenc/CMakeLists.txt index 93053c1edb9e13..1efeca52280047 100644 --- a/plugins/obs-nvenc/CMakeLists.txt +++ b/plugins/obs-nvenc/CMakeLists.txt @@ -6,7 +6,6 @@ mark_as_advanced(ENABLE_NVENC_FFMPEG_IDS) if(NOT ENABLE_NVENC) target_disable_feature(obs-nvenc "NVIDIA Hardware Encoder") - target_disable(obs-nvenc) return() endif() diff --git a/plugins/obs-qsv11/CMakeLists.txt b/plugins/obs-qsv11/CMakeLists.txt index 85b150f6d2ece4..9d06d4304e2bf1 100644 --- a/plugins/obs-qsv11/CMakeLists.txt +++ b/plugins/obs-qsv11/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_QSV11 "Build Intel QSV11 Hardware Encoder." TRUE) if(NOT ENABLE_QSV11) target_disable_feature(obs-qsv11 "Intel QSV11 Hardware Encoder") - target_disable(obs-qsv11) return() endif() diff --git a/plugins/obs-vst/CMakeLists.txt b/plugins/obs-vst/CMakeLists.txt index 8a2b4eb7938733..3bc8c75403cf1b 100644 --- a/plugins/obs-vst/CMakeLists.txt +++ b/plugins/obs-vst/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_VST "Enable building OBS with VST plugin" ON) if(NOT ENABLE_VST) - target_disable(obs-vst) return() endif() diff --git a/plugins/obs-webrtc/CMakeLists.txt b/plugins/obs-webrtc/CMakeLists.txt index d57ca8af5297b5..fcc53a7119bdbc 100644 --- a/plugins/obs-webrtc/CMakeLists.txt +++ b/plugins/obs-webrtc/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_WEBRTC "Enable WebRTC Output support" ON) if(NOT ENABLE_WEBRTC) - target_disable(obs-webrtc) return() endif() diff --git a/plugins/oss-audio/CMakeLists.txt b/plugins/oss-audio/CMakeLists.txt index 5ae20e2cc7394a..b08a2bf51a384a 100644 --- a/plugins/oss-audio/CMakeLists.txt +++ b/plugins/oss-audio/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_OSS "Enable building with OSS audio support" ON) if(NOT ENABLE_OSS) - target_disable(oss-audio) return() endif() diff --git a/plugins/sndio/CMakeLists.txt b/plugins/sndio/CMakeLists.txt index f76e7c07e1f385..008e54bb2721c2 100644 --- a/plugins/sndio/CMakeLists.txt +++ b/plugins/sndio/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_SNDIO "Build OBS with sndio support" OFF) if(NOT ENABLE_SNDIO) - target_disable(sndio) return() endif() diff --git a/plugins/text-freetype2/CMakeLists.txt b/plugins/text-freetype2/CMakeLists.txt index 8643dd0b7bb6e9..955cb73577a689 100644 --- a/plugins/text-freetype2/CMakeLists.txt +++ b/plugins/text-freetype2/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.28...3.30) option(ENABLE_FREETYPE "Enable FreeType text plugin" ON) if(NOT ENABLE_FREETYPE) - target_disable(text-freetype2) return() endif() diff --git a/plugins/vlc-video/CMakeLists.txt b/plugins/vlc-video/CMakeLists.txt index 871036c9f52b38..0b6ecbec16d150 100644 --- a/plugins/vlc-video/CMakeLists.txt +++ b/plugins/vlc-video/CMakeLists.txt @@ -23,7 +23,6 @@ endmacro() option(ENABLE_VLC "Build OBS with VLC plugin support" ON) if(NOT ENABLE_VLC) - target_disable(vlc-video) return() endif() diff --git a/plugins/win-capture/game-capture-file-init.c b/plugins/win-capture/game-capture-file-init.c index fdfa95e63be2aa..0c8dab7dafebe8 100644 --- a/plugins/win-capture/game-capture-file-init.c +++ b/plugins/win-capture/game-capture-file-init.c @@ -177,7 +177,7 @@ static bool update_hook_file(bool b64) wchar_t dst_json[MAX_PATH]; StringCbCopyW(temp, sizeof(temp), - L"..\\..\\data\\obs-plugins\\" + L"..\\..\\data\\core\\" L"win-capture\\"); make_filename(temp, L"obs-vulkan", L".json"); diff --git a/plugins/win-dshow/virtualcam-module/CMakeLists.txt b/plugins/win-dshow/virtualcam-module/CMakeLists.txt index 8ac916e17a9a5d..87cda18fa4eb57 100644 --- a/plugins/win-dshow/virtualcam-module/CMakeLists.txt +++ b/plugins/win-dshow/virtualcam-module/CMakeLists.txt @@ -138,12 +138,12 @@ target_sources(obs-virtualcam-module PRIVATE cmake/windows/${_OUTPUT_NAME}.def) configure_file(virtualcam-install.bat.in virtualcam-install.bat) target_add_resource(obs-virtualcam-module "${CMAKE_CURRENT_BINARY_DIR}/virtualcam-install.bat" - "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow" + "${OBS_PLUGIN_DESTINATION}/win-dshow/data" ) configure_file(virtualcam-uninstall.bat.in virtualcam-uninstall.bat) target_add_resource(obs-virtualcam-module "${CMAKE_CURRENT_BINARY_DIR}/virtualcam-uninstall.bat" - "${OBS_DATA_DESTINATION}/obs-plugins/win-dshow" + "${OBS_PLUGIN_DESTINATION}/win-dshow/data" ) set_target_properties_obs( diff --git a/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in b/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in index f28696e8b41902..51166e8b37b33d 100644 --- a/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in +++ b/plugins/win-dshow/virtualcam-module/virtualcam-install.bat.in @@ -36,8 +36,8 @@ goto checkAdmin :install32DLL echo Installing 32-bit Virtual Cam... - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" ( - regsvr32.exe /i /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ( + regsvr32.exe /i /s "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ) else ( regsvr32.exe /i /s obs-virtualcam-module32.dll ) @@ -54,8 +54,8 @@ goto checkAdmin :install64DLL echo Installing 64-bit Virtual Cam... - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" ( - regsvr32.exe /i /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ( + regsvr32.exe /i /s "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ) else ( regsvr32.exe /i /s obs-virtualcam-module64.dll ) diff --git a/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in b/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in index aefebf1b852b28..fb9e417d038fca 100644 --- a/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in +++ b/plugins/win-dshow/virtualcam-module/virtualcam-uninstall.bat.in @@ -12,13 +12,13 @@ goto checkAdmin ) :uninstallDLLs - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" ( - regsvr32.exe /u /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ( + regsvr32.exe /u /s "%~dp0\data\core\win-dshow\obs-virtualcam-module32.dll" ) else ( regsvr32.exe /u /s obs-virtualcam-module32.dll ) - if exist "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" ( - regsvr32.exe /u /s "%~dp0\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll" + if exist "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ( + regsvr32.exe /u /s "%~dp0\data\core\win-dshow\obs-virtualcam-module64.dll" ) else ( regsvr32.exe /u /s obs-virtualcam-module64.dll )