| @@ -0,0 +1,366 @@ | |||
| //------------------------------------------------------------------------ | |||
| // Project : SDK Base | |||
| // Version : 1.0 | |||
| // | |||
| // Category : Helpers | |||
| // Filename : base/source/fcommandline.h | |||
| // Created by : Steinberg, 2007 | |||
| // Description : Very simple command-line parser. | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| //------------------------------------------------------------------------ | |||
| /** @file base/source/fcommandline.h | |||
| Very simple command-line parser. | |||
| @see Steinberg::CommandLine */ | |||
| //------------------------------------------------------------------------ | |||
| #pragma once | |||
| #include <algorithm> | |||
| #include <deque> | |||
| #include <initializer_list> | |||
| #include <iterator> | |||
| #include <map> | |||
| #include <sstream> | |||
| #include <vector> | |||
| namespace Steinberg { | |||
| //------------------------------------------------------------------------ | |||
| /** Very simple command-line parser. | |||
| Parses the command-line into a CommandLine::VariablesMap.\n | |||
| The command-line parser uses CommandLine::Descriptions to define the available options. | |||
| @b Example: | |||
| \code | |||
| #include "base/source/fcommandline.h" | |||
| #include <iostream> | |||
| int main (int argc, char* argv[]) | |||
| { | |||
| using namespace std; | |||
| CommandLine::Descriptions desc; | |||
| CommandLine::VariablesMap valueMap; | |||
| desc.addOptions ("myTool") | |||
| ("help", "produce help message") | |||
| ("opt1", string(), "option 1") | |||
| ("opt2", string(), "option 2") | |||
| ; | |||
| CommandLine::parse (argc, argv, desc, valueMap); | |||
| if (valueMap.hasError () || valueMap.count ("help")) | |||
| { | |||
| cout << desc << "\n"; | |||
| return 1; | |||
| } | |||
| if (valueMap.count ("opt1")) | |||
| { | |||
| cout << "Value of option 1 " << valueMap["opt1"] << "\n"; | |||
| } | |||
| if (valueMap.count ("opt2")) | |||
| { | |||
| cout << "Value of option 2 " << valueMap["opt2"] << "\n"; | |||
| } | |||
| return 0; | |||
| } | |||
| \endcode | |||
| @note | |||
| This is a "header only" implementation.\n | |||
| If you need the declarations in more than one cpp file, you have to define | |||
| @c SMTG_NO_IMPLEMENTATION in all but one file. | |||
| */ | |||
| //------------------------------------------------------------------------ | |||
| namespace CommandLine { | |||
| //------------------------------------------------------------------------ | |||
| /** Command-line parsing result. | |||
| This is the result of the parser.\n | |||
| - Use hasError() to check for errors.\n | |||
| - To test if a option was specified on the command-line use: count()\n | |||
| - To retrieve the value of an options, use operator [](const VariablesMapContainer::key_type k)\n | |||
| */ | |||
| //------------------------------------------------------------------------ | |||
| class VariablesMap | |||
| { | |||
| bool mParaError; | |||
| using VariablesMapContainer = std::map<std::string, std::string>; | |||
| VariablesMapContainer mVariablesMapContainer; | |||
| public: | |||
| VariablesMap () : mParaError (false) {} ///< Constructor. Creates a empty VariablesMap. | |||
| bool hasError () const { return mParaError; } ///< Returns @c true when an error has occurred. | |||
| void setError () { mParaError = true; } ///< Sets the error state to @c true. | |||
| std::string& operator [](const VariablesMapContainer::key_type k); ///< Retrieve the value of option @c k. | |||
| const std::string& operator [](const VariablesMapContainer::key_type k) const; ///< Retrieve the value of option @c k. | |||
| VariablesMapContainer::size_type count (const VariablesMapContainer::key_type k) const; ///< Returns @c != @c 0 if command-line contains option @c k. | |||
| }; | |||
| //! type of the list of elements on the command line that are not handled by options parsing | |||
| using FilesVector = std::vector<std::string>; | |||
| //------------------------------------------------------------------------ | |||
| /** The description of one single command-line option. | |||
| Normally you rarely use a Description directly.\n | |||
| In most cases you will use the Descriptions::addOptions (const std::string&) method to create and add descriptions. | |||
| */ | |||
| //------------------------------------------------------------------------ | |||
| class Description : public std::string | |||
| { | |||
| public: | |||
| Description (const std::string& name, const std::string& help, const std::string& valueType ); ///< Construct a Description | |||
| std::string mHelp; ///< The help string for this option. | |||
| std::string mType; ///< The type of this option (kBool, kString). | |||
| static const std::string kBool; | |||
| static const std::string kString; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| /** List of command-line option descriptions. | |||
| Use addOptions(const std::string&) to add Descriptions. | |||
| */ | |||
| //------------------------------------------------------------------------ | |||
| class Descriptions | |||
| { | |||
| using DescriptionsList = std::deque<Description>; | |||
| DescriptionsList mDescriptions; | |||
| std::string mCaption; | |||
| public: | |||
| /** Sets the command-line tool caption and starts adding Descriptions. */ | |||
| Descriptions& addOptions (const std::string& caption = "", | |||
| std::initializer_list<Description>&& options = {}); | |||
| /** Parse the command-line. */ | |||
| bool parse (int ac, char* av[], VariablesMap& result, FilesVector* files = nullptr) const; | |||
| /** Print a brief description for the command-line tool into the stream @c os. */ | |||
| void print (std::ostream& os) const; | |||
| /** Add a new switch. Only */ | |||
| Descriptions& operator() (const std::string& name, const std::string& help); | |||
| /** Add a new option of type @c inType. Currently only std::string is supported. */ | |||
| template <typename Type> | |||
| Descriptions& operator () (const std::string& name, const Type& inType, std::string help); | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| // If you need the declarations in more than one cpp file you have to define | |||
| // SMTG_NO_IMPLEMENTATION in all but one file. | |||
| //------------------------------------------------------------------------ | |||
| #ifndef SMTG_NO_IMPLEMENTATION | |||
| //------------------------------------------------------------------------ | |||
| /*! If command-line contains option @c k more than once, only the last value will survive. */ | |||
| //------------------------------------------------------------------------ | |||
| std::string& VariablesMap::operator [](const VariablesMapContainer::key_type k) | |||
| { | |||
| return mVariablesMapContainer[k]; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! If command-line contains option @c k more than once, only the last value will survive. */ | |||
| //------------------------------------------------------------------------ | |||
| const std::string& VariablesMap::operator [](const VariablesMapContainer::key_type k) const | |||
| { | |||
| return (*const_cast<VariablesMap*>(this))[k]; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| VariablesMap::VariablesMapContainer::size_type VariablesMap::count (const VariablesMapContainer::key_type k) const | |||
| { | |||
| return mVariablesMapContainer.count (k); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /** Add a new option with a string as parameter. */ | |||
| //------------------------------------------------------------------------ | |||
| template <> Descriptions& Descriptions::operator() (const std::string& name, const std::string& inType, std::string help) | |||
| { | |||
| mDescriptions.emplace_back (name, help, inType); | |||
| return *this; | |||
| } | |||
| bool parse (int ac, char* av[], const Descriptions& desc, VariablesMap& result, FilesVector* files = nullptr); ///< Parse the command-line. | |||
| std::ostream& operator<< (std::ostream& os, const Descriptions& desc); ///< Make Descriptions stream able. | |||
| const std::string Description::kBool = "bool"; | |||
| const std::string Description::kString = "string"; | |||
| //------------------------------------------------------------------------ | |||
| /*! In most cases you will use the Descriptions::addOptions (const std::string&) method to create and add descriptions. | |||
| @param[in] name of the option. | |||
| @param[in] help a help description for this option. | |||
| @param[out] valueType Description::kBool or Description::kString. | |||
| */ | |||
| Description::Description (const std::string& name, const std::string& help, const std::string& valueType) | |||
| : std::string (name) | |||
| , mHelp (help) | |||
| , mType (valueType) | |||
| { | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! Returning a reference to *this, enables chaining of calls to operator()(const std::string&, | |||
| const std::string&). | |||
| @param[in] name of the added option. | |||
| @param[in] help a help description for this option. | |||
| @return a reference to *this. | |||
| */ | |||
| Descriptions& Descriptions::operator () (const std::string& name, const std::string& help) | |||
| { | |||
| mDescriptions.emplace_back (name, help, Description::kBool); | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! <b>Usage example:</b> | |||
| @code | |||
| CommandLine::Descriptions desc; | |||
| desc.addOptions ("myTool") // Set caption to "myTool" | |||
| ("help", "produce help message") // add switch -help | |||
| ("opt1", string(), "option 1") // add string option -opt1 | |||
| ("opt2", string(), "option 2") // add string option -opt2 | |||
| ; | |||
| @endcode | |||
| @note | |||
| The operator() is used for every additional option. | |||
| Or with initializer list : | |||
| @code | |||
| CommandLine::Descriptions desc; | |||
| desc.addOptions ("myTool", // Set caption to "myTool" | |||
| {{"help", "produce help message", Description::kBool}, // add switch -help | |||
| {"opt1", "option 1", Description::kString}, // add string option -opt1 | |||
| {"opt2", "option 2", Description::kString}} // add string option -opt2 | |||
| ); | |||
| @endcode | |||
| @param[in] caption the caption of the command-line tool. | |||
| @param[in] options initializer list with options | |||
| @return a reverense to *this. | |||
| */ | |||
| Descriptions& Descriptions::addOptions (const std::string& caption, | |||
| std::initializer_list<Description>&& options) | |||
| { | |||
| mCaption = caption; | |||
| std::move (options.begin (), options.end (), std::back_inserter (mDescriptions)); | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! @param[in] ac count of command-line parameters | |||
| @param[in] av command-line as array of strings | |||
| @param[out] result the parsing result | |||
| @param[out] files optional list of elements on the command line that are not handled by options parsing | |||
| */ | |||
| bool Descriptions::parse (int ac, char* av[], VariablesMap& result, FilesVector* files) const | |||
| { | |||
| using namespace std; | |||
| for (int i = 1; i < ac; i++) | |||
| { | |||
| string current = av[i]; | |||
| if (current[0] == '-') | |||
| { | |||
| int pos = current[1] == '-' ? 2 : 1; | |||
| current = current.substr (pos, string::npos); | |||
| DescriptionsList::const_iterator found = | |||
| find (mDescriptions.begin (), mDescriptions.end (), current); | |||
| if (found != mDescriptions.end ()) | |||
| { | |||
| result[*found] = "true"; | |||
| if (found->mType != Description::kBool) | |||
| { | |||
| if (((i + 1) < ac) && *av[i + 1] != '-') | |||
| { | |||
| result[*found] = av[++i]; | |||
| } | |||
| else | |||
| { | |||
| result[*found] = "error!"; | |||
| result.setError (); | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| result.setError (); | |||
| return false; | |||
| } | |||
| } | |||
| else if (files) | |||
| files->push_back (av[i]); | |||
| } | |||
| return true; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! The description includes the help strings for all options. */ | |||
| //------------------------------------------------------------------------ | |||
| void Descriptions::print (std::ostream& os) const | |||
| { | |||
| if (!mCaption.empty ()) | |||
| os << mCaption << ":\n"; | |||
| size_t maxLength = 0u; | |||
| std::for_each (mDescriptions.begin (), mDescriptions.end (), | |||
| [&] (const Description& d) { maxLength = std::max (maxLength, d.size ()); }); | |||
| for (const Description& opt : mDescriptions) | |||
| { | |||
| os << "-" << opt; | |||
| for (auto s = opt.size (); s < maxLength; ++s) | |||
| os << " "; | |||
| os << " | " << opt.mHelp << "\n"; | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::ostream& operator<< (std::ostream& os, const Descriptions& desc) | |||
| { | |||
| desc.print (os); | |||
| return os; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| /*! @param[in] ac count of command-line parameters | |||
| @param[in] av command-line as array of strings | |||
| @param[in] desc Descriptions including all allowed options | |||
| @param[out] result the parsing result | |||
| @param[out] files optional list of elements on the command line that are not handled by options parsing | |||
| */ | |||
| bool parse (int ac, char* av[], const Descriptions& desc, VariablesMap& result, FilesVector* files) | |||
| { | |||
| return desc.parse (ac, av, result, files); | |||
| } | |||
| #endif | |||
| } //namespace CommandLine | |||
| } //namespace Steinberg | |||
| @@ -0,0 +1,366 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfotool | |||
| // Filename : public.sdk/samples/vst-utilities/moduleinfotool/main.cpp | |||
| // Created by : Steinberg, 12/2021 | |||
| // Description : main entry point | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "public.sdk/source/vst/hosting/module.h" | |||
| #include "public.sdk/source/vst/moduleinfo/moduleinfocreator.h" | |||
| #include "public.sdk/source/vst/moduleinfo/moduleinfoparser.h" | |||
| #include "base/source/fcommandline.h" | |||
| #include "pluginterfaces/vst/vsttypes.h" | |||
| #include <cstdio> | |||
| #include <fstream> | |||
| #include <iostream> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg { | |||
| namespace ModuleInfoTool { | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| constexpr auto BUILD_INFO = "moduleinfotool 1.0.0 [Built " __DATE__ "]"; | |||
| //------------------------------------------------------------------------ | |||
| //-- Options | |||
| constexpr auto optHelp = "help"; | |||
| constexpr auto optCreate = "create"; | |||
| constexpr auto optValidate = "validate"; | |||
| constexpr auto optModuleVersion = "version"; | |||
| constexpr auto optModulePath = "path"; | |||
| constexpr auto optInfoPath = "infopath"; | |||
| constexpr auto optModuleCompatPath = "compat"; | |||
| constexpr auto optOutputPath = "output"; | |||
| //------------------------------------------------------------------------ | |||
| void printUsage (std::ostream& s) | |||
| { | |||
| s << "Usage:\n"; | |||
| s << " moduleinfotool -create -version VERSION -path MODULE_PATH [-compat PATH -output PATH]\n"; | |||
| s << " moduleinfotool -validate -path MODULE_PATH [-infopath PATH]\n"; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::string loadFile (const std::string& path) | |||
| { | |||
| std::ifstream file (path, std::ios_base::in | std::ios_base::binary); | |||
| if (!file.is_open ()) | |||
| return {}; | |||
| auto size = file.seekg (0, std::ios_base::end).tellg (); | |||
| file.seekg (0, std::ios_base::beg); | |||
| std::string data; | |||
| data.resize (size); | |||
| file.read (data.data (), data.size ()); | |||
| if (file.bad ()) | |||
| return {}; | |||
| return data; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::optional<ModuleInfo::CompatibilityList> openAndParseCompatJSON (const std::string& path) | |||
| { | |||
| auto data = loadFile (path); | |||
| if (data.empty ()) | |||
| { | |||
| std::cout << "Can not read '" << path << "'\n"; | |||
| printUsage (std::cout); | |||
| return {}; | |||
| } | |||
| std::stringstream error; | |||
| auto result = ModuleInfoLib::parseCompatibilityJson (data, &error); | |||
| if (!result) | |||
| { | |||
| std::cout << "Can not parse '" << path << "'\n"; | |||
| std::cout << error.str (); | |||
| printUsage (std::cout); | |||
| return {}; | |||
| } | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| int createJSON (const std::optional<ModuleInfo::CompatibilityList>& compat, | |||
| const std::string& modulePath, const std::string& moduleVersion, | |||
| std::ostream& outStream) | |||
| { | |||
| std::string errorStr; | |||
| auto module = VST3::Hosting::Module::create (modulePath, errorStr); | |||
| if (!module) | |||
| { | |||
| std::cout << errorStr; | |||
| return 1; | |||
| } | |||
| auto moduleInfo = ModuleInfoLib::createModuleInfo (*module, false); | |||
| if (compat) | |||
| moduleInfo.compatibility = *compat; | |||
| moduleInfo.version = moduleVersion; | |||
| std::stringstream output; | |||
| ModuleInfoLib::outputJson (moduleInfo, output); | |||
| auto str = output.str (); | |||
| outStream << str; | |||
| return 0; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| struct validate_error : std::exception | |||
| { | |||
| validate_error (const std::string& str) : str (str) {} | |||
| const char* what () const noexcept override { return str.data (); } | |||
| private: | |||
| std::string str; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| void validate (const ModuleInfo& moduleInfo, const VST3::Hosting::Module& module) | |||
| { | |||
| auto factory = module.getFactory (); | |||
| auto factoryInfo = factory.info (); | |||
| auto classInfoList = factory.classInfos (); | |||
| auto snapshotList = module.getSnapshots (module.getPath ()); | |||
| if (factoryInfo.vendor () != moduleInfo.factoryInfo.vendor) | |||
| throw validate_error ("factoryInfo.vendor different: " + moduleInfo.factoryInfo.vendor); | |||
| if (factoryInfo.url () != moduleInfo.factoryInfo.url) | |||
| throw validate_error ("factoryInfo.url different: " + moduleInfo.factoryInfo.url); | |||
| if (factoryInfo.email () != moduleInfo.factoryInfo.email) | |||
| throw validate_error ("factoryInfo.email different: " + moduleInfo.factoryInfo.email); | |||
| if (factoryInfo.flags () != moduleInfo.factoryInfo.flags) | |||
| throw validate_error ("factoryInfo.flags different: " + | |||
| std::to_string (moduleInfo.factoryInfo.flags)); | |||
| for (const auto& ci : moduleInfo.classes) | |||
| { | |||
| auto cid = VST3::UID::fromString (ci.cid); | |||
| if (!cid) | |||
| throw validate_error ("could not parse class UID: " + ci.cid); | |||
| auto it = std::find_if (classInfoList.begin (), classInfoList.end (), | |||
| [&] (const auto& el) { return el.ID () == *cid; }); | |||
| if (it == classInfoList.end ()) | |||
| throw validate_error ("cannot find CID in class list: " + ci.cid); | |||
| if (it->name () != ci.name) | |||
| throw validate_error ("class name different: " + ci.name); | |||
| if (it->category () != ci.category) | |||
| throw validate_error ("class category different: " + ci.category); | |||
| if (it->vendor () != ci.vendor) | |||
| throw validate_error ("class vendor different: " + ci.vendor); | |||
| if (it->version () != ci.version) | |||
| throw validate_error ("class version different: " + ci.version); | |||
| if (it->sdkVersion () != ci.sdkVersion) | |||
| throw validate_error ("class sdkVersion different: " + ci.sdkVersion); | |||
| if (it->subCategories () != ci.subCategories) | |||
| throw validate_error ("class subCategories different: " /* + ci.subCategories*/); | |||
| if (it->cardinality () != ci.cardinality) | |||
| throw validate_error ("class cardinality different: " + | |||
| std::to_string (ci.cardinality)); | |||
| if (it->classFlags () != ci.flags) | |||
| throw validate_error ("class flags different: " + std::to_string (ci.flags)); | |||
| classInfoList.erase (it); | |||
| auto snapshotListIt = std::find_if (snapshotList.begin (), snapshotList.end (), | |||
| [&] (const auto& el) { return el.uid == *cid; }); | |||
| if (snapshotListIt == snapshotList.end () && !ci.snapshots.empty ()) | |||
| throw validate_error ("cannot find snapshots for: " + ci.cid); | |||
| for (const auto& snapshot : ci.snapshots) | |||
| { | |||
| auto snapshotIt = std::find_if ( | |||
| snapshotListIt->images.begin (), snapshotListIt->images.end (), | |||
| [&] (const auto& el) { return el.scaleFactor == snapshot.scaleFactor; }); | |||
| if (snapshotIt == snapshotListIt->images.end ()) | |||
| throw validate_error ("cannot find snapshots for scale factor: " + | |||
| std::to_string (snapshot.scaleFactor)); | |||
| std::string_view path (snapshotIt->path); | |||
| if (path.find (module.getPath ()) == 0) | |||
| path.remove_prefix (module.getPath ().size () + 1); | |||
| if (path != snapshot.path) | |||
| throw validate_error ("cannot find snapshots with path: " + snapshot.path); | |||
| snapshotListIt->images.erase (snapshotIt); | |||
| } | |||
| if (snapshotListIt != snapshotList.end () && !snapshotListIt->images.empty ()) | |||
| { | |||
| std::string errorStr = "Missing Snapshots in moduleinfo:\n"; | |||
| for (const auto& s : snapshotListIt->images) | |||
| { | |||
| errorStr += s.path + '\n'; | |||
| } | |||
| throw validate_error (errorStr); | |||
| } | |||
| if (snapshotListIt != snapshotList.end ()) | |||
| snapshotList.erase (snapshotListIt); | |||
| } | |||
| if (!classInfoList.empty ()) | |||
| throw validate_error ("Missing classes in moduleinfo"); | |||
| if (!snapshotList.empty ()) | |||
| throw validate_error ("Missing snapshots in moduleinfo"); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| int validate (const std::string& modulePath, std::string infoJsonPath) | |||
| { | |||
| if (infoJsonPath.empty ()) | |||
| { | |||
| auto path = VST3::Hosting::Module::getModuleInfoPath (modulePath); | |||
| if (!path) | |||
| { | |||
| std::cerr << "Module does not contain a moduleinfo.json: '" << modulePath << "'" | |||
| << '\n'; | |||
| return 1; | |||
| } | |||
| infoJsonPath = *path; | |||
| } | |||
| auto data = loadFile (infoJsonPath); | |||
| if (data.empty ()) | |||
| { | |||
| std::cerr << "Empty or non existing file: '" << infoJsonPath << "'" << '\n'; | |||
| printUsage (std::cout); | |||
| return 1; | |||
| } | |||
| auto moduleInfo = ModuleInfoLib::parseJson (data, &std::cerr); | |||
| if (moduleInfo) | |||
| { | |||
| std::string errorStr; | |||
| auto module = VST3::Hosting::Module::create (modulePath, errorStr); | |||
| if (!module) | |||
| { | |||
| std::cerr << errorStr; | |||
| printUsage (std::cout); | |||
| return 1; | |||
| } | |||
| try | |||
| { | |||
| validate (*moduleInfo, *module); | |||
| } | |||
| catch (const std::exception& exc) | |||
| { | |||
| std::cerr << "Error:\n" << exc.what () << '\n'; | |||
| printUsage (std::cout); | |||
| return 1; | |||
| } | |||
| return 0; | |||
| } | |||
| printUsage (std::cout); | |||
| return 1; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| int run (int argc, char* argv[]) | |||
| { | |||
| // parse command line | |||
| CommandLine::Descriptions desc; | |||
| CommandLine::VariablesMap valueMap; | |||
| CommandLine::FilesVector files; | |||
| using Description = CommandLine::Description; | |||
| desc.addOptions ( | |||
| BUILD_INFO, | |||
| { | |||
| {optCreate, "Create moduleinfo", Description::kBool}, | |||
| {optValidate, "Validate moduleinfo", Description::kBool}, | |||
| {optModuleVersion, "Module version", Description::kString}, | |||
| {optModulePath, "Path to module", Description::kString}, | |||
| {optInfoPath, "Path to moduleinfo.json", Description::kString}, | |||
| {optModuleCompatPath, "Path to compatibility.json", Description::kString}, | |||
| {optOutputPath, "Write json to file instead of stdout", Description::kString}, | |||
| {optHelp, "Print help", Description::kBool}, | |||
| }); | |||
| CommandLine::parse (argc, argv, desc, valueMap, &files); | |||
| bool isCreate = valueMap.count (optCreate) != 0 && valueMap.count (optModuleVersion) != 0 && | |||
| valueMap.count (optModulePath) != 0; | |||
| bool isValidate = valueMap.count (optValidate) && valueMap.count (optModulePath) != 0; | |||
| if (valueMap.hasError () || valueMap.count (optHelp) || !(isCreate || isValidate)) | |||
| { | |||
| std::cout << '\n' << desc << '\n'; | |||
| printUsage (std::cout); | |||
| return 1; | |||
| } | |||
| int result = 1; | |||
| const auto& modulePath = valueMap[optModulePath]; | |||
| if (isCreate) | |||
| { | |||
| auto* outputStream = &std::cout; | |||
| std::optional<ModuleInfo::CompatibilityList> compat; | |||
| if (valueMap.count (optModuleCompatPath) != 0) | |||
| { | |||
| const auto& compatPath = valueMap[optModuleCompatPath]; | |||
| compat = openAndParseCompatJSON (compatPath); | |||
| if (!compat) | |||
| return 1; | |||
| } | |||
| bool writeToFile = false; | |||
| if (valueMap.count (optOutputPath) != 0) | |||
| { | |||
| writeToFile = true; | |||
| auto outputFile = valueMap[optOutputPath]; | |||
| auto ostream = new std::ofstream (outputFile); | |||
| if (ostream->is_open ()) | |||
| outputStream = ostream; | |||
| else | |||
| { | |||
| std::cout << "Cannot create output file: " << outputFile << '\n'; | |||
| return result; | |||
| } | |||
| } | |||
| const auto& moduleVersion = valueMap[optModuleVersion]; | |||
| result = createJSON (compat, modulePath, moduleVersion, *outputStream); | |||
| if (writeToFile) | |||
| delete outputStream; | |||
| } | |||
| else if (isValidate) | |||
| { | |||
| std::string moduleInfoJsonPath; | |||
| if (valueMap.count (optInfoPath) != 0) | |||
| moduleInfoJsonPath = valueMap[optInfoPath]; | |||
| result = validate (modulePath, moduleInfoJsonPath); | |||
| } | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // ModuleInfoTool | |||
| } // Steinberg | |||
| //------------------------------------------------------------------------ | |||
| int main (int argc, char* argv[]) | |||
| { | |||
| return Steinberg::ModuleInfoTool::run (argc, argv); | |||
| } | |||
| @@ -0,0 +1,340 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/hosting/module.cpp | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : hosting module classes | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "module.h" | |||
| #include "public.sdk/source/vst/utility/stringconvert.h" | |||
| #include "public.sdk/source/vst/utility/optional.h" | |||
| #include <sstream> | |||
| #include <utility> | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| namespace Hosting { | |||
| //------------------------------------------------------------------------ | |||
| FactoryInfo::FactoryInfo (PFactoryInfo&& other) noexcept | |||
| { | |||
| *this = std::move (other); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| FactoryInfo& FactoryInfo::operator= (FactoryInfo&& other) noexcept | |||
| { | |||
| info = std::move (other.info); | |||
| other.info = {}; | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| FactoryInfo& FactoryInfo::operator= (PFactoryInfo&& other) noexcept | |||
| { | |||
| info = std::move (other); | |||
| other = {}; | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::string FactoryInfo::vendor () const noexcept | |||
| { | |||
| return StringConvert::convert (info.vendor, PFactoryInfo::kNameSize); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::string FactoryInfo::url () const noexcept | |||
| { | |||
| return StringConvert::convert (info.url, PFactoryInfo::kURLSize); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::string FactoryInfo::email () const noexcept | |||
| { | |||
| return StringConvert::convert (info.email, PFactoryInfo::kEmailSize); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Steinberg::int32 FactoryInfo::flags () const noexcept | |||
| { | |||
| return info.flags; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| bool FactoryInfo::classesDiscardable () const noexcept | |||
| { | |||
| return (info.flags & PFactoryInfo::kClassesDiscardable) != 0; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| bool FactoryInfo::licenseCheck () const noexcept | |||
| { | |||
| return (info.flags & PFactoryInfo::kLicenseCheck) != 0; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| bool FactoryInfo::componentNonDiscardable () const noexcept | |||
| { | |||
| return (info.flags & PFactoryInfo::kComponentNonDiscardable) != 0; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| //------------------------------------------------------------------------ | |||
| //------------------------------------------------------------------------ | |||
| PluginFactory::PluginFactory (const PluginFactoryPtr& factory) noexcept : factory (factory) | |||
| { | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void PluginFactory::setHostContext (Steinberg::FUnknown* context) const noexcept | |||
| { | |||
| if (auto f = Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> (factory)) | |||
| f->setHostContext (context); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| FactoryInfo PluginFactory::info () const noexcept | |||
| { | |||
| Steinberg::PFactoryInfo i; | |||
| factory->getFactoryInfo (&i); | |||
| return FactoryInfo (std::move (i)); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| uint32_t PluginFactory::classCount () const noexcept | |||
| { | |||
| auto count = factory->countClasses (); | |||
| assert (count >= 0); | |||
| return static_cast<uint32_t> (count); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept | |||
| { | |||
| auto count = classCount (); | |||
| Optional<FactoryInfo> factoryInfo; | |||
| ClassInfos classes; | |||
| classes.reserve (count); | |||
| auto f3 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> (factory); | |||
| auto f2 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory2> (factory); | |||
| Steinberg::PClassInfo ci; | |||
| Steinberg::PClassInfo2 ci2; | |||
| Steinberg::PClassInfoW ci3; | |||
| for (uint32_t i = 0; i < count; ++i) | |||
| { | |||
| if (f3 && f3->getClassInfoUnicode (i, &ci3) == Steinberg::kResultTrue) | |||
| classes.emplace_back (ci3); | |||
| else if (f2 && f2->getClassInfo2 (i, &ci2) == Steinberg::kResultTrue) | |||
| classes.emplace_back (ci2); | |||
| else if (factory->getClassInfo (i, &ci) == Steinberg::kResultTrue) | |||
| classes.emplace_back (ci); | |||
| auto& classInfo = classes.back (); | |||
| if (classInfo.vendor ().empty ()) | |||
| { | |||
| if (!factoryInfo) | |||
| factoryInfo = Optional<FactoryInfo> (info ()); | |||
| classInfo.get ().vendor = factoryInfo->vendor (); | |||
| } | |||
| } | |||
| return classes; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| //------------------------------------------------------------------------ | |||
| //------------------------------------------------------------------------ | |||
| const UID& ClassInfo::ID () const noexcept | |||
| { | |||
| return data.classID; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| int32_t ClassInfo::cardinality () const noexcept | |||
| { | |||
| return data.cardinality; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const std::string& ClassInfo::category () const noexcept | |||
| { | |||
| return data.category; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const std::string& ClassInfo::name () const noexcept | |||
| { | |||
| return data.name; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const std::string& ClassInfo::vendor () const noexcept | |||
| { | |||
| return data.vendor; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const std::string& ClassInfo::version () const noexcept | |||
| { | |||
| return data.version; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const std::string& ClassInfo::sdkVersion () const noexcept | |||
| { | |||
| return data.sdkVersion; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| const ClassInfo::SubCategories& ClassInfo::subCategories () const noexcept | |||
| { | |||
| return data.subCategories; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Steinberg::uint32 ClassInfo::classFlags () const noexcept | |||
| { | |||
| return data.classFlags; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| ClassInfo::ClassInfo (const PClassInfo& info) noexcept | |||
| { | |||
| data.classID = info.cid; | |||
| data.cardinality = info.cardinality; | |||
| data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); | |||
| data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| ClassInfo::ClassInfo (const PClassInfo2& info) noexcept | |||
| { | |||
| data.classID = info.cid; | |||
| data.cardinality = info.cardinality; | |||
| data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); | |||
| data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); | |||
| data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize); | |||
| data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize); | |||
| data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize); | |||
| parseSubCategories ( | |||
| StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize)); | |||
| data.classFlags = info.classFlags; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| ClassInfo::ClassInfo (const PClassInfoW& info) noexcept | |||
| { | |||
| data.classID = info.cid; | |||
| data.cardinality = info.cardinality; | |||
| data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); | |||
| data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); | |||
| data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize); | |||
| data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize); | |||
| data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize); | |||
| parseSubCategories ( | |||
| StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize)); | |||
| data.classFlags = info.classFlags; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void ClassInfo::parseSubCategories (const std::string& str) noexcept | |||
| { | |||
| std::stringstream stream (str); | |||
| std::string item; | |||
| while (std::getline (stream, item, '|')) | |||
| data.subCategories.emplace_back (move (item)); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::string ClassInfo::subCategoriesString () const noexcept | |||
| { | |||
| std::string result; | |||
| if (data.subCategories.empty ()) | |||
| return result; | |||
| result = data.subCategories[0]; | |||
| for (auto index = 1u; index < data.subCategories.size (); ++index) | |||
| result += "|" + data.subCategories[index]; | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| std::pair<size_t, size_t> rangeOfScaleFactor (const std::string& name) | |||
| { | |||
| auto result = std::make_pair (std::string::npos, std::string::npos); | |||
| size_t xIndex = name.find_last_of ('x'); | |||
| if (xIndex == std::string::npos) | |||
| return result; | |||
| size_t indicatorIndex = name.find_last_of ('_'); | |||
| if (indicatorIndex == std::string::npos) | |||
| return result; | |||
| if (xIndex < indicatorIndex) | |||
| return result; | |||
| result.first = indicatorIndex + 1; | |||
| result.second = xIndex; | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| Optional<double> Module::Snapshot::decodeScaleFactor (const std::string& name) | |||
| { | |||
| auto range = rangeOfScaleFactor (name); | |||
| if (range.first == std::string::npos || range.second == std::string::npos) | |||
| return {}; | |||
| std::string tmp (name.data () + range.first, range.second - range.first); | |||
| std::istringstream sstream (tmp); | |||
| sstream.imbue (std::locale::classic ()); | |||
| sstream.precision (static_cast<std::streamsize> (3)); | |||
| double result; | |||
| sstream >> result; | |||
| return Optional<double> (result); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<UID> Module::Snapshot::decodeUID (const std::string& filename) | |||
| { | |||
| if (filename.size () < 45) | |||
| return {}; | |||
| if (filename.find ("_snapshot") != 32) | |||
| return {}; | |||
| auto uidStr = filename.substr (0, 32); | |||
| return UID::fromString (uidStr); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Hosting | |||
| } // VST3 | |||
| @@ -0,0 +1,212 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/hosting/module.h | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : hosting module classes | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include "../utility/uid.h" | |||
| #include "pluginterfaces/base/ipluginbase.h" | |||
| #include <utility> | |||
| #include <vector> | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| namespace Hosting { | |||
| //------------------------------------------------------------------------ | |||
| class FactoryInfo | |||
| { | |||
| public: | |||
| //------------------------------------------------------------------------ | |||
| using PFactoryInfo = Steinberg::PFactoryInfo; | |||
| FactoryInfo () noexcept {} | |||
| ~FactoryInfo () noexcept {} | |||
| FactoryInfo (const FactoryInfo&) noexcept = default; | |||
| FactoryInfo (PFactoryInfo&&) noexcept; | |||
| FactoryInfo (FactoryInfo&&) noexcept = default; | |||
| FactoryInfo& operator= (const FactoryInfo&) noexcept = default; | |||
| FactoryInfo& operator= (FactoryInfo&&) noexcept; | |||
| FactoryInfo& operator= (PFactoryInfo&&) noexcept; | |||
| std::string vendor () const noexcept; | |||
| std::string url () const noexcept; | |||
| std::string email () const noexcept; | |||
| Steinberg::int32 flags () const noexcept; | |||
| bool classesDiscardable () const noexcept; | |||
| bool licenseCheck () const noexcept; | |||
| bool componentNonDiscardable () const noexcept; | |||
| PFactoryInfo& get () noexcept { return info; } | |||
| //------------------------------------------------------------------------ | |||
| private: | |||
| PFactoryInfo info {}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| class ClassInfo | |||
| { | |||
| public: | |||
| //------------------------------------------------------------------------ | |||
| using SubCategories = std::vector<std::string>; | |||
| using PClassInfo = Steinberg::PClassInfo; | |||
| using PClassInfo2 = Steinberg::PClassInfo2; | |||
| using PClassInfoW = Steinberg::PClassInfoW; | |||
| //------------------------------------------------------------------------ | |||
| ClassInfo () noexcept {} | |||
| explicit ClassInfo (const PClassInfo& info) noexcept; | |||
| explicit ClassInfo (const PClassInfo2& info) noexcept; | |||
| explicit ClassInfo (const PClassInfoW& info) noexcept; | |||
| ClassInfo (const ClassInfo&) = default; | |||
| ClassInfo& operator= (const ClassInfo&) = default; | |||
| ClassInfo (ClassInfo&&) = default; | |||
| ClassInfo& operator= (ClassInfo&&) = default; | |||
| const UID& ID () const noexcept; | |||
| int32_t cardinality () const noexcept; | |||
| const std::string& category () const noexcept; | |||
| const std::string& name () const noexcept; | |||
| const std::string& vendor () const noexcept; | |||
| const std::string& version () const noexcept; | |||
| const std::string& sdkVersion () const noexcept; | |||
| const SubCategories& subCategories () const noexcept; | |||
| std::string subCategoriesString () const noexcept; | |||
| Steinberg::uint32 classFlags () const noexcept; | |||
| struct Data | |||
| { | |||
| UID classID; | |||
| int32_t cardinality; | |||
| std::string category; | |||
| std::string name; | |||
| std::string vendor; | |||
| std::string version; | |||
| std::string sdkVersion; | |||
| SubCategories subCategories; | |||
| Steinberg::uint32 classFlags = 0; | |||
| }; | |||
| Data& get () noexcept { return data; } | |||
| //------------------------------------------------------------------------ | |||
| private: | |||
| void parseSubCategories (const std::string& str) noexcept; | |||
| Data data {}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| class PluginFactory | |||
| { | |||
| public: | |||
| //------------------------------------------------------------------------ | |||
| using ClassInfos = std::vector<ClassInfo>; | |||
| using PluginFactoryPtr = Steinberg::IPtr<Steinberg::IPluginFactory>; | |||
| //------------------------------------------------------------------------ | |||
| explicit PluginFactory (const PluginFactoryPtr& factory) noexcept; | |||
| void setHostContext (Steinberg::FUnknown* context) const noexcept; | |||
| FactoryInfo info () const noexcept; | |||
| uint32_t classCount () const noexcept; | |||
| ClassInfos classInfos () const noexcept; | |||
| template <typename T> | |||
| Steinberg::IPtr<T> createInstance (const UID& classID) const noexcept; | |||
| const PluginFactoryPtr& get () const noexcept { return factory; } | |||
| //------------------------------------------------------------------------ | |||
| private: | |||
| PluginFactoryPtr factory; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| //------------------------------------------------------------------------ | |||
| class Module | |||
| { | |||
| public: | |||
| //------------------------------------------------------------------------ | |||
| struct Snapshot | |||
| { | |||
| struct ImageDesc | |||
| { | |||
| double scaleFactor {1.}; | |||
| std::string path; | |||
| }; | |||
| UID uid; | |||
| std::vector<ImageDesc> images; | |||
| static Optional<double> decodeScaleFactor (const std::string& path); | |||
| static Optional<UID> decodeUID (const std::string& filename); | |||
| }; | |||
| using Ptr = std::shared_ptr<Module>; | |||
| using PathList = std::vector<std::string>; | |||
| using SnapshotList = std::vector<Snapshot>; | |||
| //------------------------------------------------------------------------ | |||
| static Ptr create (const std::string& path, std::string& errorDescription); | |||
| static PathList getModulePaths (); | |||
| static SnapshotList getSnapshots (const std::string& modulePath); | |||
| /** get the path to the module info json file if it exists */ | |||
| static Optional<std::string> getModuleInfoPath (const std::string& modulePath); | |||
| const std::string& getName () const noexcept { return name; } | |||
| const std::string& getPath () const noexcept { return path; } | |||
| const PluginFactory& getFactory () const noexcept { return factory; } | |||
| //------------------------------------------------------------------------ | |||
| protected: | |||
| virtual ~Module () noexcept = default; | |||
| virtual bool load (const std::string& path, std::string& errorDescription) = 0; | |||
| PluginFactory factory {nullptr}; | |||
| std::string name; | |||
| std::string path; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| template <typename T> | |||
| inline Steinberg::IPtr<T> PluginFactory::createInstance (const UID& classID) const noexcept | |||
| { | |||
| T* obj = nullptr; | |||
| if (factory->createInstance (classID.data (), T::iid, reinterpret_cast<void**> (&obj)) == | |||
| Steinberg::kResultTrue) | |||
| return Steinberg::owned (obj); | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Hosting | |||
| } // VST3 | |||
| @@ -0,0 +1,361 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/hosting/module_linux.cpp | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : hosting module classes (linux implementation) | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "module.h" | |||
| #include "../utility/optional.h" | |||
| #include "../utility/stringconvert.h" | |||
| #include <algorithm> | |||
| #include <dlfcn.h> | |||
| #include <sys/types.h> | |||
| #include <sys/utsname.h> | |||
| #include <unistd.h> | |||
| #if (__cplusplus >= 201707L) | |||
| #if __has_include(<filesystem>) | |||
| #define USE_EXPERIMENTAL_FS 0 | |||
| #elif __has_include(<experimental/filesystem>) | |||
| #define USE_EXPERIMENTAL_FS 1 | |||
| #endif | |||
| #else | |||
| #define USE_EXPERIMENTAL_FS 1 | |||
| #endif | |||
| #if USE_EXPERIMENTAL_FS == 1 | |||
| #include <experimental/filesystem> | |||
| namespace filesystem = std::experimental::filesystem; | |||
| #else | |||
| #include <filesystem> | |||
| namespace filesystem = std::filesystem; | |||
| #endif | |||
| //------------------------------------------------------------------------ | |||
| extern "C" { | |||
| using ModuleEntryFunc = bool (PLUGIN_API*) (void*); | |||
| using ModuleExitFunc = bool (PLUGIN_API*) (); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| namespace Hosting { | |||
| using Path = filesystem::path; | |||
| //------------------------------------------------------------------------ | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| Optional<std::string> getCurrentMachineName () | |||
| { | |||
| struct utsname unameData; | |||
| int res = uname (&unameData); | |||
| if (res != 0) | |||
| return {}; | |||
| return {unameData.machine}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<Path> getApplicationPath () | |||
| { | |||
| std::string appPath = ""; | |||
| pid_t pid = getpid (); | |||
| char buf[10]; | |||
| sprintf (buf, "%d", pid); | |||
| std::string _link = "/proc/"; | |||
| _link.append (buf); | |||
| _link.append ("/exe"); | |||
| char proc[1024]; | |||
| int ch = readlink (_link.c_str (), proc, 1024); | |||
| if (ch == -1) | |||
| return {}; | |||
| proc[ch] = 0; | |||
| appPath = proc; | |||
| std::string::size_type t = appPath.find_last_of ("/"); | |||
| appPath = appPath.substr (0, t); | |||
| return Path {appPath}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| class LinuxModule : public Module | |||
| { | |||
| public: | |||
| template <typename T> | |||
| T getFunctionPointer (const char* name) | |||
| { | |||
| return reinterpret_cast<T> (dlsym (mModule, name)); | |||
| } | |||
| ~LinuxModule () override | |||
| { | |||
| factory = PluginFactory (nullptr); | |||
| if (mModule) | |||
| { | |||
| if (auto moduleExit = getFunctionPointer<ModuleExitFunc> ("ModuleExit")) | |||
| moduleExit (); | |||
| dlclose (mModule); | |||
| } | |||
| } | |||
| static Optional<Path> getSOPath (const std::string& inPath) | |||
| { | |||
| Path modulePath {inPath}; | |||
| if (!filesystem::is_directory (modulePath)) | |||
| return {}; | |||
| auto stem = modulePath.stem (); | |||
| modulePath /= "Contents"; | |||
| if (!filesystem::is_directory (modulePath)) | |||
| return {}; | |||
| // use the Machine Hardware Name (from uname cmd-line) as prefix for "-linux" | |||
| auto machine = getCurrentMachineName (); | |||
| if (!machine) | |||
| return {}; | |||
| modulePath /= *machine + "-linux"; | |||
| if (!filesystem::is_directory (modulePath)) | |||
| return {}; | |||
| stem.replace_extension (".so"); | |||
| modulePath /= stem; | |||
| return Optional<Path> (std::move (modulePath)); | |||
| } | |||
| bool load (const std::string& inPath, std::string& errorDescription) override | |||
| { | |||
| auto modulePath = getSOPath (inPath); | |||
| if (!modulePath) | |||
| { | |||
| errorDescription = inPath + " is not a module directory."; | |||
| return false; | |||
| } | |||
| mModule = dlopen (reinterpret_cast<const char*> (modulePath->generic_string ().data ()), | |||
| RTLD_LAZY); | |||
| if (!mModule) | |||
| { | |||
| errorDescription = "dlopen failed.\n"; | |||
| errorDescription += dlerror (); | |||
| return false; | |||
| } | |||
| // ModuleEntry is mandatory | |||
| auto moduleEntry = getFunctionPointer<ModuleEntryFunc> ("ModuleEntry"); | |||
| if (!moduleEntry) | |||
| { | |||
| errorDescription = | |||
| "The shared library does not export the required 'ModuleEntry' function"; | |||
| return false; | |||
| } | |||
| // ModuleExit is mandatory | |||
| auto moduleExit = getFunctionPointer<ModuleExitFunc> ("ModuleExit"); | |||
| if (!moduleExit) | |||
| { | |||
| errorDescription = | |||
| "The shared library does not export the required 'ModuleExit' function"; | |||
| return false; | |||
| } | |||
| auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory"); | |||
| if (!factoryProc) | |||
| { | |||
| errorDescription = | |||
| "The shared library does not export the required 'GetPluginFactory' function"; | |||
| return false; | |||
| } | |||
| if (!moduleEntry (mModule)) | |||
| { | |||
| errorDescription = "Calling 'ModuleEntry' failed"; | |||
| return false; | |||
| } | |||
| auto f = Steinberg::FUnknownPtr<Steinberg::IPluginFactory> (owned (factoryProc ())); | |||
| if (!f) | |||
| { | |||
| errorDescription = "Calling 'GetPluginFactory' returned nullptr"; | |||
| return false; | |||
| } | |||
| factory = PluginFactory (f); | |||
| return true; | |||
| } | |||
| void* mModule {nullptr}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| void findFilesWithExt (const std::string& path, const std::string& ext, Module::PathList& pathList, | |||
| bool recursive = true) | |||
| { | |||
| try | |||
| { | |||
| for (auto& p : filesystem::directory_iterator (path)) | |||
| { | |||
| if (p.path ().extension () == ext) | |||
| { | |||
| pathList.push_back (p.path ().generic_string ()); | |||
| } | |||
| else if (recursive && p.status ().type () == filesystem::file_type::directory) | |||
| { | |||
| findFilesWithExt (p.path (), ext, pathList); | |||
| } | |||
| } | |||
| } | |||
| catch (...) | |||
| { | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void findModules (const std::string& path, Module::PathList& pathList) | |||
| { | |||
| findFilesWithExt (path, ".vst3", pathList); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| Module::Ptr Module::create (const std::string& path, std::string& errorDescription) | |||
| { | |||
| auto _module = std::make_shared<LinuxModule> (); | |||
| if (_module->load (path, errorDescription)) | |||
| { | |||
| _module->path = path; | |||
| auto it = std::find_if (path.rbegin (), path.rend (), | |||
| [] (const std::string::value_type& c) { return c == '/'; }); | |||
| if (it != path.rend ()) | |||
| _module->name = {it.base (), path.end ()}; | |||
| return _module; | |||
| } | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::PathList Module::getModulePaths () | |||
| { | |||
| /* VST3 component locations on linux : | |||
| * User privately installed : $HOME/.vst3/ | |||
| * Distribution installed : /usr/lib/vst3/ | |||
| * Locally installed : /usr/local/lib/vst3/ | |||
| * Application : /$APPFOLDER/vst3/ | |||
| */ | |||
| const auto systemPaths = {"/usr/lib/vst3/", "/usr/local/lib/vst3/"}; | |||
| PathList list; | |||
| if (auto homeDir = getenv ("HOME")) | |||
| { | |||
| filesystem::path homePath (homeDir); | |||
| homePath /= ".vst3"; | |||
| findModules (homePath.generic_string (), list); | |||
| } | |||
| for (auto path : systemPaths) | |||
| findModules (path, list); | |||
| // application level | |||
| auto appPath = getApplicationPath (); | |||
| if (appPath) | |||
| { | |||
| *appPath /= "vst3"; | |||
| findModules (appPath->generic_string (), list); | |||
| } | |||
| return list; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::SnapshotList Module::getSnapshots (const std::string& modulePath) | |||
| { | |||
| SnapshotList result; | |||
| filesystem::path path (modulePath); | |||
| path /= "Contents"; | |||
| path /= "Resources"; | |||
| path /= "Snapshots"; | |||
| PathList pngList; | |||
| findFilesWithExt (path, ".png", pngList, false); | |||
| for (auto& png : pngList) | |||
| { | |||
| filesystem::path p (png); | |||
| auto filename = p.filename ().generic_string (); | |||
| auto uid = Snapshot::decodeUID (filename); | |||
| if (!uid) | |||
| continue; | |||
| auto scaleFactor = 1.; | |||
| if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename)) | |||
| scaleFactor = *decodedScaleFactor; | |||
| Module::Snapshot::ImageDesc desc; | |||
| desc.scaleFactor = scaleFactor; | |||
| desc.path = std::move (png); | |||
| bool found = false; | |||
| for (auto& entry : result) | |||
| { | |||
| if (entry.uid != *uid) | |||
| continue; | |||
| found = true; | |||
| entry.images.emplace_back (std::move (desc)); | |||
| break; | |||
| } | |||
| if (found) | |||
| continue; | |||
| Module::Snapshot snapshot; | |||
| snapshot.uid = *uid; | |||
| snapshot.images.emplace_back (std::move (desc)); | |||
| result.emplace_back (std::move (snapshot)); | |||
| } | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath) | |||
| { | |||
| SnapshotList result; | |||
| filesystem::path path (modulePath); | |||
| path /= "Contents"; | |||
| path /= "moduleinfo.json"; | |||
| if (filesystem::exists (path)) | |||
| return {path.generic_string ()}; | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Hosting | |||
| } // VST3 | |||
| @@ -0,0 +1,390 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/hosting/module_mac.mm | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : hosting module classes (macOS implementation) | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #import "module.h" | |||
| #import <Cocoa/Cocoa.h> | |||
| #import <CoreFoundation/CoreFoundation.h> | |||
| #if !__has_feature(objc_arc) | |||
| #error this file needs to be compiled with automatic reference counting enabled | |||
| #endif | |||
| //------------------------------------------------------------------------ | |||
| extern "C" { | |||
| typedef bool (*BundleEntryFunc) (CFBundleRef); | |||
| typedef bool (*BundleExitFunc) (); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| namespace Hosting { | |||
| //------------------------------------------------------------------------ | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| template <typename T> | |||
| class CFPtr | |||
| { | |||
| public: | |||
| inline CFPtr (const T& obj = nullptr) : obj (obj) {} | |||
| inline CFPtr (CFPtr&& other) { *this = other; } | |||
| inline ~CFPtr () | |||
| { | |||
| if (obj) | |||
| CFRelease (obj); | |||
| } | |||
| inline CFPtr& operator= (CFPtr&& other) | |||
| { | |||
| obj = other.obj; | |||
| other.obj = nullptr; | |||
| return *this; | |||
| } | |||
| inline CFPtr& operator= (const T& o) | |||
| { | |||
| if (obj) | |||
| CFRelease (obj); | |||
| obj = o; | |||
| return *this; | |||
| } | |||
| inline operator T () const { return obj; } // act as T | |||
| private: | |||
| CFPtr (const CFPtr& other) = delete; | |||
| CFPtr& operator= (const CFPtr& other) = delete; | |||
| T obj = nullptr; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| class MacModule : public Module | |||
| { | |||
| public: | |||
| template <typename T> | |||
| T getFunctionPointer (const char* name) | |||
| { | |||
| assert (bundle); | |||
| CFPtr<CFStringRef> functionName ( | |||
| CFStringCreateWithCString (kCFAllocatorDefault, name, kCFStringEncodingASCII)); | |||
| return reinterpret_cast<T> (CFBundleGetFunctionPointerForName (bundle, functionName)); | |||
| } | |||
| bool loadInternal (const std::string& path, std::string& errorDescription) | |||
| { | |||
| CFPtr<CFURLRef> url (CFURLCreateFromFileSystemRepresentation ( | |||
| kCFAllocatorDefault, reinterpret_cast<const UInt8*> (path.data ()), path.length (), | |||
| true)); | |||
| if (!url) | |||
| return false; | |||
| bundle = CFBundleCreate (kCFAllocatorDefault, url); | |||
| CFErrorRef error = nullptr; | |||
| if (!bundle || !CFBundleLoadExecutableAndReturnError (bundle, &error)) | |||
| { | |||
| if (error) | |||
| { | |||
| CFPtr<CFStringRef> errorString (CFErrorCopyDescription (error)); | |||
| if (errorString) | |||
| { | |||
| auto stringLength = CFStringGetLength (errorString); | |||
| auto maxSize = | |||
| CFStringGetMaximumSizeForEncoding (stringLength, kCFStringEncodingUTF8); | |||
| auto buffer = std::make_unique<char[]> (maxSize); | |||
| if (CFStringGetCString (errorString, buffer.get (), maxSize, | |||
| kCFStringEncodingUTF8)) | |||
| errorDescription = buffer.get (); | |||
| CFRelease (error); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| errorDescription = "Could not create Bundle for path: " + path; | |||
| } | |||
| return false; | |||
| } | |||
| // bundleEntry is mandatory | |||
| auto bundleEntry = getFunctionPointer<BundleEntryFunc> ("bundleEntry"); | |||
| if (!bundleEntry) | |||
| { | |||
| errorDescription = "Bundle does not export the required 'bundleEntry' function"; | |||
| return false; | |||
| } | |||
| // bundleExit is mandatory | |||
| auto bundleExit = getFunctionPointer<BundleExitFunc> ("bundleExit"); | |||
| if (!bundleExit) | |||
| { | |||
| errorDescription = "Bundle does not export the required 'bundleExit' function"; | |||
| return false; | |||
| } | |||
| auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory"); | |||
| if (!factoryProc) | |||
| { | |||
| errorDescription = "Bundle does not export the required 'GetPluginFactory' function"; | |||
| return false; | |||
| } | |||
| if (!bundleEntry (bundle)) | |||
| { | |||
| errorDescription = "Calling 'bundleEntry' failed"; | |||
| return false; | |||
| } | |||
| auto f = owned (factoryProc ()); | |||
| if (!f) | |||
| { | |||
| errorDescription = "Calling 'GetPluginFactory' returned nullptr"; | |||
| return false; | |||
| } | |||
| factory = PluginFactory (f); | |||
| return true; | |||
| } | |||
| bool load (const std::string& path, std::string& errorDescription) override | |||
| { | |||
| if (!path.empty () && path[0] != '/') | |||
| { | |||
| auto buffer = std::make_unique<char[]> (PATH_MAX); | |||
| auto workDir = getcwd (buffer.get (), PATH_MAX); | |||
| if (workDir) | |||
| { | |||
| std::string wd (workDir); | |||
| wd += "/"; | |||
| return loadInternal (wd + path, errorDescription); | |||
| } | |||
| } | |||
| return loadInternal (path, errorDescription); | |||
| } | |||
| ~MacModule () override | |||
| { | |||
| factory = PluginFactory (nullptr); | |||
| if (bundle) | |||
| { | |||
| if (auto bundleExit = getFunctionPointer<BundleExitFunc> ("bundleExit")) | |||
| bundleExit (); | |||
| } | |||
| } | |||
| CFPtr<CFBundleRef> bundle; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| void findModulesInDirectory (NSURL* dirUrl, Module::PathList& result) | |||
| { | |||
| dirUrl = [dirUrl URLByResolvingSymlinksInPath]; | |||
| if (!dirUrl) | |||
| return; | |||
| NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] | |||
| enumeratorAtURL: dirUrl | |||
| includingPropertiesForKeys:nil | |||
| options:NSDirectoryEnumerationSkipsPackageDescendants | |||
| errorHandler:nil]; | |||
| for (NSURL* url in enumerator) | |||
| { | |||
| if ([[[url lastPathComponent] pathExtension] isEqualToString:@"vst3"]) | |||
| { | |||
| CFPtr<CFArrayRef> archs ( | |||
| CFBundleCopyExecutableArchitecturesForURL (static_cast<CFURLRef> (url))); | |||
| if (archs) | |||
| result.emplace_back ([url.path UTF8String]); | |||
| } | |||
| else | |||
| { | |||
| id resValue; | |||
| if (![url getResourceValue:&resValue forKey:NSURLIsSymbolicLinkKey error:nil]) | |||
| continue; | |||
| if (!static_cast<NSNumber*> (resValue).boolValue) | |||
| continue; | |||
| auto resolvedUrl = [url URLByResolvingSymlinksInPath]; | |||
| if (![resolvedUrl getResourceValue:&resValue forKey:NSURLIsDirectoryKey error:nil]) | |||
| continue; | |||
| if (!static_cast<NSNumber*> (resValue).boolValue) | |||
| continue; | |||
| findModulesInDirectory (resolvedUrl, result); | |||
| } | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void getModules (NSSearchPathDomainMask domain, Module::PathList& result) | |||
| { | |||
| NSURL* libraryUrl = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory | |||
| inDomain:domain | |||
| appropriateForURL:nil | |||
| create:NO | |||
| error:nil]; | |||
| if (libraryUrl == nil) | |||
| return; | |||
| NSURL* audioUrl = [libraryUrl URLByAppendingPathComponent:@"Audio"]; | |||
| if (audioUrl == nil) | |||
| return; | |||
| NSURL* plugInsUrl = [audioUrl URLByAppendingPathComponent:@"Plug-Ins"]; | |||
| if (plugInsUrl == nil) | |||
| return; | |||
| NSURL* vst3Url = | |||
| [[plugInsUrl URLByAppendingPathComponent:@"VST3"] URLByResolvingSymlinksInPath]; | |||
| if (vst3Url == nil) | |||
| return; | |||
| findModulesInDirectory (vst3Url, result); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void getApplicationModules (Module::PathList& result) | |||
| { | |||
| auto bundle = CFBundleGetMainBundle (); | |||
| if (!bundle) | |||
| return; | |||
| auto bundleUrl = static_cast<NSURL*> (CFBridgingRelease (CFBundleCopyBundleURL (bundle))); | |||
| if (!bundleUrl) | |||
| return; | |||
| auto resUrl = [bundleUrl URLByAppendingPathComponent:@"Contents"]; | |||
| if (!resUrl) | |||
| return; | |||
| auto vst3Url = [resUrl URLByAppendingPathComponent:@"VST3"]; | |||
| if (!vst3Url) | |||
| return; | |||
| findModulesInDirectory (vst3Url, result); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void getModuleSnapshots (const std::string& path, Module::SnapshotList& result) | |||
| { | |||
| auto nsString = [NSString stringWithUTF8String:path.data ()]; | |||
| if (!nsString) | |||
| return; | |||
| auto bundleUrl = [NSURL fileURLWithPath:nsString]; | |||
| if (!bundleUrl) | |||
| return; | |||
| auto urls = [NSBundle URLsForResourcesWithExtension:@"png" | |||
| subdirectory:@"Snapshots" | |||
| inBundleWithURL:bundleUrl]; | |||
| if (!urls || [urls count] == 0) | |||
| return; | |||
| for (NSURL* url in urls) | |||
| { | |||
| std::string fullpath ([[url path] UTF8String]); | |||
| std::string filename ([[[url path] lastPathComponent] UTF8String]); | |||
| auto uid = Module::Snapshot::decodeUID (filename); | |||
| if (!uid) | |||
| continue; | |||
| auto scaleFactor = 1.; | |||
| if (auto decodedScaleFactor = Module::Snapshot::decodeScaleFactor (filename)) | |||
| scaleFactor = *decodedScaleFactor; | |||
| Module::Snapshot::ImageDesc desc; | |||
| desc.scaleFactor = scaleFactor; | |||
| desc.path = std::move (fullpath); | |||
| bool found = false; | |||
| for (auto& entry : result) | |||
| { | |||
| if (entry.uid != *uid) | |||
| continue; | |||
| found = true; | |||
| entry.images.emplace_back (std::move (desc)); | |||
| break; | |||
| } | |||
| if (found) | |||
| continue; | |||
| Module::Snapshot snapshot; | |||
| snapshot.uid = *uid; | |||
| snapshot.images.emplace_back (std::move (desc)); | |||
| result.emplace_back (std::move (snapshot)); | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| Module::Ptr Module::create (const std::string& path, std::string& errorDescription) | |||
| { | |||
| auto module = std::make_shared<MacModule> (); | |||
| if (module->load (path, errorDescription)) | |||
| { | |||
| module->path = path; | |||
| auto it = std::find_if (path.rbegin (), path.rend (), | |||
| [] (const std::string::value_type& c) { return c == '/'; }); | |||
| if (it != path.rend ()) | |||
| module->name = {it.base (), path.end ()}; | |||
| return std::move (module); | |||
| } | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::PathList Module::getModulePaths () | |||
| { | |||
| PathList list; | |||
| getModules (NSUserDomainMask, list); | |||
| getModules (NSLocalDomainMask, list); | |||
| // TODO getModules (NSNetworkDomainMask, list); | |||
| getApplicationModules (list); | |||
| return list; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::SnapshotList Module::getSnapshots (const std::string& modulePath) | |||
| { | |||
| SnapshotList list; | |||
| getModuleSnapshots (modulePath, list); | |||
| return list; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath) | |||
| { | |||
| auto nsString = [NSString stringWithUTF8String:modulePath.data ()]; | |||
| if (!nsString) | |||
| return {}; | |||
| auto bundleUrl = [NSURL fileURLWithPath:nsString]; | |||
| if (!bundleUrl) | |||
| return {}; | |||
| auto contentsUrl = [bundleUrl URLByAppendingPathComponent:@"Contents"]; | |||
| if (!contentsUrl) | |||
| return {}; | |||
| auto moduleInfoUrl = [contentsUrl URLByAppendingPathComponent:@"moduleinfo.json"]; | |||
| if (!moduleInfoUrl) | |||
| return {}; | |||
| NSError* error = nil; | |||
| if ([moduleInfoUrl checkResourceIsReachableAndReturnError:&error]) | |||
| return {std::string (moduleInfoUrl.fileSystemRepresentation)}; | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Hosting | |||
| } // VST3 | |||
| @@ -0,0 +1,610 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/hosting/module_win32.cpp | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : hosting module classes (win32 implementation) | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "../utility/optional.h" | |||
| #include "../utility/stringconvert.h" | |||
| #include "module.h" | |||
| #include <shlobj.h> | |||
| #include <windows.h> | |||
| #include <algorithm> | |||
| #include <iostream> | |||
| #if SMTG_CPP17 | |||
| #if __has_include(<filesystem>) | |||
| #define USE_FILESYSTEM 1 | |||
| #elif __has_include(<experimental/filesystem>) | |||
| #define USE_FILESYSTEM 0 | |||
| #endif | |||
| #else // !SMTG_CPP17 | |||
| #define USE_FILESYSTEM 0 | |||
| #endif // SMTG_CPP17 | |||
| #if USE_FILESYSTEM == 1 | |||
| #include <filesystem> | |||
| namespace filesystem = std::filesystem; | |||
| #else // USE_FILESYSTEM == 0 | |||
| // The <experimental/filesystem> header is deprecated. It is superseded by the C++17 <filesystem> | |||
| // header. You can define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING to silence the | |||
| // warning, otherwise the build will fail in VS2019 16.3.0 | |||
| #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING | |||
| #include <experimental/filesystem> | |||
| namespace filesystem = std::experimental::filesystem; | |||
| #endif // USE_FILESYSTEM | |||
| #pragma comment(lib, "Shell32") | |||
| //------------------------------------------------------------------------ | |||
| extern "C" { | |||
| using InitModuleFunc = bool (PLUGIN_API*) (); | |||
| using ExitModuleFunc = bool (PLUGIN_API*) (); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| namespace Hosting { | |||
| constexpr unsigned long kIPPathNameMax = 1024; | |||
| //------------------------------------------------------------------------ | |||
| namespace { | |||
| #define USE_OLE !USE_FILESYSTEM | |||
| #if SMTG_PLATFORM_64 | |||
| #if SMTG_OS_WINDOWS_ARM | |||
| #if SMTG_CPU_ARM_64EC | |||
| constexpr auto architectureString = "arm64ec-win"; | |||
| constexpr auto architectureX64String = "x86_64-win"; | |||
| #else // !SMTG_CPU_ARM_64EC | |||
| constexpr auto architectureString = "arm64-win"; | |||
| #endif // SMTG_CPU_ARM_64EC | |||
| constexpr auto architectureArm64XString = "arm64x-win"; | |||
| #else // !SMTG_OS_WINDOWS_ARM | |||
| constexpr auto architectureString = "x86_64-win"; | |||
| #endif // SMTG_OS_WINDOWS_ARM | |||
| #else // !SMTG_PLATFORM_64 | |||
| #if SMTG_OS_WINDOWS_ARM | |||
| constexpr auto architectureString = "arm-win"; | |||
| #else // !SMTG_OS_WINDOWS_ARM | |||
| constexpr auto architectureString = "x86-win"; | |||
| #endif // SMTG_OS_WINDOWS_ARM | |||
| #endif // SMTG_PLATFORM_64 | |||
| #if USE_OLE | |||
| //------------------------------------------------------------------------ | |||
| struct Ole | |||
| { | |||
| static Ole& instance () | |||
| { | |||
| static Ole gInstance; | |||
| return gInstance; | |||
| } | |||
| private: | |||
| Ole () { OleInitialize (nullptr); } | |||
| ~Ole () { OleUninitialize (); } | |||
| }; | |||
| #endif // USE_OLE | |||
| //------------------------------------------------------------------------ | |||
| class Win32Module : public Module | |||
| { | |||
| public: | |||
| template <typename T> | |||
| T getFunctionPointer (const char* name) | |||
| { | |||
| return reinterpret_cast<T> (GetProcAddress (mModule, name)); | |||
| } | |||
| ~Win32Module () override | |||
| { | |||
| factory = PluginFactory (nullptr); | |||
| if (mModule) | |||
| { | |||
| // ExitDll is optional | |||
| if (auto dllExit = getFunctionPointer<ExitModuleFunc> ("ExitDll")) | |||
| dllExit (); | |||
| FreeLibrary ((HMODULE)mModule); | |||
| } | |||
| } | |||
| //--- ----------------------------------------------------------------------- | |||
| HINSTANCE loadAsPackage (const std::string& inPath, const char* archString = architectureString) | |||
| { | |||
| filesystem::path p (inPath); | |||
| auto filename = p.filename (); | |||
| p /= "Contents"; | |||
| p /= archString; | |||
| p /= filename; | |||
| auto wideStr = StringConvert::convert (p.string ()); | |||
| HINSTANCE instance = LoadLibraryW (reinterpret_cast<LPCWSTR> (wideStr.data ())); | |||
| #if SMTG_CPU_ARM_64EC | |||
| if (instance == nullptr) | |||
| instance = loadAsPackage (inPath, architectureArm64XString); | |||
| if (instance == nullptr) | |||
| instance = loadAsPackage (inPath, architectureX64String); | |||
| #endif | |||
| return instance; | |||
| } | |||
| //--- ----------------------------------------------------------------------- | |||
| HINSTANCE loadAsDll (const std::string& inPath, std::string& errorDescription) | |||
| { | |||
| auto wideStr = StringConvert::convert (inPath); | |||
| HINSTANCE instance = LoadLibraryW (reinterpret_cast<LPCWSTR> (wideStr.data ())); | |||
| if (instance == nullptr) | |||
| { | |||
| auto lastError = GetLastError (); | |||
| LPVOID lpMessageBuffer {nullptr}; | |||
| if (FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, | |||
| nullptr, lastError, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), | |||
| (LPSTR)&lpMessageBuffer, 0, nullptr) > 0) | |||
| { | |||
| errorDescription = "LoadLibray failed: " + std::string ((char*)lpMessageBuffer); | |||
| LocalFree (lpMessageBuffer); | |||
| } | |||
| else | |||
| { | |||
| errorDescription = | |||
| "LoadLibrary failed with error number : " + std::to_string (lastError); | |||
| } | |||
| } | |||
| return instance; | |||
| } | |||
| //--- ----------------------------------------------------------------------- | |||
| bool load (const std::string& inPath, std::string& errorDescription) override | |||
| { | |||
| if (filesystem::is_directory (inPath)) | |||
| { | |||
| // try as package (bundle) | |||
| mModule = loadAsPackage (inPath); | |||
| } | |||
| else | |||
| { | |||
| // try old definition without package | |||
| mModule = loadAsDll (inPath, errorDescription); | |||
| } | |||
| if (mModule == nullptr) | |||
| return false; | |||
| auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory"); | |||
| if (!factoryProc) | |||
| { | |||
| errorDescription = "The dll does not export the required 'GetPluginFactory' function"; | |||
| return false; | |||
| } | |||
| // InitDll is optional | |||
| auto dllEntry = getFunctionPointer<InitModuleFunc> ("InitDll"); | |||
| if (dllEntry && !dllEntry ()) | |||
| { | |||
| errorDescription = "Calling 'InitDll' failed"; | |||
| return false; | |||
| } | |||
| auto f = Steinberg::FUnknownPtr<Steinberg::IPluginFactory> (owned (factoryProc ())); | |||
| if (!f) | |||
| { | |||
| errorDescription = "Calling 'GetPluginFactory' returned nullptr"; | |||
| return false; | |||
| } | |||
| factory = PluginFactory (f); | |||
| return true; | |||
| } | |||
| HINSTANCE mModule {nullptr}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| bool openVST3Package (const filesystem::path& p, const char* archString, | |||
| filesystem::path* result = nullptr) | |||
| { | |||
| auto path = p; | |||
| path /= "Contents"; | |||
| path /= archString; | |||
| path /= p.filename (); | |||
| auto hFile = CreateFileW (reinterpret_cast<LPCWSTR> (path.c_str ()), GENERIC_READ, | |||
| FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); | |||
| if (hFile != INVALID_HANDLE_VALUE) | |||
| { | |||
| CloseHandle (hFile); | |||
| if (result) | |||
| *result = path; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| bool checkVST3Package (const filesystem::path& p, filesystem::path* result = nullptr, | |||
| const char* archString = architectureString) | |||
| { | |||
| if (openVST3Package (p, archString, result)) | |||
| return true; | |||
| #if SMTG_CPU_ARM_64EC | |||
| if (openVST3Package (p, architectureArm64XString, result)) | |||
| return true; | |||
| if (openVST3Package (p, architectureX64String, result)) | |||
| return true; | |||
| #endif | |||
| return false; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| bool isFolderSymbolicLink (const filesystem::path& p) | |||
| { | |||
| #if USE_FILESYSTEM | |||
| if (/*filesystem::exists (p) &&*/ filesystem::is_symlink (p)) | |||
| return true; | |||
| #else | |||
| std::wstring wString = p.generic_wstring (); | |||
| auto attrib = GetFileAttributesW (reinterpret_cast<LPCWSTR> (wString.c_str ())); | |||
| if (attrib & FILE_ATTRIBUTE_REPARSE_POINT) | |||
| { | |||
| auto hFile = CreateFileW (reinterpret_cast<LPCWSTR> (wString.c_str ()), GENERIC_READ, | |||
| FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); | |||
| if (hFile == INVALID_HANDLE_VALUE) | |||
| return true; | |||
| CloseHandle (hFile); | |||
| } | |||
| #endif | |||
| return false; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<std::string> getKnownFolder (REFKNOWNFOLDERID folderID) | |||
| { | |||
| PWSTR wideStr {}; | |||
| if (FAILED (SHGetKnownFolderPath (folderID, 0, nullptr, &wideStr))) | |||
| return {}; | |||
| return StringConvert::convert (Steinberg::wscast (wideStr)); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| VST3::Optional<filesystem::path> resolveShellLink (const filesystem::path& p) | |||
| { | |||
| #if USE_FILESYSTEM | |||
| return {filesystem::read_symlink (p).lexically_normal ()}; | |||
| #elif USE_OLE | |||
| Ole::instance (); | |||
| IShellLink* shellLink = nullptr; | |||
| if (!SUCCEEDED (CoCreateInstance (CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, | |||
| IID_IShellLink, reinterpret_cast<LPVOID*> (&shellLink)))) | |||
| return {}; | |||
| IPersistFile* persistFile = nullptr; | |||
| if (!SUCCEEDED ( | |||
| shellLink->QueryInterface (IID_IPersistFile, reinterpret_cast<void**> (&persistFile)))) | |||
| return {}; | |||
| if (!SUCCEEDED (persistFile->Load (p.wstring ().data (), STGM_READ))) | |||
| return {}; | |||
| if (!SUCCEEDED (shellLink->Resolve (nullptr, MAKELONG (SLR_NO_UI, 500)))) | |||
| return {}; | |||
| WCHAR resolvedPath[kIPPathNameMax]; | |||
| if (!SUCCEEDED (shellLink->GetPath (resolvedPath, kIPPathNameMax, nullptr, SLGP_SHORTPATH))) | |||
| return {}; | |||
| std::wstring longPath; | |||
| longPath.resize (kIPPathNameMax); | |||
| auto numChars = | |||
| GetLongPathNameW (resolvedPath, const_cast<wchar_t*> (longPath.data ()), kIPPathNameMax); | |||
| if (!numChars) | |||
| return {}; | |||
| longPath.resize (numChars); | |||
| persistFile->Release (); | |||
| shellLink->Release (); | |||
| return {filesystem::path (longPath)}; | |||
| #else | |||
| return {}; | |||
| #endif | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void findFilesWithExt (const filesystem::path& path, const std::string& ext, | |||
| Module::PathList& pathList, bool recursive = true) | |||
| { | |||
| for (auto& p : filesystem::directory_iterator (path)) | |||
| { | |||
| #if USE_FILESYSTEM | |||
| filesystem::path finalPath (p); | |||
| if (isFolderSymbolicLink (p)) | |||
| { | |||
| if (auto res = resolveShellLink (p)) | |||
| { | |||
| finalPath = *res; | |||
| if (!filesystem::exists (finalPath)) | |||
| continue; | |||
| } | |||
| else | |||
| continue; | |||
| } | |||
| const auto& cpExt = finalPath.extension (); | |||
| if (cpExt == ext) | |||
| { | |||
| filesystem::path result; | |||
| if (checkVST3Package (finalPath, &result)) | |||
| { | |||
| pathList.push_back (result.generic_string ()); | |||
| continue; | |||
| } | |||
| } | |||
| if (filesystem::is_directory (finalPath)) | |||
| { | |||
| if (recursive) | |||
| findFilesWithExt (finalPath, ext, pathList, recursive); | |||
| } | |||
| else if (cpExt == ext) | |||
| pathList.push_back (finalPath.generic_string ()); | |||
| #else | |||
| const auto& cp = p.path (); | |||
| const auto& cpExt = cp.extension (); | |||
| if (cpExt == ext) | |||
| { | |||
| if ((p.status ().type () == filesystem::file_type::directory) || | |||
| isFolderSymbolicLink (p)) | |||
| { | |||
| filesystem::path result; | |||
| if (checkVST3Package (p, &result)) | |||
| { | |||
| pathList.push_back (result.generic_u8string ()); | |||
| continue; | |||
| } | |||
| findFilesWithExt (cp, ext, pathList, recursive); | |||
| } | |||
| else | |||
| pathList.push_back (cp.generic_u8string ()); | |||
| } | |||
| else if (recursive) | |||
| { | |||
| if (p.status ().type () == filesystem::file_type::directory) | |||
| { | |||
| findFilesWithExt (cp, ext, pathList, recursive); | |||
| } | |||
| else if (cpExt == ".lnk") | |||
| { | |||
| if (auto resolvedLink = resolveShellLink (cp)) | |||
| { | |||
| if (resolvedLink->extension () == ext) | |||
| { | |||
| if (filesystem::is_directory (*resolvedLink) || | |||
| isFolderSymbolicLink (*resolvedLink)) | |||
| { | |||
| filesystem::path result; | |||
| if (checkVST3Package (*resolvedLink, &result)) | |||
| { | |||
| pathList.push_back (result.generic_u8string ()); | |||
| continue; | |||
| } | |||
| findFilesWithExt (*resolvedLink, ext, pathList, recursive); | |||
| } | |||
| else | |||
| pathList.push_back (resolvedLink->generic_u8string ()); | |||
| } | |||
| else if (filesystem::is_directory (*resolvedLink)) | |||
| { | |||
| const auto& str = resolvedLink->generic_u8string (); | |||
| if (cp.generic_u8string ().compare (0, str.size (), str.data (), | |||
| str.size ()) != 0) | |||
| findFilesWithExt (*resolvedLink, ext, pathList, recursive); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| #endif | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void findModules (const filesystem::path& path, Module::PathList& pathList) | |||
| { | |||
| if (filesystem::exists (path)) | |||
| findFilesWithExt (path, ".vst3", pathList); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<filesystem::path> getContentsDirectoryFromModuleExecutablePath ( | |||
| const std::string& modulePath) | |||
| { | |||
| filesystem::path path (modulePath); | |||
| path = path.parent_path (); | |||
| if (path.filename () != architectureString) | |||
| return {}; | |||
| path = path.parent_path (); | |||
| if (path.filename () != "Contents") | |||
| return {}; | |||
| return Optional<filesystem::path> {std::move (path)}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| Module::Ptr Module::create (const std::string& path, std::string& errorDescription) | |||
| { | |||
| auto _module = std::make_shared<Win32Module> (); | |||
| if (_module->load (path, errorDescription)) | |||
| { | |||
| _module->path = path; | |||
| auto it = std::find_if (path.rbegin (), path.rend (), | |||
| [] (const std::string::value_type& c) { return c == '/'; }); | |||
| if (it != path.rend ()) | |||
| _module->name = {it.base (), path.end ()}; | |||
| return _module; | |||
| } | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::PathList Module::getModulePaths () | |||
| { | |||
| // find plug-ins located in common/VST3 | |||
| PathList list; | |||
| if (auto knownFolder = getKnownFolder (FOLDERID_UserProgramFilesCommon)) | |||
| { | |||
| filesystem::path p (*knownFolder); | |||
| p.append ("VST3"); | |||
| findModules (p, list); | |||
| } | |||
| if (auto knownFolder = getKnownFolder (FOLDERID_ProgramFilesCommon)) | |||
| { | |||
| filesystem::path p (*knownFolder); | |||
| p.append ("VST3"); | |||
| findModules (p, list); | |||
| } | |||
| // find plug-ins located in VST3 (application folder) | |||
| WCHAR modulePath[kIPPathNameMax]; | |||
| GetModuleFileNameW (nullptr, modulePath, kIPPathNameMax); | |||
| auto appPath = StringConvert::convert (Steinberg::wscast (modulePath)); | |||
| filesystem::path path (appPath); | |||
| path = path.parent_path (); | |||
| path = path.append ("VST3"); | |||
| findModules (path, list); | |||
| return list; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath) | |||
| { | |||
| auto path = getContentsDirectoryFromModuleExecutablePath (modulePath); | |||
| if (!path) | |||
| { | |||
| filesystem::path p; | |||
| if (!checkVST3Package ({modulePath}, &p)) | |||
| return {}; | |||
| p = p.parent_path (); | |||
| p = p.parent_path (); | |||
| path = Optional<filesystem::path> {p}; | |||
| } | |||
| *path /= "moduleinfo.json"; | |||
| if (filesystem::exists (*path)) | |||
| { | |||
| return {path->generic_string ()}; | |||
| } | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| Module::SnapshotList Module::getSnapshots (const std::string& modulePath) | |||
| { | |||
| SnapshotList result; | |||
| auto path = getContentsDirectoryFromModuleExecutablePath (modulePath); | |||
| if (!path) | |||
| { | |||
| filesystem::path p; | |||
| if (!checkVST3Package ({modulePath}, &p)) | |||
| return result; | |||
| p = p.parent_path (); | |||
| p = p.parent_path (); | |||
| path = Optional<filesystem::path> (p); | |||
| } | |||
| *path /= "Resources"; | |||
| *path /= "Snapshots"; | |||
| if (filesystem::exists (*path) == false) | |||
| return result; | |||
| PathList pngList; | |||
| findFilesWithExt (*path, ".png", pngList, false); | |||
| for (auto& png : pngList) | |||
| { | |||
| filesystem::path p (png); | |||
| auto filename = p.filename ().generic_string (); | |||
| auto uid = Snapshot::decodeUID (filename); | |||
| if (!uid) | |||
| continue; | |||
| auto scaleFactor = 1.; | |||
| if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename)) | |||
| scaleFactor = *decodedScaleFactor; | |||
| Module::Snapshot::ImageDesc desc; | |||
| desc.scaleFactor = scaleFactor; | |||
| desc.path = std::move (png); | |||
| bool found = false; | |||
| for (auto& entry : result) | |||
| { | |||
| if (entry.uid != *uid) | |||
| continue; | |||
| found = true; | |||
| entry.images.emplace_back (std::move (desc)); | |||
| break; | |||
| } | |||
| if (found) | |||
| continue; | |||
| Module::Snapshot snapshot; | |||
| snapshot.uid = *uid; | |||
| snapshot.images.emplace_back (std::move (desc)); | |||
| result.emplace_back (std::move (snapshot)); | |||
| } | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Hosting | |||
| } // VST3 | |||
| @@ -0,0 +1,43 @@ | |||
| # ModuleInfoLib | |||
| This is a c++17 library to parse and create the Steinberg moduleinfo.json files. | |||
| ## Parsing | |||
| To parse a moduleinfo.json file you need to include the following files to your project: | |||
| * moduleinfoparser.cpp | |||
| * moduleinfoparser.h | |||
| * moduleinfo.h | |||
| * json.h | |||
| * jsoncxx.h | |||
| And add a header search path to the root folder of the VST SDK. | |||
| Now to parse a moduleinfo.json file in code you need to read the moduleinfo.json into a memory buffer and call | |||
| ``` c++ | |||
| auto moduleInfo = ModuleInfoLib::parseCompatibilityJson (std::string_view (buffer, bufferSize), &std::cerr); | |||
| ``` | |||
| Afterwards if parsing succeeded the moduleInfo optional has a value containing the ModuleInfo. | |||
| ## Creating | |||
| The VST3 SDK contains the moduleinfotool utility that can create moduleinfo.json files from VST3 modules. | |||
| To add this capability to your own project you need to link to the sdk_hosting library from the SDK and include the following files to your project: | |||
| * moduleinfocreator.cpp | |||
| * moduleinfocreator.h | |||
| * moduleinfo.h | |||
| Additionally you need to add the module platform implementation from the hosting directory (module_win32.cpp, module_mac.mm or module_linux.cpp). | |||
| Now you can use the two methods in moduleinfocreator.h to create a moduleinfo.json file: | |||
| ``` c++ | |||
| auto moduleInfo = ModuleInfoLib::createModuleInfo (module, false); | |||
| ModuleInfoLib::outputJson (moduleInfo, std::cout); | |||
| ``` | |||
| @@ -0,0 +1,427 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : | |||
| // Filename : public.sdk/source/vst/moduleinfo/jsoncxx.h | |||
| // Created by : Steinberg, 12/2021 | |||
| // Description : | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include "json.h" | |||
| #include <cassert> | |||
| #include <cstdlib> | |||
| #include <optional> | |||
| #include <string> | |||
| #include <string_view> | |||
| #include <variant> | |||
| #if defined(_MSC_VER) || __has_include(<charconv>) | |||
| #include <charconv> | |||
| #define SMTG_HAS_CHARCONV | |||
| #endif | |||
| //------------------------------------------------------------------------ | |||
| namespace JSON { | |||
| namespace Detail { | |||
| //------------------------------------------------------------------------ | |||
| template <typename JsonT> | |||
| struct Base | |||
| { | |||
| explicit Base (JsonT* o) : object_ (o) {} | |||
| explicit Base (const Base& o) : object_ (o.object_) {} | |||
| Base& operator= (const Base& o) = default; | |||
| operator JsonT* () const { return object_; } | |||
| JsonT* jsonValue () const { return object_; } | |||
| protected: | |||
| Base () : object_ (nullptr) {} | |||
| JsonT* object_; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| template <typename JsonElement> | |||
| struct Iterator | |||
| { | |||
| explicit Iterator (JsonElement el) : el (el) {} | |||
| bool operator== (const Iterator& other) const { return other.el == el; } | |||
| bool operator!= (const Iterator& other) const { return other.el != el; } | |||
| JsonElement operator* () const { return el; } | |||
| JsonElement operator-> () const { return el; } | |||
| Iterator& operator++ () | |||
| { | |||
| if (el) | |||
| el = el.next (); | |||
| return *this; | |||
| } | |||
| Iterator operator++ (int) | |||
| { | |||
| auto it = Iterator (el); | |||
| operator++ (); | |||
| return it; | |||
| } | |||
| private: | |||
| JsonElement el; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| } // Detail | |||
| struct Object; | |||
| struct Array; | |||
| struct String; | |||
| struct Number; | |||
| struct Boolean; | |||
| //------------------------------------------------------------------------ | |||
| enum class Type | |||
| { | |||
| Object, | |||
| Array, | |||
| String, | |||
| Number, | |||
| True, | |||
| False, | |||
| Null, | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct SourceLocation | |||
| { | |||
| size_t offset; | |||
| size_t line; | |||
| size_t row; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Value : Detail::Base<json_value_s> | |||
| { | |||
| using Detail::Base<json_value_s>::Base; | |||
| using VariantT = std::variant<Object, Array, String, Number, Boolean, std::nullptr_t>; | |||
| std::optional<Object> asObject () const; | |||
| std::optional<Array> asArray () const; | |||
| std::optional<String> asString () const; | |||
| std::optional<Number> asNumber () const; | |||
| std::optional<Boolean> asBoolean () const; | |||
| std::optional<std::nullptr_t> asNull () const; | |||
| VariantT asVariant () const; | |||
| Type type () const; | |||
| SourceLocation getSourceLocation () const; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Boolean | |||
| { | |||
| Boolean (size_t type) : value (type == json_type_true) {} | |||
| operator bool () const { return value; } | |||
| private: | |||
| bool value; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct String : Detail::Base<json_string_s> | |||
| { | |||
| using Detail::Base<json_string_s>::Base; | |||
| std::string_view text () const { return {jsonValue ()->string, jsonValue ()->string_size}; } | |||
| SourceLocation getSourceLocation () const; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Number : Detail::Base<json_number_s> | |||
| { | |||
| using Detail::Base<json_number_s>::Base; | |||
| std::string_view text () const { return {jsonValue ()->number, jsonValue ()->number_size}; } | |||
| std::optional<int64_t> getInteger () const; | |||
| std::optional<double> getDouble () const; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct ObjectElement : Detail::Base<json_object_element_s> | |||
| { | |||
| using Detail::Base<json_object_element_s>::Base; | |||
| String name () const { return String (jsonValue ()->name); } | |||
| Value value () const { return Value (jsonValue ()->value); } | |||
| ObjectElement next () const { return ObjectElement (jsonValue ()->next); } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Object : Detail::Base<json_object_s> | |||
| { | |||
| using Detail::Base<json_object_s>::Base; | |||
| using Iterator = Detail::Iterator<ObjectElement>; | |||
| size_t size () const { return jsonValue ()->length; } | |||
| Iterator begin () const { return Iterator (ObjectElement (jsonValue ()->start)); } | |||
| Iterator end () const { return Iterator (ObjectElement (nullptr)); } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct ArrayElement : Detail::Base<json_array_element_s> | |||
| { | |||
| using Detail::Base<json_array_element_s>::Base; | |||
| Value value () const { return Value (jsonValue ()->value); } | |||
| ArrayElement next () const { return ArrayElement (jsonValue ()->next); } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Array : Detail::Base<json_array_s> | |||
| { | |||
| using Detail::Base<json_array_s>::Base; | |||
| using Iterator = Detail::Iterator<ArrayElement>; | |||
| size_t size () const { return jsonValue ()->length; } | |||
| Iterator begin () const { return Iterator (ArrayElement (jsonValue ()->start)); } | |||
| Iterator end () const { return Iterator (ArrayElement (nullptr)); } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Document : Value | |||
| { | |||
| static std::variant<Document, json_parse_result_s> parse (std::string_view data) | |||
| { | |||
| auto allocate = [] (void*, size_t allocSize) { return std::malloc (allocSize); }; | |||
| json_parse_result_s parse_result {}; | |||
| auto value = json_parse_ex (data.data (), data.size (), | |||
| json_parse_flags_allow_json5 | | |||
| json_parse_flags_allow_location_information, | |||
| allocate, nullptr, &parse_result); | |||
| if (value) | |||
| return Document (value); | |||
| return parse_result; | |||
| } | |||
| ~Document () noexcept | |||
| { | |||
| if (object_) | |||
| std::free (object_); | |||
| } | |||
| Document (Document&& doc) noexcept { *this = std::move (doc); } | |||
| Document& operator= (Document&& doc) noexcept | |||
| { | |||
| std::swap (object_, doc.object_); | |||
| return *this; | |||
| } | |||
| private: | |||
| using Value::Value; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<Object> Value::asObject () const | |||
| { | |||
| if (type () != Type::Object) | |||
| return {}; | |||
| return Object (json_value_as_object (jsonValue ())); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<Array> Value::asArray () const | |||
| { | |||
| if (type () != Type::Array) | |||
| return {}; | |||
| return Array (json_value_as_array (jsonValue ())); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<String> Value::asString () const | |||
| { | |||
| if (type () != Type::String) | |||
| return {}; | |||
| return String (json_value_as_string (jsonValue ())); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<Number> Value::asNumber () const | |||
| { | |||
| if (type () != Type::Number) | |||
| return {}; | |||
| return Number (json_value_as_number (jsonValue ())); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<Boolean> Value::asBoolean () const | |||
| { | |||
| if (type () == Type::True || type () == Type::False) | |||
| return Boolean (jsonValue ()->type); | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<std::nullptr_t> Value::asNull () const | |||
| { | |||
| if (type () != Type::Null) | |||
| return {}; | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline Type Value::type () const | |||
| { | |||
| switch (jsonValue ()->type) | |||
| { | |||
| case json_type_string: return Type::String; | |||
| case json_type_number: return Type::Number; | |||
| case json_type_object: return Type::Object; | |||
| case json_type_array: return Type::Array; | |||
| case json_type_true: return Type::True; | |||
| case json_type_false: return Type::False; | |||
| case json_type_null: return Type::Null; | |||
| } | |||
| assert (false); | |||
| return Type::Null; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline Value::VariantT Value::asVariant () const | |||
| { | |||
| switch (type ()) | |||
| { | |||
| case Type::String: return *asString (); | |||
| case Type::Number: return *asNumber (); | |||
| case Type::Object: return *asObject (); | |||
| case Type::Array: return *asArray (); | |||
| case Type::True: return *asBoolean (); | |||
| case Type::False: return *asBoolean (); | |||
| case Type::Null: return *asNull (); | |||
| } | |||
| assert (false); | |||
| return nullptr; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline SourceLocation Value::getSourceLocation () const | |||
| { | |||
| auto exValue = reinterpret_cast<json_value_ex_s*> (jsonValue ()); | |||
| return {exValue->offset, exValue->line_no, exValue->row_no}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline SourceLocation String::getSourceLocation () const | |||
| { | |||
| auto exValue = reinterpret_cast<json_string_ex_s*> (jsonValue ()); | |||
| return {exValue->offset, exValue->line_no, exValue->row_no}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<int64_t> Number::getInteger () const | |||
| { | |||
| #if defined(SMTG_HAS_CHARCONV) | |||
| int64_t result {0}; | |||
| auto res = std::from_chars (jsonValue ()->number, | |||
| jsonValue ()->number + jsonValue ()->number_size, result); | |||
| if (res.ec == std::errc ()) | |||
| return result; | |||
| return {}; | |||
| #else | |||
| int64_t result {0}; | |||
| std::string str (jsonValue ()->number, jsonValue ()->number + jsonValue ()->number_size); | |||
| if (std::sscanf (str.data (), "%lld", &result) != 1) | |||
| return {}; | |||
| return result; | |||
| #endif | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::optional<double> Number::getDouble () const | |||
| { | |||
| #if 1 // clang still has no floting point from_chars version | |||
| size_t ctrl {0}; | |||
| auto result = std::stod (std::string (jsonValue ()->number, jsonValue ()->number_size), &ctrl); | |||
| if (ctrl > 0) | |||
| return result; | |||
| #else | |||
| double result {0.}; | |||
| auto res = std::from_chars (jsonValue ()->number, | |||
| jsonValue ()->number + jsonValue ()->number_size, result); | |||
| if (res.ec == std::errc ()) | |||
| return result; | |||
| #endif | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::string_view errorToString (json_parse_error_e error) | |||
| { | |||
| switch (error) | |||
| { | |||
| case json_parse_error_e::json_parse_error_none: return {}; | |||
| case json_parse_error_e::json_parse_error_expected_comma_or_closing_bracket: | |||
| return "json_parse_error_expected_comma_or_closing_bracket"; | |||
| case json_parse_error_e::json_parse_error_expected_colon: | |||
| return "json_parse_error_expected_colon"; | |||
| case json_parse_error_e::json_parse_error_expected_opening_quote: | |||
| return "json_parse_error_expected_opening_quote"; | |||
| case json_parse_error_e::json_parse_error_invalid_string_escape_sequence: | |||
| return "json_parse_error_invalid_string_escape_sequence"; | |||
| case json_parse_error_e::json_parse_error_invalid_number_format: | |||
| return "json_parse_error_invalid_number_format"; | |||
| case json_parse_error_e::json_parse_error_invalid_value: | |||
| return "json_parse_error_invalid_value"; | |||
| case json_parse_error_e::json_parse_error_premature_end_of_buffer: | |||
| return "json_parse_error_premature_end_of_buffer"; | |||
| case json_parse_error_e::json_parse_error_invalid_string: | |||
| return "json_parse_error_invalid_string"; | |||
| case json_parse_error_e::json_parse_error_allocator_failed: | |||
| return "json_parse_error_allocator_failed"; | |||
| case json_parse_error_e::json_parse_error_unexpected_trailing_characters: | |||
| return "json_parse_error_unexpected_trailing_characters"; | |||
| case json_parse_error_e::json_parse_error_unknown: return "json_parse_error_unknown"; | |||
| } | |||
| return {}; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // JSON | |||
| @@ -0,0 +1,99 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfo | |||
| // Filename : public.sdk/source/vst/moduleinfo/moduleinfo.h | |||
| // Created by : Steinberg, 12/2021 | |||
| // Description : | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include <string> | |||
| #include <vector> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg { | |||
| //------------------------------------------------------------------------ | |||
| struct ModuleInfo | |||
| { | |||
| //------------------------------------------------------------------------ | |||
| struct FactoryInfo | |||
| { | |||
| std::string vendor; | |||
| std::string url; | |||
| std::string email; | |||
| int32_t flags {0}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Snapshot | |||
| { | |||
| double scaleFactor {1.}; | |||
| std::string path; | |||
| }; | |||
| using SnapshotList = std::vector<Snapshot>; | |||
| //------------------------------------------------------------------------ | |||
| struct ClassInfo | |||
| { | |||
| std::string cid; | |||
| std::string category; | |||
| std::string name; | |||
| std::string vendor; | |||
| std::string version; | |||
| std::string sdkVersion; | |||
| std::vector<std::string> subCategories; | |||
| SnapshotList snapshots; | |||
| int32_t cardinality {0x7FFFFFFF}; | |||
| uint32_t flags {0}; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct Compatibility | |||
| { | |||
| std::string newCID; | |||
| std::vector<std::string> oldCID; | |||
| }; | |||
| using ClassList = std::vector<ClassInfo>; | |||
| using CompatibilityList = std::vector<Compatibility>; | |||
| std::string name; | |||
| std::string version; | |||
| FactoryInfo factoryInfo; | |||
| ClassList classes; | |||
| CompatibilityList compatibility; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| } // Steinberg | |||
| @@ -0,0 +1,309 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfo | |||
| // Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp | |||
| // Created by : Steinberg, 12/2021 | |||
| // Description : utility functions to create moduleinfo json files | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "moduleinfocreator.h" | |||
| #include "jsoncxx.h" | |||
| #include <algorithm> | |||
| #include <stdexcept> | |||
| #include <string> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg::ModuleInfoLib { | |||
| using namespace VST3; | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| struct JSON5Writer | |||
| { | |||
| private: | |||
| std::ostream& stream; | |||
| bool beautify; | |||
| bool lastIsComma {false}; | |||
| int32_t intend {0}; | |||
| void doBeautify () | |||
| { | |||
| if (beautify) | |||
| { | |||
| stream << '\n'; | |||
| for (int i = 0; i < intend; ++i) | |||
| stream << " "; | |||
| } | |||
| } | |||
| void writeComma () | |||
| { | |||
| if (lastIsComma) | |||
| return; | |||
| stream << ","; | |||
| lastIsComma = true; | |||
| } | |||
| void startObject () | |||
| { | |||
| stream << "{"; | |||
| ++intend; | |||
| lastIsComma = false; | |||
| } | |||
| void endObject () | |||
| { | |||
| --intend; | |||
| doBeautify (); | |||
| stream << "}"; | |||
| lastIsComma = false; | |||
| } | |||
| void startArray () | |||
| { | |||
| stream << "["; | |||
| ++intend; | |||
| lastIsComma = false; | |||
| } | |||
| void endArray () | |||
| { | |||
| --intend; | |||
| doBeautify (); | |||
| stream << "]"; | |||
| lastIsComma = false; | |||
| } | |||
| public: | |||
| JSON5Writer (std::ostream& stream, bool beautify = true) : stream (stream), beautify (beautify) | |||
| { | |||
| } | |||
| void string (std::string_view str) | |||
| { | |||
| stream << "\"" << str << "\""; | |||
| lastIsComma = false; | |||
| } | |||
| void boolean (bool val) | |||
| { | |||
| stream << (val ? "true" : "false"); | |||
| lastIsComma = false; | |||
| } | |||
| template <typename ValueT> | |||
| void value (ValueT val) | |||
| { | |||
| stream << val; | |||
| lastIsComma = false; | |||
| } | |||
| template <typename Proc> | |||
| void object (Proc proc) | |||
| { | |||
| startObject (); | |||
| proc (); | |||
| endObject (); | |||
| } | |||
| template <typename Iterator, typename Proc> | |||
| void array (Iterator begin, Iterator end, Proc proc) | |||
| { | |||
| startArray (); | |||
| while (begin != end) | |||
| { | |||
| doBeautify (); | |||
| proc (begin); | |||
| ++begin; | |||
| writeComma (); | |||
| } | |||
| endArray (); | |||
| } | |||
| template <typename Proc> | |||
| void keyValue (std::string_view key, Proc proc) | |||
| { | |||
| doBeautify (); | |||
| string (key); | |||
| stream << ": "; | |||
| proc (); | |||
| writeComma (); | |||
| } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| void writeSnapshots (const ModuleInfo::SnapshotList& snapshots, JSON5Writer& w) | |||
| { | |||
| w.keyValue ("Snapshots", [&] () { | |||
| w.array (snapshots.begin (), snapshots.end (), [&] (const auto& el) { | |||
| w.object ([&] () { | |||
| w.keyValue ("Scale Factor", [&] () { w.value (el->scaleFactor); }); | |||
| w.keyValue ("Path", [&] () { w.string (el->path); }); | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void writeClassInfo (const ModuleInfo::ClassInfo& cls, JSON5Writer& w) | |||
| { | |||
| w.keyValue ("CID", [&] () { w.string (cls.cid); }); | |||
| w.keyValue ("Category", [&] () { w.string (cls.category); }); | |||
| w.keyValue ("Name", [&] () { w.string (cls.name); }); | |||
| w.keyValue ("Vendor", [&] () { w.string (cls.vendor); }); | |||
| w.keyValue ("Version", [&] () { w.string (cls.version); }); | |||
| w.keyValue ("SDKVersion", [&] () { w.string (cls.sdkVersion); }); | |||
| const auto& sc = cls.subCategories; | |||
| if (!sc.empty ()) | |||
| { | |||
| w.keyValue ("Sub Categories", [&] () { | |||
| w.array (sc.begin (), sc.end (), [&] (const auto& cat) { w.string (*cat); }); | |||
| }); | |||
| } | |||
| w.keyValue ("Class Flags", [&] () { w.value (cls.flags); }); | |||
| w.keyValue ("Cardinality", [&] () { w.value (cls.cardinality); }); | |||
| writeSnapshots (cls.snapshots, w); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void writePluginCompatibility (const ModuleInfo::CompatibilityList& compat, JSON5Writer& w) | |||
| { | |||
| if (compat.empty ()) | |||
| return; | |||
| w.keyValue ("Compatibility", [&] () { | |||
| w.array (compat.begin (), compat.end (), [&] (auto& el) { | |||
| w.object ([&] () { | |||
| w.keyValue ("New", [&] () { w.string (el->newCID); }); | |||
| w.keyValue ("Old", [&] () { | |||
| w.array (el->oldCID.begin (), el->oldCID.end (), | |||
| [&] (auto& oldEl) { w.string (*oldEl); }); | |||
| }); | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void writeFactoryInfo (const ModuleInfo::FactoryInfo& fi, JSON5Writer& w) | |||
| { | |||
| w.keyValue ("Factory Info", [&] () { | |||
| w.object ([&] () { | |||
| w.keyValue ("Vendor", [&] () { w.string (fi.vendor); }); | |||
| w.keyValue ("URL", [&] () { w.string (fi.url); }); | |||
| w.keyValue ("E-Mail", [&] () { w.string (fi.email); }); | |||
| w.keyValue ("Flags", [&] () { | |||
| w.object ([&] () { | |||
| w.keyValue ("Unicode", | |||
| [&] () { w.boolean (fi.flags & PFactoryInfo::kUnicode); }); | |||
| w.keyValue ("Classes Discardable", [&] () { | |||
| w.boolean (fi.flags & PFactoryInfo::kClassesDiscardable); | |||
| }); | |||
| w.keyValue ("Component Non Discardable", [&] () { | |||
| w.boolean (fi.flags & PFactoryInfo::kComponentNonDiscardable); | |||
| }); | |||
| }); | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses) | |||
| { | |||
| ModuleInfo info; | |||
| const auto& factory = module.getFactory (); | |||
| auto factoryInfo = factory.info (); | |||
| info.name = module.getName (); | |||
| auto pos = info.name.find_last_of ('.'); | |||
| if (pos != std::string::npos) | |||
| info.name.erase (pos); | |||
| info.factoryInfo.vendor = factoryInfo.vendor (); | |||
| info.factoryInfo.url = factoryInfo.url (); | |||
| info.factoryInfo.email = factoryInfo.email (); | |||
| info.factoryInfo.flags = factoryInfo.flags (); | |||
| if (factoryInfo.classesDiscardable () == false || | |||
| (factoryInfo.classesDiscardable () && includeDiscardableClasses)) | |||
| { | |||
| auto snapshots = VST3::Hosting::Module::getSnapshots (module.getPath ()); | |||
| for (const auto& ci : factory.classInfos ()) | |||
| { | |||
| ModuleInfo::ClassInfo classInfo; | |||
| classInfo.cid = ci.ID ().toString (); | |||
| classInfo.category = ci.category (); | |||
| classInfo.name = ci.name (); | |||
| classInfo.vendor = ci.vendor (); | |||
| classInfo.version = ci.version (); | |||
| classInfo.sdkVersion = ci.sdkVersion (); | |||
| classInfo.subCategories = ci.subCategories (); | |||
| classInfo.cardinality = ci.cardinality (); | |||
| classInfo.flags = ci.classFlags (); | |||
| auto snapshotIt = std::find_if (snapshots.begin (), snapshots.end (), | |||
| [&] (const auto& el) { return el.uid == ci.ID (); }); | |||
| if (snapshotIt != snapshots.end ()) | |||
| { | |||
| for (auto& s : snapshotIt->images) | |||
| { | |||
| std::string_view path (s.path); | |||
| if (path.find (module.getPath ()) == 0) | |||
| path.remove_prefix (module.getPath ().size () + 1); | |||
| classInfo.snapshots.emplace_back ( | |||
| ModuleInfo::Snapshot {s.scaleFactor, {path.data (), path.size ()}}); | |||
| } | |||
| snapshots.erase (snapshotIt); | |||
| } | |||
| info.classes.emplace_back (std::move (classInfo)); | |||
| } | |||
| } | |||
| return info; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| void outputJson (const ModuleInfo& info, std::ostream& output) | |||
| { | |||
| JSON5Writer w (output); | |||
| w.object ([&] () { | |||
| w.keyValue ("Name", [&] () { w.string (info.name); }); | |||
| w.keyValue ("Version", [&] () { w.string (info.version); }); | |||
| writeFactoryInfo (info.factoryInfo, w); | |||
| writePluginCompatibility (info.compatibility, w); | |||
| w.keyValue ("Classes", [&] () { | |||
| w.array (info.classes.begin (), info.classes.end (), | |||
| [&] (const auto& cls) { w.object ([&] () { writeClassInfo (*cls, w); }); }); | |||
| }); | |||
| }); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Steinberg::ModuelInfoLib | |||
| @@ -0,0 +1,67 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfo | |||
| // Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.h | |||
| // Created by : Steinberg, 12/2021 | |||
| // Description : utility functions to create moduleinfo json files | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include "moduleinfo.h" | |||
| #include "public.sdk/source/vst/hosting/module.h" | |||
| #include <iostream> | |||
| #include <optional> | |||
| #include <string_view> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg::ModuleInfoLib { | |||
| //------------------------------------------------------------------------ | |||
| /** create a ModuleInfo from a module | |||
| * | |||
| * @param module module to create the module info from | |||
| * @param includeDiscardableClasses if true adds the current available classes to the module info | |||
| * @return a ModuleInfo struct with the classes and factory info of the module | |||
| */ | |||
| ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses); | |||
| //------------------------------------------------------------------------ | |||
| /** output the ModuleInfo as json to the stream | |||
| * | |||
| * @param info module info | |||
| * @param output output stream | |||
| */ | |||
| void outputJson (const ModuleInfo& info, std::ostream& output); | |||
| //------------------------------------------------------------------------ | |||
| } // Steinberg::ModuelInfoLib | |||
| @@ -0,0 +1,536 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfo | |||
| // Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp | |||
| // Created by : Steinberg, 01/2022 | |||
| // Description : utility functions to parse moduleinfo json files | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #include "moduleinfoparser.h" | |||
| #include "jsoncxx.h" | |||
| #include "pluginterfaces/base/ipluginbase.h" | |||
| #include <stdexcept> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg::ModuleInfoLib { | |||
| namespace { | |||
| //------------------------------------------------------------------------ | |||
| void printJsonParseError (json_parse_result_s& parseResult, std::ostream& errorOut) | |||
| { | |||
| errorOut << "error : " | |||
| << JSON::errorToString (static_cast<json_parse_error_e> (parseResult.error)) << '\n'; | |||
| errorOut << "offset : " << parseResult.error_offset << '\n'; | |||
| errorOut << "line no: " << parseResult.error_line_no << '\n'; | |||
| errorOut << "row no : " << parseResult.error_row_no << '\n'; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| struct parse_error : std::exception | |||
| { | |||
| parse_error (const std::string& str, const JSON::Value& value) | |||
| : str (str), location (value.getSourceLocation ()) | |||
| { | |||
| addLocation (location); | |||
| } | |||
| parse_error (const std::string& str, const JSON::String& value) | |||
| : str (str), location (value.getSourceLocation ()) | |||
| { | |||
| addLocation (location); | |||
| } | |||
| const char* what () const noexcept override { return str.data (); } | |||
| private: | |||
| void addLocation (const JSON::SourceLocation& loc) | |||
| { | |||
| str += '\n'; | |||
| str += "offset:"; | |||
| str += std::to_string (loc.offset); | |||
| str += '\n'; | |||
| str += "line:"; | |||
| str += std::to_string (loc.line); | |||
| str += '\n'; | |||
| str += "row:"; | |||
| str += std::to_string (loc.row); | |||
| str += '\n'; | |||
| } | |||
| std::string str; | |||
| JSON::SourceLocation location; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| struct ModuleInfoJsonParser | |||
| { | |||
| ModuleInfoJsonParser () = default; | |||
| std::string_view getText (const JSON::Value& value) const | |||
| { | |||
| if (auto str = value.asString ()) | |||
| return str->text (); | |||
| throw parse_error ("Expect a String here", value); | |||
| } | |||
| template <typename T> | |||
| T getInteger (const JSON::Value& value) const | |||
| { | |||
| if (auto number = value.asNumber ()) | |||
| { | |||
| if (auto result = number->getInteger ()) | |||
| { | |||
| if (result > static_cast<int64_t> (std::numeric_limits<T>::max ()) || | |||
| result < static_cast<int64_t> (std::numeric_limits<T>::min ())) | |||
| throw parse_error ("Value is out of range here", value); | |||
| return static_cast<T> (*result); | |||
| } | |||
| throw parse_error ("Expect an Integer here", value); | |||
| } | |||
| throw parse_error ("Expect a Number here", value); | |||
| } | |||
| double getDouble (const JSON::Value& value) const | |||
| { | |||
| if (auto number = value.asNumber ()) | |||
| { | |||
| if (auto result = number->getDouble ()) | |||
| return *result; | |||
| throw parse_error ("Expect a Double here", value); | |||
| } | |||
| throw parse_error ("Expect a Number here", value); | |||
| } | |||
| void parseFactoryInfo (const JSON::Value& value) | |||
| { | |||
| enum ParsedBits | |||
| { | |||
| Vendor = 1 << 0, | |||
| URL = 1 << 1, | |||
| EMail = 1 << 2, | |||
| Flags = 1 << 3, | |||
| }; | |||
| uint32_t parsed {0}; | |||
| if (auto obj = value.asObject ()) | |||
| { | |||
| for (const auto& el : *obj) | |||
| { | |||
| auto elementName = el.name ().text (); | |||
| if (elementName == "Vendor") | |||
| { | |||
| if (parsed & ParsedBits::Vendor) | |||
| throw parse_error ("Only one 'Vendor' key allowed", el.name ()); | |||
| parsed |= ParsedBits::Vendor; | |||
| info.factoryInfo.vendor = getText (el.value ()); | |||
| } | |||
| else if (elementName == "URL") | |||
| { | |||
| if (parsed & ParsedBits::URL) | |||
| throw parse_error ("Only one 'URL' key allowed", el.name ()); | |||
| parsed |= ParsedBits::URL; | |||
| info.factoryInfo.url = getText (el.value ()); | |||
| } | |||
| else if (elementName == "E-Mail") | |||
| { | |||
| if (parsed & ParsedBits::EMail) | |||
| throw parse_error ("Only one 'E-Mail' key allowed", el.name ()); | |||
| parsed |= ParsedBits::EMail; | |||
| info.factoryInfo.email = getText (el.value ()); | |||
| } | |||
| else if (elementName == "Flags") | |||
| { | |||
| if (parsed & ParsedBits::Flags) | |||
| throw parse_error ("Only one 'Flags' key allowed", el.name ()); | |||
| auto flags = el.value ().asObject (); | |||
| if (!flags) | |||
| throw parse_error ("Expect 'Flags' to be a JSON Object", el.name ()); | |||
| for (const auto& flag : *flags) | |||
| { | |||
| auto flagName = flag.name ().text (); | |||
| auto flagValue = flag.value ().asBoolean (); | |||
| if (!flagValue) | |||
| throw parse_error ("Flag must be a boolean", flag.value ()); | |||
| if (flagName == "Classes Discardable") | |||
| { | |||
| if (*flagValue) | |||
| info.factoryInfo.flags |= PFactoryInfo::kClassesDiscardable; | |||
| } | |||
| else if (flagName == "Component Non Discardable") | |||
| { | |||
| if (*flagValue) | |||
| info.factoryInfo.flags |= PFactoryInfo::kComponentNonDiscardable; | |||
| } | |||
| else if (flagName == "Unicode") | |||
| { | |||
| if (*flagValue) | |||
| info.factoryInfo.flags |= PFactoryInfo::kUnicode; | |||
| } | |||
| else | |||
| throw parse_error ("Unknown flag", flag.name ()); | |||
| } | |||
| parsed |= ParsedBits::Flags; | |||
| } | |||
| } | |||
| } | |||
| if (!(parsed & ParsedBits::Vendor)) | |||
| throw std::logic_error ("Missing 'Vendor' in Factory Info"); | |||
| if (!(parsed & ParsedBits::URL)) | |||
| throw std::logic_error ("Missing 'URL' in Factory Info"); | |||
| if (!(parsed & ParsedBits::EMail)) | |||
| throw std::logic_error ("Missing 'EMail' in Factory Info"); | |||
| if (!(parsed & ParsedBits::Flags)) | |||
| throw std::logic_error ("Missing 'Flags' in Factory Info"); | |||
| } | |||
| void parseClasses (const JSON::Value& value) | |||
| { | |||
| enum ParsedBits | |||
| { | |||
| CID = 1 << 0, | |||
| Category = 1 << 1, | |||
| Name = 1 << 2, | |||
| Vendor = 1 << 3, | |||
| Version = 1 << 4, | |||
| SDKVersion = 1 << 5, | |||
| SubCategories = 1 << 6, | |||
| ClassFlags = 1 << 7, | |||
| Snapshots = 1 << 8, | |||
| Cardinality = 1 << 9, | |||
| }; | |||
| auto array = value.asArray (); | |||
| if (!array) | |||
| throw parse_error ("Expect Classes Array", value); | |||
| for (const auto& classInfoEl : *array) | |||
| { | |||
| auto classInfo = classInfoEl.value ().asObject (); | |||
| if (!classInfo) | |||
| throw parse_error ("Expect Class Object", classInfoEl.value ()); | |||
| ModuleInfo::ClassInfo ci {}; | |||
| uint32_t parsed {0}; | |||
| for (const auto& el : *classInfo) | |||
| { | |||
| auto elementName = el.name ().text (); | |||
| if (elementName == "CID") | |||
| { | |||
| if (parsed & ParsedBits::CID) | |||
| throw parse_error ("Only one 'CID' key allowed", el.name ()); | |||
| ci.cid = getText (el.value ()); | |||
| parsed |= ParsedBits::CID; | |||
| } | |||
| else if (elementName == "Category") | |||
| { | |||
| if (parsed & ParsedBits::Category) | |||
| throw parse_error ("Only one 'Category' key allowed", el.name ()); | |||
| ci.category = getText (el.value ()); | |||
| parsed |= ParsedBits::Category; | |||
| } | |||
| else if (elementName == "Name") | |||
| { | |||
| if (parsed & ParsedBits::Name) | |||
| throw parse_error ("Only one 'Name' key allowed", el.name ()); | |||
| ci.name = getText (el.value ()); | |||
| parsed |= ParsedBits::Name; | |||
| } | |||
| else if (elementName == "Vendor") | |||
| { | |||
| if (parsed & ParsedBits::Vendor) | |||
| throw parse_error ("Only one 'Vendor' key allowed", el.name ()); | |||
| ci.vendor = getText (el.value ()); | |||
| parsed |= ParsedBits::Vendor; | |||
| } | |||
| else if (elementName == "Version") | |||
| { | |||
| if (parsed & ParsedBits::Version) | |||
| throw parse_error ("Only one 'Version' key allowed", el.name ()); | |||
| ci.version = getText (el.value ()); | |||
| parsed |= ParsedBits::Version; | |||
| } | |||
| else if (elementName == "SDKVersion") | |||
| { | |||
| if (parsed & ParsedBits::SDKVersion) | |||
| throw parse_error ("Only one 'SDKVersion' key allowed", el.name ()); | |||
| ci.sdkVersion = getText (el.value ()); | |||
| parsed |= ParsedBits::SDKVersion; | |||
| } | |||
| else if (elementName == "Sub Categories") | |||
| { | |||
| if (parsed & ParsedBits::SubCategories) | |||
| throw parse_error ("Only one 'Sub Categories' key allowed", el.name ()); | |||
| auto subCatArr = el.value ().asArray (); | |||
| if (!subCatArr) | |||
| throw parse_error ("Expect Array here", el.value ()); | |||
| for (const auto& catEl : *subCatArr) | |||
| { | |||
| auto cat = getText (catEl.value ()); | |||
| ci.subCategories.emplace_back (cat); | |||
| } | |||
| parsed |= ParsedBits::SubCategories; | |||
| } | |||
| else if (elementName == "Class Flags") | |||
| { | |||
| if (parsed & ParsedBits::ClassFlags) | |||
| throw parse_error ("Only one 'Class Flags' key allowed", el.name ()); | |||
| ci.flags = getInteger<uint32_t> (el.value ()); | |||
| parsed |= ParsedBits::ClassFlags; | |||
| } | |||
| else if (elementName == "Cardinality") | |||
| { | |||
| if (parsed & ParsedBits::Cardinality) | |||
| throw parse_error ("Only one 'Cardinality' key allowed", el.name ()); | |||
| ci.cardinality = getInteger<int32_t> (el.value ()); | |||
| parsed |= ParsedBits::Cardinality; | |||
| } | |||
| else if (elementName == "Snapshots") | |||
| { | |||
| if (parsed & ParsedBits::Snapshots) | |||
| throw parse_error ("Only one 'Snapshots' key allowed", el.name ()); | |||
| auto snapArr = el.value ().asArray (); | |||
| if (!snapArr) | |||
| throw parse_error ("Expect Array here", el.value ()); | |||
| for (const auto& snapEl : *snapArr) | |||
| { | |||
| auto snap = snapEl.value ().asObject (); | |||
| if (!snap) | |||
| throw parse_error ("Expect Object here", snapEl.value ()); | |||
| ModuleInfo::Snapshot snapshot; | |||
| for (const auto& spEl : *snap) | |||
| { | |||
| auto spElName = spEl.name ().text (); | |||
| if (spElName == "Path") | |||
| snapshot.path = getText (spEl.value ()); | |||
| else if (spElName == "Scale Factor") | |||
| snapshot.scaleFactor = getDouble (spEl.value ()); | |||
| else | |||
| throw parse_error ("Unexpected key", spEl.name ()); | |||
| } | |||
| if (snapshot.scaleFactor == 0. || snapshot.path.empty ()) | |||
| throw parse_error ("Missing Snapshot keys", snapEl.value ()); | |||
| ci.snapshots.emplace_back (std::move (snapshot)); | |||
| } | |||
| parsed |= ParsedBits::Snapshots; | |||
| } | |||
| else | |||
| throw parse_error ("Unexpected key", el.name ()); | |||
| } | |||
| if (!(parsed & ParsedBits::CID)) | |||
| throw parse_error ("'CID' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::Category)) | |||
| throw parse_error ("'Category' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::Name)) | |||
| throw parse_error ("'Name' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::Vendor)) | |||
| throw parse_error ("'Vendor' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::Version)) | |||
| throw parse_error ("'Version' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::SDKVersion)) | |||
| throw parse_error ("'SDK Version' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::ClassFlags)) | |||
| throw parse_error ("'Class Flags' key missing", classInfoEl.value ()); | |||
| if (!(parsed & ParsedBits::Cardinality)) | |||
| throw parse_error ("'Cardinality' key missing", classInfoEl.value ()); | |||
| info.classes.emplace_back (std::move (ci)); | |||
| } | |||
| } | |||
| void parseCompatibility (const JSON::Value& value) | |||
| { | |||
| auto arr = value.asArray (); | |||
| if (!arr) | |||
| throw parse_error ("Expect Array here", value); | |||
| for (const auto& el : *arr) | |||
| { | |||
| auto obj = el.value ().asObject (); | |||
| if (!obj) | |||
| throw parse_error ("Expect Object here", el.value ()); | |||
| ModuleInfo::Compatibility compat; | |||
| for (const auto& objEl : *obj) | |||
| { | |||
| auto elementName = objEl.name ().text (); | |||
| if (elementName == "New") | |||
| compat.newCID = getText (objEl.value ()); | |||
| else if (elementName == "Old") | |||
| { | |||
| auto oldElArr = objEl.value ().asArray (); | |||
| if (!oldElArr) | |||
| throw parse_error ("Expect Array here", objEl.value ()); | |||
| for (const auto& old : *oldElArr) | |||
| { | |||
| compat.oldCID.emplace_back (getText (old.value ())); | |||
| } | |||
| } | |||
| } | |||
| if (compat.newCID.empty ()) | |||
| throw parse_error ("Expect New CID here", el.value ()); | |||
| if (compat.oldCID.empty ()) | |||
| throw parse_error ("Expect Old CID here", el.value ()); | |||
| info.compatibility.emplace_back (std::move (compat)); | |||
| } | |||
| } | |||
| void parse (const JSON::Document& doc) | |||
| { | |||
| auto docObj = doc.asObject (); | |||
| if (!docObj) | |||
| throw parse_error ("Unexpected", doc); | |||
| enum ParsedBits | |||
| { | |||
| Name = 1 << 0, | |||
| Version = 1 << 1, | |||
| FactoryInfo = 1 << 2, | |||
| Compatibility = 1 << 3, | |||
| Classes = 1 << 4, | |||
| }; | |||
| uint32_t parsed {0}; | |||
| for (const auto& el : *docObj) | |||
| { | |||
| auto elementName = el.name ().text (); | |||
| if (elementName == "Name") | |||
| { | |||
| if (parsed & ParsedBits::Name) | |||
| throw parse_error ("Only one 'Name' key allowed", el.name ()); | |||
| parsed |= ParsedBits::Name; | |||
| info.name = getText (el.value ()); | |||
| } | |||
| else if (elementName == "Version") | |||
| { | |||
| if (parsed & ParsedBits::Version) | |||
| throw parse_error ("Only one 'Version' key allowed", el.name ()); | |||
| parsed |= ParsedBits::Version; | |||
| info.version = getText (el.value ()); | |||
| } | |||
| else if (elementName == "Factory Info") | |||
| { | |||
| if (parsed & ParsedBits::FactoryInfo) | |||
| throw parse_error ("Only one 'Factory Info' key allowed", el.name ()); | |||
| parseFactoryInfo (el.value ()); | |||
| parsed |= ParsedBits::FactoryInfo; | |||
| } | |||
| else if (elementName == "Compatibility") | |||
| { | |||
| if (parsed & ParsedBits::Compatibility) | |||
| throw parse_error ("Only one 'Compatibility' key allowed", el.name ()); | |||
| parseCompatibility (el.value ()); | |||
| parsed |= ParsedBits::Compatibility; | |||
| } | |||
| else if (elementName == "Classes") | |||
| { | |||
| if (parsed & ParsedBits::Classes) | |||
| throw parse_error ("Only one 'Classes' key allowed", el.name ()); | |||
| parseClasses (el.value ()); | |||
| parsed |= ParsedBits::Classes; | |||
| } | |||
| else | |||
| { | |||
| throw parse_error ("Unexpected JSON Token", el.name ()); | |||
| } | |||
| } | |||
| if (!(parsed & ParsedBits::Name)) | |||
| throw std::logic_error ("'Name' key missing"); | |||
| if (!(parsed & ParsedBits::Version)) | |||
| throw std::logic_error ("'Version' key missing"); | |||
| if (!(parsed & ParsedBits::FactoryInfo)) | |||
| throw std::logic_error ("'Factory Info' key missing"); | |||
| if (!(parsed & ParsedBits::Classes)) | |||
| throw std::logic_error ("'Classes' key missing"); | |||
| } | |||
| ModuleInfo&& takeInfo () { return std::move (info); } | |||
| private: | |||
| ModuleInfo info; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| } // anonymous | |||
| //------------------------------------------------------------------------ | |||
| std::optional<ModuleInfo> parseJson (std::string_view jsonData, std::ostream* optErrorOutput) | |||
| { | |||
| auto docVar = JSON::Document::parse (jsonData); | |||
| if (auto res = std::get_if<json_parse_result_s> (&docVar)) | |||
| { | |||
| if (optErrorOutput) | |||
| printJsonParseError (*res, *optErrorOutput); | |||
| return {}; | |||
| } | |||
| auto doc = std::get_if<JSON::Document> (&docVar); | |||
| assert (doc); | |||
| try | |||
| { | |||
| ModuleInfoJsonParser parser; | |||
| parser.parse (*doc); | |||
| return parser.takeInfo (); | |||
| } | |||
| catch (std::exception& error) | |||
| { | |||
| if (optErrorOutput) | |||
| *optErrorOutput << error.what () << '\n'; | |||
| return {}; | |||
| } | |||
| // unreachable | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| std::optional<ModuleInfo::CompatibilityList> parseCompatibilityJson (std::string_view jsonData, | |||
| std::ostream* optErrorOutput) | |||
| { | |||
| auto docVar = JSON::Document::parse (jsonData); | |||
| if (auto res = std::get_if<json_parse_result_s> (&docVar)) | |||
| { | |||
| if (optErrorOutput) | |||
| printJsonParseError (*res, *optErrorOutput); | |||
| return {}; | |||
| } | |||
| auto doc = std::get_if<JSON::Document> (&docVar); | |||
| assert (doc); | |||
| try | |||
| { | |||
| ModuleInfoJsonParser parser; | |||
| parser.parseCompatibility (*doc); | |||
| return parser.takeInfo ().compatibility; | |||
| } | |||
| catch (std::exception& error) | |||
| { | |||
| if (optErrorOutput) | |||
| *optErrorOutput << error.what () << '\n'; | |||
| return {}; | |||
| } | |||
| // unreachable | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // Steinberg::ModuelInfoLib | |||
| @@ -0,0 +1,68 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // Flags : clang-format SMTGSequencer | |||
| // | |||
| // Category : moduleinfo | |||
| // Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.h | |||
| // Created by : Steinberg, 01/2022 | |||
| // Description : utility functions to parse moduleinfo json files | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include "moduleinfo.h" | |||
| #include <iostream> | |||
| #include <optional> | |||
| #include <string_view> | |||
| //------------------------------------------------------------------------ | |||
| namespace Steinberg::ModuleInfoLib { | |||
| //------------------------------------------------------------------------ | |||
| /** parse a json formatted string to a ModuleInfo struct | |||
| * | |||
| * @param jsonData a string view to a json formatted string | |||
| * @param optErrorOutput optional error output stream where to print parse error | |||
| * @return ModuleInfo if parsing succeeded | |||
| */ | |||
| std::optional<ModuleInfo> parseJson (std::string_view jsonData, std::ostream* optErrorOutput); | |||
| //------------------------------------------------------------------------ | |||
| /** parse a json formatted string to a ModuleInfo::CompatibilityList | |||
| * | |||
| * @param jsonData a string view to a json formatted string | |||
| * @param optErrorOutput optional error output stream where to print parse error | |||
| * @return ModuleInfo::CompatibilityList if parsing succeeded | |||
| */ | |||
| std::optional<ModuleInfo::CompatibilityList> parseCompatibilityJson (std::string_view jsonData, | |||
| std::ostream* optErrorOutput); | |||
| //------------------------------------------------------------------------ | |||
| } // Steinberg::ModuelInfoLib | |||
| @@ -0,0 +1,135 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/utility/optional.h | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : optional helper | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include <cassert> | |||
| #include <memory> | |||
| #include <utility> | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| //------------------------------------------------------------------------ | |||
| template <typename T> | |||
| struct Optional | |||
| { | |||
| Optional () noexcept : valid (false) {} | |||
| explicit Optional (const T& v) noexcept : _value (v), valid (true) {} | |||
| Optional (T&& v) noexcept : _value (std::move (v)), valid (true) {} | |||
| Optional (Optional&& other) noexcept { *this = std::move (other); } | |||
| Optional& operator= (Optional&& other) noexcept | |||
| { | |||
| valid = other.valid; | |||
| _value = std::move (other._value); | |||
| return *this; | |||
| } | |||
| explicit operator bool () const noexcept | |||
| { | |||
| setValidationChecked (); | |||
| return valid; | |||
| } | |||
| const T& operator* () const noexcept | |||
| { | |||
| checkValid (); | |||
| return _value; | |||
| } | |||
| const T* operator-> () const noexcept | |||
| { | |||
| checkValid (); | |||
| return &_value; | |||
| } | |||
| T& operator* () noexcept | |||
| { | |||
| checkValid (); | |||
| return _value; | |||
| } | |||
| T* operator-> () noexcept | |||
| { | |||
| checkValid (); | |||
| return &_value; | |||
| } | |||
| T&& value () noexcept | |||
| { | |||
| checkValid (); | |||
| return move (_value); | |||
| } | |||
| const T& value () const noexcept | |||
| { | |||
| checkValid (); | |||
| return _value; | |||
| } | |||
| void swap (T& other) noexcept | |||
| { | |||
| checkValid (); | |||
| auto tmp = std::move (other); | |||
| other = std::move (_value); | |||
| _value = std::move (tmp); | |||
| } | |||
| private: | |||
| T _value {}; | |||
| bool valid; | |||
| #if !defined(NDEBUG) | |||
| mutable bool validationChecked {false}; | |||
| #endif | |||
| void setValidationChecked () const | |||
| { | |||
| #if !defined(NDEBUG) | |||
| validationChecked = true; | |||
| #endif | |||
| } | |||
| void checkValid () const | |||
| { | |||
| #if !defined(NDEBUG) | |||
| assert (validationChecked); | |||
| #endif | |||
| } | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| } | |||
| @@ -0,0 +1,294 @@ | |||
| //----------------------------------------------------------------------------- | |||
| // Project : VST SDK | |||
| // | |||
| // Category : Helpers | |||
| // Filename : public.sdk/source/vst/utility/uid.h | |||
| // Created by : Steinberg, 08/2016 | |||
| // Description : UID | |||
| // | |||
| //----------------------------------------------------------------------------- | |||
| // LICENSE | |||
| // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved | |||
| //----------------------------------------------------------------------------- | |||
| // Redistribution and use in source and binary forms, with or without modification, | |||
| // are permitted provided that the following conditions are met: | |||
| // | |||
| // * Redistributions of source code must retain the above copyright notice, | |||
| // this list of conditions and the following disclaimer. | |||
| // * Redistributions in binary form must reproduce the above copyright notice, | |||
| // this list of conditions and the following disclaimer in the documentation | |||
| // and/or other materials provided with the distribution. | |||
| // * Neither the name of the Steinberg Media Technologies nor the names of its | |||
| // contributors may be used to endorse or promote products derived from this | |||
| // software without specific prior written permission. | |||
| // | |||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
| // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
| // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |||
| // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |||
| // OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| //----------------------------------------------------------------------------- | |||
| #pragma once | |||
| #include "optional.h" | |||
| #include "pluginterfaces/base/funknown.h" | |||
| #include <string> | |||
| //------------------------------------------------------------------------ | |||
| namespace VST3 { | |||
| //------------------------------------------------------------------------ | |||
| struct UID | |||
| { | |||
| #if defined(SMTG_OS_WINDOWS) && SMTG_OS_WINDOWS == 1 | |||
| static constexpr bool defaultComFormat = true; | |||
| #else | |||
| static constexpr bool defaultComFormat = false; | |||
| #endif | |||
| using TUID = Steinberg::TUID; | |||
| constexpr UID () noexcept = default; | |||
| UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat = defaultComFormat) | |||
| noexcept; | |||
| UID (const TUID& uid) noexcept; | |||
| UID (const UID& uid) noexcept; | |||
| UID& operator= (const UID& uid) noexcept; | |||
| UID& operator= (const TUID& uid) noexcept; | |||
| constexpr const TUID& data () const noexcept; | |||
| constexpr size_t size () const noexcept; | |||
| std::string toString (bool comFormat = defaultComFormat) const noexcept; | |||
| template<typename StringT> | |||
| static Optional<UID> fromString (const StringT& str, | |||
| bool comFormat = defaultComFormat) noexcept; | |||
| static UID fromTUID (const TUID _uid) noexcept; | |||
| //------------------------------------------------------------------------ | |||
| private: | |||
| Steinberg::TUID _data {}; | |||
| struct GUID | |||
| { | |||
| uint32_t Data1; | |||
| uint16_t Data2; | |||
| uint16_t Data3; | |||
| uint8_t Data4[8]; | |||
| }; | |||
| }; | |||
| //------------------------------------------------------------------------ | |||
| inline bool operator== (const UID& uid1, const UID& uid2) | |||
| { | |||
| const uint64_t* p1 = reinterpret_cast<const uint64_t*> (uid1.data ()); | |||
| const uint64_t* p2 = reinterpret_cast<const uint64_t*> (uid2.data ()); | |||
| return p1[0] == p2[0] && p1[1] == p2[1]; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline bool operator!= (const UID& uid1, const UID& uid2) | |||
| { | |||
| return !(uid1 == uid2); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline bool operator< (const UID& uid1, const UID& uid2) | |||
| { | |||
| const uint64_t* p1 = reinterpret_cast<const uint64_t*> (uid1.data ()); | |||
| const uint64_t* p2 = reinterpret_cast<const uint64_t*> (uid2.data ()); | |||
| return (p1[0] < p2[0]) && (p1[1] < p2[1]); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID::UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat) noexcept | |||
| { | |||
| if (comFormat) | |||
| { | |||
| _data[0] = static_cast<int8_t> ((l1 & 0x000000FF)); | |||
| _data[1] = static_cast<int8_t> ((l1 & 0x0000FF00) >> 8); | |||
| _data[2] = static_cast<int8_t> ((l1 & 0x00FF0000) >> 16); | |||
| _data[3] = static_cast<int8_t> ((l1 & 0xFF000000) >> 24); | |||
| _data[4] = static_cast<int8_t> ((l2 & 0x00FF0000) >> 16); | |||
| _data[5] = static_cast<int8_t> ((l2 & 0xFF000000) >> 24); | |||
| _data[6] = static_cast<int8_t> ((l2 & 0x000000FF)); | |||
| _data[7] = static_cast<int8_t> ((l2 & 0x0000FF00) >> 8); | |||
| _data[8] = static_cast<int8_t> ((l3 & 0xFF000000) >> 24); | |||
| _data[9] = static_cast<int8_t> ((l3 & 0x00FF0000) >> 16); | |||
| _data[10] = static_cast<int8_t> ((l3 & 0x0000FF00) >> 8); | |||
| _data[11] = static_cast<int8_t> ((l3 & 0x000000FF)); | |||
| _data[12] = static_cast<int8_t> ((l4 & 0xFF000000) >> 24); | |||
| _data[13] = static_cast<int8_t> ((l4 & 0x00FF0000) >> 16); | |||
| _data[14] = static_cast<int8_t> ((l4 & 0x0000FF00) >> 8); | |||
| _data[15] = static_cast<int8_t> ((l4 & 0x000000FF)); | |||
| } | |||
| else | |||
| { | |||
| _data[0] = static_cast<int8_t> ((l1 & 0xFF000000) >> 24); | |||
| _data[1] = static_cast<int8_t> ((l1 & 0x00FF0000) >> 16); | |||
| _data[2] = static_cast<int8_t> ((l1 & 0x0000FF00) >> 8); | |||
| _data[3] = static_cast<int8_t> ((l1 & 0x000000FF)); | |||
| _data[4] = static_cast<int8_t> ((l2 & 0xFF000000) >> 24); | |||
| _data[5] = static_cast<int8_t> ((l2 & 0x00FF0000) >> 16); | |||
| _data[6] = static_cast<int8_t> ((l2 & 0x0000FF00) >> 8); | |||
| _data[7] = static_cast<int8_t> ((l2 & 0x000000FF)); | |||
| _data[8] = static_cast<int8_t> ((l3 & 0xFF000000) >> 24); | |||
| _data[9] = static_cast<int8_t> ((l3 & 0x00FF0000) >> 16); | |||
| _data[10] = static_cast<int8_t> ((l3 & 0x0000FF00) >> 8); | |||
| _data[11] = static_cast<int8_t> ((l3 & 0x000000FF)); | |||
| _data[12] = static_cast<int8_t> ((l4 & 0xFF000000) >> 24); | |||
| _data[13] = static_cast<int8_t> ((l4 & 0x00FF0000) >> 16); | |||
| _data[14] = static_cast<int8_t> ((l4 & 0x0000FF00) >> 8); | |||
| _data[15] = static_cast<int8_t> ((l4 & 0x000000FF)); | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID::UID (const TUID& uid) noexcept | |||
| { | |||
| *this = uid; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID::UID (const UID& uid) noexcept | |||
| { | |||
| *this = uid; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID& UID::operator= (const UID& uid) noexcept | |||
| { | |||
| *this = uid.data (); | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID& UID::operator= (const TUID& uid) noexcept | |||
| { | |||
| uint64_t* p1 = reinterpret_cast<uint64_t*> (_data); | |||
| const uint64_t* p2 = reinterpret_cast<const uint64_t*> (uid); | |||
| p1[0] = p2[0]; | |||
| p1[1] = p2[1]; | |||
| return *this; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline constexpr auto UID::data () const noexcept -> const TUID& | |||
| { | |||
| return _data; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline constexpr size_t UID::size () const noexcept | |||
| { | |||
| return sizeof (TUID); | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline std::string UID::toString (bool comFormat) const noexcept | |||
| { | |||
| std::string result; | |||
| result.reserve (32); | |||
| if (comFormat) | |||
| { | |||
| const auto& g = reinterpret_cast<const GUID*> (_data); | |||
| char tmp[21] {}; | |||
| snprintf (tmp, 21, "%08X%04X%04X", g->Data1, g->Data2, g->Data3); | |||
| result = tmp; | |||
| for (uint32_t i = 0; i < 8; ++i) | |||
| { | |||
| char s[3] {}; | |||
| snprintf (s, 3, "%02X", g->Data4[i]); | |||
| result += s; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (uint32_t i = 0; i < 16; ++i) | |||
| { | |||
| char s[3] {}; | |||
| snprintf (s, 3, "%02X", static_cast<uint8_t> (_data[i])); | |||
| result += s; | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| template<typename StringT> | |||
| inline Optional<UID> UID::fromString (const StringT& str, bool comFormat) noexcept | |||
| { | |||
| if (str.length () != 32) | |||
| return {}; | |||
| // TODO: this is a copy from FUID. there are no input validation checks !!! | |||
| if (comFormat) | |||
| { | |||
| TUID uid {}; | |||
| GUID g; | |||
| char s[33]; | |||
| strcpy (s, str.data ()); | |||
| s[8] = 0; | |||
| sscanf (s, "%x", &g.Data1); | |||
| strcpy (s, str.data () + 8); | |||
| s[4] = 0; | |||
| sscanf (s, "%hx", &g.Data2); | |||
| strcpy (s, str.data () + 12); | |||
| s[4] = 0; | |||
| sscanf (s, "%hx", &g.Data3); | |||
| memcpy (uid, &g, 8); | |||
| for (uint32_t i = 8; i < 16; ++i) | |||
| { | |||
| char s2[3] {}; | |||
| s2[0] = str[i * 2]; | |||
| s2[1] = str[i * 2 + 1]; | |||
| int32_t d = 0; | |||
| sscanf (s2, "%2x", &d); | |||
| uid[i] = static_cast<char> (d); | |||
| } | |||
| return {uid}; | |||
| } | |||
| else | |||
| { | |||
| TUID uid {}; | |||
| for (uint32_t i = 0; i < 16; ++i) | |||
| { | |||
| char s[3] {}; | |||
| s[0] = str[i * 2]; | |||
| s[1] = str[i * 2 + 1]; | |||
| int32_t d = 0; | |||
| sscanf (s, "%2x", &d); | |||
| uid[i] = static_cast<char> (d); | |||
| } | |||
| return {uid}; | |||
| } | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| inline UID UID::fromTUID (const TUID _uid) noexcept | |||
| { | |||
| UID result; | |||
| uint64_t* p1 = reinterpret_cast<uint64_t*> (result._data); | |||
| const uint64_t* p2 = reinterpret_cast<const uint64_t*> (_uid); | |||
| p1[0] = p2[0]; | |||
| p1[1] = p2[1]; | |||
| return result; | |||
| } | |||
| //------------------------------------------------------------------------ | |||
| } // VST3 | |||