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