You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
13KB

  1. #include <thread>
  2. #include <map>
  3. #include <stdexcept>
  4. #include <tuple>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <unistd.h>
  8. #include <sys/param.h> // for MAXPATHLEN
  9. #include <fcntl.h>
  10. #if defined ARCH_WIN
  11. #include <windows.h>
  12. #include <direct.h>
  13. #else
  14. #include <dlfcn.h> // for dlopen
  15. #endif
  16. #include <dirent.h>
  17. #include <osdialog.h>
  18. #include <jansson.h>
  19. #include <plugin.hpp>
  20. #include <system.hpp>
  21. #include <asset.hpp>
  22. #include <string.hpp>
  23. #include <context.hpp>
  24. #include <plugin/callbacks.hpp>
  25. #include <settings.hpp>
  26. namespace rack {
  27. namespace core {
  28. void init(rack::plugin::Plugin* plugin);
  29. } // namespace core
  30. namespace plugin {
  31. ////////////////////
  32. // private API
  33. ////////////////////
  34. static void* getSymbol(void* handle, const char* name) {
  35. if (!handle)
  36. return NULL;
  37. #if defined ARCH_WIN
  38. return (void*) GetProcAddress((HMODULE) handle, name);
  39. #else
  40. return dlsym(handle, name);
  41. #endif
  42. }
  43. /** Returns library handle */
  44. static void* loadLibrary(std::string libraryPath) {
  45. #if defined ARCH_WIN
  46. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  47. std::wstring libraryFilenameW = string::UTF8toUTF16(libraryPath);
  48. HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str());
  49. SetErrorMode(0);
  50. if (!handle) {
  51. int error = GetLastError();
  52. throw Exception("Failed to load library %s: code %d", libraryPath.c_str(), error);
  53. }
  54. #else
  55. // Since Rack 2, plugins on Linux/Mac link to the absolute path /tmp/Rack2/libRack.<ext>
  56. // Create a symlink at /tmp/Rack2 to the system dir containting libRack.
  57. std::string systemDir = system::getAbsolute(asset::systemDir);
  58. std::string linkPath = "/tmp/Rack2";
  59. if (!settings::devMode) {
  60. // Clean up old symbolic link in case a different edition was run earlier
  61. system::remove(linkPath);
  62. system::createSymbolicLink(systemDir, linkPath);
  63. }
  64. // Load library with dlopen
  65. void* handle = NULL;
  66. #if defined ARCH_LIN
  67. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  68. #elif defined ARCH_MAC
  69. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  70. #endif
  71. if (!settings::devMode) {
  72. system::remove(linkPath);
  73. }
  74. if (!handle)
  75. throw Exception("Failed to load library %s: %s", libraryPath.c_str(), dlerror());
  76. #endif
  77. return handle;
  78. }
  79. typedef void (*InitCallback)(Plugin*);
  80. static InitCallback loadPluginCallback(Plugin* plugin) {
  81. // Load plugin library
  82. std::string libraryExt;
  83. #if defined ARCH_LIN
  84. libraryExt = "so";
  85. #elif defined ARCH_WIN
  86. libraryExt = "dll";
  87. #elif ARCH_MAC
  88. libraryExt = "dylib";
  89. #endif
  90. #if defined ARCH_X64
  91. // Use `plugin.EXT` on x64 for backward compatibility.
  92. // Change to `plugin-OS-CPU.EXT` in Rack 3.
  93. std::string libraryFilename = "plugin." + libraryExt;
  94. #else
  95. // Use `plugin-CPU.EXT` on other CPUs like ARM64
  96. std::string libraryFilename = "plugin-" + APP_CPU + "." + libraryExt;
  97. #endif
  98. std::string libraryPath = system::join(plugin->path, libraryFilename);
  99. // Check file existence
  100. if (!system::isFile(libraryPath))
  101. throw Exception("Plugin binary not found at %s", libraryPath.c_str());
  102. // Load dynamic/shared library
  103. plugin->handle = loadLibrary(libraryPath);
  104. // Get plugin's init() function
  105. InitCallback initCallback = (InitCallback) getSymbol(plugin->handle, "init");
  106. if (!initCallback)
  107. throw Exception("Failed to read init() symbol in %s", libraryPath.c_str());
  108. return initCallback;
  109. }
  110. /** If path is blank, loads Core */
  111. static Plugin* loadPlugin(std::string path) {
  112. if (path == "")
  113. INFO("Loading Core plugin");
  114. else
  115. INFO("Loading plugin from %s", path.c_str());
  116. Plugin* plugin = new Plugin;
  117. try {
  118. // Set plugin path
  119. plugin->path = (path == "") ? asset::systemDir : path;
  120. // Get modified timestamp
  121. if (path != "") {
  122. struct stat statbuf;
  123. if (!stat(path.c_str(), &statbuf)) {
  124. #if defined ARCH_MAC
  125. plugin->modifiedTimestamp = (double) statbuf.st_mtimespec.tv_sec + statbuf.st_mtimespec.tv_nsec * 1e-9;
  126. #elif defined ARCH_WIN
  127. plugin->modifiedTimestamp = (double) statbuf.st_mtime;
  128. #elif defined ARCH_LIN
  129. plugin->modifiedTimestamp = (double) statbuf.st_mtim.tv_sec + statbuf.st_mtim.tv_nsec * 1e-9;
  130. #endif
  131. }
  132. }
  133. // Load plugin.json
  134. std::string manifestFilename = (path == "") ? asset::system("Core.json") : system::join(path, "plugin.json");
  135. FILE* file = std::fopen(manifestFilename.c_str(), "r");
  136. if (!file)
  137. throw Exception("Manifest file %s does not exist", manifestFilename.c_str());
  138. DEFER({std::fclose(file);});
  139. json_error_t error;
  140. json_t* rootJ = json_loadf(file, 0, &error);
  141. if (!rootJ)
  142. throw Exception("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text);
  143. DEFER({json_decref(rootJ);});
  144. // Load manifest
  145. plugin->fromJson(rootJ);
  146. // Reject plugin if slug already exists
  147. Plugin* existingPlugin = getPlugin(plugin->slug);
  148. if (existingPlugin)
  149. throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str());
  150. // Call init callback
  151. InitCallback initCallback;
  152. if (path == "") {
  153. initCallback = core::init;
  154. }
  155. else {
  156. initCallback = loadPluginCallback(plugin);
  157. }
  158. initCallback(plugin);
  159. // Load modules manifest
  160. json_t* modulesJ = json_object_get(rootJ, "modules");
  161. plugin->modulesFromJson(modulesJ);
  162. // Call settingsFromJson() if exists
  163. // Returns NULL for Core.
  164. auto settingsFromJson = (decltype(&::settingsFromJson)) getSymbol(plugin->handle, "settingsFromJson");
  165. if (settingsFromJson) {
  166. json_t* settingsJ = json_object_get(settings::pluginSettingsJ, plugin->slug.c_str());
  167. if (settingsJ)
  168. settingsFromJson(settingsJ);
  169. }
  170. }
  171. catch (Exception& e) {
  172. WARN("Could not load plugin %s: %s", path.c_str(), e.what());
  173. delete plugin;
  174. return NULL;
  175. }
  176. INFO("Loaded %s %s", plugin->slug.c_str(), plugin->version.c_str());
  177. plugins.push_back(plugin);
  178. return plugin;
  179. }
  180. static void loadPlugins(std::string path) {
  181. for (std::string pluginPath : system::getEntries(path)) {
  182. if (!system::isDirectory(pluginPath))
  183. continue;
  184. if (!loadPlugin(pluginPath)) {
  185. // Ignore bad plugins. They are reported in the log.
  186. }
  187. }
  188. }
  189. static void extractPackages(std::string path) {
  190. std::string message;
  191. for (std::string packagePath : system::getEntries(path)) {
  192. if (!system::isFile(packagePath))
  193. continue;
  194. if (system::getExtension(packagePath) != ".vcvplugin")
  195. continue;
  196. // Extract package
  197. INFO("Extracting package %s", packagePath.c_str());
  198. try {
  199. system::unarchiveToDirectory(packagePath, path);
  200. }
  201. catch (Exception& e) {
  202. WARN("Plugin package %s failed to extract: %s", packagePath.c_str(), e.what());
  203. message += string::f("Could not extract plugin package %s\n", packagePath.c_str());
  204. continue;
  205. }
  206. // Remove package
  207. system::remove(packagePath.c_str());
  208. }
  209. if (!message.empty()) {
  210. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  211. }
  212. }
  213. ////////////////////
  214. // public API
  215. ////////////////////
  216. void init() {
  217. // Don't re-initialize
  218. assert(plugins.empty());
  219. // Load Core
  220. loadPlugin("");
  221. pluginsPath = asset::user("plugins");
  222. // Get user plugins directory
  223. system::createDirectory(pluginsPath);
  224. // Don't load plugins if safe mode is enabled
  225. if (settings::safeMode)
  226. return;
  227. // Extract packages and load plugins
  228. extractPackages(pluginsPath);
  229. loadPlugins(pluginsPath);
  230. // If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
  231. if (!settings::devMode && !getPlugin("Fundamental")) {
  232. std::string fundamentalSrc = asset::system("Fundamental.vcvplugin");
  233. std::string fundamentalDir = system::join(pluginsPath, "Fundamental");
  234. if (system::isFile(fundamentalSrc)) {
  235. INFO("Extracting bundled Fundamental package");
  236. try {
  237. system::unarchiveToDirectory(fundamentalSrc.c_str(), pluginsPath.c_str());
  238. loadPlugin(fundamentalDir);
  239. }
  240. catch (Exception& e) {
  241. WARN("Could not extract Fundamental package: %s", e.what());
  242. }
  243. }
  244. }
  245. }
  246. static void destroyPlugin(Plugin* plugin) {
  247. void* handle = plugin->handle;
  248. // Call destroy() if defined in the plugin library
  249. typedef void (*DestroyCallback)();
  250. DestroyCallback destroyCallback = NULL;
  251. if (handle) {
  252. destroyCallback = (DestroyCallback) getSymbol(handle, "destroy");
  253. }
  254. if (destroyCallback) {
  255. try {
  256. destroyCallback();
  257. }
  258. catch (Exception& e) {
  259. WARN("Could not destroy plugin %s", plugin->slug.c_str());
  260. }
  261. }
  262. // We must delete the Plugin instance *before* freeing the library, because the vtables of Model subclasses are defined in the library, which are needed in the Plugin destructor.
  263. delete plugin;
  264. // Free library handle
  265. if (handle) {
  266. #if defined ARCH_WIN
  267. FreeLibrary((HINSTANCE) handle);
  268. #else
  269. dlclose(handle);
  270. #endif
  271. }
  272. }
  273. void destroy() {
  274. while (!plugins.empty()) {
  275. Plugin* plugin = plugins.back();
  276. INFO("Destroying plugin %s", plugin->name.c_str());
  277. destroyPlugin(plugin);
  278. plugins.pop_back();
  279. }
  280. assert(plugins.empty());
  281. }
  282. void settingsMergeJson(json_t* rootJ) {
  283. for (Plugin* plugin : plugins) {
  284. auto settingsToJson = (decltype(&::settingsToJson)) getSymbol(plugin->handle, "settingsToJson");
  285. if (settingsToJson) {
  286. json_t* settingsJ = settingsToJson();
  287. json_object_set_new(rootJ, plugin->slug.c_str(), settingsJ);
  288. }
  289. else {
  290. json_object_del(rootJ, plugin->slug.c_str());
  291. }
  292. }
  293. }
  294. /** Given slug => fallback slug.
  295. Supports bidirectional fallbacks.
  296. To request fallback slugs to be added to this list, contact VCV support.
  297. */
  298. static const std::map<std::string, std::string> pluginSlugFallbacks = {
  299. {"VultModulesFree", "VultModules"},
  300. {"VultModules", "VultModulesFree"},
  301. {"AudibleInstrumentsPreview", "AudibleInstruments"},
  302. {"SequelSequencers", "DanielDavies"},
  303. {"DelexanderVol1", "DelexandraVol1"},
  304. // {"", ""},
  305. };
  306. Plugin* getPlugin(const std::string& pluginSlug) {
  307. if (pluginSlug.empty())
  308. return NULL;
  309. auto it = std::find_if(plugins.begin(), plugins.end(), [=](Plugin* p) {
  310. return p->slug == pluginSlug;
  311. });
  312. if (it != plugins.end())
  313. return *it;
  314. return NULL;
  315. }
  316. Plugin* getPluginFallback(const std::string& pluginSlug) {
  317. if (pluginSlug.empty())
  318. return NULL;
  319. // Attempt example plugin
  320. Plugin* p = getPlugin(pluginSlug);
  321. if (p)
  322. return p;
  323. // Attempt fallback plugin slug
  324. auto it = pluginSlugFallbacks.find(pluginSlug);
  325. if (it != pluginSlugFallbacks.end())
  326. return getPlugin(it->second);
  327. return NULL;
  328. }
  329. /** Given slug => fallback slug.
  330. Correctly handles bidirectional fallbacks.
  331. To request fallback slugs to be added to this list, open a GitHub issue.
  332. */
  333. using PluginModuleSlug = std::tuple<std::string, std::string>;
  334. static const std::map<PluginModuleSlug, PluginModuleSlug> moduleSlugFallbacks = {
  335. {{"MindMeld-ShapeMasterPro", "ShapeMasterPro"}, {"MindMeldModular", "ShapeMaster"}},
  336. {{"MindMeldModular", "ShapeMaster"}, {"MindMeld-ShapeMasterPro", "ShapeMasterPro"}},
  337. // {{"", ""}, {"", ""}},
  338. };
  339. Model* getModel(const std::string& pluginSlug, const std::string& modelSlug) {
  340. if (pluginSlug.empty() || modelSlug.empty())
  341. return NULL;
  342. Plugin* p = getPlugin(pluginSlug);
  343. if (!p)
  344. return NULL;
  345. return p->getModel(modelSlug);
  346. }
  347. Model* getModelFallback(const std::string& pluginSlug, const std::string& modelSlug) {
  348. if (pluginSlug.empty() || modelSlug.empty())
  349. return NULL;
  350. // Attempt exact plugin and model
  351. Model* m = getModel(pluginSlug, modelSlug);
  352. if (m)
  353. return m;
  354. // Attempt fallback module
  355. auto it = moduleSlugFallbacks.find(std::make_tuple(pluginSlug, modelSlug));
  356. if (it != moduleSlugFallbacks.end()) {
  357. Model* m = getModel(std::get<0>(it->second), std::get<1>(it->second));
  358. if (m)
  359. return m;
  360. }
  361. // Attempt fallback plugin
  362. auto it2 = pluginSlugFallbacks.find(pluginSlug);
  363. if (it2 != pluginSlugFallbacks.end()) {
  364. Model* m = getModel(it2->second, modelSlug);
  365. if (m)
  366. return m;
  367. }
  368. return NULL;
  369. }
  370. Model* modelFromJson(json_t* moduleJ) {
  371. // Get slugs
  372. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  373. if (!pluginSlugJ)
  374. throw Exception("\"plugin\" property not found in module JSON");
  375. std::string pluginSlug = json_string_value(pluginSlugJ);
  376. pluginSlug = normalizeSlug(pluginSlug);
  377. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  378. if (!modelSlugJ)
  379. throw Exception("\"model\" property not found in module JSON");
  380. std::string modelSlug = json_string_value(modelSlugJ);
  381. modelSlug = normalizeSlug(modelSlug);
  382. // Get Model
  383. Model* model = getModelFallback(pluginSlug, modelSlug);
  384. if (!model)
  385. throw Exception("Could not find module %s/%s", pluginSlug.c_str(), modelSlug.c_str());
  386. return model;
  387. }
  388. bool isSlugValid(const std::string& slug) {
  389. for (char c : slug) {
  390. if (!(std::isalnum(c) || c == '-' || c == '_'))
  391. return false;
  392. }
  393. return true;
  394. }
  395. std::string normalizeSlug(const std::string& slug) {
  396. std::string s;
  397. for (char c : slug) {
  398. if (!(std::isalnum(c) || c == '-' || c == '_'))
  399. continue;
  400. s += c;
  401. }
  402. return s;
  403. }
  404. std::string pluginsPath;
  405. std::vector<Plugin*> plugins;
  406. } // namespace plugin
  407. } // namespace rack