From cbb1eeb2bf261b98ae4d4a95af41d069fa06af05 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Sun, 26 Jul 2026 19:40:02 -0500 Subject: [PATCH] Abstract BitmapInformation class and implement cross-platform using ImageSharp --- .../UIX.RenderApi.OpenGL.csproj | 8 +- .../Iris/Render/Bitmaps/BitmapInformation.cs | 25 ++++ .../Bitmaps/ImageSharpBitmapInformation.cs | 127 ++++++++++++++++ .../Render/Bitmaps/SpBitmapInformation.cs | 136 ++++++++++++++++++ .../Render/Extensions/BitmapInformation.cs | 28 ---- .../Iris/Render/Extensions/ImageCacheItem.cs | 3 +- .../Iris/Render/Extensions/ImageLoader.cs | 96 +++++-------- .../Microsoft/Iris/Render/Internal/HRESULT.cs | 26 ++-- UIX.RenderApi/UIX.RenderApi.csproj | 8 +- UIX.RenderApi/sixlabors-sample.lic | 1 + 10 files changed, 358 insertions(+), 100 deletions(-) create mode 100644 UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/BitmapInformation.cs create mode 100644 UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/ImageSharpBitmapInformation.cs create mode 100644 UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/SpBitmapInformation.cs delete mode 100644 UIX.RenderApi/Microsoft/Iris/Render/Extensions/BitmapInformation.cs create mode 100644 UIX.RenderApi/sixlabors-sample.lic diff --git a/UIX.RenderApi.OpenGL/UIX.RenderApi.OpenGL.csproj b/UIX.RenderApi.OpenGL/UIX.RenderApi.OpenGL.csproj index 431887e..4fcf15c 100644 --- a/UIX.RenderApi.OpenGL/UIX.RenderApi.OpenGL.csproj +++ b/UIX.RenderApi.OpenGL/UIX.RenderApi.OpenGL.csproj @@ -11,16 +11,16 @@ - - - - + + + + diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/BitmapInformation.cs b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/BitmapInformation.cs new file mode 100644 index 0000000..889fd39 --- /dev/null +++ b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/BitmapInformation.cs @@ -0,0 +1,25 @@ +// Based on Microsoft.Iris.Render.Extensions.BitmapInformation + +using System; +using Microsoft.Iris.Render.Extensions; +using Microsoft.Iris.Render.Internal; + +namespace Microsoft.Iris.Render.Bitmaps; + +public abstract class BitmapInformation : IDisposable +{ + public ImageInformation ImageInfo { get; protected set; } + + public abstract HRESULT LoadFile(string filename, ImageRequirements req); + + public abstract HRESULT LoadResource(string moduleName, string resourceId, ImageRequirements req); + + public abstract HRESULT LoadBuffer(IntPtr pvSrc, int cbSize, ImageRequirements req); + + public abstract HRESULT LoadRaw(Size sizeActualPxl, int nStride, SurfaceFormat nFormat, nint pvData, + ImageRequirements req); + + public abstract HRESULT LoadHeader(IntPtr pvSrc, int cbSize, ImageRequirements req); + + public abstract void Dispose(); +} \ No newline at end of file diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/ImageSharpBitmapInformation.cs b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/ImageSharpBitmapInformation.cs new file mode 100644 index 0000000..ff2a3cb --- /dev/null +++ b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/ImageSharpBitmapInformation.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using Microsoft.Iris.Render.Extensions; +using Microsoft.Iris.Render.Internal; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace Microsoft.Iris.Render.Bitmaps; + +public sealed class ImageSharpBitmapInformation : BitmapInformation +{ + private Image _image; + + public override HRESULT LoadFile(string filename, ImageRequirements req) => + Try(() => _LoadFile(filename, req)); + + public override HRESULT LoadResource(string moduleName, string resourceId, ImageRequirements req) => + Try(() => _LoadResource(moduleName, resourceId, req)); + + public override HRESULT LoadBuffer(nint pvSrc, int cbSize, ImageRequirements req) => + Try(() => _LoadBuffer(pvSrc, cbSize, req)); + + public override HRESULT LoadRaw(Size sizeActualPxl, int nStride, SurfaceFormat nFormat, IntPtr pvData, ImageRequirements req) => + Try(() => _LoadRaw(sizeActualPxl, nStride, nFormat, pvData, req)); + + public override HRESULT LoadHeader(nint pvSrc, int cbSize, ImageRequirements req) => + Try(() => _LoadHeader(pvSrc, cbSize, req)); + + public override void Dispose() + { + _image?.Dispose(); + _image = null; + } + + private void _LoadFile(string filename, ImageRequirements req) + { + _image = Image.Load(filename); + + ApplyImageTransitions(req); + UpdateImageInfo(_image); + } + + private void _LoadResource(string moduleName, string resourceId, ImageRequirements req) + { + throw new NotImplementedException(); + } + + private unsafe void _LoadBuffer(nint pvSrc, int cbSize, ImageRequirements req) + { + ReadOnlySpan fileBuffer = new(pvSrc.ToPointer(), cbSize); + _image = Image.Load(fileBuffer).Clone(_ => {}); + + ApplyImageTransitions(req); + UpdateImageInfo(_image); + } + + private void _LoadRaw(Size sizeActualPxl, int nStride, SurfaceFormat nFormat, IntPtr pvData, ImageRequirements req) + { + throw new NotImplementedException(); + } + + private unsafe void _LoadHeader(nint pvSrc, int cbSize, ImageRequirements req) + { + ReadOnlySpan fileBuffer = new(pvSrc.ToPointer(), cbSize); + var imageInfo = Image.Identify(fileBuffer); + + UpdateImageInfo(imageInfo); + } + + private void ApplyImageTransitions(ImageRequirements req) + { + if (req.Flippable) + _image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + private void UpdateImageInfo(Image image) => UpdateImageInfo(new ImageInfo(image.Size, image.Metadata)); + + private void UpdateImageInfo(ImageInfo imageInfo) + { + Size imageSize = new(imageInfo.Width, imageInfo.Height); + + // TODO: Read pixel data format + SurfaceFormat format = SurfaceFormat.None; + + ImageInfo = new ImageInformation + { + Header = new ImageHeader + { + sizeActualPxl = imageSize, + sizeOriginalPxl = imageSize, + nStride = imageInfo.PixelType.BitsPerPixel / 8, + nFormat = format + }, + Data = default + }; + } + + private static HRESULT Try(Action action) + { + try + { + action(); + } + catch (FileNotFoundException) + { + return 0x80070002; + } + catch (ArgumentNullException) + { + return 0x80004003; + } + catch (ArgumentException) + { + return 0x80070057; + } + catch (NotImplementedException) + { + return 0x80004001; + } + catch + { + return HRESULT.E_FAIL; + } + + return HRESULT.S_OK; + } +} \ No newline at end of file diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/SpBitmapInformation.cs b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/SpBitmapInformation.cs new file mode 100644 index 0000000..910836e --- /dev/null +++ b/UIX.RenderApi/Microsoft/Iris/Render/Bitmaps/SpBitmapInformation.cs @@ -0,0 +1,136 @@ +// Based on decompilation with JetBrains decompiler +// Type: Microsoft.Iris.Render.Extensions.BitmapInformation +// Assembly: UIX.RenderApi, Version=4.8.0.0, Culture=neutral, PublicKeyToken=ddd0da4d3e678217 +// MVID: D47658B8-A8EA-43D6-8837-ECE823BFFFC1 +// Assembly location: C:\Program Files\Zune\UIX.RenderApi.dll + +using System; +using Microsoft.Iris.Render.Extensions; +using Microsoft.Iris.Render.Internal; +using Microsoft.Iris.Render.Protocol; + +namespace Microsoft.Iris.Render.Bitmaps; + +internal sealed class SpBitmapInformation : BitmapInformation +{ + public HSpBitmap hBitmap; + + public override HRESULT LoadFile(string filename, ImageRequirements req) + { + var nOptions = ExtensionsApi.BitmapOptions.Decode; + if (req.Flippable) + nOptions |= ExtensionsApi.BitmapOptions.Flip; + + var hresult = ExtensionsApi.SpBitmapLoadFile(filename, req, nOptions, + out hBitmap, out var imageInformation); + + if (hresult.IsSuccess()) + { + ImageInfo = imageInformation; + } + else + { + ImageInfo = default; + hBitmap = default; + } + + return hresult; + } + + public override HRESULT LoadResource(string moduleName, string resourceId, ImageRequirements req) + { + var hinst = ModuleManager.Instance.LoadModule(moduleName); + if (hinst == Win32Api.HINSTANCE.NULL) + return 0x80070006; + + var nOptions = ExtensionsApi.BitmapOptions.Decode; + if (req.Flippable) + nOptions |= ExtensionsApi.BitmapOptions.Flip; + + var hresult = ExtensionsApi.SpBitmapLoadResource(hinst, resourceId, 10, req, nOptions, + out hBitmap, out var imageInformation); + + if (hresult.IsSuccess()) + { + ImageInfo = imageInformation; + } + else + { + ImageInfo = default; + hBitmap = default; + } + + return hresult; + } + + public override HRESULT LoadBuffer(IntPtr pvSrc, int cbSize, ImageRequirements req) + { + var nOptions = ExtensionsApi.BitmapOptions.Decode; + if (req.Flippable) + nOptions |= ExtensionsApi.BitmapOptions.Flip; + + var hresult = ExtensionsApi.SpBitmapLoadBuffer(pvSrc, (uint)cbSize, req, nOptions, + out hBitmap, out var imageInformation); + + if (hresult.IsSuccess()) + { + ImageInfo = imageInformation; + } + else + { + ImageInfo = default; + hBitmap = default; + } + + return hresult; + } + + public override HRESULT LoadRaw(Size sizeActualPxl, int nStride, SurfaceFormat nFormat, IntPtr pvData, ImageRequirements req) + { + var nOptions = ExtensionsApi.BitmapOptions.Decode; + if (req.Flippable) + nOptions |= ExtensionsApi.BitmapOptions.Flip; + + var hresult = ExtensionsApi.SpBitmapLoadRaw(sizeActualPxl, nStride, nFormat, pvData, req, nOptions, + out hBitmap, out var imageInformation); + + if (hresult.IsSuccess()) + { + ImageInfo = imageInformation; + } + else + { + ImageInfo = default; + hBitmap = default; + } + + return hresult; + } + + public override HRESULT LoadHeader(IntPtr pvSrc, int cbSize, ImageRequirements req) + { + var hresult = ExtensionsApi.SpBitmapLoadBuffer(pvSrc, (uint)cbSize, req, ExtensionsApi.BitmapOptions.None, + out hBitmap, out var imageInformation); + + if (hresult.IsSuccess()) + { + ImageInfo = imageInformation; + } + else + { + ImageInfo = default; + hBitmap = default; + } + + return hresult; + } + + public override void Dispose() + { + if (hBitmap == HSpBitmap.NULL) + return; + + EngineApi.IFC(ExtensionsApi.SpBitmapDelete(hBitmap)); + hBitmap = HSpBitmap.NULL; + } +} \ No newline at end of file diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/BitmapInformation.cs b/UIX.RenderApi/Microsoft/Iris/Render/Extensions/BitmapInformation.cs deleted file mode 100644 index 6102a63..0000000 --- a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/BitmapInformation.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Decompiled with JetBrains decompiler -// Type: Microsoft.Iris.Render.Extensions.BitmapInformation -// Assembly: UIX.RenderApi, Version=4.8.0.0, Culture=neutral, PublicKeyToken=ddd0da4d3e678217 -// MVID: D47658B8-A8EA-43D6-8837-ECE823BFFFC1 -// Assembly location: C:\Program Files\Zune\UIX.RenderApi.dll - -using Microsoft.Iris.Render.Protocol; -using System; - -namespace Microsoft.Iris.Render.Extensions -{ - public class BitmapInformation : IDisposable - { - public ImageInformation imageInfo; - internal HSpBitmap hBitmap; - - public void Dispose() => ReleaseData(); - - private void ReleaseData() - { - if (hBitmap == HSpBitmap.NULL) - return; - - EngineApi.IFC(ExtensionsApi.SpBitmapDelete(hBitmap)); - hBitmap = HSpBitmap.NULL; - } - } -} diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageCacheItem.cs b/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageCacheItem.cs index 8b08fd0..340aed8 100644 --- a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageCacheItem.cs +++ b/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageCacheItem.cs @@ -6,6 +6,7 @@ using System; using System.Collections; +using Microsoft.Iris.Render.Bitmaps; namespace Microsoft.Iris.Render.Extensions { @@ -254,7 +255,7 @@ namespace Microsoft.Iris.Render.Extensions return false; m_info = bitmapInfo; - SetSize(m_info.imageInfo.Header.sizeActualPxl); + SetSize(m_info.ImageInfo.Header.sizeActualPxl); return true; } diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageLoader.cs b/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageLoader.cs index 035fcfa..69d6401 100644 --- a/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageLoader.cs +++ b/UIX.RenderApi/Microsoft/Iris/Render/Extensions/ImageLoader.cs @@ -8,11 +8,21 @@ using Microsoft.Iris.Render.Common; using Microsoft.Iris.Render.Internal; using System; using System.Runtime.InteropServices; +using Microsoft.Iris.Render.Bitmaps; namespace Microsoft.Iris.Render.Extensions { public static class ImageLoader { + private static BitmapInformation CreateBitmapInformation() + { +#if WINDOWS + return new SpBitmapInformation(); +#else + return new ImageSharpBitmapInformation(); +#endif + } + public static bool LoadHeader(IntPtr rgData, int length, out ImageHeader header) => LoadHeader(rgData, length, new ImageRequirements(), out header); @@ -26,17 +36,16 @@ namespace Microsoft.Iris.Render.Extensions Debug2.Validate(length > 0, typeof(ArgumentOutOfRangeException), "Must provide non-zero length data to load"); Debug2.Validate(req != null, typeof(ArgumentNullException), "Must provide valid ImageRequirements"); - var bitmapInformation = new BitmapInformation(); - var hresult = new HRESULT(-1); + var bitmapInformation = CreateBitmapInformation(); + HRESULT hresult = -1; try { - hresult = ExtensionsApi.SpBitmapLoadBuffer(rgData, (uint)length, req, ExtensionsApi.BitmapOptions.None, - out bitmapInformation.hBitmap, out bitmapInformation.imageInfo); + hresult = bitmapInformation.LoadBuffer(rgData, length, req); header = !hresult.IsSuccess() ? new ImageHeader() - : bitmapInformation.imageInfo.Header; + : bitmapInformation.ImageInfo.Header; } finally { @@ -65,14 +74,9 @@ namespace Microsoft.Iris.Render.Extensions AntialiasEdges = antialiasEdges, MaximumSize = maxSize }; - - var nOptions = ExtensionsApi.BitmapOptions.Decode; - if (flipRTL) - nOptions |= ExtensionsApi.BitmapOptions.Flip; - - var bitmapInformation = new BitmapInformation(); - var hresult = ExtensionsApi.SpBitmapLoadFile(filename, req, nOptions, - out bitmapInformation.hBitmap, out bitmapInformation.imageInfo); + + var bitmapInformation = CreateBitmapInformation(); + var hresult = bitmapInformation.LoadFile(filename, req); if (!hresult.IsSuccess()) { bitmapInfo = null; @@ -80,10 +84,10 @@ namespace Microsoft.Iris.Render.Extensions } var loadSuccess = image.LoadContent( - SurfaceFormatInfo.ToImageFormat(bitmapInformation.imageInfo.Header.nFormat), - bitmapInformation.imageInfo.Header.sizeActualPxl, - bitmapInformation.imageInfo.Header.nStride, - bitmapInformation.imageInfo.Data.rgData); + SurfaceFormatInfo.ToImageFormat(bitmapInformation.ImageInfo.Header.nFormat), + bitmapInformation.ImageInfo.Header.sizeActualPxl, + bitmapInformation.ImageInfo.Header.nStride, + bitmapInformation.ImageInfo.Data.rgData); if (loadSuccess) { @@ -119,20 +123,8 @@ namespace Microsoft.Iris.Render.Extensions MaximumSize = maxSize }; - var hinst = ModuleManager.Instance.LoadModule(moduleName); - if (hinst == Win32Api.HINSTANCE.NULL) - { - bitmapInfo = null; - return false; - } - - var nOptions = ExtensionsApi.BitmapOptions.Decode; - if (flipRTL) - nOptions |= ExtensionsApi.BitmapOptions.Flip; - - var bitmapInformation = new BitmapInformation(); - var hresult = ExtensionsApi.SpBitmapLoadResource(hinst, resourceID, 10, req, nOptions, - out bitmapInformation.hBitmap, out bitmapInformation.imageInfo); + var bitmapInformation = CreateBitmapInformation(); + var hresult = bitmapInformation.LoadResource(moduleName, resourceID, req); if (hresult.IsError()) { bitmapInfo = null; @@ -140,10 +132,10 @@ namespace Microsoft.Iris.Render.Extensions } var loadSuccess = image.LoadContent( - SurfaceFormatInfo.ToImageFormat(bitmapInformation.imageInfo.Header.nFormat), - bitmapInformation.imageInfo.Header.sizeActualPxl, - bitmapInformation.imageInfo.Header.nStride, - bitmapInformation.imageInfo.Data.rgData); + SurfaceFormatInfo.ToImageFormat(bitmapInformation.ImageInfo.Header.nFormat), + bitmapInformation.ImageInfo.Header.sizeActualPxl, + bitmapInformation.ImageInfo.Header.nStride, + bitmapInformation.ImageInfo.Data.rgData); if (loadSuccess) { @@ -180,13 +172,8 @@ namespace Microsoft.Iris.Render.Extensions MaximumSize = maxSize }; - var nOptions = ExtensionsApi.BitmapOptions.Decode; - if (flipRTL) - nOptions |= ExtensionsApi.BitmapOptions.Flip; - - var bitmapInformation = new BitmapInformation(); - var hresult = ExtensionsApi.SpBitmapLoadBuffer(buffer, (uint)length, req, nOptions, - out bitmapInformation.hBitmap, out bitmapInformation.imageInfo); + var bitmapInformation = CreateBitmapInformation(); + var hresult = bitmapInformation.LoadBuffer(buffer, length, req); if (!hresult.IsSuccess()) { bitmapInfo = null; @@ -194,10 +181,10 @@ namespace Microsoft.Iris.Render.Extensions } var loadSuccess = image.LoadContent( - SurfaceFormatInfo.ToImageFormat(bitmapInformation.imageInfo.Header.nFormat), - bitmapInformation.imageInfo.Header.sizeActualPxl, - bitmapInformation.imageInfo.Header.nStride, - bitmapInformation.imageInfo.Data.rgData); + SurfaceFormatInfo.ToImageFormat(bitmapInformation.ImageInfo.Header.nFormat), + bitmapInformation.ImageInfo.Header.sizeActualPxl, + bitmapInformation.ImageInfo.Header.nStride, + bitmapInformation.ImageInfo.Data.rgData); if (loadSuccess) { @@ -236,13 +223,8 @@ namespace Microsoft.Iris.Render.Extensions MaximumSize = maxSize }; - var nOptions = ExtensionsApi.BitmapOptions.Decode; - if (flipRTL) - nOptions |= ExtensionsApi.BitmapOptions.Flip; - - var bitmapInformation = new BitmapInformation(); - var hresult = ExtensionsApi.SpBitmapLoadRaw(imageSize, stride, format, buffer, req, nOptions, - out bitmapInformation.hBitmap, out bitmapInformation.imageInfo); + var bitmapInformation = CreateBitmapInformation(); + var hresult = bitmapInformation.LoadRaw(imageSize, stride, format, buffer, req); if (!hresult.IsSuccess()) { bitmapInfo = null; @@ -250,10 +232,10 @@ namespace Microsoft.Iris.Render.Extensions } var loadSuccess = image.LoadContent( - SurfaceFormatInfo.ToImageFormat(bitmapInformation.imageInfo.Header.nFormat), - bitmapInformation.imageInfo.Header.sizeActualPxl, - bitmapInformation.imageInfo.Header.nStride, - bitmapInformation.imageInfo.Data.rgData); + SurfaceFormatInfo.ToImageFormat(bitmapInformation.ImageInfo.Header.nFormat), + bitmapInformation.ImageInfo.Header.sizeActualPxl, + bitmapInformation.ImageInfo.Header.nStride, + bitmapInformation.ImageInfo.Data.rgData); if (loadSuccess) { diff --git a/UIX.RenderApi/Microsoft/Iris/Render/Internal/HRESULT.cs b/UIX.RenderApi/Microsoft/Iris/Render/Internal/HRESULT.cs index 7c7077e..14ccdee 100644 --- a/UIX.RenderApi/Microsoft/Iris/Render/Internal/HRESULT.cs +++ b/UIX.RenderApi/Microsoft/Iris/Render/Internal/HRESULT.cs @@ -8,31 +8,39 @@ using System.Runtime.InteropServices; namespace Microsoft.Iris.Render.Internal { - internal struct HRESULT + public struct HRESULT { - public int hr; + internal int hr; public HRESULT(int hr) => this.hr = hr; public static bool operator ==(HRESULT hrA, HRESULT hrB) => hrA.hr == hrB.hr; public static bool operator !=(HRESULT hrA, HRESULT hrB) => hrA.hr != hrB.hr; + + public static implicit operator HRESULT(int hr) => new(hr); + public static implicit operator HRESULT(uint hr) => new(unchecked((int)hr)); + public static implicit operator int(HRESULT hr) => hr.Int; - public override bool Equals(object oCompare) => oCompare is HRESULT hresult && this.hr == hresult.hr; + public override bool Equals(object oCompare) => oCompare is HRESULT hresult && hr == hresult.hr; - public override int GetHashCode() => this.hr; + public override int GetHashCode() => hr; - public bool IsError() => this.hr < 0; + public bool IsError() => hr < 0; - public bool IsSuccess() => this.hr >= 0; + public bool IsSuccess() => hr >= 0; public void HandleError() { - if (!this.IsError()) + if (!IsError()) return; - Marshal.ThrowExceptionForHR(this.hr); + + Marshal.ThrowExceptionForHR(hr); } - public int Int => this.hr; + public int Int => hr; + + public static HRESULT S_OK => new(0); + public static HRESULT E_FAIL => 0x80004005; } } diff --git a/UIX.RenderApi/UIX.RenderApi.csproj b/UIX.RenderApi/UIX.RenderApi.csproj index 36eadfb..43bf858 100644 --- a/UIX.RenderApi/UIX.RenderApi.csproj +++ b/UIX.RenderApi/UIX.RenderApi.csproj @@ -8,6 +8,12 @@ false true - true + ./sixlabors-sample.lic + + + + + + \ No newline at end of file diff --git a/UIX.RenderApi/sixlabors-sample.lic b/UIX.RenderApi/sixlabors-sample.lic new file mode 100644 index 0000000..26c704d --- /dev/null +++ b/UIX.RenderApi/sixlabors-sample.lic @@ -0,0 +1 @@ +Id=ctm_01k4a0m3dxnya9epg35rqqxs29;Kind=Community;ExpiryDateUtc=2026-12-03T09:33:00.1566670Z;Key=M+2oIq6h6sAag2H476SNAgeI8LUHBYIhPnTHNzADw2WBuJ9+KYC8ouKV6kgbg78CuxMC2FbgnNUcHvvIT/s8Qg== \ No newline at end of file