obs-module.c 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "util/platform.h"
  15. #include "util/dstr.h"
  16. #include "obs-defs.h"
  17. #include "obs-internal.h"
  18. #include "obs-module.h"
  19. extern const char *get_module_extension(void);
  20. obs_module_t *loadingModule = NULL;
  21. static inline int req_func_not_found(const char *name, const char *path)
  22. {
  23. blog(LOG_DEBUG,
  24. "Required module function '%s' in module '%s' not "
  25. "found, loading of module failed",
  26. name, path);
  27. return MODULE_MISSING_EXPORTS;
  28. }
  29. static int load_module_exports(struct obs_module *mod, const char *path)
  30. {
  31. mod->load = os_dlsym(mod->module, "obs_module_load");
  32. if (!mod->load)
  33. return req_func_not_found("obs_module_load", path);
  34. mod->set_pointer = os_dlsym(mod->module, "obs_module_set_pointer");
  35. if (!mod->set_pointer)
  36. return req_func_not_found("obs_module_set_pointer", path);
  37. mod->ver = os_dlsym(mod->module, "obs_module_ver");
  38. if (!mod->ver)
  39. return req_func_not_found("obs_module_ver", path);
  40. /* optional exports */
  41. mod->unload = os_dlsym(mod->module, "obs_module_unload");
  42. mod->post_load = os_dlsym(mod->module, "obs_module_post_load");
  43. mod->set_locale = os_dlsym(mod->module, "obs_module_set_locale");
  44. mod->free_locale = os_dlsym(mod->module, "obs_module_free_locale");
  45. mod->name = os_dlsym(mod->module, "obs_module_name");
  46. mod->description = os_dlsym(mod->module, "obs_module_description");
  47. mod->author = os_dlsym(mod->module, "obs_module_author");
  48. mod->get_string = os_dlsym(mod->module, "obs_module_get_string");
  49. return MODULE_SUCCESS;
  50. }
  51. bool obs_module_get_locale_string(const obs_module_t *mod, const char *lookup_string, const char **translated_string)
  52. {
  53. if (mod->get_string) {
  54. return mod->get_string(lookup_string, translated_string);
  55. }
  56. return false;
  57. }
  58. const char *obs_module_get_locale_text(const obs_module_t *mod, const char *text)
  59. {
  60. const char *str = text;
  61. obs_module_get_locale_string(mod, text, &str);
  62. return str;
  63. }
  64. static inline char *get_module_name(const char *file)
  65. {
  66. static size_t ext_len = 0;
  67. struct dstr name = {0};
  68. if (ext_len == 0) {
  69. const char *ext = get_module_extension();
  70. ext_len = strlen(ext);
  71. }
  72. dstr_copy(&name, file);
  73. dstr_resize(&name, name.len - ext_len);
  74. return name.array;
  75. }
  76. #ifdef _WIN32
  77. extern void reset_win32_symbol_paths(void);
  78. #endif
  79. int obs_module_load_metadata(struct obs_module *mod)
  80. {
  81. struct obs_module_metadata *md = NULL;
  82. /* Check if the metadata file exists */
  83. struct dstr path = {0};
  84. dstr_copy(&path, mod->data_path);
  85. if (!dstr_is_empty(&path) && dstr_end(&path) != '/') {
  86. dstr_cat_ch(&path, '/');
  87. }
  88. dstr_cat(&path, "manifest.json");
  89. if (os_file_exists(path.array)) {
  90. /* If we find a metadata file, allocate a new metadata. */
  91. md = bmalloc(sizeof(obs_module_metadata_t));
  92. obs_data_t *metadata = obs_data_create_from_json_file(path.array);
  93. md->display_name = bstrdup(obs_data_get_string(metadata, "display_name"));
  94. md->id = bstrdup(obs_data_get_string(metadata, "id"));
  95. md->version = bstrdup(obs_data_get_string(metadata, "version"));
  96. md->os_arch = bstrdup(obs_data_get_string(metadata, "os_arch"));
  97. md->name = bstrdup(obs_data_get_string(metadata, "name"));
  98. md->description = bstrdup(obs_data_get_string(metadata, "description"));
  99. md->long_description = bstrdup(obs_data_get_string(metadata, "long_description"));
  100. obs_data_t *urls = obs_data_get_obj(metadata, "urls");
  101. md->repository_url = bstrdup(obs_data_get_string(urls, "repository"));
  102. md->website_url = bstrdup(obs_data_get_string(urls, "website"));
  103. md->support_url = bstrdup(obs_data_get_string(urls, "support"));
  104. obs_data_release(urls);
  105. md->has_banner = obs_data_get_bool(metadata, "has_banner");
  106. md->has_icon = obs_data_get_bool(metadata, "has_icon");
  107. obs_data_release(metadata);
  108. }
  109. dstr_free(&path);
  110. mod->metadata = md;
  111. return MODULE_SUCCESS;
  112. }
  113. int obs_open_module(obs_module_t **module, const char *path, const char *data_path)
  114. {
  115. struct obs_module mod = {0};
  116. int errorcode;
  117. if (!module || !path || !obs)
  118. return MODULE_ERROR;
  119. #ifdef __APPLE__
  120. /* HACK: Do not load obsolete obs-browser build on macOS; the
  121. * obs-browser plugin used to live in the Application Support
  122. * directory. */
  123. if (astrstri(path, "Library/Application Support/obs-studio") != NULL && astrstri(path, "obs-browser") != NULL) {
  124. blog(LOG_WARNING, "Ignoring old obs-browser.so version");
  125. return MODULE_HARDCODED_SKIP;
  126. }
  127. #endif
  128. blog(LOG_DEBUG, "---------------------------------");
  129. mod.module = os_dlopen(path);
  130. if (!mod.module) {
  131. blog(LOG_WARNING, "Module '%s' not loaded", path);
  132. return MODULE_FAILED_TO_OPEN;
  133. }
  134. errorcode = load_module_exports(&mod, path);
  135. if (errorcode != MODULE_SUCCESS)
  136. return errorcode;
  137. /* Reject plugins compiled with a newer libobs. Patch version (lower 16-bit) is ignored. */
  138. uint32_t ver = mod.ver ? mod.ver() & 0xFFFF0000 : 0;
  139. if (ver > LIBOBS_API_VER) {
  140. blog(LOG_WARNING, "Module '%s' compiled with newer libobs %d.%d", path, (ver >> 24) & 0xFF,
  141. (ver >> 16) & 0xFF);
  142. return MODULE_INCOMPATIBLE_VER;
  143. }
  144. mod.bin_path = bstrdup(path);
  145. mod.file = strrchr(mod.bin_path, '/');
  146. mod.file = (!mod.file) ? mod.bin_path : (mod.file + 1);
  147. mod.mod_name = get_module_name(mod.file);
  148. mod.data_path = bstrdup(data_path);
  149. mod.next = obs->first_module;
  150. mod.load_state = OBS_MODULE_ENABLED;
  151. da_init(mod.sources);
  152. da_init(mod.outputs);
  153. da_init(mod.encoders);
  154. da_init(mod.services);
  155. if (mod.file) {
  156. blog(LOG_DEBUG, "Loading module: %s", mod.file);
  157. }
  158. obs_module_load_metadata(&mod);
  159. *module = bmemdup(&mod, sizeof(mod));
  160. obs->first_module = (*module);
  161. mod.set_pointer(*module);
  162. if (mod.set_locale)
  163. mod.set_locale(obs->locale);
  164. return MODULE_SUCCESS;
  165. }
  166. bool obs_create_disabled_module(obs_module_t **module, const char *path, const char *data_path,
  167. enum obs_module_load_state state)
  168. {
  169. struct obs_module mod = {0};
  170. mod.bin_path = bstrdup(path);
  171. mod.file = strrchr(mod.bin_path, '/');
  172. mod.file = (!mod.file) ? mod.bin_path : (mod.file + 1);
  173. mod.mod_name = get_module_name(mod.file);
  174. mod.data_path = bstrdup(data_path);
  175. mod.next = obs->first_disabled_module;
  176. mod.load_state = state;
  177. da_init(mod.sources);
  178. da_init(mod.outputs);
  179. da_init(mod.encoders);
  180. da_init(mod.services);
  181. obs_module_load_metadata(&mod);
  182. *module = bmemdup(&mod, sizeof(mod));
  183. obs->first_disabled_module = (*module);
  184. return true;
  185. }
  186. bool obs_init_module(obs_module_t *module)
  187. {
  188. if (!module || !obs)
  189. return false;
  190. if (module->loaded)
  191. return true;
  192. const char *profile_name =
  193. profile_store_name(obs_get_profiler_name_store(), "obs_init_module(%s)", module->file);
  194. profile_start(profile_name);
  195. loadingModule = module;
  196. module->loaded = module->load();
  197. loadingModule = NULL;
  198. if (!module->loaded)
  199. blog(LOG_WARNING, "Failed to initialize module '%s'", module->file);
  200. profile_end(profile_name);
  201. return module->loaded;
  202. }
  203. void obs_log_loaded_modules(void)
  204. {
  205. blog(LOG_INFO, " Loaded Modules:");
  206. for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
  207. blog(LOG_INFO, " %s", mod->file);
  208. }
  209. const char *obs_get_module_file_name(obs_module_t *module)
  210. {
  211. return module ? module->file : NULL;
  212. }
  213. const char *obs_get_module_name(obs_module_t *module)
  214. {
  215. if (module && module->metadata && module->metadata->display_name) {
  216. return module->metadata->display_name;
  217. }
  218. return (module && module->name) ? module->name() : NULL;
  219. }
  220. const char *obs_get_module_author(obs_module_t *module)
  221. {
  222. return (module && module->author) ? module->author() : NULL;
  223. }
  224. const char *obs_get_module_description(obs_module_t *module)
  225. {
  226. return (module && module->description) ? module->description() : NULL;
  227. }
  228. const char *obs_get_module_binary_path(obs_module_t *module)
  229. {
  230. return module ? module->bin_path : NULL;
  231. }
  232. const char *obs_get_module_data_path(obs_module_t *module)
  233. {
  234. return module ? module->data_path : NULL;
  235. }
  236. const char *obs_get_module_id(obs_module_t *module)
  237. {
  238. return module && module->metadata ? module->metadata->id : NULL;
  239. }
  240. const char *obs_get_module_version(obs_module_t *module)
  241. {
  242. return module && module->metadata ? module->metadata->version : NULL;
  243. }
  244. void obs_module_add_source(obs_module_t *module, const char *id)
  245. {
  246. char *source_id = bstrdup(id);
  247. if (module) {
  248. da_push_back(module->sources, &source_id);
  249. }
  250. }
  251. void obs_module_add_output(obs_module_t *module, const char *id)
  252. {
  253. char *output_id = bstrdup(id);
  254. if (module) {
  255. da_push_back(module->outputs, &output_id);
  256. }
  257. }
  258. void obs_module_add_encoder(obs_module_t *module, const char *id)
  259. {
  260. char *encoder_id = bstrdup(id);
  261. if (module) {
  262. da_push_back(module->encoders, &encoder_id);
  263. }
  264. }
  265. void obs_module_add_service(obs_module_t *module, const char *id)
  266. {
  267. char *service_id = bstrdup(id);
  268. if (module) {
  269. da_push_back(module->services, &service_id);
  270. }
  271. }
  272. obs_module_t *obs_get_module(const char *name)
  273. {
  274. obs_module_t *module = obs->first_module;
  275. while (module) {
  276. if (strcmp(module->mod_name, name) == 0) {
  277. return module;
  278. }
  279. module = module->next;
  280. }
  281. return NULL;
  282. }
  283. obs_module_t *obs_get_disabled_module(const char *name)
  284. {
  285. obs_module_t *module = obs->first_disabled_module;
  286. while (module) {
  287. if (strcmp(module->mod_name, name) == 0) {
  288. return module;
  289. }
  290. module = module->next;
  291. }
  292. return NULL;
  293. }
  294. void *obs_get_module_lib(obs_module_t *module)
  295. {
  296. return module ? module->module : NULL;
  297. }
  298. char *obs_find_module_file(obs_module_t *module, const char *file)
  299. {
  300. struct dstr output = {0};
  301. if (!file)
  302. file = "";
  303. if (!module)
  304. return NULL;
  305. dstr_copy(&output, module->data_path);
  306. if (!dstr_is_empty(&output) && dstr_end(&output) != '/' && *file)
  307. dstr_cat_ch(&output, '/');
  308. dstr_cat(&output, file);
  309. if (!os_file_exists(output.array))
  310. dstr_free(&output);
  311. return output.array;
  312. }
  313. char *obs_module_get_config_path(obs_module_t *module, const char *file)
  314. {
  315. struct dstr output = {0};
  316. dstr_copy(&output, obs->module_config_path);
  317. if (!dstr_is_empty(&output) && dstr_end(&output) != '/')
  318. dstr_cat_ch(&output, '/');
  319. dstr_cat(&output, module->mod_name);
  320. dstr_cat_ch(&output, '/');
  321. dstr_cat(&output, file);
  322. return output.array;
  323. }
  324. void obs_add_module_path(const char *bin, const char *data)
  325. {
  326. struct obs_module_path omp;
  327. if (!obs || !bin || !data)
  328. return;
  329. omp.bin = bstrdup(bin);
  330. omp.data = bstrdup(data);
  331. da_push_back(obs->module_paths, &omp);
  332. }
  333. void obs_add_safe_module(const char *name)
  334. {
  335. if (!obs || !name)
  336. return;
  337. char *item = bstrdup(name);
  338. da_push_back(obs->safe_modules, &item);
  339. }
  340. void obs_add_core_module(const char *name)
  341. {
  342. if (!obs || !name)
  343. return;
  344. char *item = bstrdup(name);
  345. da_push_back(obs->core_modules, &item);
  346. }
  347. void obs_add_disabled_module(const char *name)
  348. {
  349. if (!obs || !name)
  350. return;
  351. char *item = bstrdup(name);
  352. da_push_back(obs->disabled_modules, &item);
  353. }
  354. extern void get_plugin_info(const char *path, bool *is_obs_plugin);
  355. struct fail_info {
  356. struct dstr fail_modules;
  357. size_t fail_count;
  358. };
  359. static bool is_safe_module(const char *name)
  360. {
  361. if (!obs->safe_modules.num)
  362. return true;
  363. for (size_t i = 0; i < obs->safe_modules.num; i++) {
  364. if (strcmp(name, obs->safe_modules.array[i]) == 0)
  365. return true;
  366. }
  367. return false;
  368. }
  369. static bool is_core_module(const char *name)
  370. {
  371. for (size_t i = 0; i < obs->core_modules.num; i++) {
  372. if (strcmp(name, obs->core_modules.array[i]) == 0)
  373. return true;
  374. }
  375. return false;
  376. }
  377. static bool is_disabled_module(const char *name)
  378. {
  379. if (obs->disabled_modules.num == 0)
  380. return false;
  381. for (size_t i = 0; i < obs->disabled_modules.num; i++) {
  382. if (strcmp(name, obs->disabled_modules.array[i]) == 0)
  383. return true;
  384. }
  385. return false;
  386. }
  387. bool obs_get_module_allow_disable(const char *name)
  388. {
  389. return !is_core_module(name);
  390. }
  391. static void load_all_callback(void *param, const struct obs_module_info2 *info)
  392. {
  393. struct fail_info *fail_info = param;
  394. obs_module_t *module;
  395. obs_module_t *disabled_module;
  396. bool is_obs_plugin;
  397. get_plugin_info(info->bin_path, &is_obs_plugin);
  398. if (!is_obs_plugin) {
  399. blog(LOG_WARNING, "Skipping module '%s', not an OBS plugin", info->bin_path);
  400. return;
  401. }
  402. if (!is_safe_module(info->name)) {
  403. obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_DISABLED_SAFE);
  404. blog(LOG_WARNING, "Skipping module '%s', not on safe list", info->name);
  405. return;
  406. }
  407. if (is_disabled_module(info->name)) {
  408. obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_DISABLED);
  409. blog(LOG_WARNING, "Skipping module '%s', is disabled", info->name);
  410. return;
  411. }
  412. int code = obs_open_module(&module, info->bin_path, info->data_path);
  413. switch (code) {
  414. case MODULE_MISSING_EXPORTS:
  415. blog(LOG_DEBUG, "Failed to load module file '%s', not an OBS plugin", info->bin_path);
  416. return;
  417. case MODULE_FAILED_TO_OPEN:
  418. blog(LOG_DEBUG, "Failed to load module file '%s', module failed to open", info->bin_path);
  419. return;
  420. case MODULE_ERROR:
  421. blog(LOG_DEBUG, "Failed to load module file '%s'", info->bin_path);
  422. goto load_failure;
  423. case MODULE_INCOMPATIBLE_VER:
  424. blog(LOG_DEBUG, "Failed to load module file '%s', incompatible version", info->bin_path);
  425. goto load_failure;
  426. case MODULE_HARDCODED_SKIP:
  427. return;
  428. }
  429. if (!obs_init_module(module)) {
  430. free_module(module);
  431. obs_create_disabled_module(&disabled_module, info->bin_path, info->data_path, OBS_MODULE_ERROR);
  432. }
  433. UNUSED_PARAMETER(param);
  434. return;
  435. load_failure:
  436. if (fail_info) {
  437. dstr_cat(&fail_info->fail_modules, info->name);
  438. dstr_cat(&fail_info->fail_modules, ";");
  439. fail_info->fail_count++;
  440. }
  441. }
  442. static const char *obs_load_all_modules_name = "obs_load_all_modules";
  443. #ifdef _WIN32
  444. static const char *reset_win32_symbol_paths_name = "reset_win32_symbol_paths";
  445. #endif
  446. void obs_load_all_modules(void)
  447. {
  448. profile_start(obs_load_all_modules_name);
  449. obs_find_modules2(load_all_callback, NULL);
  450. #ifdef _WIN32
  451. profile_start(reset_win32_symbol_paths_name);
  452. reset_win32_symbol_paths();
  453. profile_end(reset_win32_symbol_paths_name);
  454. #endif
  455. profile_end(obs_load_all_modules_name);
  456. }
  457. static const char *obs_load_all_modules2_name = "obs_load_all_modules2";
  458. void obs_load_all_modules2(struct obs_module_failure_info *mfi)
  459. {
  460. struct fail_info fail_info = {0};
  461. memset(mfi, 0, sizeof(*mfi));
  462. profile_start(obs_load_all_modules2_name);
  463. obs_find_modules2(load_all_callback, &fail_info);
  464. #ifdef _WIN32
  465. profile_start(reset_win32_symbol_paths_name);
  466. reset_win32_symbol_paths();
  467. profile_end(reset_win32_symbol_paths_name);
  468. #endif
  469. profile_end(obs_load_all_modules2_name);
  470. mfi->count = fail_info.fail_count;
  471. mfi->failed_modules = strlist_split(fail_info.fail_modules.array, ';', false);
  472. dstr_free(&fail_info.fail_modules);
  473. }
  474. void obs_module_failure_info_free(struct obs_module_failure_info *mfi)
  475. {
  476. if (mfi->failed_modules) {
  477. bfree(mfi->failed_modules);
  478. mfi->failed_modules = NULL;
  479. }
  480. }
  481. void obs_post_load_modules(void)
  482. {
  483. for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
  484. if (mod->post_load)
  485. mod->post_load();
  486. }
  487. static inline void make_data_dir(struct dstr *parsed_data_dir, const char *data_dir, const char *name)
  488. {
  489. dstr_copy(parsed_data_dir, data_dir);
  490. dstr_replace(parsed_data_dir, "%module%", name);
  491. if (dstr_end(parsed_data_dir) == '/')
  492. dstr_resize(parsed_data_dir, parsed_data_dir->len - 1);
  493. }
  494. static char *make_data_directory(const char *module_name, const char *data_dir)
  495. {
  496. struct dstr parsed_data_dir = {0};
  497. bool found = false;
  498. make_data_dir(&parsed_data_dir, data_dir, module_name);
  499. found = os_file_exists(parsed_data_dir.array);
  500. if (!found && astrcmpi_n(module_name, "lib", 3) == 0)
  501. make_data_dir(&parsed_data_dir, data_dir, module_name + 3);
  502. return parsed_data_dir.array;
  503. }
  504. static bool parse_binary_from_directory(struct dstr *parsed_bin_path, const char *bin_path, const char *file)
  505. {
  506. struct dstr directory = {0};
  507. bool found = true;
  508. dstr_copy(&directory, bin_path);
  509. dstr_replace(&directory, "%module%", file);
  510. if (dstr_end(&directory) != '/')
  511. dstr_cat_ch(&directory, '/');
  512. dstr_copy_dstr(parsed_bin_path, &directory);
  513. dstr_cat(parsed_bin_path, file);
  514. #ifdef __APPLE__
  515. if (!os_file_exists(parsed_bin_path->array)) {
  516. dstr_cat(parsed_bin_path, ".so");
  517. }
  518. #else
  519. dstr_cat(parsed_bin_path, get_module_extension());
  520. #endif
  521. if (!os_file_exists(parsed_bin_path->array)) {
  522. /* Legacy fallback: Check for plugin with .so suffix*/
  523. dstr_cat(parsed_bin_path, ".so");
  524. /* if the file doesn't exist, check with 'lib' prefix */
  525. dstr_copy_dstr(parsed_bin_path, &directory);
  526. dstr_cat(parsed_bin_path, "lib");
  527. dstr_cat(parsed_bin_path, file);
  528. dstr_cat(parsed_bin_path, get_module_extension());
  529. /* if neither exist, don't include this as a library */
  530. if (!os_file_exists(parsed_bin_path->array)) {
  531. dstr_free(parsed_bin_path);
  532. found = false;
  533. }
  534. }
  535. dstr_free(&directory);
  536. return found;
  537. }
  538. static void process_found_module(struct obs_module_path *omp, const char *path, bool directory,
  539. obs_find_module_callback2_t callback, void *param)
  540. {
  541. struct obs_module_info2 info;
  542. struct dstr name = {0};
  543. struct dstr parsed_bin_path = {0};
  544. const char *file;
  545. char *parsed_data_dir;
  546. bool bin_found = true;
  547. file = strrchr(path, '/');
  548. file = file ? (file + 1) : path;
  549. if (strcmp(file, ".") == 0 || strcmp(file, "..") == 0)
  550. return;
  551. dstr_copy(&name, file);
  552. char *ext = strrchr(name.array, '.');
  553. if (ext)
  554. dstr_resize(&name, ext - name.array);
  555. if (!directory) {
  556. dstr_copy(&parsed_bin_path, path);
  557. } else {
  558. bin_found = parse_binary_from_directory(&parsed_bin_path, omp->bin, name.array);
  559. }
  560. parsed_data_dir = make_data_directory(name.array, omp->data);
  561. if (parsed_data_dir && bin_found) {
  562. info.bin_path = parsed_bin_path.array;
  563. info.data_path = parsed_data_dir;
  564. info.name = name.array;
  565. callback(param, &info);
  566. }
  567. bfree(parsed_data_dir);
  568. dstr_free(&name);
  569. dstr_free(&parsed_bin_path);
  570. }
  571. static void find_modules_in_path(struct obs_module_path *omp, obs_find_module_callback2_t callback, void *param)
  572. {
  573. struct dstr search_path = {0};
  574. char *module_start;
  575. bool search_directories = false;
  576. os_glob_t *gi;
  577. dstr_copy(&search_path, omp->bin);
  578. module_start = strstr(search_path.array, "%module%");
  579. if (module_start) {
  580. dstr_resize(&search_path, module_start - search_path.array);
  581. search_directories = true;
  582. }
  583. if (!dstr_is_empty(&search_path) && dstr_end(&search_path) != '/')
  584. dstr_cat_ch(&search_path, '/');
  585. dstr_cat_ch(&search_path, '*');
  586. if (!search_directories)
  587. dstr_cat(&search_path, get_module_extension());
  588. if (os_glob(search_path.array, 0, &gi) == 0) {
  589. for (size_t i = 0; i < gi->gl_pathc; i++) {
  590. if (search_directories == gi->gl_pathv[i].directory)
  591. process_found_module(omp, gi->gl_pathv[i].path, search_directories, callback, param);
  592. }
  593. os_globfree(gi);
  594. }
  595. dstr_free(&search_path);
  596. }
  597. void obs_find_modules2(obs_find_module_callback2_t callback, void *param)
  598. {
  599. if (!obs)
  600. return;
  601. for (size_t i = 0; i < obs->module_paths.num; i++) {
  602. struct obs_module_path *omp = obs->module_paths.array + i;
  603. find_modules_in_path(omp, callback, param);
  604. }
  605. }
  606. void obs_find_modules(obs_find_module_callback_t callback, void *param)
  607. {
  608. /* the structure is ABI compatible so we can just cast the callback */
  609. obs_find_modules2((obs_find_module_callback2_t)callback, param);
  610. }
  611. void obs_enum_modules(obs_enum_module_callback_t callback, void *param)
  612. {
  613. struct obs_module *module;
  614. if (!obs)
  615. return;
  616. module = obs->first_module;
  617. while (module) {
  618. callback(param, module);
  619. module = module->next;
  620. }
  621. }
  622. void free_module(struct obs_module *mod)
  623. {
  624. if (!mod)
  625. return;
  626. if (mod->module) {
  627. if (mod->free_locale)
  628. mod->free_locale();
  629. if (mod->loaded && mod->unload)
  630. mod->unload();
  631. /* there is no real reason to close the dynamic libraries,
  632. * and sometimes this can cause issues. */
  633. /* os_dlclose(mod->module); */
  634. }
  635. /* Is this module an active / loaded module, or a disabled module? */
  636. if (mod->load_state == OBS_MODULE_ENABLED) {
  637. for (obs_module_t *m = obs->first_module; !!m; m = m->next) {
  638. if (m->next == mod) {
  639. m->next = mod->next;
  640. break;
  641. }
  642. }
  643. if (obs->first_module == mod)
  644. obs->first_module = mod->next;
  645. } else {
  646. for (obs_module_t *m = obs->first_disabled_module; !!m; m = m->next) {
  647. if (m->next == mod) {
  648. m->next = mod->next;
  649. break;
  650. }
  651. }
  652. if (obs->first_disabled_module == mod)
  653. obs->first_disabled_module = mod->next;
  654. }
  655. bfree(mod->mod_name);
  656. bfree(mod->bin_path);
  657. bfree(mod->data_path);
  658. for (size_t i = 0; i < mod->sources.num; i++) {
  659. bfree(mod->sources.array[i]);
  660. }
  661. da_free(mod->sources);
  662. for (size_t i = 0; i < mod->outputs.num; i++) {
  663. bfree(mod->outputs.array[i]);
  664. }
  665. da_free(mod->outputs);
  666. for (size_t i = 0; i < mod->encoders.num; i++) {
  667. bfree(mod->encoders.array[i]);
  668. }
  669. da_free(mod->encoders);
  670. for (size_t i = 0; i < mod->services.num; i++) {
  671. bfree(mod->services.array[i]);
  672. }
  673. da_free(mod->services);
  674. if (mod->metadata) {
  675. free_module_metadata(mod->metadata);
  676. bfree(mod->metadata);
  677. }
  678. bfree(mod);
  679. }
  680. lookup_t *obs_module_load_locale(obs_module_t *module, const char *default_locale, const char *locale)
  681. {
  682. struct dstr str = {0};
  683. lookup_t *lookup = NULL;
  684. if (!module || !default_locale || !locale) {
  685. blog(LOG_WARNING, "obs_module_load_locale: Invalid parameters");
  686. return NULL;
  687. }
  688. dstr_copy(&str, "locale/");
  689. dstr_cat(&str, default_locale);
  690. dstr_cat(&str, ".ini");
  691. char *file = obs_find_module_file(module, str.array);
  692. if (file)
  693. lookup = text_lookup_create(file);
  694. bfree(file);
  695. if (!lookup) {
  696. blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'", default_locale, module->file);
  697. goto cleanup;
  698. }
  699. if (astrcmpi(locale, default_locale) == 0)
  700. goto cleanup;
  701. dstr_copy(&str, "/locale/");
  702. dstr_cat(&str, locale);
  703. dstr_cat(&str, ".ini");
  704. file = obs_find_module_file(module, str.array);
  705. if (!text_lookup_add(lookup, file))
  706. blog(LOG_WARNING, "Failed to load '%s' text for module: '%s'", locale, module->file);
  707. bfree(file);
  708. cleanup:
  709. dstr_free(&str);
  710. return lookup;
  711. }
  712. #define REGISTER_OBS_DEF(size_var, structure, dest, info) \
  713. do { \
  714. struct structure data = {0}; \
  715. if (!size_var) { \
  716. blog(LOG_ERROR, "Tried to register " #structure " outside of obs_module_load"); \
  717. return; \
  718. } \
  719. \
  720. if (size_var > sizeof(data)) { \
  721. blog(LOG_ERROR, \
  722. "Tried to register " #structure " with size %llu which is more " \
  723. "than libobs currently supports " \
  724. "(%llu)", \
  725. (long long unsigned)size_var, (long long unsigned)sizeof(data)); \
  726. goto error; \
  727. } \
  728. \
  729. memcpy(&data, info, size_var); \
  730. da_push_back(dest, &data); \
  731. } while (false)
  732. #define HAS_VAL(type, info, val) ((offsetof(type, val) + sizeof(info->val) <= size) && info->val)
  733. #define CHECK_REQUIRED_VAL(type, info, val, func) \
  734. do { \
  735. if (!HAS_VAL(type, info, val)) { \
  736. blog(LOG_ERROR, \
  737. "Required value '" #val "' for " \
  738. "'%s' not found. " #func " failed.", \
  739. info->id); \
  740. goto error; \
  741. } \
  742. } while (false)
  743. #define CHECK_REQUIRED_VAL_EITHER(type, info, val1, val2, func) \
  744. do { \
  745. if (!HAS_VAL(type, info, val1) && !HAS_VAL(type, info, val2)) { \
  746. blog(LOG_ERROR, \
  747. "Neither '" #val1 "' nor '" #val2 "' " \
  748. "for '%s' found. " #func " failed.", \
  749. info->id); \
  750. goto error; \
  751. } \
  752. } while (false)
  753. #define HANDLE_ERROR(size_var, structure, info) \
  754. do { \
  755. struct structure data = {0}; \
  756. if (!size_var) \
  757. return; \
  758. \
  759. memcpy(&data, info, sizeof(data) < size_var ? sizeof(data) : size_var); \
  760. \
  761. if (data.type_data && data.free_type_data) \
  762. data.free_type_data(data.type_data); \
  763. } while (false)
  764. #define source_warn(format, ...) blog(LOG_WARNING, "obs_register_source: " format, ##__VA_ARGS__)
  765. #define output_warn(format, ...) blog(LOG_WARNING, "obs_register_output: " format, ##__VA_ARGS__)
  766. #define encoder_warn(format, ...) blog(LOG_WARNING, "obs_register_encoder: " format, ##__VA_ARGS__)
  767. #define service_warn(format, ...) blog(LOG_WARNING, "obs_register_service: " format, ##__VA_ARGS__)
  768. void obs_register_source_s(const struct obs_source_info *info, size_t size)
  769. {
  770. struct obs_source_info data = {0};
  771. obs_source_info_array_t *array = NULL;
  772. if (info->type == OBS_SOURCE_TYPE_INPUT) {
  773. array = &obs->input_types;
  774. } else if (info->type == OBS_SOURCE_TYPE_FILTER) {
  775. array = &obs->filter_types;
  776. } else if (info->type == OBS_SOURCE_TYPE_TRANSITION) {
  777. array = &obs->transition_types;
  778. } else if (info->type != OBS_SOURCE_TYPE_SCENE) {
  779. source_warn("Tried to register unknown source type: %u", info->type);
  780. goto error;
  781. }
  782. if (get_source_info2(info->id, info->version)) {
  783. source_warn("Source '%s' already exists! "
  784. "Duplicate library?",
  785. info->id);
  786. goto error;
  787. }
  788. if (size > sizeof(data)) {
  789. source_warn("Tried to register obs_source_info with size "
  790. "%llu which is more than libobs currently "
  791. "supports (%llu)",
  792. (long long unsigned)size, (long long unsigned)sizeof(data));
  793. goto error;
  794. }
  795. /* NOTE: The assignment of data.module must occur before memcpy! */
  796. if (loadingModule) {
  797. char *source_id = bstrdup(info->id);
  798. da_push_back(loadingModule->sources, &source_id);
  799. }
  800. memcpy(&data, info, size);
  801. /* mark audio-only filters as an async filter categorically */
  802. if (data.type == OBS_SOURCE_TYPE_FILTER) {
  803. if ((data.output_flags & OBS_SOURCE_VIDEO) == 0)
  804. data.output_flags |= OBS_SOURCE_ASYNC;
  805. }
  806. if (data.type == OBS_SOURCE_TYPE_TRANSITION) {
  807. if (data.get_width)
  808. source_warn("get_width ignored registering "
  809. "transition '%s'",
  810. data.id);
  811. if (data.get_height)
  812. source_warn("get_height ignored registering "
  813. "transition '%s'",
  814. data.id);
  815. data.output_flags |= OBS_SOURCE_COMPOSITE | OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW;
  816. }
  817. if ((data.output_flags & OBS_SOURCE_COMPOSITE) != 0) {
  818. if ((data.output_flags & OBS_SOURCE_AUDIO) != 0) {
  819. source_warn("Source '%s': Composite sources "
  820. "cannot be audio sources",
  821. info->id);
  822. goto error;
  823. }
  824. if ((data.output_flags & OBS_SOURCE_ASYNC) != 0) {
  825. source_warn("Source '%s': Composite sources "
  826. "cannot be async sources",
  827. info->id);
  828. goto error;
  829. }
  830. }
  831. #define CHECK_REQUIRED_VAL_(info, val, func) CHECK_REQUIRED_VAL(struct obs_source_info, info, val, func)
  832. CHECK_REQUIRED_VAL_(info, get_name, obs_register_source);
  833. if (info->type != OBS_SOURCE_TYPE_FILTER && info->type != OBS_SOURCE_TYPE_TRANSITION &&
  834. (info->output_flags & OBS_SOURCE_VIDEO) != 0 && (info->output_flags & OBS_SOURCE_ASYNC) == 0) {
  835. CHECK_REQUIRED_VAL_(info, get_width, obs_register_source);
  836. CHECK_REQUIRED_VAL_(info, get_height, obs_register_source);
  837. }
  838. if ((data.output_flags & OBS_SOURCE_COMPOSITE) != 0) {
  839. CHECK_REQUIRED_VAL_(info, audio_render, obs_register_source);
  840. }
  841. #undef CHECK_REQUIRED_VAL_
  842. /* version-related stuff */
  843. data.unversioned_id = data.id;
  844. if (data.version) {
  845. struct dstr versioned_id = {0};
  846. dstr_printf(&versioned_id, "%s_v%d", data.id, (int)data.version);
  847. data.id = versioned_id.array;
  848. } else {
  849. data.id = bstrdup(data.id);
  850. }
  851. if (array)
  852. da_push_back(*array, &data);
  853. da_push_back(obs->source_types, &data);
  854. return;
  855. error:
  856. HANDLE_ERROR(size, obs_source_info, info);
  857. }
  858. void obs_register_output_s(const struct obs_output_info *info, size_t size)
  859. {
  860. if (find_output(info->id)) {
  861. output_warn("Output id '%s' already exists! "
  862. "Duplicate library?",
  863. info->id);
  864. goto error;
  865. }
  866. #define CHECK_REQUIRED_VAL_(info, val, func) CHECK_REQUIRED_VAL(struct obs_output_info, info, val, func)
  867. CHECK_REQUIRED_VAL_(info, get_name, obs_register_output);
  868. CHECK_REQUIRED_VAL_(info, create, obs_register_output);
  869. CHECK_REQUIRED_VAL_(info, destroy, obs_register_output);
  870. CHECK_REQUIRED_VAL_(info, start, obs_register_output);
  871. CHECK_REQUIRED_VAL_(info, stop, obs_register_output);
  872. if (info->flags & OBS_OUTPUT_SERVICE)
  873. CHECK_REQUIRED_VAL_(info, protocols, obs_register_output);
  874. if (info->flags & OBS_OUTPUT_ENCODED) {
  875. CHECK_REQUIRED_VAL_(info, encoded_packet, obs_register_output);
  876. } else {
  877. if (info->flags & OBS_OUTPUT_VIDEO)
  878. CHECK_REQUIRED_VAL_(info, raw_video, obs_register_output);
  879. if (info->flags & OBS_OUTPUT_AUDIO) {
  880. if (info->flags & OBS_OUTPUT_MULTI_TRACK) {
  881. CHECK_REQUIRED_VAL_(info, raw_audio2, obs_register_output);
  882. } else {
  883. CHECK_REQUIRED_VAL_(info, raw_audio, obs_register_output);
  884. }
  885. }
  886. }
  887. #undef CHECK_REQUIRED_VAL_
  888. REGISTER_OBS_DEF(size, obs_output_info, obs->output_types, info);
  889. if (info->flags & OBS_OUTPUT_SERVICE) {
  890. char **protocols = strlist_split(info->protocols, ';', false);
  891. for (char **protocol = protocols; *protocol; ++protocol) {
  892. bool skip = false;
  893. for (size_t i = 0; i < obs->data.protocols.num; i++) {
  894. if (strcmp(*protocol, obs->data.protocols.array[i]) == 0)
  895. skip = true;
  896. }
  897. if (skip)
  898. continue;
  899. char *new_prtcl = bstrdup(*protocol);
  900. da_push_back(obs->data.protocols, &new_prtcl);
  901. }
  902. strlist_free(protocols);
  903. }
  904. if (loadingModule) {
  905. char *output_id = bstrdup(info->id);
  906. da_push_back(loadingModule->outputs, &output_id);
  907. }
  908. return;
  909. error:
  910. HANDLE_ERROR(size, obs_output_info, info);
  911. }
  912. void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size)
  913. {
  914. if (find_encoder(info->id)) {
  915. encoder_warn("Encoder id '%s' already exists! "
  916. "Duplicate library?",
  917. info->id);
  918. goto error;
  919. }
  920. if (((info->caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 && info->caps & OBS_ENCODER_CAP_SCALING) != 0) {
  921. encoder_warn("Texture encoders cannot self-scale. Encoder id '%s' not registered.", info->id);
  922. goto error;
  923. }
  924. #define CHECK_REQUIRED_VAL_(info, val, func) CHECK_REQUIRED_VAL(struct obs_encoder_info, info, val, func)
  925. CHECK_REQUIRED_VAL_(info, get_name, obs_register_encoder);
  926. CHECK_REQUIRED_VAL_(info, create, obs_register_encoder);
  927. CHECK_REQUIRED_VAL_(info, destroy, obs_register_encoder);
  928. if ((info->caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0)
  929. CHECK_REQUIRED_VAL_EITHER(struct obs_encoder_info, info, encode_texture, encode_texture2,
  930. obs_register_encoder);
  931. else
  932. CHECK_REQUIRED_VAL_(info, encode, obs_register_encoder);
  933. if (info->type == OBS_ENCODER_AUDIO)
  934. CHECK_REQUIRED_VAL_(info, get_frame_size, obs_register_encoder);
  935. #undef CHECK_REQUIRED_VAL_
  936. REGISTER_OBS_DEF(size, obs_encoder_info, obs->encoder_types, info);
  937. if (loadingModule) {
  938. char *encoder_id = bstrdup(info->id);
  939. da_push_back(loadingModule->encoders, &encoder_id);
  940. }
  941. return;
  942. error:
  943. HANDLE_ERROR(size, obs_encoder_info, info);
  944. }
  945. void obs_register_service_s(const struct obs_service_info *info, size_t size)
  946. {
  947. if (find_service(info->id)) {
  948. service_warn("Service id '%s' already exists! "
  949. "Duplicate library?",
  950. info->id);
  951. goto error;
  952. }
  953. #define CHECK_REQUIRED_VAL_(info, val, func) CHECK_REQUIRED_VAL(struct obs_service_info, info, val, func)
  954. CHECK_REQUIRED_VAL_(info, get_name, obs_register_service);
  955. CHECK_REQUIRED_VAL_(info, create, obs_register_service);
  956. CHECK_REQUIRED_VAL_(info, destroy, obs_register_service);
  957. CHECK_REQUIRED_VAL_(info, get_protocol, obs_register_service);
  958. #undef CHECK_REQUIRED_VAL_
  959. REGISTER_OBS_DEF(size, obs_service_info, obs->service_types, info);
  960. if (loadingModule) {
  961. char *service_id = bstrdup(info->id);
  962. da_push_back(loadingModule->services, &service_id);
  963. }
  964. return;
  965. error:
  966. HANDLE_ERROR(size, obs_service_info, info);
  967. }