/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "AddonInfoBuilder.h" #include "CompileInfo.h" #include "LangInfo.h" #include "addons/Repository.h" #include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "utils/JSONVariantParser.h" #include "utils/JSONVariantWriter.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include "utils/XBMCTinyXML.h" #include "utils/log.h" #include #include #include namespace { // Note that all of these characters are url-safe const std::string VALID_ADDON_IDENTIFIER_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_@!$"; } namespace ADDON { CAddonInfoBuilderFromDB::CAddonInfoBuilderFromDB() : m_addonInfo(std::make_shared()) { } void CAddonInfoBuilderFromDB::SetId(std::string id) { m_addonInfo->m_id = std::move(id); } void CAddonInfoBuilderFromDB::SetName(std::string name) { m_addonInfo->m_name = std::move(name); } void CAddonInfoBuilderFromDB::SetLicense(std::string license) { m_addonInfo->m_license = std::move(license); } void CAddonInfoBuilderFromDB::SetSummary(std::string summary) { m_addonInfo->m_summary.insert(std::pair("unk", std::move(summary))); } void CAddonInfoBuilderFromDB::SetDescription(std::string description) { m_addonInfo->m_description.insert( std::pair("unk", std::move(description))); } void CAddonInfoBuilderFromDB::SetDisclaimer(std::string disclaimer) { m_addonInfo->m_disclaimer.insert( std::pair("unk", std::move(disclaimer))); } void CAddonInfoBuilderFromDB::SetAuthor(std::string author) { m_addonInfo->m_author = std::move(author); } void CAddonInfoBuilderFromDB::SetSource(std::string source) { m_addonInfo->m_source = std::move(source); } void CAddonInfoBuilderFromDB::SetWebsite(std::string website) { m_addonInfo->m_website = std::move(website); } void CAddonInfoBuilderFromDB::SetForum(std::string forum) { m_addonInfo->m_forum = std::move(forum); } void CAddonInfoBuilderFromDB::SetEMail(std::string email) { m_addonInfo->m_email = std::move(email); } void CAddonInfoBuilderFromDB::SetIcon(std::string icon) { m_addonInfo->m_icon = std::move(icon); } void CAddonInfoBuilderFromDB::SetArt(const std::string& type, std::string value) { m_addonInfo->m_art[type] = std::move(value); } void CAddonInfoBuilderFromDB::SetArt(std::map art) { m_addonInfo->m_art = std::move(art); } void CAddonInfoBuilderFromDB::SetScreenshots(std::vector screenshots) { m_addonInfo->m_screenshots = std::move(screenshots); } void CAddonInfoBuilderFromDB::SetChangelog(std::string changelog) { m_addonInfo->m_changelog.insert(std::pair("unk", std::move(changelog))); } void CAddonInfoBuilderFromDB::SetLifecycleState(AddonLifecycleState state, std::string description) { m_addonInfo->m_lifecycleState = state; m_addonInfo->m_lifecycleStateDescription.emplace("unk", std::move(description)); } void CAddonInfoBuilderFromDB::SetPath(std::string path) { m_addonInfo->m_path = std::move(path); } void CAddonInfoBuilderFromDB::SetLibName(std::string libname) { m_addonInfo->m_libname = std::move(libname); } void CAddonInfoBuilderFromDB::SetVersion(CAddonVersion version) { m_addonInfo->m_version = std::move(version); } void CAddonInfoBuilderFromDB::SetDependencies(std::vector dependencies) { m_addonInfo->m_dependencies = std::move(dependencies); } void CAddonInfoBuilderFromDB::SetExtrainfo(InfoMap extrainfo) { m_addonInfo->m_extrainfo = std::move(extrainfo); } void CAddonInfoBuilderFromDB::SetInstallDate(const CDateTime& installDate) { m_addonInfo->m_installDate = installDate; } void CAddonInfoBuilderFromDB::SetLastUpdated(const CDateTime& lastUpdated) { m_addonInfo->m_lastUpdated = lastUpdated; } void CAddonInfoBuilderFromDB::SetLastUsed(const CDateTime& lastUsed) { m_addonInfo->m_lastUsed = lastUsed; } void CAddonInfoBuilderFromDB::SetOrigin(std::string origin) { m_addonInfo->m_origin = std::move(origin); } void CAddonInfoBuilderFromDB::SetPackageSize(uint64_t size) { m_addonInfo->m_packageSize = size; } void CAddonInfoBuilderFromDB::SetExtensions(CAddonType addonType) { if (!addonType.GetValue("provides").empty()) addonType.SetProvides(addonType.GetValue("provides").asString()); m_addonInfo->m_types.push_back(std::move(addonType)); m_addonInfo->m_mainType = addonType.m_type; } AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& id, AddonType type) { // Check addon identifier for forbidden characters // The identifier is used e.g. in URLs so we shouldn't allow just // any character to go through. if (id.empty() || id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier '{}' is invalid", __FUNCTION__, id); return nullptr; } AddonInfoPtr addon = std::make_shared(); addon->m_id = id; addon->m_mainType = type; return addon; } AddonInfoPtr CAddonInfoBuilder::Generate(const std::string& addonPath, bool platformCheck /*= true*/) { auto addonRealPath = CSpecialProtocol::TranslatePath(addonPath); CXBMCTinyXML xmlDoc; if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(addonRealPath, "addon.xml"))) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Unable to load '{}', Line {}\n{}", __FUNCTION__, URIUtils::AddFileToFolder(addonRealPath, "addon.xml"), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); return nullptr; } AddonInfoPtr addon = std::make_shared(); if (!ParseXML(addon, xmlDoc.RootElement(), addonRealPath)) return nullptr; if (!platformCheck || PlatformSupportsAddon(addon)) return addon; return nullptr; } AddonInfoPtr CAddonInfoBuilder::Generate(const TiXmlElement* baseElement, const RepositoryDirInfo& repo, bool platformCheck /*= true*/) { AddonInfoPtr addon = std::make_shared(); if (!ParseXML(addon, baseElement, repo.datadir, repo)) return nullptr; if (!platformCheck || PlatformSupportsAddon(addon)) return addon; return nullptr; } void CAddonInfoBuilder::SetInstallData(const AddonInfoPtr& addon, const CDateTime& installDate, const CDateTime& lastUpdated, const CDateTime& lastUsed, const std::string& origin) { if (!addon) return; addon->m_installDate = installDate; addon->m_lastUpdated = lastUpdated; addon->m_lastUsed = lastUsed; addon->m_origin = origin; } bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, const TiXmlElement* element, const std::string& addonPath) { return ParseXML(addon, element, addonPath, {}); } bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, const TiXmlElement* element, const std::string& addonPath, const RepositoryDirInfo& repo) { /* * Following values currently not set from creator: * - CDateTime installDate; * - CDateTime lastUpdated; * - CDateTime lastUsed; * - std::string origin; */ if (!StringUtils::EqualsNoCase(element->Value(), "addon")) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file from '{}' doesn't contain ", __FUNCTION__, addonPath); return false; } /* * The function variable "repo" is only used when reading data stored on the internet. * A boolean value is then set here for easier identification. */ const bool isRepoXMLContent = !repo.datadir.empty(); /* * Parse addon.xml: * */ addon->m_id = StringUtils::CreateFromCString(element->Attribute("id")); addon->m_name = StringUtils::CreateFromCString(element->Attribute("name")); addon->m_author = StringUtils::CreateFromCString(element->Attribute("provider-name")); const std::string version = StringUtils::CreateFromCString(element->Attribute("version")); addon->m_version = CAddonVersion(version); if (addon->m_id.empty() || addon->m_version.empty()) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain required values on id='{}', version='{}'", __FUNCTION__, addonPath, addon->m_id.empty() ? "missing" : addon->m_id, addon->m_version.empty() ? "missing" : addon->m_version.asString()); return false; } // Check addon identifier for forbidden characters // The identifier is used e.g. in URLs so we shouldn't allow just // any character to go through. if (addon->m_id.find_first_not_of(VALID_ADDON_IDENTIFIER_CHARACTERS) != std::string::npos) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: identifier {} is invalid", __FUNCTION__, addon->m_id); return false; } /* * Parse addon.xml: * */ const TiXmlElement* backwards = element->FirstChildElement("backwards-compatibility"); if (backwards) { const std::string minVersion = StringUtils::CreateFromCString(backwards->Attribute("abi")); addon->m_minversion = CAddonVersion(minVersion); } /* * Parse addon.xml: * * * */ const TiXmlElement* requires = element->FirstChildElement("requires"); if (requires) { for (const TiXmlElement* child = requires->FirstChildElement("import"); child != nullptr; child = child->NextSiblingElement("import")) { if (child->Attribute("addon")) { const std::string minVersion = StringUtils::CreateFromCString(child->Attribute("minversion")); const std::string version = StringUtils::CreateFromCString(child->Attribute("version")); bool optional = false; child->QueryBoolAttribute("optional", &optional); addon->m_dependencies.emplace_back(child->Attribute("addon"), CAddonVersion(minVersion), CAddonVersion(version), optional); } } } std::string assetBasePath; if (!isRepoXMLContent && !addonPath.empty()) { // Default for add-on information not loaded from repository assetBasePath = addonPath; addon->m_path = addonPath; } else { assetBasePath = URIUtils::AddFileToFolder(repo.artdir, addon->m_id); addon->m_path = URIUtils::AddFileToFolder(repo.datadir, addon->m_id, StringUtils::Format("{}-{}.zip", addon->m_id, addon->m_version.asString())); } addon->m_profilePath = StringUtils::Format("special://profile/addon_data/{}/", addon->m_id); /* * Parse addon.xml: * * ... * */ for (const TiXmlElement* child = element->FirstChildElement("extension"); child != nullptr; child = child->NextSiblingElement("extension")) { const std::string point = StringUtils::CreateFromCString(child->Attribute("point")); if (point == "kodi.addon.metadata" || point == "xbmc.addon.metadata") { /* * Parse addon.xml "..." (special related to repository path), * do first and if present override the default. Also set assetBasePath to * find screenshots and icons. */ element = child->FirstChildElement("path"); if (element && element->GetText() != nullptr && !repo.datadir.empty()) { addon->m_path = URIUtils::AddFileToFolder(repo.datadir, element->GetText()); assetBasePath = URIUtils::GetDirectory(URIUtils::AddFileToFolder(repo.artdir, element->GetText())); } /* * Parse addon.xml "..." */ GetTextList(child, "summary", addon->m_summary); /* * Parse addon.xml "..." */ GetTextList(child, "description", addon->m_description); /* * Parse addon.xml "..." */ GetTextList(child, "disclaimer", addon->m_disclaimer); /* * Parse addon.xml "..." */ const TiXmlElement* element = child->FirstChildElement("assets"); if (element) { for (const TiXmlElement* elementsAssets = element->FirstChildElement(); elementsAssets != nullptr; elementsAssets = elementsAssets->NextSiblingElement()) { std::string value = elementsAssets->Value(); if (value == "icon") { if (elementsAssets->GetText() != nullptr) addon->m_icon = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); } else if (value == "screenshot") { if (elementsAssets->GetText() != nullptr) addon->m_screenshots.emplace_back(URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText())); } else if (value == "fanart") { if (elementsAssets->GetText() != nullptr) addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); } else if (value == "banner") { if (elementsAssets->GetText() != nullptr) addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); } else if (value == "clearlogo") { if (elementsAssets->GetText() != nullptr) addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); } else if (value == "thumb") { if (elementsAssets->GetText() != nullptr) addon->m_art[value] = URIUtils::AddFileToFolder(assetBasePath, elementsAssets->GetText()); } } } /* Parse addon.xml "..." */ element = child->FirstChildElement("platform"); if (element && element->GetText() != nullptr) { auto platforms = StringUtils::Split(element->GetText(), {" ", "\t", "\n", "\r"}); platforms.erase(std::remove_if(platforms.begin(), platforms.end(), [](const std::string& platform) { return platform.empty(); }), platforms.cend()); addon->m_platforms = platforms; } /* Parse addon.xml "..." */ element = child->FirstChildElement("license"); if (element && element->GetText() != nullptr) addon->m_license = element->GetText(); /* Parse addon.xml "..." */ element = child->FirstChildElement("source"); if (element && element->GetText() != nullptr) addon->m_source = element->GetText(); /* Parse addon.xml "..." */ element = child->FirstChildElement("email"); if (element && element->GetText() != nullptr) addon->m_email = element->GetText(); /* Parse addon.xml "..." */ element = child->FirstChildElement("website"); if (element && element->GetText() != nullptr) addon->m_website = element->GetText(); /* Parse addon.xml "..." */ element = child->FirstChildElement("forum"); if (element && element->GetText() != nullptr) addon->m_forum = element->GetText(); /* Parse addon.xml "..." * NOTE: Replaced with , available for backward compatibility */ element = child->FirstChildElement("broken"); if (element && element->GetText() != nullptr) { addon->m_lifecycleState = AddonLifecycleState::BROKEN; addon->m_lifecycleStateDescription.emplace(KODI_ADDON_DEFAULT_LANGUAGE_CODE, element->GetText()); } /* Parse addon.xml "..." */ element = child->FirstChildElement("lifecyclestate"); if (element && element->GetText() != nullptr) { const char* lang = element->Attribute("type"); if (lang) { if (strcmp(lang, "broken") == 0) addon->m_lifecycleState = AddonLifecycleState::BROKEN; else if (strcmp(lang, "deprecated") == 0) addon->m_lifecycleState = AddonLifecycleState::DEPRECATED; else addon->m_lifecycleState = AddonLifecycleState::NORMAL; GetTextList(child, "lifecyclestate", addon->m_lifecycleStateDescription); } } /* Parse addon.xml "..." */ element = child->FirstChildElement("language"); if (element && element->GetText() != nullptr) addon->AddExtraInfo("language", element->GetText()); /* Parse addon.xml "..." */ element = child->FirstChildElement("reuselanguageinvoker"); if (element && element->GetText() != nullptr) addon->AddExtraInfo("reuselanguageinvoker", element->GetText()); /* Parse addon.xml "..." */ element = child->FirstChildElement("size"); if (element && element->GetText() != nullptr) addon->m_packageSize = StringUtils::ToUint64(element->GetText(), 0); /* Parse addon.xml "..." * * In the event that the changelog (news) in addon.xml is empty, check * whether it is an installed addon and read a changelog.txt as a * replacement, if available. */ GetTextList(child, "news", addon->m_changelog); if (addon->m_changelog.empty() && !isRepoXMLContent && !addonPath.empty()) { using XFILE::CFile; const std::string changelog = URIUtils::AddFileToFolder(addonPath, "changelog.txt"); if (CFile::Exists(changelog)) { CFile file; std::vector buf; if (file.LoadFile(changelog, buf) > 0) addon->m_changelog[KODI_ADDON_DEFAULT_LANGUAGE_CODE].assign( reinterpret_cast(buf.data()), buf.size()); } } } else { AddonType type = CAddonInfo::TranslateType(point); if (type == AddonType::UNKNOWN || type >= AddonType::MAX_TYPES) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain a valid add-on type name ({})", __FUNCTION__, addon->m_path, point); return false; } CAddonType addonType(type); if (ParseXMLTypes(addonType, addon, child)) addon->m_types.emplace_back(std::move(addonType)); } } /* * If nothing is defined in addon.xml set addon as unknown to have minimum one * instance type present. */ if (addon->m_types.empty()) { CAddonType addonType(AddonType::UNKNOWN); addon->m_types.emplace_back(std::move(addonType)); } addon->m_mainType = addon->m_types[0].Type(); addon->m_libname = addon->m_types[0].m_libname; if (!addon->m_types[0].GetValue("provides").empty()) addon->AddExtraInfo("provides", addon->m_types[0].GetValue("provides").asString()); // Ensure binary types have a valid library for the platform if (addon->m_mainType == AddonType::VISUALIZATION || addon->m_mainType == AddonType::SCREENSAVER || addon->m_mainType == AddonType::PVRDLL || addon->m_mainType == AddonType::AUDIOENCODER || addon->m_mainType == AddonType::AUDIODECODER || addon->m_mainType == AddonType::VFS || addon->m_mainType == AddonType::IMAGEDECODER || addon->m_mainType == AddonType::INPUTSTREAM || addon->m_mainType == AddonType::PERIPHERALDLL || addon->m_mainType == AddonType::GAMEDLL) { if (addon->m_libname.empty()) { // Prevent log file entry if data is from repository, there normal on // addons for other OS's if (!isRepoXMLContent) CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml from '{}' for binary type '{}' doesn't contain library and addon becomes ignored", __FUNCTION__, addon->ID(), CAddonInfo::TranslateType(addon->m_mainType)); return false; } } if (!isRepoXMLContent) { using XFILE::CFile; if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "settings.xml"))) addon->m_supportsAddonSettings = true; if (CFile::Exists(URIUtils::AddFileToFolder(addonPath, "resources", "instance-settings.xml"))) addon->m_supportsInstanceSettings = true; } addon->m_addonInstanceSupportType = CAddonInfo::InstanceSupportType(addon->m_mainType); return true; } bool CAddonInfoBuilder::ParseXMLTypes(CAddonType& addonType, const AddonInfoPtr& info, const TiXmlElement* child) { if (child) { addonType.m_path = info->Path(); // Get add-on library file name (if present) const char* library = child->Attribute("library"); if (library == nullptr) library = GetPlatformLibraryName(child); if (library != nullptr) { addonType.m_libname = library; try { // linux is different and has the version number after the suffix static const std::regex libRegex("^.*" + CCompileInfo::CCompileInfo::GetSharedLibrarySuffix() + "\\.?[0-9]*\\.?[0-9]*\\.?[0-9]*$"); if (std::regex_match(library, libRegex)) { info->SetBinary(true); CLog::Log(LOGDEBUG, "CAddonInfoBuilder::{}: Binary addon found: {}", __func__, info->ID()); } } catch (const std::regex_error& e) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: Regex error caught: {}", __func__, e.what()); } } if (!ParseXMLExtension(addonType, child)) { CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: addon.xml file doesn't contain a valid add-on extensions ({})", __FUNCTION__, info->ID()); return false; } if (!addonType.GetValue("provides").empty()) addonType.SetProvides(addonType.GetValue("provides").asString()); return true; } return false; } bool CAddonInfoBuilder::ParseXMLExtension(CAddonExtensions& addonExt, const TiXmlElement* element) { addonExt.m_point = StringUtils::CreateFromCString(element->Attribute("point")); EXT_VALUE extension; const TiXmlAttribute* attribute = element->FirstAttribute(); while (attribute) { std::string name = attribute->Name(); if (name != "point") { const std::string value = StringUtils::CreateFromCString(attribute->Value()); if (!value.empty()) { name = "@" + name; extension.emplace_back(std::make_pair(name, SExtValue(value))); } } attribute = attribute->Next(); } if (!extension.empty()) addonExt.m_values.emplace_back(std::pair("", std::move(extension))); const TiXmlElement* childElement = element->FirstChildElement(); while (childElement) { const std::string id = StringUtils::CreateFromCString(childElement->Value()); if (!id.empty()) { EXT_VALUE extension; const TiXmlAttribute* attribute = childElement->FirstAttribute(); while (attribute) { std::string name = attribute->Name(); if (name != "point") { const std::string value = StringUtils::CreateFromCString(attribute->Value()); if (!value.empty()) { name = id + "@" + name; extension.emplace_back(std::make_pair(name, SExtValue(value))); } } attribute = attribute->Next(); } const std::string childElementText = StringUtils::CreateFromCString(childElement->GetText()); if (!childElementText.empty()) { extension.emplace_back(std::make_pair(id, SExtValue(childElementText))); } if (!extension.empty()) addonExt.m_values.emplace_back(std::make_pair(id, std::move(extension))); if (childElementText.empty()) { const TiXmlElement* childSubElement = childElement->FirstChildElement(); if (childSubElement) { CAddonExtensions subElement; if (ParseXMLExtension(subElement, childElement)) addonExt.m_children.emplace_back(std::make_pair(id, std::move(subElement))); } } } childElement = childElement->NextSiblingElement(); } return true; } bool CAddonInfoBuilder::GetTextList(const TiXmlElement* element, const std::string& tag, std::unordered_map& translatedValues) { if (!element) return false; translatedValues.clear(); for (const TiXmlElement* child = element->FirstChildElement(tag); child != nullptr; child = child->NextSiblingElement(tag)) { const char* lang = child->Attribute("lang"); const char* text = child->GetText(); if (lang != nullptr) { if (strcmp(lang, "no") == 0) translatedValues.insert(std::make_pair("nb_NO", text != nullptr ? text : "")); else translatedValues.insert(std::make_pair(lang, text != nullptr ? text : "")); } else translatedValues.insert( std::make_pair(KODI_ADDON_DEFAULT_LANGUAGE_CODE, text != nullptr ? text : "")); } return !translatedValues.empty(); } const char* CAddonInfoBuilder::GetPlatformLibraryName(const TiXmlElement* element) { const char* libraryName; #if defined(TARGET_ANDROID) libraryName = element->Attribute("library_android"); #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) #if defined(TARGET_FREEBSD) libraryName = element->Attribute("library_freebsd"); if (libraryName == nullptr) #endif libraryName = element->Attribute("library_linux"); #elif defined(TARGET_WINDOWS_DESKTOP) libraryName = element->Attribute("library_windx"); if (libraryName == nullptr) libraryName = element->Attribute("library_windows"); #elif defined(TARGET_WINDOWS_STORE) libraryName = element->Attribute("library_windowsstore"); #elif defined(TARGET_DARWIN) #if defined(TARGET_DARWIN_EMBEDDED) libraryName = element->Attribute("library_darwin_embedded"); #else libraryName = element->Attribute("library_osx"); #endif #endif return libraryName; } bool CAddonInfoBuilder::PlatformSupportsAddon(const AddonInfoPtr& addon) { if (addon->m_platforms.empty()) return true; std::vector supportedPlatforms = { "all", #if defined(TARGET_ANDROID) "android", #if defined(__ARM_ARCH_7A__) "android-armv7", #elif defined(__aarch64__) "android-aarch64", #elif defined(__i686__) "android-i686", #elif defined(__x86_64__) "android-x86_64", #else #warning no architecture dependant platform tag #endif #elif defined(TARGET_FREEBSD) "freebsd", #elif defined(TARGET_LINUX) "linux", #if defined(__ARM_ARCH_7A__) "linux-armv7", #elif defined(__aarch64__) "linux-aarch64", #elif defined(__i686__) "linux-i686", #elif defined(__x86_64__) "linux-x86_64", #else #warning no architecture dependant platform tag #endif #elif defined(TARGET_WINDOWS_DESKTOP) "windx", "windows", #if defined(_M_IX86) "windows-i686", #elif defined(_M_AMD64) "windows-x86_64", #else #error no architecture dependant platform tag #endif #elif defined(TARGET_WINDOWS_STORE) "windowsstore", #elif defined(TARGET_DARWIN_EMBEDDED) "darwin_embedded", #if defined(TARGET_DARWIN_IOS) "ios", #if defined(__aarch64__) "ios-aarch64", #else #warning no architecture dependant platform tag #endif #elif defined(TARGET_DARWIN_TVOS) "tvos", "tvos-aarch64", #endif #elif defined(TARGET_DARWIN_OSX) "osx", #if defined(__x86_64__) "osx64", "osx-x86_64", #elif defined(__aarch64__) "osxarm64", "osx-arm64", #else #warning no architecture dependant platform tag #endif #endif }; return std::find_first_of(addon->m_platforms.begin(), addon->m_platforms.end(), supportedPlatforms.begin(), supportedPlatforms.end()) != addon->m_platforms.end(); } }