@@ -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 |