From be3bf61c823412138f6fc8bd33361398b54aa0e5 Mon Sep 17 00:00:00 2001 From: YeLike <93629620+YeLikesss@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:13:48 +0800 Subject: [PATCH] =?UTF-8?q?[CatSystem2]=20=E5=B0=81=E5=8C=85=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/CatSystem/ArcBinV1.cs | 196 ++++++++++++++++++++++++++++++ ArcFormats/CatSystem/ArcPidaV1.cs | 86 +++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 ArcFormats/CatSystem/ArcBinV1.cs create mode 100644 ArcFormats/CatSystem/ArcPidaV1.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 8b715459..1c3664b1 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -176,6 +176,8 @@ + + diff --git a/ArcFormats/CatSystem/ArcBinV1.cs b/ArcFormats/CatSystem/ArcBinV1.cs new file mode 100644 index 00000000..27fdb700 --- /dev/null +++ b/ArcFormats/CatSystem/ArcBinV1.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; + +namespace GameRes.Formats.CatSystem +{ + internal class BinEntryV1 : Entry + { + public long Size64 { get; set; } + } + + internal class BinStreamV1 : Stream + { + private Stream mBaseStream; + private readonly long mOffset; + private readonly long mLength; + private long mPosition = 0L; + private bool mDisposed = false; + + public BinStreamV1(Stream stream, long offset, long length) + { + this.mBaseStream = stream; + this.mOffset = offset; + this.mLength = length; + } + + public override bool CanRead => !this.mDisposed; + public override bool CanSeek => !this.mDisposed; + public override bool CanWrite => false; + public override long Length => this.mLength; + public override long Position + { + get + { + return this.mPosition; + } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (value > this.mLength) + { + throw new ArgumentOutOfRangeException(); + } + this.mPosition = value; + } + } + + public override void Flush() + { + } + public override int Read(byte[] buffer, int offset, int count) + { + Stream stream = this.mBaseStream; + + stream.Position = this.mOffset + this.mPosition; + int bytesRead = stream.Read(buffer, offset, (int)Math.Min(this.mLength - this.mPosition, count)); + + this.Decrypt(buffer, offset, bytesRead, this.mOffset, this.mPosition); + this.mPosition += bytesRead; + + return bytesRead; + } + public override long Seek(long offset, SeekOrigin origin) + { + long pos = 0L; + switch (origin) + { + case SeekOrigin.Begin: + { + pos = offset; + break; + } + case SeekOrigin.Current: + { + pos = this.mPosition + offset; + break; + } + case SeekOrigin.End: + { + pos = this.mLength + offset; + break; + } + } + + if (pos < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (pos > this.mLength) + { + throw new ArgumentOutOfRangeException(); + } + + this.mPosition = pos; + return pos; + } + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + protected override void Dispose(bool disposing) + { + if (!this.mDisposed) + { + if (disposing) + { + this.mBaseStream.Dispose(); + this.mBaseStream = Stream.Null; + } + this.mDisposed = true; + base.Dispose(disposing); + } + } + + protected virtual void Decrypt(byte[] buffer, long offset, int count, long fileOffset, long arcOffset) + { + for(int i = 0; i < count; ++i) + { + byte key = (byte)((fileOffset + arcOffset + i) * 0x9D + (arcOffset + i) * 0x773); + buffer[offset + i] -= key; + } + } + } + + [Export(typeof(ArchiveFormat))] + public class BinOpenerV1 : ArchiveFormat + { + public override string Tag => "BinV1/CSPACK"; + public override string Description => "CatSystem2 resource archive"; + public override uint Signature => 0x40674461u; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public override ArcFile TryOpen(ArcView file) + { + using (ArcViewStream stream = file.CreateStream()) + { + using (BinaryReader br = new BinaryReader(stream, Encoding.Unicode, true)) + { + stream.Position = 8L; + + List entries = new List(); + { + string fn = br.ReadString(); + while (!string.IsNullOrEmpty(fn)) + { + BinEntryV1 e = Create(fn); + e.Offset = br.ReadUInt32(); + e.Size64 = 0L; + + entries.Add(e); + + fn = br.ReadString(); + } + } + + if (entries.Any()) + { + { + BinEntryV1 last = entries.Last(); + last.Size64 = stream.Length - last.Offset; + last.Size = (uint)last.Size64; + } + for (int i = 0; i < entries.Count - 1; ++i) + { + BinEntryV1 curr = entries[i + 0]; + BinEntryV1 next = entries[i + 1]; + curr.Size64 = next.Offset - curr.Offset; + curr.Size = (uint)curr.Size64; + } + } + + return new ArcFile(file, this, entries.Cast().ToList()); + } + } + } + public override Stream OpenEntry(ArcFile arc, Entry entry) + { + if(!(entry is BinEntryV1 e)) + { + return base.OpenEntry(arc, entry); + } + return new BinStreamV1(arc.File.CreateStream(), e.Offset, e.Size64); + } + } +} diff --git a/ArcFormats/CatSystem/ArcPidaV1.cs b/ArcFormats/CatSystem/ArcPidaV1.cs new file mode 100644 index 00000000..aaca6ce6 --- /dev/null +++ b/ArcFormats/CatSystem/ArcPidaV1.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; + +namespace GameRes.Formats.CatSystem +{ + internal class PidaEntryV1 : Entry + { + public ushort Width; + public ushort Height; + public short OffsetX; + public short OffsetY; + } + + [Export(typeof(ArchiveFormat))] + public class PidaOpenerV1 : ArchiveFormat + { + public override string Tag => "PidaV1"; + public override string Description => "CatSystem2 engine multi-image"; + public override uint Signature => 0x6DF22373u; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public PidaOpenerV1() + { + ContainedFormats = new[] { "PNG" }; + } + + public override ArcFile TryOpen(ArcView file) + { + using (ArcViewStream stream = file.CreateStream()) + { + using (BinaryReader br = new BinaryReader(stream, Encoding.Unicode, true)) + { + stream.Position = 8L; + + List entries = new List(); + { + string fn = br.ReadString(); + while (!string.IsNullOrEmpty(fn)) + { + PidaEntryV1 e = Create(fn); + e.Offset = br.ReadUInt32(); + e.Size = 0u; + + e.Width = br.ReadUInt16(); + e.Height = br.ReadUInt16(); + e.OffsetX = br.ReadInt16(); + e.OffsetY = br.ReadInt16(); + + e.Type = "image"; + entries.Add(e); + + fn = br.ReadString(); + } + } + + if (entries.Any()) + { + long imageDataOffset = stream.Position; + + foreach (PidaEntryV1 e in entries) + { + e.Offset += imageDataOffset; + } + { + PidaEntryV1 last = entries.Last(); + last.Size = (uint)(stream.Length - last.Offset); + } + for (int i = 0; i < entries.Count - 1; ++i) + { + PidaEntryV1 curr = entries[i + 0]; + PidaEntryV1 next = entries[i + 1]; + curr.Size = (uint)(next.Offset - curr.Offset); + } + } + + return new ArcFile(file, this, entries.Cast().ToList()); + } + } + } + } +}