From eb2a53ca250ad236f6803a8b89e7a5ebac236993 Mon Sep 17 00:00:00 2001 From: jyvenugo <48108725+jyvenugo@users.noreply.github.com> Date: Fri, 6 Nov 2020 11:20:41 -0800 Subject: [PATCH] Implement flat bundle creation (#389) * Attempt to create bundle method * ProcessCommonOptions method * Pass flags * Update * Create appxbundlefactory and bundlewriter * Added interfaces to implement * edit bundle options * Remove comments * Create vector of packages to write to bundle manifest * Create bundle manifest bundle element * Write Bundle Manifest Elements * Add AppxBundleManifest, BlockMap, contenttype.xml to zip * Validations to store fields into manifest * Write Resources and Dependencies to Bundle Manifest * Fetches correct value for resources and tdfs * Created AppxBundleWriter and BundleWriterHelper files * Writer verion to manifest, address PR comments * process common and input options * Use stdstrin instead of wchar * Address PR comments * Removed closeinternal from AppxBundleWriter.cpp * Use exceptions instead of hresult * Implement AppxManifestReader->GetQualifiedResources * Write resources to bundle manifest * Write correct namespaces to manifest file * Code review comments * Resolve build on other platforms * Add packbundle to if MSIX_PACK * Build should pass now * Remove ectra qualification for build to pass * add to msix_pack * STATFLAG_NONAME error * Resolve compiler error * UINT64 and std::uint64 compiler error * Use std::time instead of systemtime * Build break * resolve UINT64 ios compiler error * Comment out arm64 mac jobs- known config issue * Mac pipeline failure * Remove Text data structure as per PR feedback * gitignore, move zlib file, update filename to versionhelpers * Resolve build * Remove thumbs.db file --- .gitignore | 2 + pipelines/azure-pipelines-macos.yml | 102 ++++---- src/inc/internal/AppxBundleWriter.hpp | 78 ++++++ src/inc/internal/AppxManifestObject.hpp | 83 ++++++- src/inc/internal/BundleManifestWriter.hpp | 64 +++++ src/inc/internal/BundleWriterHelper.hpp | 79 +++++++ src/inc/internal/ContentType.hpp | 1 + src/inc/internal/FileNameValidation.hpp | 2 +- src/inc/internal/IXml.hpp | 1 + src/inc/internal/VersionHelpers.hpp | 12 + src/inc/internal/XmlWriter.hpp | 1 + src/inc/internal/ZipObjectWriter.hpp | 2 + src/inc/public/AppxPackaging.hpp | 46 ++++ src/makemsix/main.cpp | 95 +++++++- src/msix/CMakeLists.txt | 7 +- src/msix/common/AppxFactory.cpp | 11 +- src/msix/common/AppxManifestObject.cpp | 28 ++- src/msix/common/FileNameValidation.cpp | 14 +- src/msix/common/IXml.cpp | 1 + src/msix/msix.cpp | 100 ++++++++ src/msix/pack/AppxBundleWriter.cpp | 233 ++++++++++++++++++ src/msix/pack/AppxPackageWriter.cpp | 6 +- src/msix/pack/BundleManifestWriter.cpp | 275 ++++++++++++++++++++++ src/msix/pack/BundleWriterHelper.cpp | 244 +++++++++++++++++++ src/msix/pack/ContentType.cpp | 18 ++ src/msix/pack/VersionHelpers.cpp | 34 +++ 26 files changed, 1474 insertions(+), 65 deletions(-) create mode 100644 src/inc/internal/AppxBundleWriter.hpp create mode 100644 src/inc/internal/BundleManifestWriter.hpp create mode 100644 src/inc/internal/BundleWriterHelper.hpp create mode 100644 src/inc/internal/VersionHelpers.hpp create mode 100644 src/msix/pack/AppxBundleWriter.cpp create mode 100644 src/msix/pack/BundleManifestWriter.cpp create mode 100644 src/msix/pack/BundleWriterHelper.cpp create mode 100644 src/msix/pack/VersionHelpers.cpp diff --git a/.gitignore b/.gitignore index a8f98d4c..cdb40cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -406,3 +406,5 @@ src/msix/common/MSIXResource.cpp src/test/MacOS-Linux/testApiResults.txt src/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.xcworkspace/ src/test/mobile/TEST-MsixSDK-AOSP.xml + +Thumbs.db diff --git a/pipelines/azure-pipelines-macos.yml b/pipelines/azure-pipelines-macos.yml index 0618042e..fbb28e51 100644 --- a/pipelines/azure-pipelines-macos.yml +++ b/pipelines/azure-pipelines-macos.yml @@ -50,21 +50,21 @@ jobs: _arguments: -b Debug --pack _artifact: MACOSchk-pack # arm64 - debug_nopack_arm64: - _arguments: -b Debug -arch arm64 --skip-tests - _artifact: MACOSarm64chk - release_nopack_arm64: - _arguments: -b MinSizeRel -arch arm64 --skip-tests - _artifact: MACOSarm64 - release_nobundle_arm64: - _arguments: -b MinSizeRel -sb -arch arm64 --skip-tests - _artifact: MACOSarm64-nobundle - release_pack_arm64: - _arguments: -b MinSizeRel --pack -arch arm64 --skip-tests - _artifact: MACOSarm64-pack - debug_pack_arm64: - _arguments: -b Debug --pack -arch arm64 --skip-tests - _artifact: MACOSarm64chk-pack + #debug_nopack_arm64: + # _arguments: -b Debug -arch arm64 --skip-tests + # _artifact: MACOSarm64chk + #release_nopack_arm64: + # _arguments: -b MinSizeRel -arch arm64 --skip-tests + # _artifact: MACOSarm64 + #release_nobundle_arm64: + # _arguments: -b MinSizeRel -sb -arch arm64 --skip-tests + # _artifact: MACOSarm64-nobundle + #release_pack_arm64: + # _arguments: -b MinSizeRel --pack -arch arm64 --skip-tests + # _artifact: MACOSarm64-pack + #debug_pack_arm64: + # _arguments: -b Debug --pack -arch arm64 --skip-tests + # _artifact: MACOSarm64chk-pack steps: # Az Pipelines has Xcode 11.6 as default. For arm64, change to supported Xcode. @@ -119,41 +119,41 @@ jobs: ArtifactName: $(_artifact) condition: succeededOrFailed() -- job: macOS_universal_nopack - dependsOn: - - 'macOS' - pool: - name: Azure Pipelines - vmImage: macOS-latest - steps: - - template: templates/macos-universal.yml - parameters: - artifact_output: MACOS-Universal - artifact_x86: MACOS - artifact_arm64: MACOSarm64 +#- job: macOS_universal_nopack +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-Universal +# artifact_x86: MACOS +# artifact_arm64: MACOSarm64 -- job: macOS_universal_nobundle - dependsOn: - - 'macOS' - pool: - name: Azure Pipelines - vmImage: macOS-latest - steps: - - template: templates/macos-universal.yml - parameters: - artifact_output: MACOS-nobundle-Universal - artifact_x86: MACOS-nobundle - artifact_arm64: MACOSarm64-nobundle +#- job: macOS_universal_nobundle +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-nobundle-Universal +# artifact_x86: MACOS-nobundle +# artifact_arm64: MACOSarm64-nobundle -- job: macOS_universal_pack - dependsOn: - - 'macOS' - pool: - name: Azure Pipelines - vmImage: macOS-latest - steps: - - template: templates/macos-universal.yml - parameters: - artifact_output: MACOS-pack-Universal - artifact_x86: MACOS-pack - artifact_arm64: MACOSarm64-pack +#- job: macOS_universal_pack +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-pack-Universal +# artifact_x86: MACOS-pack +# artifact_arm64: MACOSarm64-pack diff --git a/src/inc/internal/AppxBundleWriter.hpp b/src/inc/internal/AppxBundleWriter.hpp new file mode 100644 index 00000000..eaec3063 --- /dev/null +++ b/src/inc/internal/AppxBundleWriter.hpp @@ -0,0 +1,78 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "AppxBlockMapWriter.hpp" +#include "ContentTypeWriter.hpp" +#include "ZipObjectWriter.hpp" +#include "BundleWriterHelper.hpp" +#include "BundleManifestWriter.hpp" +#include "AppxPackageInfo.hpp" + +// internal interface +// {ca90bcd9-78a2-4773-820c-0b687de49f99} +#ifndef WIN32 +interface IBundleWriter : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IBundleWriter : public IUnknown +#endif +{ +public: + virtual void ProcessBundlePayload(const MSIX::ComPtr& from, bool flatBundle) = 0; + +}; +MSIX_INTERFACE(IBundleWriter, 0xca90bcd9,0x78a2,0x4773,0x82,0x0c,0x0b,0x68,0x7d,0xe4,0x9f,0x99); + +namespace MSIX { + class AppxBundleWriter final : public ComClass + { + public: + AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion); + ~AppxBundleWriter() {}; + + // IBundleWriter + void ProcessBundlePayload(const ComPtr& from, bool flatBundle) override; + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept override; + HRESULT STDMETHODCALLTYPE Close() noexcept override; + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AddPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddExternalPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + + protected: + typedef enum + { + Open = 1, + Closed = 2, + Failed = 3 + } + WriterState; + + void AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride = false); + + void AddPackageReferenceInternal(std::string fileName, IStream* packageStream, + bool isDefaultApplicablePackage); + + WriterState m_state; + ComPtr m_factory; + ComPtr m_zipWriter; + BlockMapWriter m_blockMapWriter; + ContentTypeWriter m_contentTypeWriter; + BundleWriterHelper m_bundleWriterHelper; + }; +} + diff --git a/src/inc/internal/AppxManifestObject.hpp b/src/inc/internal/AppxManifestObject.hpp index fc3c6d0c..0c013f6f 100644 --- a/src/inc/internal/AppxManifestObject.hpp +++ b/src/inc/internal/AppxManifestObject.hpp @@ -32,9 +32,37 @@ public: }; MSIX_INTERFACE(IAppxManifestObject, 0xeff6d561,0xa236,0x4058,0x9f,0x1d,0x8f,0x93,0x63,0x3f,0xba,0x4b); +// {daf72e2b-6252-4ed3-a476-5fb656aa0e2c} +#ifndef WIN32 +interface IAppxManifestTargetDeviceFamilyInternal : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IAppxManifestTargetDeviceFamilyInternal : public IUnknown +#endif +{ +public: + virtual const std::string& GetName() = 0; +}; +MSIX_INTERFACE(IAppxManifestTargetDeviceFamilyInternal, 0xdaf72e2b,0x6252,0x4ed3,0xa4,0x76,0x5f,0xb6,0x56,0xaa,0x0e,0x2c); + +// {9e2fb304-cec6-4ef0-8df3-10bb2ce714a3} +#ifndef WIN32 +interface IAppxManifestQualifiedResourceInternal : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IAppxManifestQualifiedResourceInternal : public IUnknown +#endif +{ +public: + virtual const std::string& GetLanguage() = 0; +}; +MSIX_INTERFACE(IAppxManifestQualifiedResourceInternal, 0x9e2fb304,0xcec6,0x4ef0,0x8d,0xf3,0x10,0xbb,0x2c,0xe7,0x14,0xa3); + namespace MSIX { - class AppxManifestTargetDeviceFamily final : public ComClass + class AppxManifestTargetDeviceFamily final : public ComClass { public: AppxManifestTargetDeviceFamily(IMsixFactory* factory, std::string& name, std::string& minVersion, std::string& maxVersion) : @@ -70,6 +98,12 @@ namespace MSIX { return m_factory->MarshalOutStringUtf8(m_name, name); } CATCH_RETURN(); + //IAppxManifestTargetDeviceFamilyInternal + const std::string& GetName() override + { + return m_name; + } + protected: ComPtr m_factory; std::string m_name; @@ -299,6 +333,51 @@ namespace MSIX { std::string m_packageFamilyName; }; + class AppxManifestQualifiedResource final : public ComClass + { + public: + AppxManifestQualifiedResource(IMsixFactory* factory, std::string& language, std::string& scale, std::string& DXFeatureLevel) : + m_factory(factory), m_language(language) + { + //TODO: Process and assign scale and DXFeatureLevel + } + + // IAppxManifestQualifiedResource + HRESULT STDMETHODCALLTYPE GetLanguage(LPWSTR *language) noexcept override try + { + ThrowErrorIf(Error::InvalidParameter, (language == nullptr || *language != nullptr), "bad pointer"); + return m_factory->MarshalOutString(m_language, language); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE GetScale(UINT32 *scale) noexcept override try + { + return static_cast(Error::NotImplemented); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE GetDXFeatureLevel(DX_FEATURE_LEVEL *dxFeatureLevel) noexcept override try + { + return static_cast(Error::NotImplemented); + } CATCH_RETURN(); + + // IAppxManifestQualifiedResourceUtf8 + HRESULT STDMETHODCALLTYPE GetLanguage(LPSTR *language) noexcept override try + { + return m_factory->MarshalOutStringUtf8(m_language, language); + } CATCH_RETURN(); + + // IAppxManifestQualifiedResourceInternal + const std::string& GetLanguage() override + { + return m_language; + } + + protected: + ComPtr m_factory; + std::string m_language; + std::uint32_t m_scale; + DX_FEATURE_LEVEL m_DXFeatureLevel; + }; + // Object backed by AppxManifest.xml class AppxManifestObject final : public ComClass, IAppxManifestReader5, IVerifierObject, IAppxManifestObject, IMsixDocumentElement> @@ -319,7 +398,7 @@ namespace MSIX { // IAppxManifestReader2 HRESULT STDMETHODCALLTYPE GetQualifiedResources(IAppxManifestQualifiedResourcesEnumerator **resources) noexcept override; - + // IAppxManifestReader3 HRESULT STDMETHODCALLTYPE GetCapabilitiesByCapabilityClass( APPX_CAPABILITY_CLASS_TYPE capabilityClass, diff --git a/src/inc/internal/BundleManifestWriter.hpp b/src/inc/internal/BundleManifestWriter.hpp new file mode 100644 index 00000000..5baf1f70 --- /dev/null +++ b/src/inc/internal/BundleManifestWriter.hpp @@ -0,0 +1,64 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "XmlWriter.hpp" +#include "ComHelper.hpp" +#include "Exceptions.hpp" +#include "UnicodeConversion.hpp" +#include "VersionHelpers.hpp" + +#include + +namespace MSIX { + + struct PackageInfo + { + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE type; + std::uint64_t version; + std::string architecture; + std::string resourceId; + std::string fileName; + std::uint64_t size; + std::uint64_t offset; + ComPtr resources; + bool isDefaultApplicablePackage; + ComPtr tdfs; + }; + + struct OptionalBundleInfo + { + std::string name; + std::string publisher; + std::uint64_t version; + std::string fileName; + std::vector optionalPackages; + }; + + class BundleManifestWriter final + { + public: + BundleManifestWriter(); + void StartBundleManifest(std::string targetXmlNamespace, + std::string name, std::string publisher, std::uint64_t version); + void StartBundleElement(); + void WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version); + void StartPackagesElement(); + void WritePackageElement(PackageInfo packageInfo); + void WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources); + void WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs); + void EndPackagesElement(); + void Close(); + + ComPtr GetStream() { return m_xmlWriter.GetStream(); } + std::string GetQualifiedName(std::string namespaceAlias, std::string name); + std::string GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name); + + protected: + XmlWriter m_xmlWriter; + std::string targetXmlNamespace; + + }; +} \ No newline at end of file diff --git a/src/inc/internal/BundleWriterHelper.hpp b/src/inc/internal/BundleWriterHelper.hpp new file mode 100644 index 00000000..7ae083a8 --- /dev/null +++ b/src/inc/internal/BundleWriterHelper.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "AppxBlockMapWriter.hpp" +#include "ContentTypeWriter.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxPackageInfo.hpp" +#include "BundleManifestWriter.hpp" + +#include + +namespace MSIX { + + class BundleWriterHelper + { + public: + BundleWriterHelper(); + + std::uint64_t GetStreamSize(IStream* stream); + + void AddPackage(std::string fileName, IAppxPackageReader* packageReader, std::uint64_t bundleOffset, + std::uint64_t packageSize, bool isDefaultApplicableResource); + + void GetValidatedPackageData( + std::string fileName, + IAppxPackageReader* packageReader, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, + IAppxManifestPackageId** packageId, + IAppxManifestQualifiedResourcesEnumerator** resources, + IAppxManifestTargetDeviceFamiliesEnumerator** tdfs); + + void AddValidatedPackageData( + std::string fileName, + std::uint64_t bundleOffset, + std::uint64_t packageSize, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, + ComPtr packageId, + bool isDefaultApplicablePackage, + IAppxManifestQualifiedResourcesEnumerator* resources, + IAppxManifestTargetDeviceFamiliesEnumerator* tdfs); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE GetPayloadPackageType( + IAppxManifestReader* packageManifestReader, + std::string fileName); + + void ValidateNameAndPublisher( + IAppxManifestPackageIdInternal* packageId, + std::string filename); + + void ValidateApplicationElement( + IAppxManifestReader* packageManifestReader, + std::string fileName); + + void AddPackageInfoToVector(std::vector& packagesVector, PackageInfo packageInfo); + + void EndBundleManifest(); + + ComPtr GetBundleManifestStream() { return m_bundleManifestWriter.GetStream(); } + + void SetBundleVersion(std::uint64_t bundleVersion) { this->bundleVersion = bundleVersion; } + + std::uint64_t GetBundleVersion() { return this->bundleVersion; } + + std::vector GetPayloadPackages() { return payloadPackages; } + + private: + std::vector payloadPackages; + std::map optionalBundles; + bool hasExternalPackages; + bool hasDefaultOrNeutralResources; + std::string mainPackageName; + std::string mainPackagePublisher; + std::uint64_t bundleVersion; + + BundleManifestWriter m_bundleManifestWriter; + }; +} \ No newline at end of file diff --git a/src/inc/internal/ContentType.hpp b/src/inc/internal/ContentType.hpp index 12cddfcb..f431c6d7 100644 --- a/src/inc/internal/ContentType.hpp +++ b/src/inc/internal/ContentType.hpp @@ -23,6 +23,7 @@ namespace MSIX { static const ContentType& GetContentTypeByExtension(std::string& ext); static const std::string GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE footprintFile); + static const std::string GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE footprintFile); private: APPX_COMPRESSION_OPTION m_compressionOpt; diff --git a/src/inc/internal/FileNameValidation.hpp b/src/inc/internal/FileNameValidation.hpp index 08914f94..72d6818f 100644 --- a/src/inc/internal/FileNameValidation.hpp +++ b/src/inc/internal/FileNameValidation.hpp @@ -13,7 +13,7 @@ namespace MSIX { { static bool IsFileNameValid(const std::string& name); static bool IsIdentifierValid(const std::string& name); - static bool IsFootPrintFile(const std::string& fileName); + static bool IsFootPrintFile(const std::string& fileName, bool isBundle); static bool IsReservedFolder(const std::string& fileName); }; } diff --git a/src/inc/internal/IXml.hpp b/src/inc/internal/IXml.hpp index 49504e5c..c9eef249 100644 --- a/src/inc/internal/IXml.hpp +++ b/src/inc/internal/IXml.hpp @@ -77,6 +77,7 @@ enum class XmlAttributeName : std::uint8_t Package_Applications_Application_Id, Category, MaxMajorVersionTested, + DXFeatureLevel, }; // {ac94449e-442d-4bed-8fca-83770c0f7ee9} diff --git a/src/inc/internal/VersionHelpers.hpp b/src/inc/internal/VersionHelpers.hpp new file mode 100644 index 00000000..8fa519e0 --- /dev/null +++ b/src/inc/internal/VersionHelpers.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "MSIXWindows.hpp" +#include "UnicodeConversion.hpp" + +namespace MSIX { + + std::uint64_t ConvertVersionStringToUint64(const std::string& versionString); + + std::string ConvertVersionToString(std::uint64_t version); +} + diff --git a/src/inc/internal/XmlWriter.hpp b/src/inc/internal/XmlWriter.hpp index a945bebb..42a5cc39 100644 --- a/src/inc/internal/XmlWriter.hpp +++ b/src/inc/internal/XmlWriter.hpp @@ -14,6 +14,7 @@ namespace MSIX { // common attribute names static const char* xmlnsAttribute = "xmlns"; + static const char* xmlNamespaceDelimiter = ":"; // This is a super light xml writer that doesn't use any xml libraries and // just writes to a stream the basics of an xml file. diff --git a/src/inc/internal/ZipObjectWriter.hpp b/src/inc/internal/ZipObjectWriter.hpp index 43c5b66f..78b51649 100644 --- a/src/inc/internal/ZipObjectWriter.hpp +++ b/src/inc/internal/ZipObjectWriter.hpp @@ -14,6 +14,8 @@ #include #include +#include + // {350dd671-0c40-4cd7-9a5b-27456d604bd0} #ifndef WIN32 interface IZipWriter : public IUnknown diff --git a/src/inc/public/AppxPackaging.hpp b/src/inc/public/AppxPackaging.hpp index 6e037fe9..c0183dc1 100644 --- a/src/inc/public/AppxPackaging.hpp +++ b/src/inc/public/AppxPackaging.hpp @@ -78,6 +78,7 @@ SpecializeUuidOfImpl(IAppxManifestQualifiedResourcesEnumerator); SpecializeUuidOfImpl(IAppxManifestQualifiedResource); SpecializeUuidOfImpl(IAppxBundleFactory); SpecializeUuidOfImpl(IAppxBundleWriter); +SpecializeUuidOfImpl(IAppxBundleWriter4); SpecializeUuidOfImpl(IAppxBundleReader); SpecializeUuidOfImpl(IAppxBundleManifestReader); SpecializeUuidOfImpl(IAppxBundleManifestPackageInfoEnumerator); @@ -124,6 +125,7 @@ interface IAppxManifestQualifiedResourcesEnumerator; interface IAppxManifestQualifiedResource; interface IAppxBundleFactory; interface IAppxBundleWriter; +interface IAppxBundleWriter4; interface IAppxBundleReader; interface IAppxBundleManifestReader; interface IAppxBundleManifestPackageInfoEnumerator; @@ -970,6 +972,31 @@ enum tagLOCKTYPE }; #endif /* __IAppxBundleWriter_INTERFACE_DEFINED__ */ +#ifndef __IAppxBundleWriter4_INTERFACE_DEFINED__ +#define __IAppxBundleWriter4_INTERFACE_DEFINED__ + + // {9CD9D523-5009-4C01-9882-DC029FBD47A3} + MSIX_INTERFACE(IAppxBundleWriter4,0x9cd9d523,0x5009,0x4c01,0x98,0x82,0xdc,0x02,0x9f,0xbd,0x47,0xa3); + interface IAppxBundleWriter4 : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE AddPayloadPackage( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* packageStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + + virtual HRESULT AddPackageReference( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* inputStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + + virtual HRESULT AddExternalPackageReference( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* inputStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + }; +#endif /* __IAppxBundleWriter4_INTERFACE_DEFINED__ */ + #ifndef __IAppxBundleReader_INTERFACE_DEFINED__ #define __IAppxBundleReader_INTERFACE_DEFINED__ @@ -1665,6 +1692,17 @@ enum MSIX_APPLICABILITY_OPTIONS MSIX_APPLICABILITY_OPTION_SKIPLANGUAGE = 0x2, } MSIX_APPLICABILITY_OPTIONS; +typedef /* [v1_enum] */ +enum MSIX_BUNDLE_OPTIONS + { + MSIX_OPTION_NONE = 0x0, + MSIX_OPTION_VERBOSE = 0x1, + MSIX_OPTION_OVERWRITE = 0x2, + MSIX_OPTION_NOOVERWRITE = 0x4, + MSIX_OPTION_VERSION = 0x8, + MSIX_BUNDLE_OPTION_FLATBUNDLE = 0x10, + } MSIX_BUNDLE_OPTIONS; + #define MSIX_PLATFORM_ALL MSIX_PLATFORM_WINDOWS10 | \ MSIX_PLATFORM_WINDOWS10 | \ MSIX_PLATFORM_WINDOWS8 | \ @@ -1731,6 +1769,14 @@ MSIX_API HRESULT STDMETHODCALLTYPE PackPackage( char* outputPackage ) noexcept; +MSIX_API HRESULT STDMETHODCALLTYPE PackBundle( + MSIX_BUNDLE_OPTIONS bundleOptions, + char* directoryPath, + char* outputBundle, + char* mappingFile, + char* version +) noexcept; + #endif // MSIX_PACK // A call to called CoCreateAppxFactory is required before start using the factory on non-windows platforms specifying diff --git a/src/makemsix/main.cpp b/src/makemsix/main.cpp index 45abef0c..7e908e56 100644 --- a/src/makemsix/main.cpp +++ b/src/makemsix/main.cpp @@ -22,7 +22,7 @@ struct Invocation; struct Option { // Constructor for flags; they can't be required and don't take parameters. - Option(std::string name, std::string help) : + Option(std::string name, std::string help) : Name(std::move(name)), Required(false), ParameterCount(0), Help(std::move(help)) {} @@ -418,6 +418,38 @@ MSIX_APPLICABILITY_OPTIONS GetApplicabilityOption(const Invocation& invocation) return applicability; } +MSIX_BUNDLE_OPTIONS GetBundleOptions(const Invocation& invocation) +{ + MSIX_BUNDLE_OPTIONS bundleOptions = MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NONE; + + if (invocation.IsOptionPresent("-v")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERBOSE; + } + + if (invocation.IsOptionPresent("-o")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE; + } + + if (invocation.IsOptionPresent("-no")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE; + } + + if (invocation.IsOptionPresent("-bv")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERSION; + } + + if (invocation.IsOptionPresent("-fb")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_FLATBUNDLE; + } + + return bundleOptions; +} + #pragma region Commands Command CreateHelpCommand(const std::vector& commands) @@ -551,6 +583,66 @@ Command CreatePackCommand() return result; } + +Command CreateBundleCommand() +{ + Command result{ "bundle", "Create a new app bundle from files on disk", + { + Option{ "-d", "Input directory path.", false, 1, "inputDirectory" }, + Option{ "-p", "Output bundle file path.", true, 1, "outputBundle" }, + Option{ "-f", "Mapping file path.", false, 1, "mappingFile" }, + Option{ "-bv", "Specifies the version number of the bundle being created. The version" + "must be in dotted - quad notation of four integers" + "... ranging from 0 to 65535 each.If the" + "/ bv option is not specified or is set to 0.0.0.0, the bundle is created" + "using the current date - time formatted as the version :" + "....", false, 1, "version" }, + Option{ "-fb", "Generates a fully sparse bundle where all packages are references to" + "packages that exist outside of the bundle file." }, + Option{ "-o", "Forces the output to overwrite any existing files with the" + "same name.By default, the user is asked whether to overwrite existing" + "files with the same name.You can't use this option with /no." }, + Option{ "-no","Prevents the output from overwriting any existing files" + "with the same name.By default, the user is asked whether to overwrite" + "existing files with the same name.You can't use this option with /o." }, + Option{ "-v", "Enables verbose output of messages to the console."}, + Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." }, + } + }; + + result.SetDescription({ + "Creates an app bundle at by adding all files from", + "either (including subfolders) or a list of files within" + ".If either source contains a bundle manifest, it will be" + "ignored." + + "Using / p will result in the bundle being unencrypted, while using / ep will" + "result in the bundle being encrypted.If you use / ep you must specify" + "either / kt or /kf.", + }); + + result.SetInvocationFunc([](const Invocation& invocation) + { + char* directoryPath = (invocation.IsOptionPresent("-d")) ? + const_cast(invocation.GetOptionValue("-d").c_str()) : nullptr; + + char* mappingFile = (invocation.IsOptionPresent("-f")) ? + const_cast(invocation.GetOptionValue("-f").c_str()) : nullptr; + + char* version = (invocation.IsOptionPresent("-bv")) ? + const_cast(invocation.GetOptionValue("-bv").c_str()) : nullptr; + + return PackBundle( + GetBundleOptions(invocation), + directoryPath, + const_cast(invocation.GetOptionValue("-p").c_str()), + mappingFile, + version); + }); + + return result; +} + #endif #pragma endregion @@ -566,6 +658,7 @@ int main(int argc, char* argv[]) CreateUnbundleCommand(), #ifdef MSIX_PACK CreatePackCommand(), + CreateBundleCommand(), #endif }; diff --git a/src/msix/CMakeLists.txt b/src/msix/CMakeLists.txt index 95a71189..5fa3c294 100644 --- a/src/msix/CMakeLists.txt +++ b/src/msix/CMakeLists.txt @@ -19,6 +19,7 @@ list(APPEND MSIX_UNPACK_EXPORTS if(MSIX_PACK) list(APPEND MSIX_PACK_EXPORTS "PackPackage" + "PackBundle" ) endif() @@ -130,6 +131,10 @@ if(MSIX_PACK) pack/ContentType.cpp pack/DeflateStream.cpp pack/ZipObjectWriter.cpp + pack/BundleManifestWriter.cpp + pack/BundleWriterHelper.cpp + pack/AppxBundleWriter.cpp + pack/VersionHelpers.cpp ) endif() @@ -224,7 +229,7 @@ endforeach() add_library(${PROJECT_NAME} SHARED msix.cpp ${MsixSrc} -) + ) # Adding dependency to the third party libs directory add_dependencies(${PROJECT_NAME} LIBS) diff --git a/src/msix/common/AppxFactory.cpp b/src/msix/common/AppxFactory.cpp index 5d4218ca..63c4cf9e 100644 --- a/src/msix/common/AppxFactory.cpp +++ b/src/msix/common/AppxFactory.cpp @@ -11,6 +11,7 @@ #include "VectorStream.hpp" #include "MsixFeatureSelector.hpp" #include "AppxPackageWriter.hpp" +#include "AppxBundleWriter.hpp" #include "ZipObjectWriter.hpp" #ifdef BUNDLE_SUPPORT @@ -88,7 +89,15 @@ namespace MSIX { HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleWriter(IStream *outputStream, UINT64 bundleVersion, IAppxBundleWriter **bundleWriter) noexcept try { THROW_IF_BUNDLE_NOT_ENABLED - NOTIMPLEMENTED; + ThrowErrorIf(Error::InvalidParameter, (outputStream == nullptr || bundleWriter == nullptr || *bundleWriter != nullptr), "Invalid parameter"); + #ifdef MSIX_PACK + ComPtr self; + ThrowHrIfFailed(QueryInterface(UuidOfImpl::iid, reinterpret_cast(&self))); + auto zip = ComPtr::Make(outputStream); + auto result = ComPtr::Make(self.Get(), zip, bundleVersion); + *bundleWriter = result.Detach(); + #endif + return static_cast(Error::OK); } CATCH_RETURN(); HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleReader(IStream *inputStream, IAppxBundleReader **bundleReader) noexcept try diff --git a/src/msix/common/AppxManifestObject.cpp b/src/msix/common/AppxManifestObject.cpp index c37a158d..c4957e5f 100644 --- a/src/msix/common/AppxManifestObject.cpp +++ b/src/msix/common/AppxManifestObject.cpp @@ -354,10 +354,34 @@ namespace MSIX { return static_cast(Error::OK); } CATCH_RETURN(); - // IAppxManifestReader2 + // IAppxManifestReader2 HRESULT STDMETHODCALLTYPE AppxManifestObject::GetQualifiedResources(IAppxManifestQualifiedResourcesEnumerator **resources) noexcept { - return static_cast(Error::NotImplemented); + ThrowErrorIf(Error::InvalidParameter, (resources == nullptr || *resources != nullptr), "bad pointer"); + + std::vector> qualifiedResources; + struct _context + { + AppxManifestObject* self; + std::vector>* qualifiedResources; + }; + _context context = { this, &qualifiedResources}; + + // Parse Resource elements + XmlVisitor visitorResource(static_cast(&context), [](void* c, const ComPtr& resourceNode)->bool + { + _context* context = reinterpret_cast<_context*>(c); + auto language = resourceNode->GetAttributeValue(XmlAttributeName::Language); + auto scale = resourceNode->GetAttributeValue(XmlAttributeName::Scale); + auto dxFeatureLevel = resourceNode->GetAttributeValue(XmlAttributeName::DXFeatureLevel); + auto resource = ComPtr::Make(context->self->m_factory.Get(), language, scale, dxFeatureLevel); + context->qualifiedResources->push_back(std::move(resource)); + return true; + }); + m_dom->ForEachElementIn(m_dom->GetDocument(), XmlQueryName::Package_Resources_Resource, visitorResource); + *resources = ComPtr:: + Make>(qualifiedResources).Detach(); + return static_cast(Error::OK); } // IAppxManifestReader3 diff --git a/src/msix/common/FileNameValidation.cpp b/src/msix/common/FileNameValidation.cpp index d7828637..2cf9ae94 100644 --- a/src/msix/common/FileNameValidation.cpp +++ b/src/msix/common/FileNameValidation.cpp @@ -288,10 +288,20 @@ namespace MSIX { return !IsProhibitedFileName(lowSegment) && !HasProhibitedPrefix(lowSegment) && !HasProhibitedSuffix(lowSegment); } - bool FileNameValidation::IsFootPrintFile(const std::string& fileName) + bool FileNameValidation::IsFootPrintFile(const std::string& fileName, bool isBundle) { + bool result = false; std::string lowIdent = Helper::tolower(fileName); - return ((lowIdent == "appxmanifest.xml") || + if (isBundle) + { + result = lowIdent == "appxmetadata/appxbundlemanifest.xml"; + } + else + { + result = lowIdent == "appxmanifest.xml"; + } + + return (result || (lowIdent == "appxsignature.p7x") || (lowIdent == "appxblockmap.xml") || (lowIdent == "[content_types].xml")); diff --git a/src/msix/common/IXml.cpp b/src/msix/common/IXml.cpp index ba22f4b0..5168a56e 100644 --- a/src/msix/common/IXml.cpp +++ b/src/msix/common/IXml.cpp @@ -28,6 +28,7 @@ static const char* attributeNames[] = { /* Package_Applications_Application_Id */"Id", /* Category */"Category", /* MaxMajorVersionTested */"MaxMajorVersionTested", + /* DXFeatureLevel */"DXFeatureLevel", }; #ifdef USING_MSXML diff --git a/src/msix/msix.cpp b/src/msix/msix.cpp index 43f86612..8c3d96c4 100644 --- a/src/msix/msix.cpp +++ b/src/msix/msix.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Exceptions.hpp" #include "FileStream.hpp" @@ -17,7 +18,9 @@ #include "AppxPackageObject.hpp" #include "MsixFeatureSelector.hpp" #include "AppxPackageWriter.hpp" +#include "AppxBundleWriter.hpp" #include "ScopeExit.hpp" +#include "VersionHelpers.hpp" #ifndef WIN32 // on non-win32 platforms, compile with -fvisibility=hidden @@ -288,4 +291,101 @@ MSIX_API HRESULT STDMETHODCALLTYPE PackPackage( return static_cast(MSIX::Error::OK); } CATCH_RETURN(); +MSIX_API HRESULT STDMETHODCALLTYPE PackBundle( + MSIX_BUNDLE_OPTIONS bundleOptions, + char* directoryPath, + char* outputBundle, + char* mappingFile, + char* version +) noexcept try +{ + std::unique_ptr> externalPackagesList; + std::uint64_t bundleVersion = 0; + bool flatBundle = false; + bool overWrite = false; + + //Process Common Options + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERSION) + { + bundleVersion = MSIX::ConvertVersionStringToUint64(version); + } + + if ((bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE) && (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE)) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You can't specify options -o and -no at the same time."); + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE) + { + overWrite = true; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE) + { + overWrite = false; + } + + if (0 == (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_FLATBUNDLE)) + { + flatBundle = true; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERBOSE) + { + //TODO: Process option for verbose + } + + //TODO: Error if outputBundle is an existing directory + + //Process Input options + if(directoryPath == nullptr && mappingFile == nullptr) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You must specify either a content directory (-d) or a mapping file (-f)."); + } + else if(directoryPath != nullptr && mappingFile != nullptr) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You can't specify both a content directory (-d) and a mapping file (-f)."); + } + //TODO:: Error if directoryPath is a file + + MSIX::ComPtr from; + if(directoryPath != nullptr && outputBundle != nullptr) + { + from = MSIX::ComPtr::Make(directoryPath); + } + else if(mappingFile != nullptr && outputBundle != nullptr) + { + //Create from list from mapping file(Currently keeping it same as above, have to + //parse from mapping file into externalPackagesList) + from = MSIX::ComPtr::Make(directoryPath); + } + + auto deleteFile = MSIX::scope_exit([&outputBundle] + { + remove(outputBundle); + }); + + MSIX::ComPtr stream; + ThrowHrIfFailed(CreateStreamOnFile(outputBundle, false, &stream)); + + MSIX::ComPtr factory; + ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, + MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_FULL, + MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_FULL, + &factory)); + + MSIX::ComPtr bundleWriter; + MSIX::ComPtr bundleWriter4; + + ThrowHrIfFailed(factory->CreateBundleWriter(stream.Get(), bundleVersion, &bundleWriter)); + bundleWriter4 = bundleWriter.As(); + + bundleWriter4.As()->ProcessBundlePayload(from, flatBundle); + ThrowHrIfFailed(bundleWriter->Close()); + deleteFile.release(); + return static_cast(MSIX::Error::OK); + +} CATCH_RETURN(); + #endif // MSIX_PACK + diff --git a/src/msix/pack/AppxBundleWriter.cpp b/src/msix/pack/AppxBundleWriter.cpp new file mode 100644 index 00000000..80814eec --- /dev/null +++ b/src/msix/pack/AppxBundleWriter.cpp @@ -0,0 +1,233 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "AppxPackaging.hpp" +#include "AppxBundleWriter.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" +#include "ContentType.hpp" +#include "Encoding.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxManifestObject.hpp" +#include "ScopeExit.hpp" +#include "FileNameValidation.hpp" +#include "StringHelper.hpp" +#include +#include + +namespace MSIX { + + AppxBundleWriter::AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion) + : m_factory(factory), m_zipWriter(zip) + { + m_state = WriterState::Open; + if(bundleVersion == 0) + { + // The generated version number has the format: YYYY.MMDD.hhmm.0 + std::time_t t = std::time(nullptr); + std::tm tm = *std::gmtime(&t); + std::stringstream ss; + ss << std::put_time(&tm, "%Y.%m%d.%H%M.0"); + this->m_bundleWriterHelper.SetBundleVersion(ConvertVersionStringToUint64(ss.str())); + } + else + { + this->m_bundleWriterHelper.SetBundleVersion(bundleVersion); + } + } + + // IBundleWriter + void AppxBundleWriter::ProcessBundlePayload(const ComPtr& from, bool flatBundle) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + auto fileMap = from->GetFilesByLastModDate(); + for (const auto& file : fileMap) + { + if (!(FileNameValidation::IsFootPrintFile(file.second, true))) + { + std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = from.As()->GetFile(file.second); + + if (flatBundle) + { + ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); + } + } + } + + //Process external packages passed as input created from mapping file + /*if (externalPackagesList != nullptr) + { + + }*/ + + failState.release(); + } + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept try + { + // TODO: implement + NOTIMPLEMENTED; + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::Close() noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + //Process AppxBundleManifest.xml and add it to the bundle + m_bundleWriterHelper.EndBundleManifest(); + + auto bundleManifestStream = m_bundleWriterHelper.GetBundleManifestStream(); + auto bundleManifestContentType = ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST); + AddFileToPackage(APPXBUNDLEMANIFEST_XML, bundleManifestStream.Get(), true, true, bundleManifestContentType.c_str()); + + // Close blockmap and add it to the bundle + m_blockMapWriter.Close(); + auto blockMapStream = m_blockMapWriter.GetStream(); + auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); + AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); + + // Close content types and add it to the bundle + m_contentTypeWriter.Close(); + auto contentTypeStream = m_contentTypeWriter.GetStream(); + AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); + + m_zipWriter->Close(); + failState.release(); + m_state = WriterState::Closed; + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + this->AddPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); + + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxBundleWriter::AddPackageReferenceInternal(std::string fileName, IStream* packageStream, + bool isDefaultApplicablePackage) + { + auto appxFactory = m_factory.As(); + + ComPtr reader; + ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream, &reader)); + + std::uint64_t packageStreamSize = this->m_bundleWriterHelper.GetStreamSize(packageStream); + + this->m_bundleWriterHelper.AddPackage(fileName, reader.Get(), 0, packageStreamSize, isDefaultApplicablePackage); + } + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept try + { + // TODO: implement + NOTIMPLEMENTED; + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddExternalPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + // TODO: implement + NOTIMPLEMENTED; + } CATCH_RETURN(); + + void AppxBundleWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) + { + std::string opcFileName; + // Don't encode [Content Type].xml + if (contentType != nullptr) + { + opcFileName = Encoding::EncodeFileName(name); + } + else + { + opcFileName = name; + } + auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); + + // Add content type to [Content Types].xml + if (contentType != nullptr) + { + m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); + } + + // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream + LARGE_INTEGER start = { 0 }; + ULARGE_INTEGER end = { 0 }; + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); + std::uint64_t uncompressedSize = static_cast(end.QuadPart); + + // Add file to block map. + if (addToBlockMap) + { + m_blockMapWriter.AddFile(name, uncompressedSize, fileInfo.first); + } + + auto& zipFileStream = fileInfo.second; + + std::uint64_t bytesToRead = uncompressedSize; + std::uint32_t crc = 0; + while (bytesToRead > 0) + { + // Calculate the size of the next block to add + std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); + bytesToRead -= blockSize; + + // read block from stream + std::vector block; + block.resize(blockSize); + ULONG bytesRead; + ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); + ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); + crc = crc32(crc, block.data(), static_cast(block.size())); + + // Write block and compress if needed + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); + + // Add block to blockmap + if (addToBlockMap) + { + m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); + } + + } + + if (toCompress) + { + // Put the stream termination on + std::vector buffer; + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); + } + + // Close File element + if (addToBlockMap) + { + m_blockMapWriter.CloseFile(); + } + + // This could be the compressed or uncompressed size + auto streamSize = zipFileStream.As()->GetSize(); + m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); + } + +} diff --git a/src/msix/pack/AppxPackageWriter.cpp b/src/msix/pack/AppxPackageWriter.cpp index e4fa5944..d5166d5b 100644 --- a/src/msix/pack/AppxPackageWriter.cpp +++ b/src/msix/pack/AppxPackageWriter.cpp @@ -21,8 +21,6 @@ #include #include -#include - namespace MSIX { AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip) : m_factory(factory), m_zipWriter(zip) @@ -44,7 +42,7 @@ namespace MSIX { { // If any footprint file is present, ignore it. We only require the AppxManifest.xml // and any other will be ignored and a new one will be created for the package. - if(!(FileNameValidation::IsFootPrintFile(file.second) || FileNameValidation::IsReservedFolder(file.second))) + if(!(FileNameValidation::IsFootPrintFile(file.second, false) || FileNameValidation::IsReservedFolder(file.second))) { std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); auto contentType = ContentType::GetContentTypeByExtension(ext); @@ -155,7 +153,7 @@ namespace MSIX { APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) { ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name), "Trying to add footprint file to package"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); ValidateCompressionOption(compressionOpt); AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); diff --git a/src/msix/pack/BundleManifestWriter.cpp b/src/msix/pack/BundleManifestWriter.cpp new file mode 100644 index 00000000..0b5f05ee --- /dev/null +++ b/src/msix/pack/BundleManifestWriter.cpp @@ -0,0 +1,275 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "XmlWriter.hpp" +#include "BundleManifestWriter.hpp" +#include "Crypto.hpp" +#include "StringHelper.hpp" + +#include "AppxManifestObject.hpp" + +#include + +namespace MSIX { + + static const char* bundleManifestElement = "Bundle"; + static const char* schemaVersionAttribute = "SchemaVersion"; + static const char* Win2019SchemaVersion = "5.0"; + static const char* identityManifestElement = "Identity"; + static const char* identityNameAttribute = "Name"; + static const char* identityPublisherAttribute = "Publisher"; + static const char* identityVersionAttribute = "Version"; + static const char* packagesManifestElement = "Packages"; + static const char* packageManifestElement = "Package"; + static const char* packageTypeAttribute = "Type"; + static const char* packageVersionAttribute = "Version"; + static const char* packageArchitectureAttribute = "Architecture"; + static const char* packageResourceIdAttribute = "ResourceId"; + static const char* packageFileNameAttribute = "FileName"; + static const char* resourcesManifestElement = "Resources"; + static const char* resourceManifestElement = "Resource"; + static const char* resourceLanguageAttribute = "Language"; + static const char* dependenciesManifestElementWithoutPrefix = "Dependencies"; + static const char* targetDeviceFamilyManifestElementWithoutPrefix = "TargetDeviceFamily"; + static const char* tdfNameAttribute = "Name"; + static const char* tdfMinVersionAttribute = "MinVersion"; + static const char* tdfMaxVersionTestedAttribute = "MaxVersionTested"; + + static const char* ApplicationPackageType = "application"; + static const char* ResourcePackageType = "resource"; + + static const char* NamespaceAlias = "b"; + static const char* Namespace = "http://schemas.microsoft.com/appx/2013/bundle"; + static const char* Namespace2016Alias = "b2"; + static const char* Namespace2016 = "http://schemas.microsoft.com/appx/2016/bundle"; + static const char* Namespace2017Alias = "b3"; + static const char* Namespace2017 = "http://schemas.microsoft.com/appx/2017/bundle"; + static const char* Namespace2018Alias = "b4"; + static const char* Namespace2018 = "http://schemas.microsoft.com/appx/2018/bundle"; + static const char* Namespace2019Alias = "b5"; + static const char* Namespace2019 = "http://schemas.microsoft.com/appx/2019/bundle"; + + BundleManifestWriter::BundleManifestWriter() : m_xmlWriter(XmlWriter(bundleManifestElement)) {} + + void BundleManifestWriter::StartBundleManifest(std::string targetXmlNamespace, std::string name, + std::string publisher, std::uint64_t version) + { + this->targetXmlNamespace = targetXmlNamespace; + StartBundleElement(); + WriteIdentityElement(name, publisher, version); + StartPackagesElement(); + } + + void BundleManifestWriter::StartBundleElement() + { + m_xmlWriter.AddAttribute(xmlnsAttribute, this->targetXmlNamespace); + m_xmlWriter.AddAttribute(schemaVersionAttribute, Win2019SchemaVersion); + + std::string bundle2018QName = GetQualifiedName(xmlnsAttribute, Namespace2018Alias); + m_xmlWriter.AddAttribute(bundle2018QName, Namespace2018); + + std::string bundle2019QName = GetQualifiedName(xmlnsAttribute, Namespace2019Alias); + m_xmlWriter.AddAttribute(bundle2019QName, Namespace2019); + + std::string ignorableNamespaces; + ignorableNamespaces.append(Namespace2018Alias); + ignorableNamespaces.append(" "); + ignorableNamespaces.append(Namespace2019Alias); + m_xmlWriter.AddAttribute("IgnorableNamespaces", ignorableNamespaces); + } + + void BundleManifestWriter::WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version) + { + m_xmlWriter.StartElement(identityManifestElement); + + m_xmlWriter.AddAttribute(identityNameAttribute, name); + m_xmlWriter.AddAttribute(identityPublisherAttribute, publisher); + + std::string versionString = MSIX::ConvertVersionToString(version); + m_xmlWriter.AddAttribute(identityVersionAttribute, versionString); + + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::StartPackagesElement() + { + m_xmlWriter.StartElement(packagesManifestElement); + } + + void BundleManifestWriter::WritePackageElement(PackageInfo packageInfo) + { + m_xmlWriter.StartElement(packageManifestElement); + + std::string packageTypeString; + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + packageTypeString = ApplicationPackageType; + } + else if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE) + { + packageTypeString = ResourcePackageType; + } + m_xmlWriter.AddAttribute(packageTypeAttribute, packageTypeString); + + std::string versionString = MSIX::ConvertVersionToString(packageInfo.version); + m_xmlWriter.AddAttribute(packageVersionAttribute, versionString); + + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + m_xmlWriter.AddAttribute(packageArchitectureAttribute, packageInfo.architecture); + } + + if (!packageInfo.resourceId.empty() && (packageInfo.resourceId.size() > 0)) + { + m_xmlWriter.AddAttribute(packageResourceIdAttribute, packageInfo.resourceId); + } + + if(!packageInfo.fileName.empty()) + { + m_xmlWriter.AddAttribute(packageFileNameAttribute, packageInfo.fileName); + } + + if(packageInfo.offset > 0) + { + //TODO: not applicable for flat bundle + } + + if (packageInfo.size > 0 && packageInfo.offset > 0) + { + //TODO: not applicable for flat bundles + } + + //WriteResourcesElement + WriteResourcesElement(packageInfo.resources.Get()); + + //WriteDependenciesElement + WriteDependenciesElement(packageInfo.tdfs.Get()); + + //End Package Tag + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources) + { + BOOL hasResources = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasResources)); + + if (hasResources) + { + //Start Resources element + m_xmlWriter.StartElement(resourcesManifestElement); + + BOOL hasNext = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasNext)); + while (hasNext) + { + ComPtr resource; + ThrowHrIfFailed(resources->GetCurrent(&resource)); + + //Start Resource element + m_xmlWriter.StartElement(resourceManifestElement); + + auto qualifiedResourceInternal = resource.As(); + std::string languageString = qualifiedResourceInternal->GetLanguage(); + if (!languageString.empty()) + { + m_xmlWriter.AddAttribute(resourceLanguageAttribute, languageString); + } + + //TODO:: Write scale and dxfeaturelevel attributes + + //End Resource element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(resources->MoveNext(&hasNext)); + } + + //End Resources element + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + BOOL hasNext = FALSE; + ThrowHrIfFailed(tdfs->GetHasCurrent(&hasNext)); + + if (hasNext) + { + std::string dependencyQName = GetElementName(Namespace2018, Namespace2018Alias, dependenciesManifestElementWithoutPrefix); + m_xmlWriter.StartElement(dependencyQName); + + while (hasNext) + { + ComPtr tdf; + ThrowHrIfFailed(tdfs->GetCurrent(&tdf)); + + //Start TargetDeviceFamily manifest element + std::string tdfQName = GetElementName(Namespace2018, Namespace2018Alias, targetDeviceFamilyManifestElementWithoutPrefix); + m_xmlWriter.StartElement(tdfQName); + + auto targetDeviceFamilyInternal = tdf.As(); + std::string name = targetDeviceFamilyInternal->GetName(); + m_xmlWriter.AddAttribute(tdfNameAttribute, name); + + //Get minversion + UINT64 minVersion; + ThrowHrIfFailed(tdf->GetMinVersion(&minVersion)); + std::string minVerionString = MSIX::ConvertVersionToString(minVersion); + m_xmlWriter.AddAttribute(tdfMinVersionAttribute, minVerionString); + + //Get maxversiontested + UINT64 maxVersionTested; + ThrowHrIfFailed(tdf->GetMaxVersionTested(&maxVersionTested)); + std::string maxVersionTestedString = MSIX::ConvertVersionToString(maxVersionTested); + m_xmlWriter.AddAttribute(tdfMaxVersionTestedAttribute, maxVersionTestedString); + + //End TargetDeviceFamily manifest element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(tdfs->MoveNext(&hasNext)); + } + + //End Dependencies Tag + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::EndPackagesElement() + { + //TODO:: Main state to check if package is added and close only if + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::Close() + { + //Ends Bundle Element + m_xmlWriter.CloseElement(); + } + + std::string BundleManifestWriter::GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name) + { + std::string qualifiedName; + if ((this->targetXmlNamespace.compare(targetNamespace) != 0) && (!targetNamespaceAlias.empty())) + { + qualifiedName = GetQualifiedName(targetNamespaceAlias, name); + } + else + { + qualifiedName = name; + } + return qualifiedName; + } + + std::string BundleManifestWriter::GetQualifiedName(std::string namespaceAlias, std::string name) + { + std::string output; + output.append(namespaceAlias); + output.append(xmlNamespaceDelimiter); + output.append(name); + return output; + } +} + diff --git a/src/msix/pack/BundleWriterHelper.cpp b/src/msix/pack/BundleWriterHelper.cpp new file mode 100644 index 00000000..41e00af4 --- /dev/null +++ b/src/msix/pack/BundleWriterHelper.cpp @@ -0,0 +1,244 @@ +#include "BundleWriterHelper.hpp" + +namespace MSIX { + + BundleWriterHelper::BundleWriterHelper() + { + this->bundleVersion = 0; + this->hasExternalPackages = false; + this->hasDefaultOrNeutralResources = false; + } + + std::uint64_t BundleWriterHelper::GetStreamSize(IStream* stream) + { + STATSTG stat; + ThrowHrIfFailed(stream->Stat(&stat, 1)); + + return stat.cbSize.QuadPart; + } + + void BundleWriterHelper::AddPackage(std::string fileName, IAppxPackageReader* packageReader, + std::uint64_t bundleOffset, std::uint64_t packageSize, bool isDefaultApplicableResource) + { + ComPtr packageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr resources; + ComPtr tdfs; + + GetValidatedPackageData(fileName, packageReader, &packageType, &packageId, &resources, &tdfs); + + AddValidatedPackageData(fileName, bundleOffset, packageSize, packageType, packageId, + isDefaultApplicableResource, resources.Get(), tdfs.Get()); + } + + void BundleWriterHelper::GetValidatedPackageData( + std::string fileName, + IAppxPackageReader* packageReader, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, + IAppxManifestPackageId** packageId, + IAppxManifestQualifiedResourcesEnumerator** resources, + IAppxManifestTargetDeviceFamiliesEnumerator** tdfs) + { + *packageId = nullptr; + *resources = nullptr; + *tdfs = nullptr; + + ComPtr loadedPackageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE loadedPackageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr loadedResources; + ComPtr loadedTdfs; + + ComPtr manifestReader; + ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); + ThrowHrIfFailed(manifestReader->GetPackageId(&loadedPackageId)); + + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + + ThrowHrIfFailed(manifestReader3->GetQualifiedResources(&loadedResources)); + + ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&loadedTdfs)); + + loadedPackageType = GetPayloadPackageType(manifestReader.Get(), fileName); + //TODO:: Validate Package matches SHA256 hash method + + auto packageIdInternal = loadedPackageId.As(); + ValidateNameAndPublisher(packageIdInternal.Get(), fileName); + + //TODO: TDF checks + + if (loadedPackageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + ValidateApplicationElement(manifestReader.Get(), fileName); + } + + *packageType = loadedPackageType; + *packageId = loadedPackageId.Detach(); + *resources = loadedResources.Detach(); + + if (loadedTdfs.Get() != nullptr) + { + *tdfs = loadedTdfs.Detach(); + } + } + + void BundleWriterHelper::ValidateApplicationElement( + IAppxManifestReader* packageManifestReader, + std::string fileName) + { + ComPtr manifestReader4; + ThrowHrIfFailed(packageManifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader4))); + + ComPtr optionalPackageInfo; + ThrowHrIfFailed(manifestReader4->GetOptionalPackageInfo(&optionalPackageInfo)); + + BOOL packageIsOptional = FALSE; + ThrowHrIfFailed(optionalPackageInfo->GetIsOptionalPackage(&packageIsOptional)); + + if (!packageIsOptional) // optional payload packages are not required to declare any elements + { + ComPtr applications; + ThrowHrIfFailed(packageManifestReader->GetApplications(&applications)); + BOOL hasApplication = FALSE; + ThrowHrIfFailed(applications->GetHasCurrent(&hasApplication)); + + if (!hasApplication) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because its manifest does not declare any Application elements."); + } + } + } + + void BundleWriterHelper::ValidateNameAndPublisher(IAppxManifestPackageIdInternal* packageId, + std::string filename) + { + if(this->mainPackageName.empty()) + { + this->mainPackageName = packageId->GetName(); + this->mainPackagePublisher = packageId->GetPublisher(); + } + else + { + std::string packageName = packageId->GetName(); + + if ((this->mainPackageName.compare(packageName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + + std::string publisherName = packageId->GetPublisher(); + if ((this->mainPackagePublisher.compare(publisherName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + } + } + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE BundleWriterHelper::GetPayloadPackageType(IAppxManifestReader* packageManifestReader, + std::string fileName) + { + ComPtr packageProperties; + ThrowHrIfFailed(packageManifestReader->GetProperties(&packageProperties)); + + BOOL isFrameworkPackage = FALSE; + ThrowHrIfFailed(packageProperties->GetBoolValue(L"Framework", &isFrameworkPackage)); + + if (isFrameworkPackage) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it is a framework package."); + } + + BOOL isResourcePackage = FALSE; + ThrowHrIfFailed(packageProperties->GetBoolValue(L"ResourcePackage", &isResourcePackage)); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = (isResourcePackage ? APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE : APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION); + return packageType; + } + + void BundleWriterHelper::AddValidatedPackageData( + std::string fileName, + std::uint64_t bundleOffset, + std::uint64_t packageSize, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, + ComPtr packageId, + bool isDefaultApplicablePackage, + IAppxManifestQualifiedResourcesEnumerator* resources, + IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + //TODO: validate package payload extension + + auto innerPackageIdInternal = packageId.As(); + + PackageInfo packageInfo; + packageInfo.type = packageType; + packageInfo.architecture = innerPackageIdInternal->GetArchitecture(); + UINT64 version; + ThrowHrIfFailed(packageId->GetVersion(&version)); + packageInfo.version = version; + packageInfo.resourceId = innerPackageIdInternal->GetResourceId(); + packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; + packageInfo.resources = resources; + packageInfo.fileName = fileName; + packageInfo.size = packageSize; + packageInfo.offset = bundleOffset; + packageInfo.tdfs = tdfs; + + AddPackageInfoToVector(this->payloadPackages, packageInfo); + } + + void BundleWriterHelper::AddPackageInfoToVector(std::vector& packagesVector, + PackageInfo packageInfo) + { + packagesVector.push_back(packageInfo); + + if (packageInfo.offset == 0) + { + this->hasExternalPackages = true; + } + + if (packageInfo.isDefaultApplicablePackage) + { + this->hasDefaultOrNeutralResources = true; + } + + BOOL hasResources = FALSE; + ThrowHrIfFailed(packageInfo.resources->GetHasCurrent(&hasResources)); + if (!hasResources) + { + this->hasDefaultOrNeutralResources = true; + } + } + + void BundleWriterHelper::EndBundleManifest() + { + std::string targetXmlNamespace = "http://schemas.microsoft.com/appx/2013/bundle"; + bool isPre2018BundleManifest = true; + + //TODO: Only use new 2018 bundle schema if the bundle contains more than 1 neutral app packages + if (this->hasDefaultOrNeutralResources) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2017/bundle"; + } + else if ((this->optionalBundles.size() > 0) || this->hasExternalPackages) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2016/bundle"; + } + + m_bundleManifestWriter.StartBundleManifest(targetXmlNamespace, this->mainPackageName, + this->mainPackagePublisher, this->bundleVersion); + + for(std::size_t i = 0; i < this->payloadPackages.size(); i++) + { + m_bundleManifestWriter.WritePackageElement(payloadPackages[i]); + } + + //TODO: this->OptionalBundles + + //Ends Packages and bundle Element + m_bundleManifestWriter.EndPackagesElement(); + m_bundleManifestWriter.Close(); + } + +} \ No newline at end of file diff --git a/src/msix/pack/ContentType.cpp b/src/msix/pack/ContentType.cpp index 57e590e3..eaac6c8e 100644 --- a/src/msix/pack/ContentType.cpp +++ b/src/msix/pack/ContentType.cpp @@ -135,4 +135,22 @@ namespace MSIX { // TODO: add other ones if needed, otherwise throw ThrowErrorAndLog(Error::NotSupported, "Payload file content type not found"); } + + const std::string ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE footprintFile) + { + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST) + { + return "application/vnd.ms-appx.bundlemanifest+xml"; + } + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_BLOCKMAP) + { + return "application/vnd.ms-appx.blockmap+xml"; + } + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE) + { + return "application/vnd.ms-appx.signature"; + } + // TODO: add other ones if needed, otherwise throw + ThrowErrorAndLog(Error::NotSupported, "Bundle Payload file content type not found"); + } } diff --git a/src/msix/pack/VersionHelpers.cpp b/src/msix/pack/VersionHelpers.cpp new file mode 100644 index 00000000..16523ddb --- /dev/null +++ b/src/msix/pack/VersionHelpers.cpp @@ -0,0 +1,34 @@ +#include "VersionHelpers.hpp" + +namespace MSIX{ + + std::uint64_t ConvertVersionStringToUint64(const std::string& versionString) + { + std::uint64_t version = 0; + size_t position = 0; + auto nextPeriod = versionString.find('.', position); + version = (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x30; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x20; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x10; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)); + + return version; + } + + std::string ConvertVersionToString(std::uint64_t version) + { + return std::to_string((version >> 0x30) & 0xFFFF) + "." + + std::to_string((version >> 0x20) & 0xFFFF) + "." + + std::to_string((version >> 0x10) & 0xFFFF) + "." + + std::to_string((version) & 0xFFFF); + } +}