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.

379 lines
9.8KB

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