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.

353 lines
9.3KB

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