mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-03-19 15:07:16 -04:00
@@ -108,6 +108,10 @@ 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}\"")
|
||||
|
||||
# cmake-format: off
|
||||
set_target_properties_obs(obs-studio PROPERTIES FOLDER frontend OUTPUT_NAME "$<IF:$<PLATFORM_ID:Windows>,obs64,obs>")
|
||||
# cmake-format: on
|
||||
|
||||
@@ -486,6 +486,10 @@ source_group(
|
||||
unset(_SOURCES)
|
||||
unset(_UI)
|
||||
|
||||
get_property(OBS_MODULE_LIST GLOBAL PROPERTY OBS_MODULE_LIST)
|
||||
list(JOIN OBS_MODULE_LIST "|" SAFE_MODULES)
|
||||
target_compile_definitions(obs PRIVATE "SAFE_MODULES=\"${SAFE_MODULES}\"")
|
||||
|
||||
define_graphic_modules(obs)
|
||||
setup_obs_app(obs)
|
||||
setup_target_resources(obs obs-studio)
|
||||
|
||||
@@ -125,6 +125,15 @@ AlreadyRunning.Title="OBS is already running"
|
||||
AlreadyRunning.Text="OBS is already running! Unless you meant to do this, please shut down any existing instances of OBS before trying to run a new instance. If you have OBS set to minimize to the system tray, please check to see if it's still running there."
|
||||
AlreadyRunning.LaunchAnyway="Launch Anyway"
|
||||
|
||||
# warning if auto Safe Mode has engaged
|
||||
AutoSafeMode.Title="Safe Mode"
|
||||
AutoSafeMode.Text="OBS did not shut down properly during your last session.\n\nWould you like to start in Safe Mode (third-party plugins, scripting, and websockets disabled)?"
|
||||
AutoSafeMode.LaunchSafe="Run in Safe Mode"
|
||||
AutoSafeMode.LaunchNormal="Run Normally"
|
||||
## 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?"
|
||||
|
||||
ChromeOS.Title="Unsupported Platform"
|
||||
ChromeOS.Text="OBS appears to be running inside a ChromeOS container. This platform is unsupported."
|
||||
|
||||
@@ -834,6 +843,8 @@ Basic.MainMenu.Help.Logs.UploadLastLog="Upload &Previous Log File"
|
||||
Basic.MainMenu.Help.Logs.ViewCurrentLog="&View Current Log"
|
||||
Basic.MainMenu.Help.CheckForUpdates="Check For Updates"
|
||||
Basic.MainMenu.Help.Repair="Check File Integrity"
|
||||
Basic.MainMenu.Help.RestartSafeMode="Restart in Safe Mode"
|
||||
Basic.MainMenu.Help.RestartNormal="Restart in Normal Mode"
|
||||
Basic.MainMenu.Help.CrashLogs="Crash &Reports"
|
||||
Basic.MainMenu.Help.CrashLogs.ShowLogs="&Show Crash Reports"
|
||||
Basic.MainMenu.Help.CrashLogs.UploadLastLog="Upload &Previous Crash Report"
|
||||
|
||||
@@ -532,9 +532,11 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionRepair"/>
|
||||
<addaction name="actionCheckForUpdates"/>
|
||||
<addaction name="actionRestartSafe"/>
|
||||
<addaction name="actionShowMacPermissions"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionShowWhatsNew"/>
|
||||
<addaction name="actionShowAbout"/>
|
||||
<addaction name="actionShowMacPermissions"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuBasic_MainMenu_Edit">
|
||||
@@ -2004,6 +2006,11 @@
|
||||
<string>Basic.MainMenu.Help.Repair</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRestartSafe">
|
||||
<property name="text">
|
||||
<string>Basic.MainMenu.Help.RestartSafeMode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInteract">
|
||||
<property name="text">
|
||||
<string>Interact</string>
|
||||
|
||||
100
UI/obs-app.cpp
100
UI/obs-app.cpp
@@ -87,6 +87,10 @@ static string lastCrashLogFile;
|
||||
|
||||
bool portable_mode = false;
|
||||
bool steam = false;
|
||||
bool safe_mode = false;
|
||||
bool disable_3p_plugins = false;
|
||||
bool unclean_shutdown = false;
|
||||
bool disable_shutdown_check = false;
|
||||
static bool multi = false;
|
||||
static bool log_verbose = false;
|
||||
static bool unfiltered_log = false;
|
||||
@@ -105,6 +109,7 @@ string opt_starting_profile;
|
||||
string opt_starting_scene;
|
||||
|
||||
bool restart = false;
|
||||
bool restart_safe = false;
|
||||
|
||||
QPointer<OBSLogViewer> obsLogViewer;
|
||||
|
||||
@@ -1715,6 +1720,12 @@ bool OBSApp::OBSInit()
|
||||
QT_VERSION_STR);
|
||||
blog(LOG_INFO, "Portable mode: %s", portable_mode ? "true" : "false");
|
||||
|
||||
if (safe_mode) {
|
||||
blog(LOG_WARNING, "Safe Mode enabled.");
|
||||
} else if (disable_3p_plugins) {
|
||||
blog(LOG_WARNING, "Third-party plugins disabled.");
|
||||
}
|
||||
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
mainWindow = new OBSBasic();
|
||||
@@ -2429,6 +2440,10 @@ static int run_program(fstream &logFile, int argc, char *argv[])
|
||||
blog(LOG_WARNING, "================================");
|
||||
blog(LOG_WARNING, "User is now running multiple "
|
||||
"instances of OBS!");
|
||||
/* Clear unclean_shutdown flag as multiple instances
|
||||
* running from the same config will lead to a
|
||||
* false-positive detection.*/
|
||||
unclean_shutdown = false;
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
@@ -2453,6 +2468,34 @@ static int run_program(fstream &logFile, int argc, char *argv[])
|
||||
if (!created_log)
|
||||
create_log_file(logFile);
|
||||
|
||||
if (unclean_shutdown) {
|
||||
blog(LOG_WARNING,
|
||||
"[Safe Mode] Unclean shutdown detected!");
|
||||
}
|
||||
|
||||
if (unclean_shutdown && !safe_mode) {
|
||||
QMessageBox mb(QMessageBox::Warning,
|
||||
QTStr("AutoSafeMode.Title"),
|
||||
QTStr("AutoSafeMode.Text"));
|
||||
QPushButton *launchSafeButton =
|
||||
mb.addButton(QTStr("AutoSafeMode.LaunchSafe"),
|
||||
QMessageBox::AcceptRole);
|
||||
QPushButton *launchNormalButton =
|
||||
mb.addButton(QTStr("AutoSafeMode.LaunchNormal"),
|
||||
QMessageBox::RejectRole);
|
||||
mb.setDefaultButton(launchNormalButton);
|
||||
mb.exec();
|
||||
|
||||
safe_mode = mb.clickedButton() == launchSafeButton;
|
||||
if (safe_mode) {
|
||||
blog(LOG_INFO,
|
||||
"[Safe Mode] User has launched in Safe Mode.");
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"[Safe Mode] User elected to launch normally.");
|
||||
}
|
||||
}
|
||||
|
||||
qInstallMessageHandler([](QtMsgType type,
|
||||
const QMessageLogContext &,
|
||||
const QString &message) {
|
||||
@@ -2540,9 +2583,18 @@ static int run_program(fstream &logFile, int argc, char *argv[])
|
||||
OBSErrorBox(nullptr, "%s", error);
|
||||
}
|
||||
|
||||
if (restart)
|
||||
QProcess::startDetached(qApp->arguments()[0],
|
||||
qApp->arguments());
|
||||
if (restart || restart_safe) {
|
||||
auto args = qApp->arguments();
|
||||
auto executable = args[0];
|
||||
|
||||
if (restart_safe) {
|
||||
args.append("--safe-mode");
|
||||
} else {
|
||||
args.removeAll("--safe-mode");
|
||||
}
|
||||
|
||||
QProcess::startDetached(executable, args);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -3194,6 +3246,32 @@ static void upgrade_settings(void)
|
||||
os_closedir(dir);
|
||||
}
|
||||
|
||||
static void check_safe_mode_sentinel(void)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* Safe Mode detection is disabled in Debug builds to keep developers
|
||||
* somewhat sane. */
|
||||
return;
|
||||
#else
|
||||
if (disable_shutdown_check)
|
||||
return;
|
||||
|
||||
BPtr sentinelPath = GetConfigPathPtr("obs-studio/safe_mode");
|
||||
if (os_file_exists(sentinelPath)) {
|
||||
unclean_shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
os_quick_write_utf8_file(sentinelPath, nullptr, 0, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void delete_safe_mode_sentinel(void)
|
||||
{
|
||||
BPtr sentinelPath = GetConfigPathPtr("obs-studio/safe_mode");
|
||||
os_unlink(sentinelPath);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
void OBSApp::SigIntSignalHandler(int s)
|
||||
{
|
||||
@@ -3281,6 +3359,17 @@ int main(int argc, char *argv[])
|
||||
} else if (arg_is(argv[i], "--verbose", nullptr)) {
|
||||
log_verbose = true;
|
||||
|
||||
} else if (arg_is(argv[i], "--safe-mode", nullptr)) {
|
||||
safe_mode = true;
|
||||
|
||||
} else if (arg_is(argv[i], "--only-bundled-plugins", nullptr)) {
|
||||
disable_3p_plugins = true;
|
||||
|
||||
} else if (arg_is(argv[i], "--disable-shutdown-check",
|
||||
nullptr)) {
|
||||
/* This exists mostly to bypass the dialog during development. */
|
||||
disable_shutdown_check = true;
|
||||
|
||||
} else if (arg_is(argv[i], "--always-on-top", nullptr)) {
|
||||
opt_always_on_top = true;
|
||||
|
||||
@@ -3347,6 +3436,9 @@ int main(int argc, char *argv[])
|
||||
"--portable, -p: Use portable mode.\n"
|
||||
#endif
|
||||
"--multi, -m: Don't warn when launching multiple instances.\n\n"
|
||||
"--safe-mode: Run in Safe Mode (disables third-party plugins, scripting, and websockets).\n"
|
||||
"--only-bundled-plugins: Only load included (first-party) plugins\n"
|
||||
"--disable-shutdown-check: Disable unclean shutdown detection.\n"
|
||||
"--verbose: Make log more verbose.\n"
|
||||
"--always-on-top: Start in 'always on top' mode.\n\n"
|
||||
"--unfiltered_log: Make log unfiltered.\n\n"
|
||||
@@ -3393,6 +3485,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
#endif
|
||||
|
||||
check_safe_mode_sentinel();
|
||||
upgrade_settings();
|
||||
|
||||
fstream logFile;
|
||||
@@ -3412,6 +3505,7 @@ int main(int argc, char *argv[])
|
||||
log_blocked_dlls();
|
||||
#endif
|
||||
|
||||
delete_safe_mode_sentinel();
|
||||
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
|
||||
base_set_log_handler(nullptr, nullptr);
|
||||
return ret;
|
||||
|
||||
@@ -267,6 +267,8 @@ static inline int GetProfilePath(char *path, size_t size, const char *file)
|
||||
|
||||
extern bool portable_mode;
|
||||
extern bool steam;
|
||||
extern bool safe_mode;
|
||||
extern bool disable_3p_plugins;
|
||||
|
||||
extern bool opt_start_streaming;
|
||||
extern bool opt_start_recording;
|
||||
@@ -278,6 +280,7 @@ extern bool opt_allow_opengl;
|
||||
extern bool opt_always_on_top;
|
||||
extern std::string opt_starting_scene;
|
||||
extern bool restart;
|
||||
extern bool restart_safe;
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" void install_dll_blocklist_hook(void);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <obs-data.h>
|
||||
#include <obs.h>
|
||||
#include <obs.hpp>
|
||||
@@ -232,6 +233,30 @@ static void AddExtraModulePaths()
|
||||
#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<string> unsafe_modules = {
|
||||
"frontend-tools", // Scripting
|
||||
"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
|
||||
}
|
||||
|
||||
extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);
|
||||
|
||||
void assignDockToggle(QDockWidget *dock, QAction *action)
|
||||
@@ -801,9 +826,18 @@ void OBSBasic::Save(const char *file)
|
||||
}
|
||||
|
||||
if (api) {
|
||||
OBSDataAutoRelease moduleObj = obs_data_create();
|
||||
api->on_save(moduleObj);
|
||||
obs_data_set_obj(saveData, "modules", moduleObj);
|
||||
if (safeModeModuleData) {
|
||||
/* If we're in Safe Mode and have retained unloaded
|
||||
* plugin data, update the existing data object instead
|
||||
* of creating a new one. */
|
||||
api->on_save(safeModeModuleData);
|
||||
obs_data_set_obj(saveData, "modules",
|
||||
safeModeModuleData);
|
||||
} else {
|
||||
OBSDataAutoRelease moduleObj = obs_data_create();
|
||||
api->on_save(moduleObj);
|
||||
obs_data_set_obj(saveData, "modules", moduleObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
|
||||
@@ -1084,6 +1118,12 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
|
||||
if (api)
|
||||
api->on_preload(modulesObj);
|
||||
|
||||
if (safe_mode || disable_3p_plugins) {
|
||||
/* Keep a reference to "modules" data so plugins that are not
|
||||
* loaded do not have their collection specific data lost. */
|
||||
safeModeModuleData = obs_data_get_obj(data, "modules");
|
||||
}
|
||||
|
||||
OBSDataArrayAutoRelease sceneOrder =
|
||||
obs_data_get_array(data, "scene_order");
|
||||
OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources");
|
||||
@@ -1259,14 +1299,14 @@ retryScene:
|
||||
if (!opt_starting_scene.empty())
|
||||
opt_starting_scene.clear();
|
||||
|
||||
if (opt_start_streaming) {
|
||||
if (opt_start_streaming && !safe_mode) {
|
||||
blog(LOG_INFO, "Starting stream due to command line parameter");
|
||||
QMetaObject::invokeMethod(this, "StartStreaming",
|
||||
Qt::QueuedConnection);
|
||||
opt_start_streaming = false;
|
||||
}
|
||||
|
||||
if (opt_start_recording) {
|
||||
if (opt_start_recording && !safe_mode) {
|
||||
blog(LOG_INFO,
|
||||
"Starting recording due to command line parameter");
|
||||
QMetaObject::invokeMethod(this, "StartRecording",
|
||||
@@ -1274,13 +1314,13 @@ retryScene:
|
||||
opt_start_recording = false;
|
||||
}
|
||||
|
||||
if (opt_start_replaybuffer) {
|
||||
if (opt_start_replaybuffer && !safe_mode) {
|
||||
QMetaObject::invokeMethod(this, "StartReplayBuffer",
|
||||
Qt::QueuedConnection);
|
||||
opt_start_replaybuffer = false;
|
||||
}
|
||||
|
||||
if (opt_start_virtualcam) {
|
||||
if (opt_start_virtualcam && !safe_mode) {
|
||||
QMetaObject::invokeMethod(this, "StartVirtualCam",
|
||||
Qt::QueuedConnection);
|
||||
opt_start_virtualcam = false;
|
||||
@@ -1961,7 +2001,14 @@ void OBSBasic::OBSInit()
|
||||
#endif
|
||||
struct obs_module_failure_info mfi;
|
||||
|
||||
AddExtraModulePaths();
|
||||
/* Safe Mode disables third-party plugins so we don't need to add earch
|
||||
* paths outside the OBS bundle/installation. */
|
||||
if (safe_mode || disable_3p_plugins) {
|
||||
SetSafeModuleNames();
|
||||
} else {
|
||||
AddExtraModulePaths();
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "---------------------------------");
|
||||
obs_load_all_modules2(&mfi);
|
||||
blog(LOG_INFO, "---------------------------------");
|
||||
@@ -2274,6 +2321,11 @@ void OBSBasic::OBSInit()
|
||||
ui->actionShowWhatsNew = nullptr;
|
||||
#endif
|
||||
|
||||
if (safe_mode) {
|
||||
ui->actionRestartSafe->setText(
|
||||
QTStr("Basic.MainMenu.Help.RestartNormal"));
|
||||
}
|
||||
|
||||
UpdatePreviewProgramIndicators();
|
||||
OnFirstLoad();
|
||||
|
||||
@@ -4904,6 +4956,7 @@ void OBSBasic::ClearSceneData()
|
||||
outputHandler->UpdateVirtualCamOutputSource();
|
||||
}
|
||||
|
||||
safeModeModuleData = nullptr;
|
||||
lastScene = nullptr;
|
||||
swapScene = nullptr;
|
||||
programScene = nullptr;
|
||||
@@ -6563,6 +6616,20 @@ void OBSBasic::on_actionRepair_triggered()
|
||||
#endif
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionRestartSafe_triggered()
|
||||
{
|
||||
QMessageBox::StandardButton button = OBSMessageBox::question(
|
||||
this, QTStr("Restart"),
|
||||
safe_mode ? QTStr("SafeMode.RestartNormal")
|
||||
: QTStr("SafeMode.Restart"));
|
||||
|
||||
if (button == QMessageBox::Yes) {
|
||||
restart = safe_mode;
|
||||
restart_safe = !safe_mode;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
|
||||
{
|
||||
ui->menuLogFiles->setEnabled(true);
|
||||
@@ -9270,6 +9337,8 @@ void OBSBasic::UpdateTitleBar()
|
||||
name << "Studio ";
|
||||
|
||||
name << App()->GetVersionString(false);
|
||||
if (safe_mode)
|
||||
name << " (SAFE MODE)";
|
||||
if (App()->IsPortableMode())
|
||||
name << " - " << Str("TitleBar.PortableMode");
|
||||
|
||||
|
||||
@@ -231,6 +231,8 @@ private:
|
||||
QList<QPointer<QDockWidget>> oldExtraDocks;
|
||||
QStringList oldExtraDockNames;
|
||||
|
||||
OBSDataAutoRelease safeModeModuleData;
|
||||
|
||||
bool loaded = false;
|
||||
long disableSaving = 1;
|
||||
bool projectChanged = false;
|
||||
@@ -1044,6 +1046,7 @@ private slots:
|
||||
void on_actionCheckForUpdates_triggered();
|
||||
void on_actionRepair_triggered();
|
||||
void on_actionShowWhatsNew_triggered();
|
||||
void on_actionRestartSafe_triggered();
|
||||
|
||||
void on_actionShowCrashLogs_triggered();
|
||||
void on_actionUploadLastCrashLog_triggered();
|
||||
|
||||
@@ -271,6 +271,15 @@ plugin modules.
|
||||
|
||||
---------------------
|
||||
|
||||
.. function:: void *obs_add_safe_module(const char *name)
|
||||
|
||||
Adds a *name* to the list of modules allowed to load in Safe Mode.
|
||||
If the list is empty, all modules are allowed.
|
||||
|
||||
:param name: The name of the module (filename sans extension).
|
||||
|
||||
---------------------
|
||||
|
||||
.. function:: void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
|
||||
|
||||
Frees data allocated data used in the *mfi* parameter (calls
|
||||
|
||||
@@ -466,6 +466,7 @@ typedef DARRAY(struct obs_source_info) obs_source_info_array_t;
|
||||
struct obs_core {
|
||||
struct obs_module *first_module;
|
||||
DARRAY(struct obs_module_path) module_paths;
|
||||
DARRAY(char *) safe_modules;
|
||||
|
||||
obs_source_info_array_t source_types;
|
||||
obs_source_info_array_t input_types;
|
||||
|
||||
@@ -274,6 +274,15 @@ void obs_add_module_path(const char *bin, const char *data)
|
||||
da_push_back(obs->module_paths, &omp);
|
||||
}
|
||||
|
||||
void obs_add_safe_module(const char *name)
|
||||
{
|
||||
if (!obs || !name)
|
||||
return;
|
||||
|
||||
char *item = bstrdup(name);
|
||||
da_push_back(obs->safe_modules, &item);
|
||||
}
|
||||
|
||||
extern void get_plugin_info(const char *path, bool *is_obs_plugin,
|
||||
bool *can_load);
|
||||
|
||||
@@ -282,6 +291,19 @@ struct fail_info {
|
||||
size_t fail_count;
|
||||
};
|
||||
|
||||
static bool is_safe_module(const char *name)
|
||||
{
|
||||
if (!obs->safe_modules.num)
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < obs->safe_modules.num; i++) {
|
||||
if (strcmp(name, obs->safe_modules.array[i]) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void load_all_callback(void *param, const struct obs_module_info2 *info)
|
||||
{
|
||||
struct fail_info *fail_info = param;
|
||||
@@ -298,6 +320,12 @@ static void load_all_callback(void *param, const struct obs_module_info2 *info)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_safe_module(info->name)) {
|
||||
blog(LOG_WARNING, "Skipping module '%s', not on safe list",
|
||||
info->name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_load_obs_plugin) {
|
||||
blog(LOG_WARNING,
|
||||
"Skipping module '%s' due to possible "
|
||||
|
||||
@@ -1441,6 +1441,10 @@ void obs_shutdown(void)
|
||||
free_module_path(obs->module_paths.array + i);
|
||||
da_free(obs->module_paths);
|
||||
|
||||
for (size_t i = 0; i < obs->safe_modules.num; i++)
|
||||
bfree(obs->safe_modules.array[i]);
|
||||
da_free(obs->safe_modules);
|
||||
|
||||
if (obs->name_store_owned)
|
||||
profiler_name_store_free(obs->name_store);
|
||||
|
||||
|
||||
@@ -517,6 +517,14 @@ EXPORT const char *obs_get_module_data_path(obs_module_t *module);
|
||||
*/
|
||||
EXPORT void obs_add_module_path(const char *bin, const char *data);
|
||||
|
||||
/**
|
||||
* Adds a module to the list of modules allowed to load in Safe Mode.
|
||||
* If the list is empty, all modules are allowed.
|
||||
*
|
||||
* @param name Specifies the module's name (filename sans extension).
|
||||
*/
|
||||
EXPORT void obs_add_safe_module(const char *name);
|
||||
|
||||
/** Automatically loads all modules from module paths (convenience function) */
|
||||
EXPORT void obs_load_all_modules(void);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user