diff --git a/.gitignore b/.gitignore index e23c412..4570597 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ *.Development.json *.Release.json -.DS_Store \ No newline at end of file +.DS_Store +*.lic \ No newline at end of file diff --git a/fxl.codes.kisekae.blazor/fxl.codes.kisekae.blazor.csproj b/fxl.codes.kisekae.blazor/fxl.codes.kisekae.blazor.csproj index d72cbff..1570b01 100644 --- a/fxl.codes.kisekae.blazor/fxl.codes.kisekae.blazor.csproj +++ b/fxl.codes.kisekae.blazor/fxl.codes.kisekae.blazor.csproj @@ -1,9 +1,10 @@ - net8.0 + net10.0 enable enable + default @@ -15,7 +16,7 @@ - + diff --git a/fxl.codes.kisekae.data.test/LhContainerTests.cs b/fxl.codes.kisekae.data.test/LhContainerTests.cs index 6529d1f..b537385 100644 --- a/fxl.codes.kisekae.data.test/LhContainerTests.cs +++ b/fxl.codes.kisekae.data.test/LhContainerTests.cs @@ -35,11 +35,12 @@ public class LhContainerTests foreach (var file in files) { - Assert.That(file.FileName, Is.Not.Null, "Filename is {0}", file.FileName); - Assert.That(file.CompressedSize, Is.GreaterThan(0), "Compressed size is {0}", file.CompressedSize); - Assert.That(file.UncompressedSize, Is.GreaterThan(0), "Uncompressed size is {0}", file.UncompressedSize); + Assert.IsNotNull(file.FileName, "Filename is {0}", file.FileName); + Assert.Greater(file.CompressedSize, 0, "Compressed size is {0}", file.CompressedSize); + Assert.Greater(file.CompressedSize, 0, "Compressed size is {0}", file.CompressedSize); + Assert.Greater(file.UncompressedSize, 0, "Uncompressed size is {0}", file.UncompressedSize); Assert.That(file.UncompressedSize, Is.GreaterThan(file.CompressedSize), "Uncompressed is larger than compressed"); - Assert.That(file.MethodId, Is.Not.Null, "Method id is {0}", file.MethodId); + Assert.IsNotNull(file.MethodId, "Method id is {0}", file.MethodId); Assert.That(file.MethodId, Does.StartWith("-l")); Assert.That(file.MethodId, Does.EndWith("-")); Console.WriteLine($"Archive name: {file.FileName}, compression method: {file.MethodId}, file at {file.FileDataPosition}"); diff --git a/fxl.codes.kisekae.data.test/fxl.codes.kisekae.data.test.csproj b/fxl.codes.kisekae.data.test/fxl.codes.kisekae.data.test.csproj index fc11ded..fa8df17 100644 --- a/fxl.codes.kisekae.data.test/fxl.codes.kisekae.data.test.csproj +++ b/fxl.codes.kisekae.data.test/fxl.codes.kisekae.data.test.csproj @@ -1,26 +1,27 @@ - net8.0 + net10.0 enable enable false true + default - - - - - - - - - - ..\..\..\..\..\usr\local\share\dotnet\shared\Microsoft.AspNetCore.App\8.0.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/fxl.codes.kisekae.data/Archives/LhContainer.cs b/fxl.codes.kisekae.data/Archives/LhContainer.cs index 3203a7d..c26a346 100644 --- a/fxl.codes.kisekae.data/Archives/LhContainer.cs +++ b/fxl.codes.kisekae.data/Archives/LhContainer.cs @@ -1,5 +1,8 @@ +using System.Runtime.CompilerServices; using System.Text; +[assembly: InternalsVisibleTo("fxl.codes.kisekae.data.test")] + namespace fxl.codes.kisekae.data.Archives; internal class LhContainer @@ -13,10 +16,10 @@ internal class LhContainer var span = bytes.AsSpan(); Level = span[20]; MethodId = Encoding.ASCII.GetString(span[2..7]); - CompressedSize = EndianUtility.ToLittleEndian(span[7..11]); + CompressedSize = span[7..11].ToLittleEndian(); CompressedFile = new byte[CompressedSize]; - UncompressedSize = EndianUtility.ToLittleEndian(span[11..15]); - Created = EndianUtility.ToDateTime(span[15..19]); + UncompressedSize = span[11..15].ToLittleEndian(); + Created = span[15..19].ToDateTime(); FileOrDirectory = span[19]; switch (Level) diff --git a/fxl.codes.kisekae.data/fxl.codes.kisekae.data.csproj b/fxl.codes.kisekae.data/fxl.codes.kisekae.data.csproj index c4c15a5..8659e21 100644 --- a/fxl.codes.kisekae.data/fxl.codes.kisekae.data.csproj +++ b/fxl.codes.kisekae.data/fxl.codes.kisekae.data.csproj @@ -1,22 +1,23 @@  - net8.0 + net10.0 enable enable Exe c3253549-351f-4bcb-9c52-ce67c39cbeaf + default - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/fxl.codes.kisekae/Models/CelModel.cs b/fxl.codes.kisekae/Models/CelModel.cs index 34d2efe..e3faab9 100644 --- a/fxl.codes.kisekae/Models/CelModel.cs +++ b/fxl.codes.kisekae/Models/CelModel.cs @@ -2,32 +2,31 @@ using System; using System.Collections.Generic; using fxl.codes.kisekae.data.Entities; -namespace fxl.codes.kisekae.Models -{ - public class CelModel - { - internal CelModel(CelConfig celConfig, int zIndex) - { - Mark = celConfig.Mark; - Fix = celConfig.Fix; - ZIndex = zIndex; - Image = $"data:image/gif;base64,{Convert.ToBase64String(celConfig.Render.Image)}"; - Height = celConfig.Cel.Height; - Width = celConfig.Cel.Width; - Opacity = (double)(255 - celConfig.Transparency) / 255; - Offset = new CoordinateModel(celConfig.Cel.OffsetX, celConfig.Cel.OffsetY); - - foreach (var position in celConfig.Positions) InitialPositions.Add(position.Set, new CoordinateModel(position.X, position.Y)); - } +namespace fxl.codes.kisekae.Models; - public int Fix { get; set; } - public int Height { get; set; } - public string Image { get; set; } - public int Mark { get; set; } - public double Opacity { get; set; } - public int Width { get; set; } - public int ZIndex { get; set; } - public IDictionary InitialPositions { get; set; } = new Dictionary(); - public CoordinateModel Offset { get; set; } +public class CelModel +{ + internal CelModel(CelConfig celConfig, int zIndex) + { + Mark = celConfig.Mark; + Fix = celConfig.Fix; + ZIndex = zIndex; + Image = $"data:image/gif;base64,{Convert.ToBase64String(celConfig.Render.Image)}"; + Height = celConfig.Cel.Height; + Width = celConfig.Cel.Width; + Opacity = (double)(255 - celConfig.Transparency) / 255; + Offset = new CoordinateModel(celConfig.Cel.OffsetX, celConfig.Cel.OffsetY); + + foreach (var position in celConfig.Positions) InitialPositions.Add(position.Set, new CoordinateModel(position.X, position.Y)); } + + public int Fix { get; set; } + public int Height { get; set; } + public string Image { get; set; } + public int Mark { get; set; } + public double Opacity { get; set; } + public int Width { get; set; } + public int ZIndex { get; set; } + public IDictionary InitialPositions { get; set; } = new Dictionary(); + public CoordinateModel Offset { get; set; } } \ No newline at end of file diff --git a/fxl.codes.kisekae/Models/CoordinateModel.cs b/fxl.codes.kisekae/Models/CoordinateModel.cs index b1a6765..609d788 100644 --- a/fxl.codes.kisekae/Models/CoordinateModel.cs +++ b/fxl.codes.kisekae/Models/CoordinateModel.cs @@ -1,14 +1,13 @@ -namespace fxl.codes.kisekae.Models -{ - public class CoordinateModel - { - internal CoordinateModel(int x, int y) - { - X = x; - Y = y; - } +namespace fxl.codes.kisekae.Models; - public int X { get; set; } - public int Y { get; set; } +public class CoordinateModel +{ + internal CoordinateModel(int x, int y) + { + X = x; + Y = y; } + + public int X { get; set; } + public int Y { get; set; } } \ No newline at end of file diff --git a/fxl.codes.kisekae/Models/ErrorViewModel.cs b/fxl.codes.kisekae/Models/ErrorViewModel.cs index 3d04f69..2634956 100644 --- a/fxl.codes.kisekae/Models/ErrorViewModel.cs +++ b/fxl.codes.kisekae/Models/ErrorViewModel.cs @@ -1,9 +1,8 @@ -namespace fxl.codes.kisekae.Models -{ - public class ErrorViewModel - { - public string RequestId { get; set; } +namespace fxl.codes.kisekae.Models; - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } +public class ErrorViewModel +{ + public string RequestId { get; init; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); } \ No newline at end of file diff --git a/fxl.codes.kisekae/Models/PlaysetModel.cs b/fxl.codes.kisekae/Models/PlaysetModel.cs index 596b1a5..b4b23c6 100644 --- a/fxl.codes.kisekae/Models/PlaysetModel.cs +++ b/fxl.codes.kisekae/Models/PlaysetModel.cs @@ -4,7 +4,7 @@ using fxl.codes.kisekae.data.Entities; namespace fxl.codes.kisekae.Models; -public class PlaysetModel +public record PlaysetModel { [JsonIgnore] public readonly string BorderColor; @@ -24,9 +24,9 @@ public class PlaysetModel for (var index = 0; index < 10; index++) Sets[index] = configuration.Cels.Any(x => x.Positions.Any(y => y.Set == index)); } - public CelModel[] Cels { get; set; } - public int Height { get; set; } - public string Name { get; set; } - public bool[] Sets { get; set; } = new bool[10]; - public int Width { get; set; } + public CelModel[] Cels { get; init; } + public int Height { get; init; } + public string Name { get; init; } + public bool[] Sets { get; init; } = new bool[10]; + public int Width { get; init; } } \ No newline at end of file diff --git a/fxl.codes.kisekae/Services/ConfigurationReaderService.cs b/fxl.codes.kisekae/Services/ConfigurationReaderService.cs index 828dd8a..e48bf9d 100644 --- a/fxl.codes.kisekae/Services/ConfigurationReaderService.cs +++ b/fxl.codes.kisekae/Services/ConfigurationReaderService.cs @@ -8,26 +8,19 @@ using Microsoft.Extensions.Logging; namespace fxl.codes.kisekae.Services; -public class ConfigurationReaderService +public class ConfigurationReaderService(ILogger logger) { private const string CelRegex = @"#(?\d*)\.?(?\d*)\s*(?[\w\d\-]*\.[cCeElL]*)\s*\*?" + @"(?\d*)?\s*\:?(?[\d\s]*)?;?(?[\w\d\-\s\%]*)"; private const string ResolutionRegexPattern = @"\((?[0-9]*).(?[0-9]*)\)"; - private readonly ILogger _logger; - - public ConfigurationReaderService(ILogger logger) - { - _logger = logger; - } - public void ReadConfiguration(Configuration dto, IDictionary backgroundColors, IDictionary cels, Dictionary palettes) { - _logger.LogInformation($"Reading {dto.Name}"); + logger.LogInformation("Reading {Name}", dto.Name); var initialPositions = new StringBuilder(); var backgroundColorIndex = 0; var paletteOrder = new List(); diff --git a/fxl.codes.kisekae/Services/DatabaseService.cs b/fxl.codes.kisekae/Services/DatabaseService.cs index 5e806d4..fce6e3a 100644 --- a/fxl.codes.kisekae/Services/DatabaseService.cs +++ b/fxl.codes.kisekae/Services/DatabaseService.cs @@ -10,36 +10,21 @@ using fxl.codes.kisekae.data; using fxl.codes.kisekae.data.Entities; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using Configuration = fxl.codes.kisekae.data.Entities.Configuration; namespace fxl.codes.kisekae.Services; -public class DatabaseService +public class DatabaseService( + ConfigurationReaderService configurationReaderService, + FileParserService fileParserService, + IDbContextFactory contextFactory) { - private readonly ConfigurationReaderService _configurationReaderService; - private readonly IDbContextFactory _contextFactory; - - private readonly FileParserService _fileParserService; - private readonly ILogger _logger; - private readonly IsolatedStorageFile _storage; - - public DatabaseService(ILogger logger, - ConfigurationReaderService configurationReaderService, - FileParserService fileParserService, - IDbContextFactory contextFactory) - { - _logger = logger; - _configurationReaderService = configurationReaderService; - _fileParserService = fileParserService; - _contextFactory = contextFactory; - _storage = IsolatedStorageFile.GetUserStoreForApplication(); - } + private readonly IsolatedStorageFile _storage = IsolatedStorageFile.GetUserStoreForApplication(); public IEnumerable GetAll() { - using var context = _contextFactory.CreateDbContext(); + using var context = contextFactory.CreateDbContext(); return context.KisekaeSets .Include(x => x.Configurations) .ToArray(); @@ -47,7 +32,7 @@ public class DatabaseService public Configuration GetConfig(int id) { - using var context = _contextFactory.CreateDbContext(); + using var context = contextFactory.CreateDbContext(); return context.Configurations .Include(x => x.Cels).ThenInclude(x => x.Render) .Include(x => x.Cels).ThenInclude(x => x.Cel) @@ -63,14 +48,14 @@ public class DatabaseService var checksum = Convert.ToBase64String(SHA256.HashData(memoryStream.GetBuffer())); memoryStream.Position = 0; // Reset for re-read - await using var context = await _contextFactory.CreateDbContextAsync(); + await using var context = await contextFactory.CreateDbContextAsync(); var existing = await context.KisekaeSets .FirstOrDefaultAsync(x => string.Equals(x.FileName, file.FileName) || string.Equals(x.CheckSum, checksum)); if (existing != null) context.KisekaeSets.Remove(existing); await context.SaveChangesAsync(); - await _fileParserService.UnzipLzh(file, memoryStream); + await fileParserService.UnzipLzh(file, memoryStream); var directory = Path.GetFileNameWithoutExtension(file.FileName); @@ -85,7 +70,7 @@ public class DatabaseService var backgroundColors = new Dictionary(); foreach (var config in kisekae.Configurations) - _configurationReaderService.ReadConfiguration(config, + configurationReaderService.ReadConfiguration(config, backgroundColors, kisekae.Cels.ToDictionary(x => x.FileName.ToLowerInvariant()), kisekae.Palettes.ToDictionary(x => x.FileName.ToLowerInvariant())); @@ -95,7 +80,7 @@ public class DatabaseService foreach (var (config, colorIndex) in backgroundColors) config.BackgroundColorHex = kisekae.Palettes.First()?.Colors[colorIndex].Hex ?? Color.White.ToHex(); foreach (var celConfig in kisekae.Configurations.SelectMany(config => config.Cels)) - _fileParserService.RenderCel(celConfig); + fileParserService.RenderCel(celConfig); await context.AddAsync(kisekae); await context.SaveChangesAsync(); @@ -109,7 +94,7 @@ public class DatabaseService if (!reader.CanRead) throw new InvalidDataException(); var bytes = new byte[reader.Length]; - reader.Read(bytes, 0, bytes.Length); + reader.ReadExactly(bytes); switch (Path.GetExtension(filename).ToLower()) { @@ -142,7 +127,7 @@ public class DatabaseService { foreach (var palette in palettes) { - var colors = _fileParserService.ParsePalette(palette, out var groups, out var colorsPerGroup); + var colors = fileParserService.ParsePalette(palette, out var groups, out var colorsPerGroup); for (var groupIndex = 0; groupIndex < groups; groupIndex++) for (var colorIndex = 0; colorIndex < colorsPerGroup; colorIndex++) diff --git a/fxl.codes.kisekae/Services/FileParserService.cs b/fxl.codes.kisekae/Services/FileParserService.cs index 60d0acd..4073fa2 100644 --- a/fxl.codes.kisekae/Services/FileParserService.cs +++ b/fxl.codes.kisekae/Services/FileParserService.cs @@ -14,30 +14,23 @@ using SixLabors.ImageSharp.PixelFormats; namespace fxl.codes.kisekae.Services; -public class FileParserService +public class FileParserService(ILogger logger) { private static readonly byte[] KissHeader = "KiSS"u8.ToArray(); private static readonly Color Transparent = Color.Black.WithAlpha(0); - private readonly ILogger _logger; - private readonly IsolatedStorageFile _storage; - - public FileParserService(ILogger logger) - { - _logger = logger; - _storage = IsolatedStorageFile.GetUserStoreForApplication(); - } + private readonly IsolatedStorageFile _storage = IsolatedStorageFile.GetUserStoreForApplication(); public async Task UnzipLzh(IFormFile file, MemoryStream memoryStream = null) { if (!_storage.FileExists(file.FileName)) { - using var writer = _storage.CreateFile(file.FileName); + await using var writer = _storage.CreateFile(file.FileName); if (memoryStream != null) - memoryStream.CopyTo(writer); + await memoryStream.CopyToAsync(writer); else - file.CopyTo(writer); + await file.CopyToAsync(writer); writer.Flush(); writer.Close(); @@ -58,7 +51,7 @@ public class FileParserService await process.WaitForExitAsync(); } - public Color[] ParsePalette(Palette palette, out int groups, out int colorsPerGroup) + public Rgba32[] ParsePalette(Palette palette, out int groups, out int colorsPerGroup) { groups = 0; colorsPerGroup = 0; @@ -67,7 +60,7 @@ public class FileParserService if (!palette.Data[..KissHeader.Length].SequenceEqual(KissHeader)) return GetColors(palette.Data); // Verify palette mark? - if (palette.Data[4] != 16) _logger.LogError($"{palette.FileName} is not a valid palette file"); + if (palette.Data[4] != 16) logger.LogError("{PaletteFileName} is not a valid palette file", palette.FileName); var colorDepth = Convert.ToInt32(palette.Data[5]); colorsPerGroup = BitConverter.ToInt16(palette.Data, 8); @@ -78,14 +71,14 @@ public class FileParserService public void RenderCel(CelConfig celConfig) { - _logger.LogTrace($"Reading cel {celConfig.Cel.FileName}"); - var buffer = celConfig.Cel?.Data?.ToArray(); - if (buffer == null || buffer.Length == 0) return; + logger.LogTrace("Reading cel {CelFileName}", celConfig.Cel.FileName); + var buffer = celConfig.Cel.Data.ToArray(); + if (buffer.Length == 0) return; if (buffer[..KissHeader.Length].SequenceEqual(KissHeader)) { // Verify cel mark? - if (buffer[4] != 32) _logger.LogError($"{celConfig.Cel.FileName} is not a valid cel file"); + if (buffer[4] != 32) logger.LogError("{CelFileName} is not a valid cel file", celConfig.Cel.FileName); var pixelBits = Convert.ToInt32(buffer[5]); var width = BitConverter.ToInt16(buffer, 8); @@ -111,11 +104,11 @@ public class FileParserService } } - private Color[] GetColors(IReadOnlyCollection bytes, int depth = 12, int colorsPerGroup = 16, int groups = 10) + private Rgba32[] GetColors(IReadOnlyCollection bytes, int depth = 12, int colorsPerGroup = 16, int groups = 10) { - _logger.LogTrace("Converting byte array to colors"); + logger.LogTrace("Converting byte array to colors"); var colorValues = GetValues(bytes, depth / 3); - var colors = new Color[colorsPerGroup * groups]; + var colors = new Rgba32[colorsPerGroup * groups]; var length = depth == 12 ? 4 : 3; var multiplier = depth == 12 ? 16 : 1; // Nibbles order for 12 bits: R, B, 0, G @@ -127,7 +120,7 @@ public class FileParserService var red = colorValues[start] * multiplier; var green = colorValues[start + (depth == 12 ? 3 : 1)] * multiplier; var blue = colorValues[start + (depth == 12 ? 1 : 2)] * multiplier; - colors[groupIndex + colorIndex] = Color.FromRgb(Convert.ToByte(red), Convert.ToByte(green), Convert.ToByte(blue)); + colors[groupIndex + colorIndex] = new Rgba32(Convert.ToByte(red), Convert.ToByte(green), Convert.ToByte(blue)); } return colors; @@ -139,7 +132,7 @@ public class FileParserService int height, int pixelBits = 4) { - _logger.LogTrace("Converting byte array to base64 encoded gifs per palette"); + logger.LogTrace("Converting byte array to base64 encoded gifs per palette"); var values = GetValues(bytes, pixelBits); using var bitmap = new Image(width, height); @@ -153,7 +146,7 @@ public class FileParserService { var value = values[index]; var isTransparent = value == 0 || value >= palette.Colors.Count; - row[x] = isTransparent ? Transparent : Color.ParseHex(palette.Colors[value].Hex); + row[x] = (isTransparent ? Transparent : Color.ParseHex(palette.Colors[value].Hex)).ToPixel(); index++; } diff --git a/fxl.codes.kisekae/fxl.codes.kisekae.csproj b/fxl.codes.kisekae/fxl.codes.kisekae.csproj index 905bde2..42cdc1f 100644 --- a/fxl.codes.kisekae/fxl.codes.kisekae.csproj +++ b/fxl.codes.kisekae/fxl.codes.kisekae.csproj @@ -1,14 +1,15 @@ - net8.0 + net10.0 true - latestmajor + default + b2886b11-62f3-4354-b5c5-eaa9ea9678d3 - - + + @@ -25,6 +26,6 @@ - +