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()); + } + } + } + } +}