From 417f0e9ca39683d84c3360eb9102a69013a2eaf8 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 24 Apr 2023 13:30:07 +0100 Subject: [PATCH] VST3: Add moduleinfotool sources --- .../VST3_SDK/base/source/fcommandline.h | 366 ++ .../moduleinfotool/source/main.cpp | 366 ++ .../public.sdk/source/vst/hosting/module.cpp | 340 ++ .../public.sdk/source/vst/hosting/module.h | 212 + .../source/vst/hosting/module_linux.cpp | 361 ++ .../source/vst/hosting/module_mac.mm | 390 ++ .../source/vst/hosting/module_win32.cpp | 610 +++ .../source/vst/moduleinfo/ReadMe.md | 43 + .../public.sdk/source/vst/moduleinfo/json.h | 3403 +++++++++++++++++ .../source/vst/moduleinfo/jsoncxx.h | 427 +++ .../source/vst/moduleinfo/moduleinfo.h | 99 + .../vst/moduleinfo/moduleinfocreator.cpp | 309 ++ .../source/vst/moduleinfo/moduleinfocreator.h | 67 + .../vst/moduleinfo/moduleinfoparser.cpp | 536 +++ .../source/vst/moduleinfo/moduleinfoparser.h | 68 + .../public.sdk/source/vst/utility/optional.h | 135 + .../public.sdk/source/vst/utility/uid.h | 294 ++ 17 files changed, 8026 insertions(+) create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h create mode 100644 modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h b/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h new file mode 100644 index 0000000000..f72ffd7f4c --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 + +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; + 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; + + //------------------------------------------------------------------------ + /** 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; + 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&& 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 + 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(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; + } + +//------------------------------------------------------------------------ + /*! Usage example: + @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&& 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp new file mode 100644 index 0000000000..0eac7f36be --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp @@ -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 +#include +#include + +//------------------------------------------------------------------------ +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 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& 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 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); +} diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp new file mode 100644 index 0000000000..7190ea1ad0 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp @@ -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 +#include + +//------------------------------------------------------------------------ +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 (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 (count); +} + +//------------------------------------------------------------------------ +PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept +{ + auto count = classCount (); + Optional factoryInfo; + ClassInfos classes; + classes.reserve (count); + auto f3 = Steinberg::FUnknownPtr (factory); + auto f2 = Steinberg::FUnknownPtr (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 (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 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 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 (3)); + double result; + sstream >> result; + return Optional (result); +} + +//------------------------------------------------------------------------ +Optional 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h new file mode 100644 index 0000000000..70d0d3746b --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h @@ -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 +#include + +//------------------------------------------------------------------------ +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; + 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; + using PluginFactoryPtr = Steinberg::IPtr; + +//------------------------------------------------------------------------ + 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 + Steinberg::IPtr 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 images; + + static Optional decodeScaleFactor (const std::string& path); + static Optional decodeUID (const std::string& filename); + }; + + using Ptr = std::shared_ptr; + using PathList = std::vector; + using SnapshotList = std::vector; + +//------------------------------------------------------------------------ + 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 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 +inline Steinberg::IPtr PluginFactory::createInstance (const UID& classID) const noexcept +{ + T* obj = nullptr; + if (factory->createInstance (classID.data (), T::iid, reinterpret_cast (&obj)) == + Steinberg::kResultTrue) + return Steinberg::owned (obj); + return nullptr; +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp new file mode 100644 index 0000000000..308c3a8ca5 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp @@ -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 +#include +#include +#include +#include + +#if (__cplusplus >= 201707L) +#if __has_include() +#define USE_EXPERIMENTAL_FS 0 +#elif __has_include() +#define USE_EXPERIMENTAL_FS 1 +#endif +#else +#define USE_EXPERIMENTAL_FS 1 +#endif + +#if USE_EXPERIMENTAL_FS == 1 +#include +namespace filesystem = std::experimental::filesystem; +#else +#include +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 getCurrentMachineName () +{ + struct utsname unameData; + + int res = uname (&unameData); + if (res != 0) + return {}; + + return {unameData.machine}; +} + +//------------------------------------------------------------------------ +Optional 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 + T getFunctionPointer (const char* name) + { + return reinterpret_cast (dlsym (mModule, name)); + } + + ~LinuxModule () override + { + factory = PluginFactory (nullptr); + + if (mModule) + { + if (auto moduleExit = getFunctionPointer ("ModuleExit")) + moduleExit (); + + dlclose (mModule); + } + } + + static Optional 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 (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 (modulePath->generic_string ().data ()), + RTLD_LAZY); + if (!mModule) + { + errorDescription = "dlopen failed.\n"; + errorDescription += dlerror (); + return false; + } + // ModuleEntry is mandatory + auto moduleEntry = getFunctionPointer ("ModuleEntry"); + if (!moduleEntry) + { + errorDescription = + "The shared library does not export the required 'ModuleEntry' function"; + return false; + } + // ModuleExit is mandatory + auto moduleExit = getFunctionPointer ("ModuleExit"); + if (!moduleExit) + { + errorDescription = + "The shared library does not export the required 'ModuleExit' function"; + return false; + } + auto factoryProc = getFunctionPointer ("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 (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 (); + 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 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm new file mode 100644 index 0000000000..1fbc4b70e7 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm @@ -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 +#import + +#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 +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 + T getFunctionPointer (const char* name) + { + assert (bundle); + CFPtr functionName ( + CFStringCreateWithCString (kCFAllocatorDefault, name, kCFStringEncodingASCII)); + return reinterpret_cast (CFBundleGetFunctionPointerForName (bundle, functionName)); + } + + bool loadInternal (const std::string& path, std::string& errorDescription) + { + CFPtr url (CFURLCreateFromFileSystemRepresentation ( + kCFAllocatorDefault, reinterpret_cast (path.data ()), path.length (), + true)); + if (!url) + return false; + bundle = CFBundleCreate (kCFAllocatorDefault, url); + CFErrorRef error = nullptr; + if (!bundle || !CFBundleLoadExecutableAndReturnError (bundle, &error)) + { + if (error) + { + CFPtr errorString (CFErrorCopyDescription (error)); + if (errorString) + { + auto stringLength = CFStringGetLength (errorString); + auto maxSize = + CFStringGetMaximumSizeForEncoding (stringLength, kCFStringEncodingUTF8); + auto buffer = std::make_unique (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 ("bundleEntry"); + if (!bundleEntry) + { + errorDescription = "Bundle does not export the required 'bundleEntry' function"; + return false; + } + // bundleExit is mandatory + auto bundleExit = getFunctionPointer ("bundleExit"); + if (!bundleExit) + { + errorDescription = "Bundle does not export the required 'bundleExit' function"; + return false; + } + auto factoryProc = getFunctionPointer ("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 (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 ("bundleExit")) + bundleExit (); + } + } + + CFPtr 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 archs ( + CFBundleCopyExecutableArchitecturesForURL (static_cast (url))); + if (archs) + result.emplace_back ([url.path UTF8String]); + } + else + { + id resValue; + if (![url getResourceValue:&resValue forKey:NSURLIsSymbolicLinkKey error:nil]) + continue; + if (!static_cast (resValue).boolValue) + continue; + auto resolvedUrl = [url URLByResolvingSymlinksInPath]; + if (![resolvedUrl getResourceValue:&resValue forKey:NSURLIsDirectoryKey error:nil]) + continue; + if (!static_cast (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 (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 (); + 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 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp new file mode 100644 index 0000000000..2ba93191fa --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp @@ -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 +#include + +#include +#include + +#if SMTG_CPP17 + +#if __has_include() +#define USE_FILESYSTEM 1 +#elif __has_include() +#define USE_FILESYSTEM 0 +#endif + +#else // !SMTG_CPP17 + +#define USE_FILESYSTEM 0 + +#endif // SMTG_CPP17 + +#if USE_FILESYSTEM == 1 + +#include +namespace filesystem = std::filesystem; + +#else // USE_FILESYSTEM == 0 + +// The header is deprecated. It is superseded by the C++17 +// 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 +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 + T getFunctionPointer (const char* name) + { + return reinterpret_cast (GetProcAddress (mModule, name)); + } + + ~Win32Module () override + { + factory = PluginFactory (nullptr); + + if (mModule) + { + // ExitDll is optional + if (auto dllExit = getFunctionPointer ("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 (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 (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 ("GetPluginFactory"); + if (!factoryProc) + { + errorDescription = "The dll does not export the required 'GetPluginFactory' function"; + return false; + } + // InitDll is optional + auto dllEntry = getFunctionPointer ("InitDll"); + if (dllEntry && !dllEntry ()) + { + errorDescription = "Calling 'InitDll' failed"; + return false; + } + auto f = Steinberg::FUnknownPtr (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 (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 (wString.c_str ())); + if (attrib & FILE_ATTRIBUTE_REPARSE_POINT) + { + auto hFile = CreateFileW (reinterpret_cast (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 getKnownFolder (REFKNOWNFOLDERID folderID) +{ + PWSTR wideStr {}; + if (FAILED (SHGetKnownFolderPath (folderID, 0, nullptr, &wideStr))) + return {}; + return StringConvert::convert (Steinberg::wscast (wideStr)); +} + +//------------------------------------------------------------------------ +VST3::Optional 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 (&shellLink)))) + return {}; + + IPersistFile* persistFile = nullptr; + if (!SUCCEEDED ( + shellLink->QueryInterface (IID_IPersistFile, reinterpret_cast (&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 (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 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 {std::move (path)}; +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +Module::Ptr Module::create (const std::string& path, std::string& errorDescription) +{ + auto _module = std::make_shared (); + 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 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 {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 (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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md new file mode 100644 index 0000000000..6c93872098 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md @@ -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); +``` diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h new file mode 100644 index 0000000000..da8a6ad779 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h @@ -0,0 +1,3403 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/json.h. +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to . +*/ + +#ifndef SHEREDOM_JSON_H_INCLUDED +#define SHEREDOM_JSON_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: 'bytes padding added after construct' */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#define json_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define json_weak __attribute__((weak)) +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_value_s; +struct json_parse_result_s; + +enum json_parse_flags_e { + json_parse_flags_default = 0, + + /* allow trailing commas in objects and arrays. For example, both [true,] and + {"a" : null,} would be allowed with this option on. */ + json_parse_flags_allow_trailing_comma = 0x1, + + /* allow unquoted keys for objects. For example, {a : null} would be allowed + with this option on. */ + json_parse_flags_allow_unquoted_keys = 0x2, + + /* allow a global unbracketed object. For example, a : null, b : true, c : {} + would be allowed with this option on. */ + json_parse_flags_allow_global_object = 0x4, + + /* allow objects to use '=' instead of ':' between key/value pairs. For + example, a = null, b : true would be allowed with this option on. */ + json_parse_flags_allow_equals_in_object = 0x8, + + /* allow that objects don't have to have comma separators between key/value + pairs. */ + json_parse_flags_allow_no_commas = 0x10, + + /* allow c-style comments (either variants) to be ignored in the input JSON + file. */ + json_parse_flags_allow_c_style_comments = 0x20, + + /* deprecated flag, unused. */ + json_parse_flags_deprecated = 0x40, + + /* record location information for each value. */ + json_parse_flags_allow_location_information = 0x80, + + /* allow strings to be 'single quoted'. */ + json_parse_flags_allow_single_quoted_strings = 0x100, + + /* allow numbers to be hexadecimal. */ + json_parse_flags_allow_hexadecimal_numbers = 0x200, + + /* allow numbers like +123 to be parsed. */ + json_parse_flags_allow_leading_plus_sign = 0x400, + + /* allow numbers like .0123 or 123. to be parsed. */ + json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800, + + /* allow Infinity, -Infinity, NaN, -NaN. */ + json_parse_flags_allow_inf_and_nan = 0x1000, + + /* allow multi line string values. */ + json_parse_flags_allow_multi_line_strings = 0x2000, + + /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set + of other parsing options. */ + json_parse_flags_allow_simplified_json = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_global_object | + json_parse_flags_allow_equals_in_object | + json_parse_flags_allow_no_commas), + + /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing + options. */ + json_parse_flags_allow_json5 = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_c_style_comments | + json_parse_flags_allow_single_quoted_strings | + json_parse_flags_allow_hexadecimal_numbers | + json_parse_flags_allow_leading_plus_sign | + json_parse_flags_allow_leading_or_trailing_decimal_point | + json_parse_flags_allow_inf_and_nan | + json_parse_flags_allow_multi_line_strings) +}; + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to malloc for the entire encoding. + * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */ +json_weak struct json_value_s *json_parse(const void *src, size_t src_size); + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to alloc_func_ptr for the entire + * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc + * failed). If an error occurred, the result struct (if not NULL) will explain + * the type of error, and the location in the input it occurred. If + * alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *, size_t), void *user_data, + struct json_parse_result_s *result); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to malloc for the entire encoding. + */ +json_weak struct json_value_s * +json_extract_value(const struct json_value_s *value); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to alloc_func_ptr for the entire + * encoding. If alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, size_t), void *user_data); + +/* Write out a minified JSON utf-8 string. This string is an encoding of the + * minimal string characters required to still encode the same data. + * json_write_minified performs 1 call to malloc for the entire encoding. Return + * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_minified(const struct json_value_s *value, + size_t *out_size); + +/* Write out a pretty JSON utf-8 string. This string is encoded such that the + * resultant JSON is pretty in that it is easily human readable. The indent and + * newline parameters allow a user to specify what kind of indentation and + * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both + * indent and newline can be NULL, indent defaults to two spaces (" "), and + * newline defaults to linux newlines ('\n' as the newline character). + * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0 + * if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_pretty(const struct json_value_s *value, + const char *indent, const char *newline, + size_t *out_size); + +/* Reinterpret a JSON value as a string. Returns null is the value was not a + * string. */ +json_weak struct json_string_s * +json_value_as_string(struct json_value_s *const value); + +/* Reinterpret a JSON value as a number. Returns null is the value was not a + * number. */ +json_weak struct json_number_s * +json_value_as_number(struct json_value_s *const value); + +/* Reinterpret a JSON value as an object. Returns null is the value was not an + * object. */ +json_weak struct json_object_s * +json_value_as_object(struct json_value_s *const value); + +/* Reinterpret a JSON value as an array. Returns null is the value was not an + * array. */ +json_weak struct json_array_s * +json_value_as_array(struct json_value_s *const value); + +/* Whether the value is true. */ +json_weak int json_value_is_true(const struct json_value_s *const value); + +/* Whether the value is false. */ +json_weak int json_value_is_false(const struct json_value_s *const value); + +/* Whether the value is null. */ +json_weak int json_value_is_null(const struct json_value_s *const value); + +/* The various types JSON values can be. Used to identify what a value is. */ +enum json_type_e { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null +}; + +/* A JSON string value. */ +struct json_string_s { + /* utf-8 string */ + const char *string; + /* The size (in bytes) of the string */ + size_t string_size; +}; + +/* A JSON string value (extended). */ +struct json_string_ex_s { + /* The JSON string this extends. */ + struct json_string_s string; + + /* The character offset for the value in the JSON input. */ + size_t offset; + + /* The line number for the value in the JSON input. */ + size_t line_no; + + /* The row number for the value in the JSON input, in bytes. */ + size_t row_no; +}; + +/* A JSON number value. */ +struct json_number_s { + /* ASCII string containing representation of the number. */ + const char *number; + /* the size (in bytes) of the number. */ + size_t number_size; +}; + +/* an element of a JSON object. */ +struct json_object_element_s { + /* the name of this element. */ + struct json_string_s *name; + /* the value of this element. */ + struct json_value_s *value; + /* the next object element (can be NULL if the last element in the object). */ + struct json_object_element_s *next; +}; + +/* a JSON object value. */ +struct json_object_s { + /* a linked list of the elements in the object. */ + struct json_object_element_s *start; + /* the number of elements in the object. */ + size_t length; +}; + +/* an element of a JSON array. */ +struct json_array_element_s { + /* the value of this element. */ + struct json_value_s *value; + /* the next array element (can be NULL if the last element in the array). */ + struct json_array_element_s *next; +}; + +/* a JSON array value. */ +struct json_array_s { + /* a linked list of the elements in the array. */ + struct json_array_element_s *start; + /* the number of elements in the array. */ + size_t length; +}; + +/* a JSON value. */ +struct json_value_s { + /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */ + /* json_array_s. Should be cast to the appropriate struct type based on what. + */ + /* the type of this value is. */ + void *payload; + /* must be one of json_type_e. If type is json_type_true, json_type_false, or. + */ + /* json_type_null, payload will be NULL. */ + size_t type; +}; + +/* a JSON value (extended). */ +struct json_value_ex_s { + /* the JSON value this extends. */ + struct json_value_s value; + + /* the character offset for the value in the JSON input. */ + size_t offset; + + /* the line number for the value in the JSON input. */ + size_t line_no; + + /* the row number for the value in the JSON input, in bytes. */ + size_t row_no; +}; + +/* a parsing error code. */ +enum json_parse_error_e { + /* no error occurred (huzzah!). */ + json_parse_error_none = 0, + + /* expected either a comma or a closing '}' or ']' to close an object or. */ + /* array! */ + json_parse_error_expected_comma_or_closing_bracket, + + /* colon separating name/value pair was missing! */ + json_parse_error_expected_colon, + + /* expected string to begin with '"'! */ + json_parse_error_expected_opening_quote, + + /* invalid escaped sequence in string! */ + json_parse_error_invalid_string_escape_sequence, + + /* invalid number format! */ + json_parse_error_invalid_number_format, + + /* invalid value! */ + json_parse_error_invalid_value, + + /* reached end of buffer before object/array was complete! */ + json_parse_error_premature_end_of_buffer, + + /* string was malformed! */ + json_parse_error_invalid_string, + + /* a call to malloc, or a user provider allocator, failed. */ + json_parse_error_allocator_failed, + + /* the JSON input had unexpected trailing characters that weren't part of the. + */ + /* JSON value. */ + json_parse_error_unexpected_trailing_characters, + + /* catch-all error for everything else that exploded (real bad chi!). */ + json_parse_error_unknown +}; + +/* error report from json_parse_ex(). */ +struct json_parse_result_s { + /* the error code (one of json_parse_error_e). */ + size_t error; + + /* the character offset for the error in the JSON input. */ + size_t error_offset; + + /* the line number for the error in the JSON input. */ + size_t error_line_no; + + /* the row number for the error, in bytes. */ + size_t error_row_no; +}; + +#ifdef __cplusplus +} /* extern "C". */ +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define json_uintmax_t unsigned __int64 +#else +#include +#define json_uintmax_t uintmax_t +#endif + +#if defined(_MSC_VER) +#define json_strtoumax _strtoui64 +#else +#define json_strtoumax strtoumax +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define json_null nullptr +#else +#define json_null 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + +/* we do one big allocation via malloc, then cast aligned slices of this for. */ +/* our structures - we don't have a way to tell the compiler we know what we. */ +/* are doing, so disable the warning instead! */ +#pragma clang diagnostic ignored "-Wcast-align" + +/* We use C style casts everywhere. */ +#pragma clang diagnostic ignored "-Wold-style-cast" + +/* We need long long for strtoull. */ +#pragma clang diagnostic ignored "-Wc++11-long-long" + +/* Who cares if nullptr doesn't work with C++98, we don't use it there! */ +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#elif defined(_MSC_VER) +#pragma warning(push) + +/* disable 'function selected for inline expansion' warning. */ +#pragma warning(disable : 4711) + +/* disable '#pragma warning: there is no warning number' warning. */ +#pragma warning(disable : 4619) + +/* disable 'warning number not a valid compiler warning' warning. */ +#pragma warning(disable : 4616) + +/* disable 'Compiler will insert Spectre mitigation for memory load if + * /Qspectre. */ +/* switch specified' warning. */ +#pragma warning(disable : 5045) +#endif + +struct json_parse_state_s { + const char *src; + size_t size; + size_t offset; + size_t flags_bitset; + char *data; + char *dom; + size_t dom_size; + size_t data_size; + size_t line_no; /* line counter for error reporting. */ + size_t line_offset; /* (offset-line_offset) is the character number (in + bytes). */ + size_t error; +}; + +json_weak int json_hexadecimal_digit(const char c); +int json_hexadecimal_digit(const char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +json_weak int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result); +int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result) { + const char *p; + int digit; + + if (size > sizeof(unsigned long) * 2) { + return 0; + } + + *result = 0; + for (p = c; (unsigned long)(p - c) < size; ++p) { + *result <<= 4; + digit = json_hexadecimal_digit(*p); + if (digit < 0 || digit > 15) { + return 0; + } + *result |= (unsigned char)digit; + } + return 1; +} + +json_weak int json_skip_whitespace(struct json_parse_state_s *state); +int json_skip_whitespace(struct json_parse_state_s *state) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + + /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and + * '\t'. */ + switch (src[offset]) { + default: + return 0; + case ' ': + case '\r': + case '\t': + case '\n': + break; + } + + do { + switch (src[offset]) { + default: + /* Update offset. */ + state->offset = offset; + return 1; + case ' ': + case '\r': + case '\t': + break; + case '\n': + state->line_no++; + state->line_offset = offset; + break; + } + + offset++; + } while (offset < size); + + /* Update offset. */ + state->offset = offset; + return 1; +} + +json_weak int json_skip_c_style_comments(struct json_parse_state_s *state); +int json_skip_c_style_comments(struct json_parse_state_s *state) { + /* do we have a comment?. */ + if ('/' == state->src[state->offset]) { + /* skip '/'. */ + state->offset++; + + if ('/' == state->src[state->offset]) { + /* we had a comment of the form //. */ + + /* skip second '/'. */ + state->offset++; + + while (state->offset < state->size) { + switch (state->src[state->offset]) { + default: + /* skip the character in the comment. */ + state->offset++; + break; + case '\n': + /* if we have a newline, our comment has ended! Skip the newline. */ + state->offset++; + + /* we entered a newline, so move our line info forward. */ + state->line_no++; + state->line_offset = state->offset; + return 1; + } + } + + /* we reached the end of the JSON file! */ + return 1; + } else if ('*' == state->src[state->offset]) { + /* we had a comment in the C-style long form. */ + + /* skip '*'. */ + state->offset++; + + while (state->offset + 1 < state->size) { + if (('*' == state->src[state->offset]) && + ('/' == state->src[state->offset + 1])) { + /* we reached the end of our comment! */ + state->offset += 2; + return 1; + } else if ('\n' == state->src[state->offset]) { + /* we entered a newline, so move our line info forward. */ + state->line_no++; + state->line_offset = state->offset; + } + + /* skip character within comment. */ + state->offset++; + } + + /* Comment wasn't ended correctly which is a failure. */ + return 1; + } + } + + /* we didn't have any comment, which is ok too! */ + return 0; +} + +json_weak int json_skip_all_skippables(struct json_parse_state_s *state); +int json_skip_all_skippables(struct json_parse_state_s *state) { + /* skip all whitespace and other skippables until there are none left. note + * that the previous version suffered from read past errors should. the + * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag. + */ + + int did_consume = 0; + const size_t size = state->size; + + if (json_parse_flags_allow_c_style_comments & state->flags_bitset) { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + + /* This should really be checked on access, not in front of every call. + */ + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume |= json_skip_c_style_comments(state); + } while (0 != did_consume); + } else { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + } while (0 != did_consume); + } + + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); + +json_weak int json_get_string_size(struct json_parse_state_s *state, + size_t is_key); +int json_get_string_size(struct json_parse_state_s *state, size_t is_key) { + size_t offset = state->offset; + const size_t size = state->size; + size_t data_size = 0; + const char *const src = state->src; + const int is_single_quote = '\'' == src[offset]; + const char quote_to_use = is_single_quote ? '\'' : '"'; + const size_t flags_bitset = state->flags_bitset; + unsigned long codepoint; + unsigned long high_surrogate = 0; + + if ((json_parse_flags_allow_location_information & flags_bitset) != 0 && + is_key != 0) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + if ('"' != src[offset]) { + /* if we are allowed single quoted strings check for that too. */ + if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + is_single_quote)) { + state->error = json_parse_error_expected_opening_quote; + state->offset = offset; + return 1; + } + } + + /* skip leading '"' or '\''. */ + offset++; + + while ((offset < size) && (quote_to_use != src[offset])) { + /* add space for the character. */ + data_size++; + + switch (src[offset]) { + default: + break; + case '\0': + case '\t': + state->error = json_parse_error_invalid_string; + state->offset = offset; + return 1; + } + + if ('\\' == src[offset]) { + /* skip reverse solidus character. */ + offset++; + + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset; + return 1; + } + + switch (src[offset]) { + default: + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + /* all valid characters! */ + offset++; + break; + case 'u': + if (!(offset + 5 < size)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + codepoint = 0; + if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) { + /* escaped unicode sequences must contain 4 hexadecimal digits! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + /* Valid sequence! + * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points. + * 1 7 U + 0000 U + 007F 0xxxxxxx. + * 2 11 U + 0080 U + 07FF 110xxxxx + * 10xxxxxx. + * 3 16 U + 0800 U + FFFF 1110xxxx + * 10xxxxxx 10xxxxxx. + * 4 21 U + 10000 U + 10FFFF 11110xxx + * 10xxxxxx 10xxxxxx 10xxxxxx. + * Note: the high and low surrogate halves used by UTF-16 (U+D800 + * through U+DFFF) and code points not encodable by UTF-16 (those after + * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must + * be treated as an invalid byte sequence. */ + + if (high_surrogate != 0) { + /* we previously read the high half of the \uxxxx\uxxxx pair, so now + * we expect the low half. */ + if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate range. */ + data_size += 3; + high_surrogate = 0; + } else { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + } else if (codepoint <= 0x7f) { + data_size += 0; + } else if (codepoint <= 0x7ff) { + data_size += 1; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate range. */ + /* The codepoint is the first half of a "utf-16 surrogate pair". so we + * need the other half for it to be valid: \uHHHH\uLLLL. */ + if (offset + 11 > size || '\\' != src[offset + 5] || + 'u' != src[offset + 6]) { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + high_surrogate = codepoint; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdfff) { /* low surrogate range. */ + /* we did not read the other half before. */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } else { + data_size += 2; + } + /* escaped codepoints after 0xffff are supported in json through utf-16 + * surrogate pairs: \uD83D\uDD25 for U+1F525. */ + + offset += 5; + break; + } + } else if (('\r' == src[offset]) || ('\n' == src[offset])) { + if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + offset++; + } else { + /* skip character (valid part of sequence). */ + offset++; + } + } + + /* If the offset is equal to the size, we had a non-terminated string! */ + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset - 1; + return 1; + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* add enough space to store the string. */ + state->data_size += data_size; + + /* one more byte for null terminator ending the string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int is_valid_unquoted_key_char(const char c); +int is_valid_unquoted_key_char(const char c) { + return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || ('_' == c)); +} + +json_weak int json_get_key_size(struct json_parse_state_s *state); +int json_get_key_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + + if (json_parse_flags_allow_unquoted_keys & flags_bitset) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + size_t data_size = state->data_size; + + /* if we are allowing unquoted keys, first grok for a quote... */ + if ('"' == src[offset]) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + ('\'' == src[offset])) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else { + while ((offset < size) && is_valid_unquoted_key_char(src[offset])) { + offset++; + data_size++; + } + + /* one more byte for null terminator ending the string! */ + data_size++; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + /* update offset. */ + state->offset = offset; + + /* update data_size. */ + state->data_size = data_size; + + return 0; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + return json_get_string_size(state, 1); + } +} + +json_weak int json_get_object_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_object_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + int found_closing_brace = 0; + + if (is_global_object) { + /* if we found an opening '{' of an object, we actually have a normal JSON + * object at the root of the DOM... */ + if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + if ('{' != src[state->offset]) { + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '{'. */ + state->offset++; + } + + state->dom_size += sizeof(struct json_object_s); + + if ((state->offset == size) && !is_global_object) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + do { + if (!is_global_object) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + found_closing_brace = 1; + + /* finished the object! */ + break; + } + } else { + /* we don't require brackets, so that means the object ends when the input + * stream ends! */ + if (json_skip_all_skippables(state)) { + break; + } + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (json_parse_flags_allow_no_commas & flags_bitset) { + /* we don't require a comma, and we didn't find one, which is ok! */ + allow_comma = 0; + } else { + /* otherwise we are required to have a comma, and we found none. */ + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_key_size(state)) { + /* key parsing failed! */ + state->error = json_parse_error_invalid_string; + return 1; + } + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + const char current = src[state->offset]; + if ((':' != current) && ('=' != current)) { + state->error = json_parse_error_expected_colon; + return 1; + } + } else { + if (':' != src[state->offset]) { + state->error = json_parse_error_expected_colon; + return 1; + } + } + + /* skip colon. */ + state->offset++; + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + if ((state->offset == size) && !is_global_object && !found_closing_brace) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + state->dom_size += sizeof(struct json_object_element_s) * elements; + + return 0; +} + +json_weak int json_get_array_size(struct json_parse_state_s *state); +int json_get_array_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t elements = 0; + int allow_comma = 0; + const char *const src = state->src; + const size_t size = state->size; + + if ('[' != src[state->offset]) { + /* expected array to begin with leading '['. */ + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '['. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_s); + + while (state->offset < size) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_element_s) * elements; + + /* finished the object! */ + return 0; + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) { + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + allow_comma = 0; + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } + + /* we consumed the entire input before finding the closing ']' of the array! + */ + state->error = json_parse_error_premature_end_of_buffer; + return 1; +} + +json_weak int json_get_number_size(struct json_parse_state_s *state); +int json_get_number_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + int had_leading_digits = 0; + const char *const src = state->src; + + state->dom_size += sizeof(struct json_number_s); + + if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) && + (offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* skip the leading 0x that identifies a hexadecimal number. */ + offset += 2; + + /* consume hexadecimal digits. */ + while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F'))) { + offset++; + } + } else { + int found_sign = 0; + int inf_or_nan = 0; + + if ((offset < size) && + (('-' == src[offset]) || + ((json_parse_flags_allow_leading_plus_sign & flags_bitset) && + ('+' == src[offset])))) { + /* skip valid leading '-' or '+'. */ + offset++; + + found_sign = 1; + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const char inf[9] = "Infinity"; + const size_t inf_strlen = sizeof(inf) - 1; + const char nan[4] = "NaN"; + const size_t nan_strlen = sizeof(nan) - 1; + + if (offset + inf_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < inf_strlen; i++) { + if (inf[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'Infinity' keyword! */ + offset += inf_strlen; + + inf_or_nan = 1; + } + } + + if (offset + nan_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < nan_strlen; i++) { + if (nan[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'NaN' keyword! */ + offset += nan_strlen; + + inf_or_nan = 1; + } + } + } + + if (found_sign && !inf_or_nan && (offset < size) && + !('0' <= src[offset] && src[offset] <= '9')) { + /* check if we are allowing leading '.'. */ + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + ('.' != src[offset])) { + /* a leading '-' must be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + if ((offset < size) && ('0' == src[offset])) { + /* skip valid '0'. */ + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + + if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + /* a leading '0' must not be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* the main digits of our number next. */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + } + + if ((offset < size) && ('.' == src[offset])) { + offset++; + + if (!('0' <= src[offset] && src[offset] <= '9')) { + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + !had_leading_digits) { + /* a decimal point must be followed by at least one digit. */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* a decimal point can be followed by more digits of course! */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + } + } + + if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) { + /* our number has an exponent! Skip 'e' or 'E'. */ + offset++; + + if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) { + /* skip optional '-' or '+'. */ + offset++; + } + + if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) { + /* an exponent must have at least one digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + + /* consume exponent digits. */ + do { + offset++; + } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')); + } + } + + if (offset < size) { + switch (src[offset]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ',': + case ']': + /* all of the above are ok. */ + break; + case '=': + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + break; + } + + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + default: + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + state->data_size += offset - state->offset; + + /* one more byte for null terminator ending the number string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_value_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + size_t offset; + const size_t size = state->size; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_value_ex_s); + } else { + state->dom_size += sizeof(struct json_value_s); + } + + if (is_global_object) { + return json_get_object_size(state, /* is_global_object = */ 1); + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + /* can cache offset now. */ + offset = state->offset; + + switch (src[offset]) { + case '"': + return json_get_string_size(state, 0); + case '\'': + if (json_parse_flags_allow_single_quoted_strings & flags_bitset) { + return json_get_string_size(state, 0); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + case '{': + return json_get_object_size(state, /* is_global_object = */ 0); + case '[': + return json_get_array_size(state); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_get_number_size(state); + case '+': + if (json_parse_flags_allow_leading_plus_sign & flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + case '.': + if (json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + state->offset += 5; + return 0; + } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] && + 'u' == state->src[offset + 1] && + 'l' == state->src[offset + 2] && + 'l' == state->src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + return json_get_number_size(state); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + return json_get_number_size(state); + } + + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + } +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); + +json_weak void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string) { + size_t offset = state->offset; + size_t bytes_written = 0; + const char *const src = state->src; + const char quote_to_use = '\'' == src[offset] ? '\'' : '"'; + char *data = state->data; + unsigned long high_surrogate = 0; + unsigned long codepoint; + + string->string = data; + + /* skip leading '"' or '\''. */ + offset++; + + while (quote_to_use != src[offset]) { + if ('\\' == src[offset]) { + /* skip the reverse solidus. */ + offset++; + + switch (src[offset++]) { + default: + return; /* we cannot ever reach here. */ + case 'u': { + codepoint = 0; + if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) { + return; /* this shouldn't happen as the value was already validated. + */ + } + + offset += 4; + + if (codepoint <= 0x7fu) { + data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */ + } else if (codepoint <= 0x7ffu) { + data[bytes_written++] = + (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate. */ + high_surrogate = codepoint; + continue; /* we need the low half to form a complete codepoint. */ + } else if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate. */ + /* combine with the previously read half to obtain the complete + * codepoint. */ + const unsigned long surrogate_offset = + 0x10000u - (0xD800u << 10) - 0xDC00u; + codepoint = (high_surrogate << 10) + codepoint + surrogate_offset; + high_surrogate = 0; + data[bytes_written++] = + (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else { + /* we assume the value was validated and thus is within the valid + * range. */ + data[bytes_written++] = + (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } + } break; + case '"': + data[bytes_written++] = '"'; + break; + case '\\': + data[bytes_written++] = '\\'; + break; + case '/': + data[bytes_written++] = '/'; + break; + case 'b': + data[bytes_written++] = '\b'; + break; + case 'f': + data[bytes_written++] = '\f'; + break; + case 'n': + data[bytes_written++] = '\n'; + break; + case 'r': + data[bytes_written++] = '\r'; + break; + case 't': + data[bytes_written++] = '\t'; + break; + case '\r': + data[bytes_written++] = '\r'; + + /* check if we have a "\r\n" sequence. */ + if ('\n' == src[offset]) { + data[bytes_written++] = '\n'; + offset++; + } + + break; + case '\n': + data[bytes_written++] = '\n'; + break; + } + } else { + /* copy the character. */ + data[bytes_written++] = src[offset++]; + } + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* record the size of the string. */ + string->string_size = bytes_written; + + /* add null terminator to string. */ + data[bytes_written++] = '\0'; + + /* move data along. */ + state->data += bytes_written; + + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string) { + if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) { + const char *const src = state->src; + char *const data = state->data; + size_t offset = state->offset; + + /* if we are allowing unquoted keys, check for quoted anyway... */ + if (('"' == src[offset]) || ('\'' == src[offset])) { + /* ... if we got a quote, just parse the key as a string as normal. */ + json_parse_string(state, string); + } else { + size_t size = 0; + + string->string = state->data; + + while (is_valid_unquoted_key_char(src[offset])) { + data[size++] = src[offset++]; + } + + /* add null terminator to string. */ + data[size] = '\0'; + + /* record the size of the string. */ + string->string_size = size++; + + /* move data along. */ + state->data += size; + + /* update offset. */ + state->offset = offset; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + json_parse_string(state, string); + } +} + +json_weak void json_parse_object(struct json_parse_state_s *state, + int is_global_object, + struct json_object_s *object); +void json_parse_object(struct json_parse_state_s *state, int is_global_object, + struct json_object_s *object) { + const size_t flags_bitset = state->flags_bitset; + const size_t size = state->size; + const char *const src = state->src; + size_t elements = 0; + int allow_comma = 0; + struct json_object_element_s *previous = json_null; + + if (is_global_object) { + /* if we skipped some whitespace, and then found an opening '{' of an. */ + /* object, we actually have a normal JSON object at the root of the DOM... + */ + if ('{' == src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + /* skip leading '{'. */ + state->offset++; + } + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + while (state->offset < size) { + struct json_object_element_s *element = json_null; + struct json_string_s *string = json_null; + struct json_value_s *value = json_null; + + if (!is_global_object) { + (void)json_skip_all_skippables(state); + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + /* finished the object! */ + break; + } + } else { + if (json_skip_all_skippables(state)) { + /* global object ends when the file ends! */ + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_object_element_s *)state->dom; + + state->dom += sizeof(struct json_object_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our object. */ + object->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_string_ex_s *string_ex = + (struct json_string_ex_s *)state->dom; + state->dom += sizeof(struct json_string_ex_s); + + string_ex->offset = state->offset; + string_ex->line_no = state->line_no; + string_ex->row_no = state->offset - state->line_offset; + + string = &(string_ex->string); + } else { + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + } + + element->name = string; + + (void)json_parse_key(state, string); + + (void)json_skip_all_skippables(state); + + /* skip colon or equals. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } + + /* if we had at least one element, end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + object->start = json_null; + } + + object->length = elements; +} + +json_weak void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array); +void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array) { + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + struct json_array_element_s *previous = json_null; + + /* skip leading '['. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + do { + struct json_array_element_s *element = json_null; + struct json_value_s *value = json_null; + + (void)json_skip_all_skippables(state); + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + /* finished the array! */ + break; + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_array_element_s *)state->dom; + + state->dom += sizeof(struct json_array_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our array. */ + array->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & state->flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + /* end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + array->start = json_null; + } + + array->length = elements; +} + +json_weak void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number); +void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + size_t bytes_written = 0; + const char *const src = state->src; + char *data = state->data; + + number->number = data; + + if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) { + if (('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* consume hexadecimal digits. */ + while ((offset < size) && + (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F') || + ('x' == src[offset]) || ('X' == src[offset]))) { + data[bytes_written++] = src[offset++]; + } + } + } + + while (offset < size) { + int end = 0; + + switch (src[offset]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'e': + case 'E': + case '+': + case '-': + data[bytes_written++] = src[offset++]; + break; + default: + end = 1; + break; + } + + if (0 != end) { + break; + } + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const size_t inf_strlen = 8; /* = strlen("Infinity");. */ + const size_t nan_strlen = 3; /* = strlen("NaN");. */ + + if (offset + inf_strlen < size) { + if ('I' == src[offset]) { + size_t i; + /* We found our special 'Infinity' keyword! */ + for (i = 0; i < inf_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + + if (offset + nan_strlen < size) { + if ('N' == src[offset]) { + size_t i; + /* We found our special 'NaN' keyword! */ + for (i = 0; i < nan_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + } + + /* record the size of the number. */ + number->number_size = bytes_written; + /* add null terminator to number string. */ + data[bytes_written++] = '\0'; + /* move data along. */ + state->data += bytes_written; + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); +void json_parse_value(struct json_parse_state_s *state, int is_global_object, + struct json_value_s *value) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t offset; + + (void)json_skip_all_skippables(state); + + /* cache offset now. */ + offset = state->offset; + + if (is_global_object) { + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 1, + (struct json_object_s *)value->payload); + } else { + switch (src[offset]) { + case '"': + case '\'': + value->type = json_type_string; + value->payload = state->dom; + state->dom += sizeof(struct json_string_s); + json_parse_string(state, (struct json_string_s *)value->payload); + break; + case '{': + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 0, + (struct json_object_s *)value->payload); + break; + case '[': + value->type = json_type_array; + value->payload = state->dom; + state->dom += sizeof(struct json_array_s); + json_parse_array(state, (struct json_array_s *)value->payload); + break; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + break; + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + value->type = json_type_true; + value->payload = json_null; + state->offset += 4; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + value->type = json_type_false; + value->payload = json_null; + state->offset += 5; + } else if ((offset + 4) <= size && 'n' == src[offset + 0] && + 'u' == src[offset + 1] && 'l' == src[offset + 2] && + 'l' == src[offset + 3]) { + value->type = json_type_null; + value->payload = json_null; + state->offset += 4; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } + break; + } + } +} + +struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *user_data, size_t size), + void *user_data, struct json_parse_result_s *result) { + struct json_parse_state_s state; + void *allocation; + struct json_value_s *value; + size_t total_size; + int input_error; + + if (result) { + result->error = json_parse_error_none; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + if (json_null == src) { + /* invalid src pointer was null! */ + return json_null; + } + + state.src = (const char *)src; + state.size = src_size; + state.offset = 0; + state.line_no = 1; + state.line_offset = 0; + state.error = json_parse_error_none; + state.dom_size = 0; + state.data_size = 0; + state.flags_bitset = flags_bitset; + + input_error = json_get_value_size( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset)); + + if (0 == input_error) { + json_skip_all_skippables(&state); + + if (state.offset != state.size) { + /* our parsing didn't have an error, but there are characters remaining in + * the input that weren't part of the JSON! */ + + state.error = json_parse_error_unexpected_trailing_characters; + input_error = 1; + } + } + + if (input_error) { + /* parsing value's size failed (most likely an invalid JSON DOM!). */ + if (result) { + result->error = state.error; + result->error_offset = state.offset; + result->error_line_no = state.line_no; + result->error_row_no = state.offset - state.line_offset; + } + return json_null; + } + + /* our total allocation is the combination of the dom and data sizes (we. */ + /* first encode the structure of the JSON, and then the data referenced by. */ + /* the JSON values). */ + total_size = state.dom_size + state.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + if (json_null == allocation) { + /* malloc failed! */ + if (result) { + result->error = json_parse_error_allocator_failed; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + return json_null; + } + + /* reset offset so we can reuse it. */ + state.offset = 0; + + /* reset the line information so we can reuse it. */ + state.line_no = 1; + state.line_offset = 0; + + state.dom = (char *)allocation; + state.data = state.dom + state.dom_size; + + if (json_parse_flags_allow_location_information & state.flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom; + state.dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state.offset; + value_ex->line_no = state.line_no; + value_ex->row_no = state.offset - state.line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state.dom; + state.dom += sizeof(struct json_value_s); + } + + json_parse_value( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset), + value); + + return (struct json_value_s *)allocation; +} + +struct json_value_s *json_parse(const void *src, size_t src_size) { + return json_parse_ex(src, src_size, json_parse_flags_default, json_null, + json_null, json_null); +} + +struct json_extract_result_s { + size_t dom_size; + size_t data_size; +}; + +struct json_value_s *json_extract_value(const struct json_value_s *value) { + return json_extract_value_ex(value, json_null, json_null); +} + +json_weak struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number); +json_weak struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string); +json_weak struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object); +json_weak struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array); +json_weak struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value); + +struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_number_s); + result.data_size = number->number_size; + return result; +} + +struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_string_s); + result.data_size = string->string_size + 1; + return result; +} + +struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object) { + struct json_extract_result_s result; + size_t i; + const struct json_object_element_s *element = object->start; + + result.dom_size = sizeof(struct json_object_s) + + (sizeof(struct json_object_element_s) * object->length); + result.data_size = 0; + + for (i = 0; i < object->length; i++) { + const struct json_extract_result_s string_result = + json_extract_get_string_size(element->name); + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += string_result.dom_size; + result.data_size += string_result.data_size; + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array) { + struct json_extract_result_s result; + size_t i; + const struct json_array_element_s *element = array->start; + + result.dom_size = sizeof(struct json_array_s) + + (sizeof(struct json_array_element_s) * array->length); + result.data_size = 0; + + for (i = 0; i < array->length; i++) { + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value) { + struct json_extract_result_s result = {0, 0}; + + switch (value->type) { + default: + break; + case json_type_object: + result = json_extract_get_object_size( + (const struct json_object_s *)value->payload); + break; + case json_type_array: + result = json_extract_get_array_size( + (const struct json_array_s *)value->payload); + break; + case json_type_number: + result = json_extract_get_number_size( + (const struct json_number_s *)value->payload); + break; + case json_type_string: + result = json_extract_get_string_size( + (const struct json_string_s *)value->payload); + break; + } + + result.dom_size += sizeof(struct json_value_s); + + return result; +} + +struct json_extract_state_s { + char *dom; + char *data; +}; + +json_weak void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value); +void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value) { + struct json_string_s *string; + struct json_number_s *number; + struct json_object_s *object; + struct json_array_s *array; + struct json_value_s *new_value; + + memcpy(state->dom, value, sizeof(struct json_value_s)); + new_value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + new_value->payload = state->dom; + + if (json_type_string == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + } else if (json_type_number == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_number_s)); + number = (struct json_number_s *)state->dom; + state->dom += sizeof(struct json_number_s); + + memcpy(state->data, number->number, number->number_size); + number->number = state->data; + state->data += number->number_size; + } else if (json_type_object == value->type) { + struct json_object_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_object_s)); + object = (struct json_object_s *)state->dom; + state->dom += sizeof(struct json_object_s); + + element = object->start; + object->start = (struct json_object_element_s *)state->dom; + + for (i = 0; i < object->length; i++) { + struct json_value_s *previous_value; + struct json_object_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_object_element_s)); + element = (struct json_object_element_s *)state->dom; + state->dom += sizeof(struct json_object_element_s); + + string = element->name; + memcpy(state->dom, string, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + element->name = string; + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_object_element_s *)state->dom; + } + } + } else if (json_type_array == value->type) { + struct json_array_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_array_s)); + array = (struct json_array_s *)state->dom; + state->dom += sizeof(struct json_array_s); + + element = array->start; + array->start = (struct json_array_element_s *)state->dom; + + for (i = 0; i < array->length; i++) { + struct json_value_s *previous_value; + struct json_array_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_array_element_s)); + element = (struct json_array_element_s *)state->dom; + state->dom += sizeof(struct json_array_element_s); + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_array_element_s *)state->dom; + } + } + } +} + +struct json_value_s *json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, + size_t), + void *user_data) { + void *allocation; + struct json_extract_result_s result; + struct json_extract_state_s state; + size_t total_size; + + if (json_null == value) { + /* invalid value was null! */ + return json_null; + } + + result = json_extract_get_value_size(value); + total_size = result.dom_size + result.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + state.dom = (char *)allocation; + state.data = state.dom + result.dom_size; + + json_extract_copy_value(&state, value); + + return (struct json_value_s *)allocation; +} + +struct json_string_s *json_value_as_string(struct json_value_s *const value) { + if (value->type != json_type_string) { + return json_null; + } + + return (struct json_string_s *)value->payload; +} + +struct json_number_s *json_value_as_number(struct json_value_s *const value) { + if (value->type != json_type_number) { + return json_null; + } + + return (struct json_number_s *)value->payload; +} + +struct json_object_s *json_value_as_object(struct json_value_s *const value) { + if (value->type != json_type_object) { + return json_null; + } + + return (struct json_object_s *)value->payload; +} + +struct json_array_s *json_value_as_array(struct json_value_s *const value) { + if (value->type != json_type_array) { + return json_null; + } + + return (struct json_array_s *)value->payload; +} + +int json_value_is_true(const struct json_value_s *const value) { + return value->type == json_type_true; +} + +int json_value_is_false(const struct json_value_s *const value) { + return value->type == json_type_false; +} + +int json_value_is_null(const struct json_value_s *const value) { + return value->type == json_type_null; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); + +json_weak int json_write_get_number_size(const struct json_number_s *number, + size_t *size); +int json_write_get_number_size(const struct json_number_s *number, + size_t *size) { + json_uintmax_t parsed_number; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + *size += i; + return 0; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '+' or '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf) { + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + *size += 22; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *size += 1; + } + } + + return 0; + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan) { + /* NaN becomes 1 because JSON can't support it. */ + *size += 1; + + return 0; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a leading decimal point. */ + *size += 1; + goto cleanup; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a trailing decimal point. */ + *size += 1; + goto cleanup; + } + +cleanup: + *size += number->number_size; /* the actual string of the number. */ + + /* if we had a leading '+' we don't record it in the JSON output. */ + if ('+' == number->number[0]) { + *size -= 1; + } + + return 0; +} + +json_weak int json_write_get_string_size(const struct json_string_s *string, + size_t *size); +int json_write_get_string_size(const struct json_string_s *string, + size_t *size) { + size_t i; + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + *size += 2; + break; + default: + *size += 1; + break; + } + } + + *size += 2; /* need to encode the surrounding '"' characters. */ + + return 0; +} + +json_weak int +json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size); +int json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size) { + struct json_array_element_s *element; + + *size += 2; /* '[' and ']'. */ + + if (1 < array->length) { + *size += array->length - 1; /* ','s seperate each element. */ + } + + for (element = array->start; json_null != element; element = element->next) { + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size); +int json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size) { + struct json_object_element_s *element; + + *size += 2; /* '{' and '}'. */ + + *size += object->length; /* ':'s seperate each name/value pair. */ + + if (1 < object->length) { + *size += object->length - 1; /* ','s seperate each element. */ + } + + for (element = object->start; json_null != element; element = element->next) { + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); +int json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_minified_get_array_size( + (struct json_array_s *)value->payload, size); + case json_type_object: + return json_write_minified_get_object_size( + (struct json_object_s *)value->payload, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); + +json_weak char *json_write_number(const struct json_number_s *number, + char *data); +char *json_write_number(const struct json_number_s *number, char *data) { + json_uintmax_t parsed_number, backup; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + /* We need a copy of parsed number twice, so take a backup of it. */ + backup = parsed_number; + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + /* Restore parsed_number to its original value stored in the backup. */ + parsed_number = backup; + + /* Now use backup to take a copy of i, or the length of the string. */ + backup = i; + + do { + *(data + i - 1) = '0' + (char)(parsed_number % 10); + parsed_number /= 10; + i--; + } while (0 != parsed_number); + + data += backup; + + return data; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf++) { + const char *dbl_max; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *data++ = '-'; + } + + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) { + *data++ = *dbl_max; + } + + return data; + } + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan++) { + /* NaN becomes 0 because JSON can't support it. */ + *data++ = '0'; + return data; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* insert a '0' to fix the leading decimal point for JSON output. */ + *data++ = '0'; + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + /* insert a '0' to fix the trailing decimal point for JSON output. */ + *data++ = '0'; + + return data; + } + + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; +} + +json_weak char *json_write_string(const struct json_string_s *string, + char *data); +char *json_write_string(const struct json_string_s *string, char *data) { + size_t i; + + *data++ = '"'; /* open the string. */ + + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + *data++ = '\\'; /* escape the control character. */ + *data++ = '"'; + break; + case '\\': + *data++ = '\\'; /* escape the control character. */ + *data++ = '\\'; + break; + case '\b': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'b'; + break; + case '\f': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'f'; + break; + case '\n': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'n'; + break; + case '\r': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'r'; + break; + case '\t': + *data++ = '\\'; /* escape the control character. */ + *data++ = 't'; + break; + default: + *data++ = string->string[i]; + break; + } + } + + *data++ = '"'; /* close the string. */ + + return data; +} + +json_weak char *json_write_minified_array(const struct json_array_s *array, + char *data); +char *json_write_minified_array(const struct json_array_s *array, char *data) { + struct json_array_element_s *element = json_null; + + *data++ = '['; /* open the array. */ + + for (element = array->start; json_null != element; element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_minified_object(const struct json_object_s *object, + char *data); +char *json_write_minified_object(const struct json_object_s *object, + char *data) { + struct json_object_element_s *element = json_null; + + *data++ = '{'; /* open the object. */ + + for (element = object->start; json_null != element; element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + *data++ = ':'; /* ':'s seperate each name/value pair. */ + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); +char *json_write_minified_value(const struct json_value_s *value, char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_minified_array((struct json_array_s *)value->payload, + data); + case json_type_object: + return json_write_minified_object((struct json_object_s *)value->payload, + data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_minified(const struct json_value_s *value, size_t *out_size) { + size_t size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_write_minified_get_value_size(value, &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_minified_value(value, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); + +json_weak int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_array_element_s *element; + + *size += 1; /* '['. */ + + if (0 < array->length) { + /* if we have any elements we need to add a newline after our '['. */ + *size += newline_size; + + *size += array->length - 1; /* ','s seperate each element. */ + + for (element = array->start; json_null != element; + element = element->next) { + /* each element gets an indent. */ + *size += (depth + 1) * indent_size; + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + + /* each element gets a newline too. */ + *size += newline_size; + } + + /* since we wrote out some elements, need to add a newline and indentation. + */ + /* to the trailing ']'. */ + *size += depth * indent_size; + } + + *size += 1; /* ']'. */ + + return 0; +} + +json_weak int +json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size); +int json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_object_element_s *element; + + *size += 1; /* '{'. */ + + if (0 < object->length) { + *size += newline_size; /* need a newline next. */ + + *size += object->length - 1; /* ','s seperate each element. */ + + for (element = object->start; json_null != element; + element = element->next) { + /* each element gets an indent and newline. */ + *size += (depth + 1) * indent_size; + *size += newline_size; + + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + *size += 3; /* seperate each name/value pair with " : ". */ + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + } + + *size += depth * indent_size; + } + + *size += 1; /* '}'. */ + + return 0; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_pretty_get_array_size( + (struct json_array_s *)value->payload, depth, indent_size, newline_size, + size); + case json_type_object: + return json_write_pretty_get_object_size( + (struct json_object_s *)value->payload, depth, indent_size, + newline_size, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); + +json_weak char *json_write_pretty_array(const struct json_array_s *array, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_array(const struct json_array_s *array, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_array_element_s *element; + + *data++ = '['; /* open the array. */ + + if (0 < array->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = array->start; json_null != element; + element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_pretty_object(const struct json_object_s *object, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_object(const struct json_object_s *object, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_object_element_s *element; + + *data++ = '{'; /* open the object. */ + + if (0 < object->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = object->start; json_null != element; + element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + /* " : "s seperate each name/value pair. */ + *data++ = ' '; + *data++ = ':'; + *data++ = ' '; + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_value(const struct json_value_s *value, size_t depth, + const char *indent, const char *newline, + char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_pretty_array((struct json_array_s *)value->payload, depth, + indent, newline, data); + case json_type_object: + return json_write_pretty_object((struct json_object_s *)value->payload, + depth, indent, newline, data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_pretty(const struct json_value_s *value, const char *indent, + const char *newline, size_t *out_size) { + size_t size = 0; + size_t indent_size = 0; + size_t newline_size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_null == indent) { + indent = " "; /* default to two spaces. */ + } + + if (json_null == newline) { + newline = "\n"; /* default to linux newlines. */ + } + + while ('\0' != indent[indent_size]) { + ++indent_size; /* skip non-null terminating characters. */ + } + + while ('\0' != newline[newline_size]) { + ++newline_size; /* skip non-null terminating characters. */ + } + + if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size, + &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_pretty_value(value, 0, indent, newline, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* SHEREDOM_JSON_H_INCLUDED. */ diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h new file mode 100644 index 0000000000..da9f3ca1b5 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h @@ -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 +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) || __has_include() +#include +#define SMTG_HAS_CHARCONV +#endif + +//------------------------------------------------------------------------ +namespace JSON { +namespace Detail { + +//------------------------------------------------------------------------ +template +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 +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 +{ + using Detail::Base::Base; + using VariantT = std::variant; + + std::optional asObject () const; + std::optional asArray () const; + std::optional asString () const; + std::optional asNumber () const; + std::optional asBoolean () const; + std::optional 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 +{ + using Detail::Base::Base; + + std::string_view text () const { return {jsonValue ()->string, jsonValue ()->string_size}; } + + SourceLocation getSourceLocation () const; +}; + +//------------------------------------------------------------------------ +struct Number : Detail::Base +{ + using Detail::Base::Base; + + std::string_view text () const { return {jsonValue ()->number, jsonValue ()->number_size}; } + + std::optional getInteger () const; + std::optional getDouble () const; +}; + +//------------------------------------------------------------------------ +struct ObjectElement : Detail::Base +{ + using Detail::Base::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 +{ + using Detail::Base::Base; + using Iterator = Detail::Iterator; + + 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 +{ + using Detail::Base::Base; + + Value value () const { return Value (jsonValue ()->value); } + + ArrayElement next () const { return ArrayElement (jsonValue ()->next); } +}; + +//------------------------------------------------------------------------ +struct Array : Detail::Base +{ + using Detail::Base::Base; + using Iterator = Detail::Iterator; + + 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 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 Value::asObject () const +{ + if (type () != Type::Object) + return {}; + return Object (json_value_as_object (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asArray () const +{ + if (type () != Type::Array) + return {}; + return Array (json_value_as_array (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asString () const +{ + if (type () != Type::String) + return {}; + return String (json_value_as_string (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asNumber () const +{ + if (type () != Type::Number) + return {}; + return Number (json_value_as_number (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asBoolean () const +{ + if (type () == Type::True || type () == Type::False) + return Boolean (jsonValue ()->type); + return {}; +} + +//------------------------------------------------------------------------ +inline std::optional 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 (jsonValue ()); + return {exValue->offset, exValue->line_no, exValue->row_no}; +} + +//------------------------------------------------------------------------ +inline SourceLocation String::getSourceLocation () const +{ + auto exValue = reinterpret_cast (jsonValue ()); + return {exValue->offset, exValue->line_no, exValue->row_no}; +} + +//------------------------------------------------------------------------ +inline std::optional 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 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h new file mode 100644 index 0000000000..ecf528b7ff --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h @@ -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 +#include + +//------------------------------------------------------------------------ +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; + +//------------------------------------------------------------------------ + struct ClassInfo + { + std::string cid; + std::string category; + std::string name; + std::string vendor; + std::string version; + std::string sdkVersion; + std::vector subCategories; + SnapshotList snapshots; + int32_t cardinality {0x7FFFFFFF}; + uint32_t flags {0}; + }; + +//------------------------------------------------------------------------ + struct Compatibility + { + std::string newCID; + std::vector oldCID; + }; + + using ClassList = std::vector; + using CompatibilityList = std::vector; + + std::string name; + std::string version; + FactoryInfo factoryInfo; + ClassList classes; + CompatibilityList compatibility; +}; + +//------------------------------------------------------------------------ +} // Steinberg diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp new file mode 100644 index 0000000000..d266a0147f --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp @@ -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 +#include +#include + +//------------------------------------------------------------------------ +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 + void value (ValueT val) + { + stream << val; + lastIsComma = false; + } + + template + void object (Proc proc) + { + startObject (); + proc (); + endObject (); + } + + template + void array (Iterator begin, Iterator end, Proc proc) + { + startArray (); + while (begin != end) + { + doBeautify (); + proc (begin); + ++begin; + writeComma (); + } + endArray (); + } + + template + 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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h new file mode 100644 index 0000000000..6be510a983 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h @@ -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 +#include +#include + +//------------------------------------------------------------------------ +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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp new file mode 100644 index 0000000000..b49b4719cf --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp @@ -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 + +//------------------------------------------------------------------------ +namespace Steinberg::ModuleInfoLib { +namespace { + +//------------------------------------------------------------------------ +void printJsonParseError (json_parse_result_s& parseResult, std::ostream& errorOut) +{ + errorOut << "error : " + << JSON::errorToString (static_cast (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 + T getInteger (const JSON::Value& value) const + { + if (auto number = value.asNumber ()) + { + if (auto result = number->getInteger ()) + { + if (result > static_cast (std::numeric_limits::max ()) || + result < static_cast (std::numeric_limits::min ())) + throw parse_error ("Value is out of range here", value); + return static_cast (*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 (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 (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 parseJson (std::string_view jsonData, std::ostream* optErrorOutput) +{ + auto docVar = JSON::Document::parse (jsonData); + if (auto res = std::get_if (&docVar)) + { + if (optErrorOutput) + printJsonParseError (*res, *optErrorOutput); + return {}; + } + auto doc = std::get_if (&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 parseCompatibilityJson (std::string_view jsonData, + std::ostream* optErrorOutput) +{ + auto docVar = JSON::Document::parse (jsonData); + if (auto res = std::get_if (&docVar)) + { + if (optErrorOutput) + printJsonParseError (*res, *optErrorOutput); + return {}; + } + auto doc = std::get_if (&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 diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h new file mode 100644 index 0000000000..910cbf5459 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h @@ -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 +#include +#include + +//------------------------------------------------------------------------ +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 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 parseCompatibilityJson (std::string_view jsonData, + std::ostream* optErrorOutput); + +//------------------------------------------------------------------------ +} // Steinberg::ModuelInfoLib diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h new file mode 100644 index 0000000000..9d648bc556 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h @@ -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 +#include +#include + +//------------------------------------------------------------------------ +namespace VST3 { + +//------------------------------------------------------------------------ +template +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 + } +}; + +//------------------------------------------------------------------------ +} diff --git a/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h new file mode 100644 index 0000000000..19c0438293 --- /dev/null +++ b/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h @@ -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 + +//------------------------------------------------------------------------ +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 + static Optional 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 (uid1.data ()); + const uint64_t* p2 = reinterpret_cast (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 (uid1.data ()); + const uint64_t* p2 = reinterpret_cast (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 ((l1 & 0x000000FF)); + _data[1] = static_cast ((l1 & 0x0000FF00) >> 8); + _data[2] = static_cast ((l1 & 0x00FF0000) >> 16); + _data[3] = static_cast ((l1 & 0xFF000000) >> 24); + _data[4] = static_cast ((l2 & 0x00FF0000) >> 16); + _data[5] = static_cast ((l2 & 0xFF000000) >> 24); + _data[6] = static_cast ((l2 & 0x000000FF)); + _data[7] = static_cast ((l2 & 0x0000FF00) >> 8); + _data[8] = static_cast ((l3 & 0xFF000000) >> 24); + _data[9] = static_cast ((l3 & 0x00FF0000) >> 16); + _data[10] = static_cast ((l3 & 0x0000FF00) >> 8); + _data[11] = static_cast ((l3 & 0x000000FF)); + _data[12] = static_cast ((l4 & 0xFF000000) >> 24); + _data[13] = static_cast ((l4 & 0x00FF0000) >> 16); + _data[14] = static_cast ((l4 & 0x0000FF00) >> 8); + _data[15] = static_cast ((l4 & 0x000000FF)); + } + else + { + _data[0] = static_cast ((l1 & 0xFF000000) >> 24); + _data[1] = static_cast ((l1 & 0x00FF0000) >> 16); + _data[2] = static_cast ((l1 & 0x0000FF00) >> 8); + _data[3] = static_cast ((l1 & 0x000000FF)); + _data[4] = static_cast ((l2 & 0xFF000000) >> 24); + _data[5] = static_cast ((l2 & 0x00FF0000) >> 16); + _data[6] = static_cast ((l2 & 0x0000FF00) >> 8); + _data[7] = static_cast ((l2 & 0x000000FF)); + _data[8] = static_cast ((l3 & 0xFF000000) >> 24); + _data[9] = static_cast ((l3 & 0x00FF0000) >> 16); + _data[10] = static_cast ((l3 & 0x0000FF00) >> 8); + _data[11] = static_cast ((l3 & 0x000000FF)); + _data[12] = static_cast ((l4 & 0xFF000000) >> 24); + _data[13] = static_cast ((l4 & 0x00FF0000) >> 16); + _data[14] = static_cast ((l4 & 0x0000FF00) >> 8); + _data[15] = static_cast ((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 (_data); + const uint64_t* p2 = reinterpret_cast (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 (_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 (_data[i])); + result += s; + } + } + return result; +} + +//------------------------------------------------------------------------ +template +inline Optional 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 (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 (d); + } + return {uid}; + } +} + +//------------------------------------------------------------------------ +inline UID UID::fromTUID (const TUID _uid) noexcept +{ + UID result; + + uint64_t* p1 = reinterpret_cast (result._data); + const uint64_t* p2 = reinterpret_cast (_uid); + p1[0] = p2[0]; + p1[1] = p2[1]; + + return result; +} + +//------------------------------------------------------------------------ +} // VST3