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.

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