diff --git a/Knossos.NET/Classes/APNGHelper.cs b/Knossos.NET/Classes/APNGHelper.cs
new file mode 100644
index 00000000..ebdf43b1
--- /dev/null
+++ b/Knossos.NET/Classes/APNGHelper.cs
@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace Knossos.NET.Classes
+{
+ ///
+ /// Helper class to read APNG files.
+ ///
+ public static class APNGHelper
+ {
+ // PNG signature
+ private static readonly byte[] PngSignature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+
+ ///
+ /// Reads a stream to verify if it is a valid APNG file
+ /// Checks for acTL chuck presence.
+ /// Doesn't close or disposes the stream.
+ /// Throws a exception if the stream doesn't contains a png file data
+ ///
+ ///
+ /// true if file is apng
+ ///
+ public static bool IsApng(Stream pngStream)
+ {
+ if (pngStream == null || !pngStream.CanRead)
+ throw new ArgumentException("Stream inválido.");
+
+ long originalPos = pngStream.Position;
+
+ try
+ {
+ using (var br = new BinaryReader(pngStream, Encoding.ASCII, leaveOpen: true))
+ {
+ var sig = br.ReadBytes(8);
+ if (!BytesEqual(sig, PngSignature))
+ throw new Exception("Incorrect PNG Signature");
+
+ // Look for acTL o IEND
+ while (pngStream.Position < pngStream.Length)
+ {
+ var lengthBytes = br.ReadBytes(4);
+ if (lengthBytes.Length < 4) break;
+ int length = ReadBigEndianInt(lengthBytes);
+
+ var chunkType = Encoding.ASCII.GetString(br.ReadBytes(4));
+
+ if (chunkType == "acTL")
+ {
+ return true;
+ }
+
+ pngStream.Seek(length + 4, SeekOrigin.Current);
+
+ if (chunkType == "IEND")
+ break;
+ }
+ }
+ return false;
+ }
+ finally
+ {
+ pngStream.Seek(originalPos, SeekOrigin.Begin);
+ }
+ }
+
+
+ // Helpers
+
+ private static bool BytesEqual(byte[] a, byte[] b)
+ {
+ if (a.Length != b.Length) return false;
+ for (int i = 0; i < a.Length; i++)
+ if (a[i] != b[i]) return false;
+ return true;
+ }
+
+ private static int ReadBigEndianInt(byte[] bytes)
+ {
+ if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
+ return BitConverter.ToInt32(bytes, 0);
+ }
+ }
+}
diff --git a/Knossos.NET/ViewModels/Windows/ModDetailsViewModel.cs b/Knossos.NET/ViewModels/Windows/ModDetailsViewModel.cs
index ebce7ae0..44dbde34 100644
--- a/Knossos.NET/ViewModels/Windows/ModDetailsViewModel.cs
+++ b/Knossos.NET/ViewModels/Windows/ModDetailsViewModel.cs
@@ -4,6 +4,7 @@
using Avalonia.Threading;
using BBcodes;
using CommunityToolkit.Mvvm.ComponentModel;
+using Knossos.NET.Classes;
using Knossos.NET.Models;
using Knossos.NET.Views;
using System;
@@ -11,7 +12,6 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
-using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
@@ -86,6 +86,8 @@ public partial class ModDetailsViewModel : ViewModelBase
[ObservableProperty]
internal string? banner = null;
[ObservableProperty]
+ internal string? apngBanner = null;
+ [ObservableProperty]
internal bool forumAvailable = false;
[ObservableProperty]
internal bool isInstalled = false;
@@ -281,9 +283,30 @@ private void LoadBanner(int selectedIndex)
if (!string.IsNullOrEmpty(modVersions[selectedIndex].banner))
{
HasBanner = true;
- if (System.IO.File.Exists(modVersions[selectedIndex].fullPath + Path.DirectorySeparatorChar + modVersions[selectedIndex].banner))
+ var bannerLocalPath = modVersions[selectedIndex].fullPath + Path.DirectorySeparatorChar + modVersions[selectedIndex].banner;
+ if (System.IO.File.Exists(bannerLocalPath))
{
- Banner = modVersions[selectedIndex].fullPath + Path.DirectorySeparatorChar + modVersions[selectedIndex].banner;
+ var isApng = false;
+ using (var stream = new FileStream(bannerLocalPath, FileMode.Open, FileAccess.Read))
+ {
+ try
+ {
+ isApng = APNGHelper.IsApng(stream);
+
+ }
+ catch { /* Not a valid png*/ }
+ }
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ if (isApng)
+ {
+ ApngBanner = bannerLocalPath;
+ }
+ else
+ {
+ Banner = bannerLocalPath;
+ }
+ });
}
else
{
@@ -293,9 +316,30 @@ private void LoadBanner(int selectedIndex)
Task.Run(async () =>
{
var fs = await KnUtils.GetRemoteResource(url).ConfigureAwait(false);
- Dispatcher.UIThread.Invoke(() => {
- Banner = fs;
- });
+ if (fs != null)
+ {
+ var isApng = false;
+ using (var stream = new FileStream(fs, FileMode.Open, FileAccess.Read))
+ {
+ try
+ {
+ isApng = APNGHelper.IsApng(stream);
+
+ }
+ catch { /* Not a valid png*/ }
+ }
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ if (isApng)
+ {
+ ApngBanner = fs;
+ }
+ else
+ {
+ Banner = fs;
+ }
+ });
+ }
}).ConfigureAwait(false);
}
}
diff --git a/Knossos.NET/Views/Windows/ModDetailsView.axaml b/Knossos.NET/Views/Windows/ModDetailsView.axaml
index d5ce51df..84a4e75c 100644
--- a/Knossos.NET/Views/Windows/ModDetailsView.axaml
+++ b/Knossos.NET/Views/Windows/ModDetailsView.axaml
@@ -27,7 +27,7 @@
-
+