From 94dbf9fe3b234b8922c4284f1da573de425104c9 Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 16 Sep 2018 20:13:39 +0400 Subject: [PATCH] (SAF): implemented version 6 archives. --- ArcFormats/ArcSAF.cs | 288 +++++++++++++++++++++++++++++-------------- 1 file changed, 196 insertions(+), 92 deletions(-) diff --git a/ArcFormats/ArcSAF.cs b/ArcFormats/ArcSAF.cs index 8145b132..d7ced0f1 100644 --- a/ArcFormats/ArcSAF.cs +++ b/ArcFormats/ArcSAF.cs @@ -2,7 +2,7 @@ //! \date Mon Jun 01 03:09:22 2015 //! \brief SAF archive file format implemenation. // -// Copyright (C) 2015 by morkt +// Copyright (C) 2015-2018 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,7 +23,6 @@ // IN THE SOFTWARE. // -using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; @@ -31,51 +30,91 @@ using System.Linq; using GameRes.Compression; using GameRes.Utility; -namespace GameRes.Formats.Lune +namespace GameRes.Formats.Rits { + internal class SafArchive : ArcFile + { + public readonly int Version; + + public bool LzssCompression { get { return (Version & 2) != 0; } } + + public SafArchive (ArcView arc, ArchiveFormat impl, ICollection dir, int version) + : base (arc, impl, dir) + { + Version = version; + } + } + [Export(typeof(ArchiveFormat))] public class SafOpener : ArchiveFormat { public override string Tag { get { return "SAF"; } } - public override string Description { get { return "Lune resource archive"; } } + public override string Description { get { return "Rit's resource archive"; } } public override uint Signature { get { return 0; } } public override bool IsHierarchic { get { return true; } } public override bool CanWrite { get { return false; } } + const byte DefaultKey5 = 0xDF; + const byte DefaultKey6 = 0xEF; + public override ArcFile TryOpen (ArcView file) { int id = file.View.ReadInt16 (0); int count = file.View.ReadInt16 (2); - if (count <= 0) + if (!IsSaneCount (count)) return null; - var index_buffer = new byte[32 * count]; - if (index_buffer.Length != file.View.Read (4, index_buffer, 0, (uint)index_buffer.Length)) + IIndexReader reader; + if ((id & 0xFF00) == 0x500) + { + var index_buffer = new byte[32 * count]; + if (index_buffer.Length != file.View.Read (4, index_buffer, 0, (uint)index_buffer.Length)) + return null; + if (0x501 == id) + DecryptIndex (index_buffer, count, DefaultKey5); + reader = new SafIndexReader5 (index_buffer, count); + } + else if ((id & 0xFF00) == 0x600) + { + int names_length = file.View.ReadInt32 (4); + if (names_length <= 0 || names_length >= file.MaxOffset) + return null; + uint index_size = (uint)count * 16; + var index_buffer = file.View.ReadBytes (8, index_size); + var names_buffer = file.View.ReadBytes (8 + index_size, (uint)names_length); + if ((id & 1) != 0) + { + DecryptIndexV6 (index_buffer, count, DefaultKey6); + DecryptNames (names_buffer, names_length); + } + reader = new SafIndexReader6 (index_buffer, names_buffer, count); + } + else return null; - if (0x501 == id) - DecryptIndex (index_buffer, count); - var reader = new IndexReader (index_buffer, count); var dir = reader.Scan(); if (0 == dir.Count || dir.Any (e => !e.CheckPlacement (file.MaxOffset))) return null; - return new ArcFile (file, this, dir); + return new SafArchive (file, this, dir, id); } public override Stream OpenEntry (ArcFile arc, Entry entry) { - var input = arc.File.CreateStream (entry.Offset, entry.Size); + var input = arc.File.CreateStream (entry.Offset, entry.Size, entry.Name); var packed_entry = entry as PackedEntry; if (null == packed_entry || !packed_entry.IsPacked) return input; - else + var sarc = arc as SafArchive; + if (null == sarc || !sarc.LzssCompression) return new ZLibStream (input, CompressionMode.Decompress); + else + return new LzssStream (input); } - void DecryptIndex (byte[] index, int count) + void DecryptIndex (byte[] index, int count, byte start_key) { int offset = 0; for (int i = 0; i < count; ++i) { - byte key = 0xdf; + byte key = start_key; for (int j = 0; j < 0x20; ++j) { index[offset++] ^= key++; @@ -83,91 +122,156 @@ namespace GameRes.Formats.Lune } } - internal class IndexReader + void DecryptIndexV6 (byte[] index, int count, byte start_key) { - byte[] m_index; - int m_count; - List m_dir; - - public List Dir { get { return m_dir; } } - - public IndexReader (byte[] index, int count) + int offset = 0; + for (int i = 0; i < count; ++i) { - m_index = index; - m_count = count; - m_dir = new List (count); - } - - bool m_ignore_dirs = false; - - public List Scan () - { - string root_name; - int root_offset; - int root_count; - if (0 == (m_index[0] & 0x80)) + byte key = start_key; + for (int j = 0; j < 0x10; ++j) { - root_name = ""; - root_offset = 0; - root_count = m_count; - m_ignore_dirs = true; - } - else - { - root_name = ReadName (0); - if ("root" == root_name) - { - root_name = ""; - } - root_offset = LittleEndian.ToInt32 (m_index, 0x14); - root_count = LittleEndian.ToInt32 (m_index, 0x1c); - } - ReadDir (root_name, root_offset, root_count); - return m_dir; - } - - void ReadDir (string dir_name, int index, int count) - { - if (index + count > m_count) - throw new InvalidFormatException(); - int index_offset = index * 0x20; - for (int i = 0; i < count; ++i, index_offset += 0x20) - { - if (m_index[index_offset] > 0x7f) - { - if (m_ignore_dirs) - continue; - int subdir_index = LittleEndian.ToInt32 (m_index, index_offset+0x14); - if (subdir_index < index + count) - continue; - var subdir_name = ReadName (index_offset); - int subdir_count = LittleEndian.ToInt32 (m_index, index_offset+0x1c); - ReadDir (Path.Combine (dir_name, subdir_name), subdir_index, subdir_count); - } - else - { - var name = ReadName (index_offset); - name = Path.Combine (dir_name, name); - var entry = new PackedEntry - { - Name = name, - Type = FormatCatalog.Instance.GetTypeFromName (name), - Offset = 0x800L * LittleEndian.ToUInt32 (m_index, index_offset+0x14), - Size = LittleEndian.ToUInt32 (m_index, index_offset+0x18), - UnpackedSize = LittleEndian.ToUInt32 (m_index, index_offset+0x1c), - }; - entry.IsPacked = entry.UnpackedSize != 0; - m_dir.Add (entry); - } + index[offset++] ^= key++; } } + } - string ReadName (int offset) + void DecryptNames (byte[] data, int count) + { + byte key = 0xFF; + for (int i = 0; i < count; ++i) { - m_index[offset] &= 0x7f; - string name = Encodings.cp932.GetString (m_index, offset, 0x14); - return name.TrimEnd(); + data[i] ^= key--; } } } + + internal interface IIndexReader + { + List Scan (); + } + + internal class SafIndexReader5 : IIndexReader + { + protected byte[] m_index; + protected int m_count; + private List m_dir; + + protected int EntrySize = 0x20; + protected int OffsetPos = 0x14; + protected int SizePos = 0x18; + protected int UnpackedPos = 0x1C; + protected int DirIndexPos = 0x14; + protected int DirCountPos = 0x1C; + + bool m_ignore_dirs = false; + + public SafIndexReader5 (byte[] index, int count) + { + m_index = index; + m_count = count; + m_dir = new List (count); + } + + public List Scan () + { + string root_name; + int root_index; + int root_count; + if (!IsDir (0)) + { + root_name = ""; + root_index = 0; + root_count = m_count; + m_ignore_dirs = true; + } + else + { + root_name = ReadName (0); + if ("root" == root_name) + { + root_name = ""; + } + root_index = m_index.ToInt32 (DirIndexPos); + root_count = m_index.ToInt32 (DirCountPos); + } + ReadDir (root_name, root_index, root_count); + return m_dir; + } + + void ReadDir (string dir_name, int index, int count) + { + if (index + count > m_count) + throw new InvalidFormatException(); + int index_offset = index * EntrySize; + for (int i = 0; i < count; ++i, index_offset += EntrySize) + { + if (IsDir (index_offset)) + { + if (m_ignore_dirs) + continue; + int subdir_index = m_index.ToInt32 (index_offset + DirIndexPos); + if (subdir_index < index + count) + continue; + var subdir_name = ReadName (index_offset); + int subdir_count = m_index.ToInt32 (index_offset + DirCountPos); + ReadDir (Path.Combine (dir_name, subdir_name), subdir_index, subdir_count); + } + else + { + var name = ReadName (index_offset); + name = Path.Combine (dir_name, name); + var entry = new PackedEntry + { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + Offset = (long)m_index.ToUInt32 (index_offset+OffsetPos) << 11, + Size = m_index.ToUInt32 (index_offset+SizePos), + UnpackedSize = m_index.ToUInt32 (index_offset+UnpackedPos), + }; + entry.IsPacked = entry.UnpackedSize != 0; + m_dir.Add (entry); + } + } + } + + protected virtual bool IsDir (int pos) + { + return m_index[pos] > 0x7F; + } + + protected virtual string ReadName (int pos) + { + m_index[pos] &= 0x7F; + string name = Encodings.cp932.GetString (m_index, pos, 0x14); + return name.TrimEnd(); + } + } + + internal class SafIndexReader6 : SafIndexReader5 + { + byte[] m_names; + + public SafIndexReader6 (byte[] index, byte[] names, int count) : base (index, count) + { + m_names = names; + + EntrySize = 16; + OffsetPos = 4; + SizePos = 8; + UnpackedPos = 12; + DirIndexPos = 4; + DirCountPos = 12; + } + + protected override bool IsDir (int pos) + { + return m_index[pos+3] > 0x7F; + } + + protected override string ReadName (int offset) + { + int name_pos = m_index.ToInt32 (offset) & 0x7FFFFFFF; + return Binary.GetCString (m_names, name_pos); + } + } }