Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions Knossos.NET/Classes/APNGHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Text;

namespace Knossos.NET.Classes
{
/// <summary>
/// Helper class to read APNG files.
/// </summary>
public static class APNGHelper
{
// PNG signature
private static readonly byte[] PngSignature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };

/// <summary>
/// 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
/// </summary>
/// <param name="pngStream"></param>
/// <returns>true if file is apng</returns>
/// <exception cref="Exception"></exception>
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);
}
}
}
56 changes: 50 additions & 6 deletions Knossos.NET/ViewModels/Windows/ModDetailsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using Avalonia.Threading;
using BBcodes;
using CommunityToolkit.Mvvm.ComponentModel;
using Knossos.NET.Classes;
using Knossos.NET.Models;
using Knossos.NET.Views;
using System;
using System.Collections.Generic;
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand All @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion Knossos.NET/Views/Windows/ModDetailsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<ScrollViewer Background="{StaticResource BackgroundColorPrimary}">
<Grid RowDefinitions="Auto,Auto,*,Auto">
<StackPanel Grid.Row="0" Margin="25">
<Image Source="{Binding Banner, Converter={StaticResource imageConverter}}" anim:ImageBehavior.AnimatedSource="{Binding Banner}" HorizontalAlignment="Stretch" Height="300" IsVisible="{Binding HasBanner}"></Image>
<Image Source="{Binding Banner, Converter={StaticResource imageConverter}}" anim:ImageBehavior.AnimatedSource="{Binding ApngBanner}" HorizontalAlignment="Stretch" Height="300" IsVisible="{Binding HasBanner}"></Image>
<Label Content="{Binding Name, Converter={StaticResource escapeUnderscoresConverter}}" FontWeight="Bold" FontSize="24" VerticalContentAlignment="Top" HorizontalContentAlignment="Center" Foreground="White"/>

<!--Buttons and Mod info-->
Expand Down