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.

432 lines
11KB

  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* loadLibrary(std::string libraryPath) {
  35. #if defined ARCH_WIN
  36. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  37. std::wstring libraryFilenameW = string::UTF8toUTF16(libraryPath);
  38. HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str());
  39. SetErrorMode(0);
  40. if (!handle) {
  41. int error = GetLastError();
  42. throw Exception("Failed to load library %s: code %d", libraryPath.c_str(), error);
  43. }
  44. #else
  45. // Since Rack 2, plugins on Linux/Mac link to the absolute path /tmp/Rack2/libRack.<ext>
  46. // Create a symlink at /tmp/Rack2 to the system dir containting libRack.
  47. std::string systemDir = system::getAbsolute(asset::systemDir);
  48. std::string linkPath = "/tmp/Rack2";
  49. if (!settings::devMode) {
  50. // Clean up old symbolic link in case a different edition was run earlier
  51. system::remove(linkPath);
  52. system::createSymbolicLink(systemDir, linkPath);
  53. }
  54. // Load library with dlopen
  55. void* handle = NULL;
  56. #if defined ARCH_LIN
  57. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  58. #elif defined ARCH_MAC
  59. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  60. #endif
  61. if (!settings::devMode) {
  62. system::remove(linkPath);
  63. }
  64. if (!handle)
  65. throw Exception("Failed to load library %s: %s", libraryPath.c_str(), dlerror());
  66. #endif
  67. return handle;
  68. }
  69. typedef void (*InitCallback)(Plugin*);
  70. static InitCallback loadPluginCallback(Plugin* plugin) {
  71. // Load plugin library
  72. std::string libraryExt;
  73. #if defined ARCH_LIN
  74. libraryExt = "so";
  75. #elif defined ARCH_WIN
  76. libraryExt = "dll";
  77. #elif ARCH_MAC
  78. libraryExt = "dylib";
  79. #endif
  80. std::string libraryPath = system::join(plugin->path, "plugin." + libraryExt);
  81. // Check file existence
  82. if (!system::isFile(libraryPath))
  83. throw Exception("Plugin binary not found at %s", libraryPath.c_str());
  84. // Load dynamic/shared library
  85. plugin->handle = loadLibrary(libraryPath);
  86. // Get plugin's init() function
  87. InitCallback initCallback;
  88. #if defined ARCH_WIN
  89. initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init");
  90. #else
  91. initCallback = (InitCallback) dlsym(plugin->handle, "init");
  92. #endif
  93. if (!initCallback)
  94. throw Exception("Failed to read init() symbol in %s", libraryPath.c_str());
  95. return initCallback;
  96. }
  97. /** If path is blank, loads Core */
  98. static Plugin* loadPlugin(std::string path) {
  99. if (path == "")
  100. INFO("Loading Core plugin");
  101. else
  102. INFO("Loading plugin from %s", path.c_str());
  103. Plugin* plugin = new Plugin;
  104. try {
  105. // Set plugin path
  106. plugin->path = (path == "") ? asset::systemDir : path;
  107. // Get modified timestamp
  108. if (path != "") {
  109. struct stat statbuf;
  110. if (!stat(path.c_str(), &statbuf)) {
  111. #if defined ARCH_MAC
  112. plugin->modifiedTimestamp = (double) statbuf.st_mtimespec.tv_sec + statbuf.st_mtimespec.tv_nsec * 1e-9;
  113. #elif defined ARCH_WIN
  114. plugin->modifiedTimestamp = (double) statbuf.st_mtime;
  115. #elif defined ARCH_LIN
  116. plugin->modifiedTimestamp = (double) statbuf.st_mtim.tv_sec + statbuf.st_mtim.tv_nsec * 1e-9;
  117. #endif
  118. }
  119. }
  120. // Load plugin.json
  121. std::string manifestFilename = (path == "") ? asset::system("Core.json") : system::join(path, "plugin.json");
  122. FILE* file = std::fopen(manifestFilename.c_str(), "r");
  123. if (!file)
  124. throw Exception("Manifest file %s does not exist", manifestFilename.c_str());
  125. DEFER({std::fclose(file);});
  126. json_error_t error;
  127. json_t* rootJ = json_loadf(file, 0, &error);
  128. if (!rootJ)
  129. throw Exception("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text);
  130. DEFER({json_decref(rootJ);});
  131. // Call init callback
  132. InitCallback initCallback;
  133. if (path == "") {
  134. initCallback = core::init;
  135. }
  136. else {
  137. initCallback = loadPluginCallback(plugin);
  138. }
  139. initCallback(plugin);
  140. // Load manifest
  141. plugin->fromJson(rootJ);
  142. // Reject plugin if slug already exists
  143. Plugin* existingPlugin = getPlugin(plugin->slug);
  144. if (existingPlugin)
  145. throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str());
  146. }
  147. catch (Exception& e) {
  148. WARN("Could not load plugin %s: %s", path.c_str(), e.what());
  149. delete plugin;
  150. return NULL;
  151. }
  152. INFO("Loaded %s v%s", plugin->slug.c_str(), plugin->version.c_str());
  153. plugins.push_back(plugin);
  154. return plugin;
  155. }
  156. static void loadPlugins(std::string path) {
  157. for (std::string pluginPath : system::getEntries(path)) {
  158. if (!system::isDirectory(pluginPath))
  159. continue;
  160. if (!loadPlugin(pluginPath)) {
  161. // Ignore bad plugins. They are reported in the log.
  162. }
  163. }
  164. }
  165. static void extractPackages(std::string path) {
  166. std::string message;
  167. for (std::string packagePath : system::getEntries(path)) {
  168. if (!system::isFile(packagePath))
  169. continue;
  170. if (system::getExtension(packagePath) != ".vcvplugin")
  171. continue;
  172. // Extract package
  173. INFO("Extracting package %s", packagePath.c_str());
  174. try {
  175. system::unarchiveToDirectory(packagePath, path);
  176. }
  177. catch (Exception& e) {
  178. WARN("Plugin package %s failed to extract: %s", packagePath.c_str(), e.what());
  179. message += string::f("Could not extract plugin package %s\n", packagePath.c_str());
  180. continue;
  181. }
  182. // Remove package
  183. system::remove(packagePath.c_str());
  184. }
  185. if (!message.empty()) {
  186. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  187. }
  188. }
  189. ////////////////////
  190. // public API
  191. ////////////////////
  192. void init() {
  193. // Don't re-initialize
  194. assert(plugins.empty());
  195. // Load Core
  196. loadPlugin("");
  197. pluginsPath = asset::user("plugins");
  198. // Get user plugins directory
  199. system::createDirectory(pluginsPath);
  200. // Extract packages and load plugins
  201. extractPackages(pluginsPath);
  202. loadPlugins(pluginsPath);
  203. // If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
  204. if (!settings::devMode && !getPlugin("Fundamental")) {
  205. std::string fundamentalSrc = asset::system("Fundamental.vcvplugin");
  206. std::string fundamentalDir = system::join(pluginsPath, "Fundamental");
  207. if (system::isFile(fundamentalSrc)) {
  208. INFO("Extracting bundled Fundamental package");
  209. system::unarchiveToDirectory(fundamentalSrc.c_str(), pluginsPath.c_str());
  210. loadPlugin(fundamentalDir);
  211. }
  212. }
  213. }
  214. void destroy() {
  215. for (Plugin* plugin : plugins) {
  216. void* handle = plugin->handle;
  217. // Call destroy() if defined in the plugin library
  218. typedef void (*DestroyCallback)();
  219. DestroyCallback destroyCallback = NULL;
  220. if (handle) {
  221. #if defined ARCH_WIN
  222. destroyCallback = (DestroyCallback) GetProcAddress((HMODULE) handle, "destroy");
  223. #else
  224. destroyCallback = (DestroyCallback) dlsym(handle, "destroy");
  225. #endif
  226. }
  227. if (destroyCallback)
  228. destroyCallback();
  229. // 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.
  230. delete plugin;
  231. // Free library handle
  232. if (handle) {
  233. #if defined ARCH_WIN
  234. FreeLibrary((HINSTANCE) handle);
  235. #else
  236. dlclose(handle);
  237. #endif
  238. }
  239. }
  240. plugins.clear();
  241. }
  242. /** Given slug => fallback slug.
  243. Correctly handles bidirectional fallbacks.
  244. To request fallback slugs to be added to this list, open a GitHub issue.
  245. */
  246. static const std::map<std::string, std::string> pluginSlugFallbacks = {
  247. {"VultModulesFree", "VultModules"},
  248. {"VultModules", "VultModulesFree"},
  249. {"AudibleInstrumentsPreview", "AudibleInstruments"},
  250. // {"", ""},
  251. };
  252. Plugin* getPlugin(const std::string& pluginSlug) {
  253. if (pluginSlug.empty())
  254. return NULL;
  255. auto it = std::find_if(plugins.begin(), plugins.end(), [=](Plugin* p) {
  256. return p->slug == pluginSlug;
  257. });
  258. if (it != plugins.end())
  259. return *it;
  260. return NULL;
  261. }
  262. Plugin* getPluginFallback(const std::string& pluginSlug) {
  263. if (pluginSlug.empty())
  264. return NULL;
  265. // Attempt example plugin
  266. Plugin* p = getPlugin(pluginSlug);
  267. if (p)
  268. return p;
  269. // Attempt fallback plugin slug
  270. auto it = pluginSlugFallbacks.find(pluginSlug);
  271. if (it != pluginSlugFallbacks.end())
  272. return getPlugin(it->second);
  273. return NULL;
  274. }
  275. /** Given slug => fallback slug.
  276. Correctly handles bidirectional fallbacks.
  277. To request fallback slugs to be added to this list, open a GitHub issue.
  278. */
  279. using PluginModuleSlug = std::tuple<std::string, std::string>;
  280. static const std::map<PluginModuleSlug, PluginModuleSlug> moduleSlugFallbacks = {
  281. {{"MindMeld-ShapeMasterPro", "ShapeMasterPro"}, {"MindMeldModular", "ShapeMaster"}},
  282. {{"MindMeldModular", "ShapeMaster"}, {"MindMeld-ShapeMasterPro", "ShapeMasterPro"}},
  283. // {{"", ""}, {"", ""}},
  284. };
  285. Model* getModel(const std::string& pluginSlug, const std::string& modelSlug) {
  286. if (pluginSlug.empty() || modelSlug.empty())
  287. return NULL;
  288. Plugin* p = getPlugin(pluginSlug);
  289. if (!p)
  290. return NULL;
  291. return p->getModel(modelSlug);
  292. }
  293. Model* getModelFallback(const std::string& pluginSlug, const std::string& modelSlug) {
  294. if (pluginSlug.empty() || modelSlug.empty())
  295. return NULL;
  296. // Attempt exact plugin and model
  297. Model* m = getModel(pluginSlug, modelSlug);
  298. if (m)
  299. return m;
  300. // Attempt fallback module
  301. auto it = moduleSlugFallbacks.find(std::make_tuple(pluginSlug, modelSlug));
  302. if (it != moduleSlugFallbacks.end()) {
  303. Model* m = getModel(std::get<0>(it->second), std::get<1>(it->second));
  304. if (m)
  305. return m;
  306. }
  307. // Attempt fallback plugin
  308. auto it2 = pluginSlugFallbacks.find(pluginSlug);
  309. if (it2 != pluginSlugFallbacks.end()) {
  310. Model* m = getModel(it2->second, modelSlug);
  311. if (m)
  312. return m;
  313. }
  314. return NULL;
  315. }
  316. Model* modelFromJson(json_t* moduleJ) {
  317. // Get slugs
  318. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  319. if (!pluginSlugJ)
  320. throw Exception("\"plugin\" property not found in module JSON");
  321. std::string pluginSlug = json_string_value(pluginSlugJ);
  322. pluginSlug = normalizeSlug(pluginSlug);
  323. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  324. if (!modelSlugJ)
  325. throw Exception("\"model\" property not found in module JSON");
  326. std::string modelSlug = json_string_value(modelSlugJ);
  327. modelSlug = normalizeSlug(modelSlug);
  328. // Get Model
  329. Model* model = getModelFallback(pluginSlug, modelSlug);
  330. if (!model)
  331. throw Exception("Could not find module \"%s\" \"%s\"", pluginSlug.c_str(), modelSlug.c_str());
  332. return model;
  333. }
  334. bool isSlugValid(const std::string& slug) {
  335. for (char c : slug) {
  336. if (!(std::isalnum(c) || c == '-' || c == '_'))
  337. return false;
  338. }
  339. return true;
  340. }
  341. std::string normalizeSlug(const std::string& slug) {
  342. std::string s;
  343. for (char c : slug) {
  344. if (!(std::isalnum(c) || c == '-' || c == '_'))
  345. continue;
  346. s += c;
  347. }
  348. return s;
  349. }
  350. std::string pluginsPath;
  351. std::vector<Plugin*> plugins;
  352. } // namespace plugin
  353. } // namespace rack