From ec8f39a2c72333050983c5ad7ad531eac18bc485 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 22 Apr 2015 05:14:41 +0400 Subject: [PATCH] Lucifen Game System resources implementation. LPK archives; ELG images. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/ArcLPK.cs | 417 ++++++++++++++++++ ArcFormats/ImageELG.cs | 585 ++++++++++++++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- supported.html | 2 + 5 files changed, 1008 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ArcLPK.cs create mode 100644 ArcFormats/ImageELG.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 84ffb53a..ebe26580 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -81,6 +81,7 @@ + @@ -140,6 +141,7 @@ + diff --git a/ArcFormats/ArcLPK.cs b/ArcFormats/ArcLPK.cs new file mode 100644 index 00000000..3e6d15ce --- /dev/null +++ b/ArcFormats/ArcLPK.cs @@ -0,0 +1,417 @@ +//! \file ArcLPK.cs +//! \date Mon Feb 16 17:21:47 2015 +//! \brief Lucifen Easy Game System archive implementation. +// +// Copyright (C) 2015 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 +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; +using GameRes.Formats.Strings; +using GameRes.Utility; + +namespace GameRes.Formats.Lucifen +{ + internal class LuciEntry : PackedEntry + { + public byte Flag; + } + + internal class LuciArchive : ArcFile + { + public LpkInfo Info; + + public LuciArchive (ArcView arc, ArchiveFormat impl, ICollection dir, LpkInfo info) + : base (arc, impl, dir) + { + Info = info; + } + } + + internal class LuciOptions : ResourceOptions + { + public LpkOpener.Key Key; + } + + internal class LpkInfo + { + public bool AlignedOffset; + public bool Flag1; + public bool IsEncrypted; + public bool PackedEntries; + public bool WholeCrypt; + public uint Key; + public byte[] Prefix; + } + + [Export(typeof(ArchiveFormat))] + public class LpkOpener : ArchiveFormat + { + public override string Tag { get { return "LPK"; } } + public override string Description { get { return "Lucifen system resource archive"; } } + public override uint Signature { get { return 0x314b504c; } } // 'LPK1' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public struct Key + { + public uint Key1, Key2; + public Key (uint k1, uint k2) + { + Key1 = k1; + Key2 = k2; + } + } + + static Key BaseKey = new Key { Key1 = 0xA5B9AC6B, Key2 = 0x9A639DE5 }; + static byte[] ScriptName = Encoding.ASCII.GetBytes ("SCRIPT"); + static Key ScriptKey = new Key { Key1 = 0, Key2 = 0 }; + + Dictionary CurrentScheme = new Dictionary(); + + static Dictionary> KnownKeys = + new Dictionary> { + { "Ne~pon? x Raipon!", new Dictionary { + { "SYS.LPK", new Key (0x67DB35ED, 0xBE4D6A37) }, + { "CHR.LPK", new Key (0x9E3BAD56, 0x95FE9DC3) }, + { "PIC.LPK", new Key (0xD9B5E6C3, 0xBA53E5D5) }, + { "BGM.LPK", new Key (0xEB6DE3B5, 0xCD571DE9) }, + { "SE.LPK", new Key (0x9AB3AD5E, 0xAD3D4D96) }, + { "VOICE.LPK", new Key (0x75CBD59D, 0x5ED59ACA) }, + { "DATA.LPK", new Key (0xDE6AD53E, 0x9E3B5E3D) }, + { "DESKTOP.LPK",new Key (0xACD5B36D, 0xD56ADB5D) } } }, + { "Doki Doki Rooming", new Dictionary { + { "SYS.LPK", new Key (0x8030AB96, 0xB9F6638E) }, + { "CHR.LPK", new Key (0xA7B9178B, 0xF79C547C) }, + { "PIC.LPK", new Key (0x34B73229, 0xB690E07D) }, + { "BGM.LPK", new Key (0x41E7505F, 0x1E10C3C5) }, + { "SE.LPK", new Key (0x34DF875E, 0x435603E8) }, + { "VOICE.LPK", new Key (0x30701D73, 0xFB524CE5) }, + { "ANIME.LPK", new Key (0x867ABB47, 0xA25F4248) }, + { "DATA.LPK", new Key (0x5BCB1333, 0x99325DFA) } } }, + }; + + public override ArcFile TryOpen (ArcView file) + { + string name = Path.GetFileName (file.Name).ToUpperInvariant(); + if (string.IsNullOrEmpty (name)) + return null; + var key = ScriptKey; + if (name != "SCRIPT.LPK" && !CurrentScheme.TryGetValue (name, out key)) + { + ImportKeys (file.Name); + if (!CurrentScheme.TryGetValue (name, out key)) + { + key = QueryEncryptionKey(); + } + } + var basename = Encodings.cp932.GetBytes (Path.GetFileNameWithoutExtension (name)); + return Open (basename, file, key); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + return Stream.Null; + var larc = arc as LuciArchive; + var lent = entry as LuciEntry; + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + if (null == larc || null == lent) + return input; + byte[] data; + using (input) + { + if (lent.IsPacked) + { + using (var reader = new LzssReader (input, (int)lent.Size, (int)lent.UnpackedSize)) + { + reader.Unpack(); + data = reader.Data; + } + } + else + { + data = new byte[lent.Size]; + input.Read (data, 0, data.Length); + } + } + if (larc.Info.IsEncrypted) + { + if (larc.Info.WholeCrypt) + { + for (int i = 0; i < data.Length; ++i) + { + int v = data[i] ^ 0x5d; + data[i] = (byte)(v >> 4 | v << 4); + } + } + int count = Math.Min (data.Length / 4, 0x40); + if (count != 0) + { + unsafe + { + fixed (byte* buf_raw = data) + { + uint key = larc.Info.Key; + uint* encoded = (uint*)buf_raw; + uint ecx = 0x31746285; + for (int i = 0; i < count; ++i) + { + encoded[i] ^= key; + ecx = (ecx >> 4) | (ecx << 28); + int cl = (int)(ecx & 0x1f); + key = (key << cl) | (key >> (32-cl)); + } + } + } + } + } + input = new MemoryStream (data); + if (null != larc.Info.Prefix) + return new PrefixStream (larc.Info.Prefix, input); + else + return input; + } + + ArcFile Open (byte[] basename, ArcView file, Key key) + { + uint key1 = BaseKey.Key1; + uint key2 = BaseKey.Key2; + for (int b = 0, e = basename.Length-1; e >= 0; ++b, --e) + { + key1 ^= basename[e]; + key2 ^= basename[b]; + key1 = (key1 << 25) | (key1 >> 7); + key2 = (key2 << 7) | (key2 >> 25); + } + key1 ^= key.Key1; + key2 ^= key.Key2; + uint code = file.View.ReadUInt32 (4) ^ key2; + int index_size = (int)(code & 0xffffff); + byte flags = (byte)(code >> 24); + if (0 != (flags & 1)) + index_size = (index_size << 11) - 8; + if (index_size < 5) + return null; + var index = new byte[index_size]; + if (index_size != file.View.Read (8, index, 0, (uint)index_size)) + return null; + var lpk_info = new LpkInfo + { + AlignedOffset = 0 != (flags & 1), + Flag1 = 0 != (flags & 2), + IsEncrypted = 0 != (flags & 4), + PackedEntries = 0 != (flags & 8), + WholeCrypt = 0 != (flags & 0x10), + Key = key1 + }; + var reader = new IndexReader (lpk_info); + unsafe + { + fixed (byte* buf_raw = index) + { + uint* encoded = (uint*)buf_raw; + uint ecx = 0x31746285; + for (int i = 0; i < index_size/4; ++i) + { + encoded[i] ^= key2; + ecx = (ecx << 4) | (ecx >> 28); + int cl = (int)(ecx & 0x1f); + key2 = (key2 << (32-cl)) | (key2 >> cl); + } + } + } + var dir = reader.Read (index); + if (null == dir) + return null; + return new LuciArchive (file, this, dir, reader.Info); + } + + void ImportKeys (string source_name) + { + var script_lpk = Path.Combine (Path.GetDirectoryName (source_name), "SCRIPT.LPK"); + using (var script_file = new ArcView (script_lpk)) + using (var script_arc = Open (ScriptName, script_file, ScriptKey)) + { + var entry = script_arc.Dir.Where (e => e.Name.Equals ("gameinit.sob", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + if (null == entry) + throw new FileNotFoundException ("Missing 'gameinit.sob' entry in SCRIPT.LPK"); + using (var gameinit = script_arc.OpenEntry (entry)) + { + var init_data = new byte[gameinit.Length]; + gameinit.Read (init_data, 0, init_data.Length); + if (!GameInit (init_data)) + throw new UnknownEncryptionScheme(); + } + } + } + + bool GameInit (byte[] sob) + { + CurrentScheme.Clear(); + if (!Binary.AsciiEqual (sob, "SOB0")) + return false; + int offset = LittleEndian.ToInt32 (sob, 4) + 8; + if (offset <= 0 || offset >= sob.Length) + return false; + unsafe + { + fixed (byte* buf_raw = sob) + { + uint* p = (uint*)(buf_raw + offset); + uint index_len = *p; + if (offset+8+(int)index_len >= sob.Length) + return false; + p += 2; + uint* last = p + index_len / 4 - 28; + + while (p < last) + { + if (p[0] == 0x28 && p[1] == 0 && p[2] != 0 && p[3] == 8 && + p[4] == 1 && p[5] == 8 && p[6] == 1 && p[7] == 8 && + p[8] != 0 && p[9] == 8 && p[10] != 0 && p[11] == 5 && + p[17] == 0x28 && p[18] == 0 && p[19] != 0 && p[20] == 8 && + p[21] != 0 && p[22] == 8 && p[23] == 0xffffffff && p[24] == 8 && + p[25] == 1 && p[26] == 8 && p[27] == 1 && p[28] == 5) + { + byte* lpk = (byte*)p + p[21] - p[2] + 0x34; + int name_index = (int)(lpk - buf_raw); + if (name_index >= sob.Length) + { + ++p; + continue; + } + string name = Binary.GetCString (sob, name_index, sob.Length-name_index); + name = name.ToUpperInvariant(); + CurrentScheme[name] = new Key { Key1 = p[8], Key2 = p[10] }; + p += 0x22; + } else + ++p; + } + return true; + } + } + } + + Key QueryEncryptionKey () + { + var options = Query (arcStrings.ArcEncryptedNotice); + if (null == options) + throw new UnknownEncryptionScheme(); + return options.Key; + } + } + + internal class IndexReader + { + byte[] m_index; + LpkInfo m_info; + List m_dir; + byte[] m_name; + int m_index_width; + int m_entries_offset; + + public LpkInfo Info { get { return m_info; } } + + public IndexReader (LpkInfo info) + { + if (!info.Flag1) + throw new NotSupportedException ("Not supported LPK index format"); + m_info = info; + m_name = new byte[260]; + } + + public List Read (byte[] index) + { + m_index = index; + int count = LittleEndian.ToInt32 (m_index, 0); + if (count <= 0 || count > 0xfffff) + return null; + int index_offset = 4; + int prefix_length = m_index[index_offset++]; + if (0 != prefix_length) + { + m_info.Prefix = new byte[prefix_length]; + Buffer.BlockCopy (m_index, index_offset, m_info.Prefix, 0, prefix_length); + index_offset += prefix_length; + } + m_index_width = 0 != m_index[index_offset++] ? 4 : 2; + int letter_table_length = LittleEndian.ToInt32 (m_index, index_offset); + index_offset += 4; + m_entries_offset = index_offset + letter_table_length; + + m_dir = new List (count); + m_name = new byte[260]; + TraverseIndex (index_offset, 0); + return m_dir.Count == count ? m_dir : null; + } + + void TraverseIndex (int index_offset, int name_length) + { + if (index_offset < 0 || index_offset >= m_index.Length) + throw new InvalidFormatException ("Error parsing LPK index"); + if (name_length >= m_name.Length) + throw new InvalidFormatException ("Entry filename is too long"); + int entries = m_index[index_offset++]; + for (int i = 0; i < entries; ++i) + { + byte next_letter = m_index[index_offset++]; + int next_offset = 4 == m_index_width ? LittleEndian.ToInt32 (m_index, index_offset) + : (int)LittleEndian.ToUInt16 (m_index, index_offset); + m_name[name_length] = next_letter; + index_offset += m_index_width;; + if (0 != next_letter) + TraverseIndex (index_offset+next_offset, name_length+1); + else + AddEntry (name_length, next_offset); + } + } + + void AddEntry (int name_length, int entry_num) + { + if (name_length < 1) + throw new InvalidFormatException ("Invalid LPK entry name"); + string name = Encodings.cp932.GetString (m_name, 0, name_length); + var entry = new LuciEntry { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + IsPacked = m_info.PackedEntries, + }; + int entry_size = m_info.PackedEntries ? 13 : 9; + int entry_pos = m_entries_offset + entry_size*entry_num; + if (entry_pos+entry_size > m_index.Length) + throw new InvalidFormatException ("Invalid LPK entry index"); + entry.Flag = m_index[entry_pos]; + uint offset = LittleEndian.ToUInt32 (m_index, entry_pos+1); + entry.Offset = m_info.AlignedOffset ? offset << 11 : offset; + entry.Size = LittleEndian.ToUInt32 (m_index, entry_pos+5); + if (entry.IsPacked) + entry.UnpackedSize = LittleEndian.ToUInt32 (m_index, entry_pos+9); + else + entry.UnpackedSize = entry.Size; + m_dir.Add (entry); + } + } +} diff --git a/ArcFormats/ImageELG.cs b/ArcFormats/ImageELG.cs new file mode 100644 index 00000000..e78adec5 --- /dev/null +++ b/ArcFormats/ImageELG.cs @@ -0,0 +1,585 @@ +//! \file ImageELG.cs +//! \date Wed Apr 22 03:14:49 2015 +//! \brief Lucifen Easy Game System image format. +// +// Copyright (C) 2015 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 +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Lucifen +{ + internal class ElgMetaData : ImageMetaData + { + public int HeaderSize; + } + + [Export(typeof(ImageFormat))] + public class ElgFormat : ImageFormat + { + public override string Tag { get { return "ELG"; } } + public override string Description { get { return "Lucifen Easy Game System image format"; } } + public override uint Signature { get { return 0x01474c45u; } } // 'ELG\001' + + public ElgFormat () + { + Signatures = new uint[] { 0x01474c45u, 0x08474c45u, 0x18474c45u, 0x20474c45u }; + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("ElgFormat.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + stream.Seek (3, SeekOrigin.Current); + using (var input = new ArcView.Reader (stream)) + { + int bpp = input.ReadByte(); + int x = 0; + int y = 0; + int header_size = 8; + if (1 == bpp) + { + bpp = input.ReadByte(); + x = input.ReadInt16(); + y = input.ReadInt16(); + header_size = 13; + } + if (8 != bpp && 24 != bpp && 32 != bpp) + return null; + uint w = input.ReadUInt16(); + uint h = input.ReadUInt16(); + return new ElgMetaData + { + Width = w, + Height = h, + OffsetX = x, + OffsetY = y, + BPP = bpp, + HeaderSize = header_size, + }; + } + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + var meta = info as ElgMetaData; + if (null == meta) + throw new ArgumentException ("ElgFormat.Read should be supplied with ElgMetaData", "info"); + file.Position = meta.HeaderSize; + using (var reader = new Reader (file, meta)) + { + reader.Unpack(); + return ImageData.Create (meta, reader.Format, reader.Palette, reader.Data); + } + } + + internal class Reader : IDisposable + { + int m_width; + int m_height; + int m_bpp; + BinaryReader m_input; + byte[] m_output; + + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + public byte[] Data { get { return m_output; } } + + public Reader (Stream stream, ElgMetaData info) + { + m_width = (int)info.Width; + m_height = (int)info.Height; + m_bpp = info.BPP; + m_output = new byte[m_width*m_height*m_bpp/8]; + m_input = new ArcView.Reader (stream); + } + + public void Unpack () + { + if (8 == m_bpp) + { + Format = PixelFormats.Indexed8; + UnpackPalette(); + UnpackIndexed (m_output); + } + else if (24 == m_bpp) + { + Format = PixelFormats.Bgr24; + UnpackRGB(); + } + else + { + Format = PixelFormats.Bgra32; + UnpackRGBA(); + UnpackAlpha(); + } + } + + void UnpackPalette () + { + byte[] palette_data = new byte[0x400]; + UnpackIndexed (palette_data); + var colors = new Color[256]; + for (int i = 0; i < 256; ++i) + colors[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]); + Palette = new BitmapPalette (colors); + } + + void UnpackIndexed (byte[] output) + { + int dst = 0; + for (;;) + { + byte flags = m_input.ReadByte(); + if (0xff == flags) + break; + int count, pos; + + if (0 == (flags & 0xc0)) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 33; + else + count = (flags & 0x1f) + 1; + + for (int i = 0; i < count; ++i) + output[dst++] = m_input.ReadByte(); + } + else if ((flags & 0xc0) == 0x40) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 35; + else + count = (flags & 0x1f) + 3; + + byte v = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + output[dst++] = v; + } + else + { + if ((flags & 0xc0) == 0x80) + { + if (0 == (flags & 0x30)) + { + count = (flags & 0xf) + 2; + pos = m_input.ReadByte() + 2; + } + else if ((flags & 0x30) == 0x10) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = m_input.ReadByte() + 4; + } + else if ((flags & 0x30) == 0x20) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = 3; + } + else + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = 4; + } + } + else if (0 != (flags & 0x20)) + { + pos = (flags & 0x1f) + 2; + count = 2; + } + else + { + pos = (flags & 0x1f) + 1; + count = 1; + } + int src = dst - pos; + Binary.CopyOverlapped (output, src, dst, count); + dst += count; + } + } + } + + void UnpackRGBA () + { + int dst = 0; + for (;;) + { + byte flags = m_input.ReadByte(); + if (0xff == flags) + break; + int count, pos, src; + + if (0 == (flags & 0xc0)) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 33; + else + count = (flags & 0x1f) + 1; + + for (int i = 0; i < count; ++i) + { + m_output[dst++] = m_input.ReadByte(); + m_output[dst++] = m_input.ReadByte(); + m_output[dst++] = m_input.ReadByte(); + m_output[dst++] = 0xff; + } + } + else if ((flags & 0xc0) == 0x40) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 34; + else + count = (flags & 0x1f) + 2; + + byte b = m_input.ReadByte(); + byte g = m_input.ReadByte(); + byte r = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + { + m_output[dst++] = b; + m_output[dst++] = g; + m_output[dst++] = r; + m_output[dst++] = 0xff; + } + } + else if ((flags & 0xc0) == 0x80) + { + if (0 == (flags & 0x30)) + { + count = (flags & 0xf) + 1; + pos = m_input.ReadByte() + 2; + } + else if ((flags & 0x30) == 0x10) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 2; + count = m_input.ReadByte() + 1; + } + else if ((flags & 0x30) == 0x20) + { + byte tmp = m_input.ReadByte(); + pos = ((((flags & 0xf) << 8) + tmp) << 8) + m_input.ReadByte() + 4098; + count = m_input.ReadByte() + 1; + } + else + { + if (0 != (flags & 8)) + pos = ((flags & 0x7) << 8) + m_input.ReadByte() + 10; + else + pos = (flags & 0x7) + 2; + count = 1; + } + + src = dst - 4 * pos; + Binary.CopyOverlapped (m_output, src, dst, count*4); + dst += count*4; + } + else + { + int y, x; + + if (0 == (flags & 0x30)) + { + if (0 == (flags & 0xc)) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = 0; + } + else if ((flags & 0xc) == 0x4) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = -1; + } + else if ((flags & 0xc) == 0x8) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = 1; + } + else + { + pos = ((flags & 0x3) << 8) + m_input.ReadByte() + 2058; + src = dst - 4 * pos; + Buffer.BlockCopy (m_output, src, m_output, dst, 4); + dst += 4; + continue; + } + } + else if ((flags & 0x30) == 0x10) + { + y = (flags & 0xf) + 1; + x = 0; + } + else if ((flags & 0x30) == 0x20) + { + y = (flags & 0xf) + 1; + x = -1; + } + else + { + y = (flags & 0xf) + 1; + x = 1; + } + src = dst + (x - m_width * y) * 4; + Buffer.BlockCopy (m_output, src, m_output, dst, 4); + dst += 4; + } + } + } + + public void UnpackAlpha () + { + int dst = 3; + while (dst < m_output.Length) + { + byte flags = m_input.ReadByte(); + if (0xff == flags) + break; + + int count, pos; + + if (0 == (flags & 0xc0)) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 33; + else + count = (flags & 0x1f) + 1; + + for (int i = 0; i < count; ++i) + { + m_output[dst] = m_input.ReadByte(); + dst += 4; + } + } + else if (0x40 == (flags & 0xc0)) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 35; + else + count = (flags & 0x1f) + 3; + + byte a = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + { + m_output[dst] = a; + dst += 4; + } + } + else + { + if (0x80 == (flags & 0xc0)) + { + if (0 == (flags & 0x30)) + { + count = (flags & 0xf) + 2; + pos = m_input.ReadByte() + 2; + } + else if (0x10 == (flags & 0x30)) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = m_input.ReadByte() + 4; + } + else if ((flags & 0x30) == 0x20) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = 3; + } + else + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 3; + count = 4; + } + } + else if (0 != (flags & 0x20)) + { + pos = (flags & 0x1f) + 2; + count = 2; + } + else + { + pos = (flags & 0x1f) + 1; + count = 1; + } + + int src = dst - 4 * pos; + for (int i = 0; i < count; ++i) + { + m_output[dst] = m_output[src]; + src += 4; + dst += 4; + } + } + } + } + + void UnpackRGB () + { + int dst = 0; + for (;;) + { + byte flags = m_input.ReadByte(); + if (0xff == flags) + break; + int count, pos, src; + if (0 == (flags & 0xc0)) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 33; + else + count = (flags & 0x1f) + 1; + + for (int i = 0; i < count; ++i) + { + m_output[dst++] = m_input.ReadByte(); + m_output[dst++] = m_input.ReadByte(); + m_output[dst++] = m_input.ReadByte(); + } + } + else if ((flags & 0xc0) == 0x40) + { + if (0 != (flags & 0x20)) + count = ((flags & 0x1f) << 8) + m_input.ReadByte() + 34; + else + count = (flags & 0x1f) + 2; + + byte b = m_input.ReadByte(); + byte g = m_input.ReadByte(); + byte r = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + { + m_output[dst++] = b; + m_output[dst++] = g; + m_output[dst++] = r; + } + } + else if ((flags & 0xc0) == 0x80) + { + if (0 == (flags & 0x30)) + { + count = (flags & 0xf) + 1; + pos = m_input.ReadByte() + 2; + } + else if ((flags & 0x30) == 0x10) + { + pos = ((flags & 0xf) << 8) + m_input.ReadByte() + 2; + count = m_input.ReadByte() + 1; + } + else if ((flags & 0x30) == 0x20) + { + byte tmp = m_input.ReadByte(); + pos = ((((flags & 0xf) << 8) + tmp) << 8) + m_input.ReadByte() + 4098; + count = m_input.ReadByte() + 1; + } + else + { + if (0 != (flags & 8)) + pos = ((flags & 0x7) << 8) + m_input.ReadByte() + 10; + else + pos = (flags & 0x7) + 2; + count = 1; + } + + src = dst - 3 * pos; + Binary.CopyOverlapped (m_output, src, dst, count*3); + dst += count*3; + } + else + { + int y, x; + + if (0 == (flags & 0x30)) + { + if (0 == (flags & 0xc)) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = 0; + } + else if ((flags & 0xc) == 0x4) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = -1; + } + else if ((flags & 0xc) == 0x8) + { + y = ((flags & 0x3) << 8) + m_input.ReadByte() + 16; + x = 1; + } + else + { + pos = ((flags & 0x3) << 8) + m_input.ReadByte() + 2058; + src = dst - 3 * pos; + m_output[dst++] = m_output[src++]; + m_output[dst++] = m_output[src++]; + m_output[dst++] = m_output[src]; + continue; + } + } + else if ((flags & 0x30) == 0x10) + { + y = (flags & 0xf) + 1; + x = 0; + } + else if ((flags & 0x30) == 0x20) + { + y = (flags & 0xf) + 1; + x = -1; + } + else + { + y = (flags & 0xf) + 1; + x = 1; + } + src = dst + (x - m_width * y) * 3; + m_output[dst++] = m_output[src++]; + m_output[dst++] = m_output[src++]; + m_output[dst++] = m_output[src]; + } + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + m_input.Dispose(); + disposed = true; + } + } + #endregion + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 29be03a1..d917e655 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.4.40")] -[assembly: AssemblyFileVersion ("1.0.4.40")] +[assembly: AssemblyVersion ("1.0.4.41")] +[assembly: AssemblyFileVersion ("1.0.4.41")] diff --git a/supported.html b/supported.html index 5721fc87..ddbf97a7 100644 --- a/supported.html +++ b/supported.html @@ -135,6 +135,8 @@ Nikutai Ten'i
*.s25S25No *ARC2
ARC1NoASTJokyoushi wo Kurau *.dat-NoAilRagna☆彡Science +*.lpkLPK1NoLucifenDoki Doki Rooming +*.elgELGNo

[1] Non-encrypted only