diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 6e5bd09e..e4a716ef 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -192,6 +192,7 @@ + diff --git a/ArcFormats/Musica/ArcPAK.cs b/ArcFormats/Musica/ArcPAK.cs new file mode 100644 index 00000000..d090af84 --- /dev/null +++ b/ArcFormats/Musica/ArcPAK.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Musica +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get; } = "PAK"; + public override string Description { get; } = "Musica engine legacy resource archive"; + public override uint Signature { get; } = 0; + public override bool IsHierarchic { get; } = true; + public override bool CanWrite { get; } = false; + + public PakOpener() + { + Extensions = new[] { "pak" }; + ContainedFormats = new string[] { "PNG", "OGG" }; + } + + static readonly HashSet PakImageNames = new HashSet() + { + "bg", "st", + }; + + static readonly HashSet PakAudioNames = new HashSet() + { + "bgm", "se", "voice", + }; + + private string GetType(string pakName, string entryName) + { + if (PakImageNames.Contains(pakName)) + { + return "image"; + } + + if (PakAudioNames.Contains(pakName)) + { + return "audio"; + } + + return FormatCatalog.Instance.GetTypeFromName(entryName, ContainedFormats); + } + + public override ArcFile TryOpen(ArcView view) + { + Stream input = view.CreateStream(); + using(input = new NegStream(input)) + { + using(ArcView.Reader reader = new ArcView.Reader(input)) + { + int count = reader.ReadInt32(); + if (count <= 0) + { + return null; + } + + List entries = new List(count); + for(int i = 0; i < count; ++i) + { + uint indexLen = reader.ReadUInt32(); + long indexStart = input.Position; + + string name = input.ReadCString(); + if (string.IsNullOrWhiteSpace(name)) + { + return null; + } + uint size = reader.ReadUInt32(); + uint offset = reader.ReadUInt32(); + + Entry entry = new Entry() { Name = name, Offset = offset, Size = size }; + if (!entry.CheckPlacement(view.MaxOffset)) + { + return null; + } + entry.Type = this.GetType(Path.GetFileNameWithoutExtension(view.Name), entry.Name); + entries.Add(entry); + + input.Position = indexStart + indexLen; + } + return new PakArchive(view, this, entries, input.Position); + } + } + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) + { + if (!(arc is PakArchive pakArc)) + { + return base.OpenEntry(arc, entry); + } + + return new NegStream(base.OpenEntry(arc, pakArc.GetEntry(entry))); + } + } + + internal class PakArchive : ArcFile + { + private long m_IndexSize; + public PakArchive(ArcView arc, ArchiveFormat impl, ICollection dir, long indexSize) : base(arc, impl, dir) + { + m_IndexSize = indexSize; + } + + public Entry GetEntry(Entry e) + { + return new Entry + { + Name = e.Name, + Offset = e.Offset + m_IndexSize, + Size = e.Size, + Type = e.Type, + }; + } + } + + //neg reg8 + public class NegStream : ProxyStream + { + public NegStream(Stream stream, bool leave_open = false) : base(stream, leave_open) + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + int read = BaseStream.Read(buffer, offset, count); + for (int i = 0; i < read; ++i) + { + buffer[offset + i] = (byte)-buffer[offset + i]; + } + return read; + } + + public override int ReadByte() + { + int b = BaseStream.ReadByte(); + if (-1 != b) + { + b = (byte)-b; + } + return b; + } + + byte[] write_buf; + + public override void Write(byte[] buffer, int offset, int count) + { + if (null == write_buf) + write_buf = new byte[81920]; + while (count > 0) + { + int chunk = Math.Min(write_buf.Length, count); + for (int i = 0; i < chunk; ++i) + { + write_buf[i] = (byte)-buffer[offset + i]; + } + BaseStream.Write(write_buf, 0, chunk); + offset += chunk; + count -= chunk; + } + } + + public override void WriteByte(byte value) + { + BaseStream.WriteByte((byte)-value); + } + } +}