From b4d3ca64f3c148ea0dcaccbc419010e2f0b64ed6 Mon Sep 17 00:00:00 2001 From: YeLike <93629620+YeLikesss@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:50:51 +0800 Subject: [PATCH] =?UTF-8?q?[NekoNovel]=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/NekoNovel/ArcNKPACK.cs | 94 +++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 ArcFormats/NekoNovel/ArcNKPACK.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 2aa2aa8c..8b715459 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -265,6 +265,7 @@ + diff --git a/ArcFormats/NekoNovel/ArcNKPACK.cs b/ArcFormats/NekoNovel/ArcNKPACK.cs new file mode 100644 index 00000000..0519d21d --- /dev/null +++ b/ArcFormats/NekoNovel/ArcNKPACK.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using GameRes.Compression; +using System.ComponentModel.Composition; +using CommunityToolkit.HighPerformance; + +namespace GameRes.Formats.NekoNovel +{ + [Export(typeof(ArchiveFormat))] + public class NkpackOpener : ArchiveFormat + { + public override string Tag => "NKPACK/NekoNovel"; + public override string Description => "NekoNovel resource archive"; + public override uint Signature => 0u; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public NkpackOpener() + { + Extensions = new string[] { "nkpack" }; + } + + private static readonly byte[] s_mHeader = new byte[] + { + 0x0F, 0x00, 0x00, 0x00, 0x4E, 0x4B, 0x4E, 0x4F, 0x45, 0x56, 0x4C, 0x20, 0x50, 0x41, 0x43, 0x4B, + 0x41, 0x47, 0x45 + }; + + public override ArcFile TryOpen(ArcView file) + { + if (!file.View.BytesEqual(0L, s_mHeader)) + { + return null; + } + using (ArcViewStream stream = file.CreateStream()) + { + stream.Seek(-4L, SeekOrigin.End); + uint entryOffset = ~stream.ReadUInt32(); + + stream.Seek(entryOffset, SeekOrigin.Begin); + string signature = ReadString(stream); + + int count = stream.ReadInt32(); + List entries = new List(count); + for (int i = 0; i < count; ++i) + { + string name = ReadString(stream); + + PackedEntry entry = Create(name); + entry.Offset = ~stream.ReadUInt32(); + entry.UnpackedSize = stream.ReadUInt32(); + entry.Size = ~stream.ReadUInt32(); + entry.IsPacked = true; + + if (!entry.CheckPlacement(file.MaxOffset)) + { + return null; + } + + entries.Add(entry); + } + + return new ArcFile(file, this, entries); + } + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) + { + if (!(entry is PackedEntry e)) + { + return arc.File.CreateStream(entry.Offset, entry.Size); + } + return new ZLibStream(arc.File.CreateStream(e.Offset, e.Size), CompressionMode.Decompress); + } + + private unsafe static string ReadString(Stream stream) + { + uint length = 0u; + stream.Read(new Span(&length, 4)); + + if (length == 0u) + { + return string.Empty; + } + + byte[] data = new byte[length]; + stream.Read(data, 0, data.Length); + + return Encoding.UTF8.GetString(data); + } + } +}