From cb04b322f231897e9c2dfe6726e054b9ed1f0a6b Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 10 Jun 2016 04:39:29 +0400 Subject: [PATCH] implemented 'vff' archives and GAL images. --- ArcFormats/ArcFormats.csproj | 9 + ArcFormats/LiveMaker/ArcVF.cs | 244 +++++++++ ArcFormats/LiveMaker/ImageGAL.cs | 570 +++++++++++++++++++++ ArcFormats/LiveMaker/WidgetGAL.xaml | 45 ++ ArcFormats/LiveMaker/WidgetGAL.xaml.cs | 73 +++ ArcFormats/Properties/Settings.Designer.cs | 12 + ArcFormats/Properties/Settings.settings | 3 + ArcFormats/Strings/arcStrings.Designer.cs | 9 + ArcFormats/Strings/arcStrings.ko-KR.resx | 4 + ArcFormats/Strings/arcStrings.resx | 3 + ArcFormats/Strings/arcStrings.ru-RU.resx | 3 + ArcFormats/Strings/arcStrings.zh-Hans.resx | 4 + ArcFormats/app.config | 3 + supported.html | 7 + 14 files changed, 989 insertions(+) create mode 100644 ArcFormats/LiveMaker/ArcVF.cs create mode 100644 ArcFormats/LiveMaker/ImageGAL.cs create mode 100644 ArcFormats/LiveMaker/WidgetGAL.xaml create mode 100644 ArcFormats/LiveMaker/WidgetGAL.xaml.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 5585735c..6a4c6f12 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -72,6 +72,7 @@ + @@ -103,6 +104,10 @@ + + + WidgetGAL.xaml + @@ -549,6 +554,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs new file mode 100644 index 00000000..eda08264 --- /dev/null +++ b/ArcFormats/LiveMaker/ArcVF.cs @@ -0,0 +1,244 @@ +//! \file ArcVF.cs +//! \date Wed Jun 08 00:27:36 2016 +//! \brief LiveMaker resource archive. +// +// Copyright (C) 2016 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.Diagnostics; +using System.IO; +using GameRes.Compression; + +namespace GameRes.Formats.LiveMaker +{ + [Export(typeof(ArchiveFormat))] + public class VffOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/vf"; } } + public override string Description { get { return "LiveMaker resource archive"; } } + public override uint Signature { get { return 0x666676; } } // 'vff' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public VffOpener () + { + Extensions = new string[] { "dat" }; + Signatures = new uint[] { 0x666676, 0 }; + } + + public override ArcFile TryOpen (ArcView file) + { + ArcView index_file = file; + ArcView extra_file = null; + try + { + // possible filesystem structure: + // game.dat -- main archive body + // game.001 -- [optional] extra part + // game.ext -- [optional] separate index (could be included into the main body) + + if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + return null; + uint signature = index_file.View.ReadUInt32 (0); + if (0x666676 != signature) + { + var ext_filename = Path.ChangeExtension (file.Name, ".ext"); + if (!VFS.FileExists (ext_filename)) + return null; + index_file = VFS.OpenView (ext_filename); + signature = index_file.View.ReadUInt32 (0); + if (0x666676 != signature) + return null; + } + int count = index_file.View.ReadInt32 (6); + if (!IsSaneCount (count)) + return null; + + var dir = ReadIndex (index_file, count); + if (null == dir) + return null; + long max_offset = file.MaxOffset; + for (int i = 0; i < dir.Count; ++i) + { + if (!dir[i].CheckPlacement (max_offset)) + { + if (extra_file != null) + { + // remove entries that don't fit into game.dat+game.001 + int discard = dir.Count - i; + Trace.WriteLine (string.Format ("{0} entries didn't fit and were discarded", discard), "[vff]"); + dir.RemoveRange (i, discard); + break; + } + var extra_filename = Path.ChangeExtension (file.Name, ".001"); + if (!VFS.FileExists (extra_filename)) + return null; + extra_file = VFS.OpenView (extra_filename); + max_offset += extra_file.MaxOffset; + if (!dir[i].CheckPlacement (max_offset)) + return null; + } + } + if (null == extra_file) + return new ArcFile (file, this, dir); + return new VffArchive (file, this, dir, extra_file); + } + catch + { + if (extra_file != null) + extra_file.Dispose(); + throw; + } + finally + { + if (index_file != file) + index_file.Dispose(); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var vff = arc as VffArchive; + Stream input = null; + if (vff != null) + input = vff.OpenStream (entry); + else + input = arc.File.CreateStream (entry.Offset, entry.Size); + + var pent = entry as PackedEntry; + if (pent != null && pent.IsPacked) + return new ZLibStream (input, CompressionMode.Decompress); + else + return input; + } + + List ReadIndex (ArcView file, int count) + { + uint index_offset = 0xA; + var name_buffer = new byte[0x100]; + var rnd = new TpRandom (0x75D6EE39u); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint name_length = file.View.ReadUInt32 (index_offset); + index_offset += 4; + if (0 == name_length || name_length > name_buffer.Length) + return null; + if (name_length != file.View.Read (index_offset, name_buffer, 0, name_length)) + return null; + index_offset += name_length; + + var name = DecryptName (name_buffer, (int)name_length, rnd); + dir.Add (FormatCatalog.Instance.Create (name)); + } + rnd.Reset(); + uint offset = file.View.ReadUInt32 (index_offset) ^ rnd.GetRand32(); + foreach (var entry in dir) + { + index_offset += 8; + uint next_offset = file.View.ReadUInt32 (index_offset) ^ rnd.GetRand32(); + entry.Offset = offset; + entry.Size = next_offset - offset; + offset = next_offset; + } + index_offset += 8; + foreach (PackedEntry entry in dir) + { + entry.IsPacked = 0 == file.View.ReadByte (index_offset++); + } + return dir; + } + + string DecryptName (byte[] name_buf, int name_length, TpRandom key) + { + for (int i = 0; i < name_length; ++i) + { + name_buf[i] ^= (byte)key.GetRand32(); + } + return Encodings.cp932.GetString (name_buf, 0, name_length); + } + } + + internal class TpRandom + { + uint m_seed; + uint m_current; + + public TpRandom (uint seed) + { + m_seed = seed; + m_current = 0; + } + + public uint GetRand32 () + { + m_current += m_current << 2; + m_current += m_seed; + return m_current; + } + + public void Reset () + { + m_current = 0; + } + } + + internal class VffArchive : ArcFile + { + readonly ArcView ExtraFile; + + public VffArchive (ArcView arc, ArchiveFormat impl, ICollection dir, ArcView extra_file) + : base (arc, impl, dir) + { + ExtraFile = extra_file; + } + + internal Stream OpenStream (Entry entry) + { + if (entry.Offset < File.MaxOffset && entry.Offset+entry.Size > File.MaxOffset) + { + var first_part = File.View.ReadBytes (entry.Offset, entry.Size); + var second_part = ExtraFile.CreateStream (0, entry.Size - (uint)first_part.Length); + return new PrefixStream (first_part, second_part); + } + else if (entry.Offset >= File.MaxOffset) + { + return ExtraFile.CreateStream (entry.Offset, entry.Size); + } + else + return File.CreateStream (entry.Offset, entry.Size); + } + + bool _vff_disposed = false; + protected override void Dispose (bool disposing) + { + if (!_vff_disposed) + { + if (disposing && ExtraFile != null) + ExtraFile.Dispose(); + _vff_disposed = true; + } + } + } +} diff --git a/ArcFormats/LiveMaker/ImageGAL.cs b/ArcFormats/LiveMaker/ImageGAL.cs new file mode 100644 index 00000000..8ec07f65 --- /dev/null +++ b/ArcFormats/LiveMaker/ImageGAL.cs @@ -0,0 +1,570 @@ +//! \file ImageGAL.cs +//! \date Wed Jun 08 03:07:41 2016 +//! \brief LiveMaker image format. +// +// Copyright (C) 2016 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.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Compression; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; +using GameRes.Utility; + +namespace GameRes.Formats.LiveMaker +{ + internal class GalMetaData : ImageMetaData + { + public int Version; + public int FrameCount; + public bool Shuffled; + public int Compression; + public uint Mask; + public int BlockWidth; + public int BlockHeight; + public int DataOffset; + } + + internal class GalOptions : ResourceOptions + { + public uint Key; + } + + [Serializable] + public class GalScheme : ResourceScheme + { + public Dictionary KnownKeys; + } + + [Export(typeof(ImageFormat))] + public class GalFormat : ImageFormat + { + public override string Tag { get { return "GAL"; } } + public override string Description { get { return "LiveMaker image format"; } } + public override uint Signature { get { return 0x656C6147; } } // 'Gale' + + public static Dictionary KnownKeys = new Dictionary(); + + public override ResourceScheme Scheme + { + get { return new GalScheme { KnownKeys = KnownKeys }; } + set { KnownKeys = ((GalScheme)value).KnownKeys; } + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x30]; + if (11 != stream.Read (header, 0, 11)) + return null; + int version = header[4] * 100 + header[5] * 10 + header[6] - 5328; + if (version < 100 || version > 107) + return null; + int header_size = LittleEndian.ToInt32 (header, 7); + if (header_size < 0x28 || header_size > 0x100) + return null; + if (header_size > header.Length) + header = new byte[header_size]; + if (header_size != stream.Read (header, 0, header_size)) + return null; + if (version != LittleEndian.ToInt32 (header, 0)) + return null; + return new GalMetaData + { + Width = LittleEndian.ToUInt32 (header, 4), + Height = LittleEndian.ToUInt32 (header, 8), + BPP = LittleEndian.ToInt32 (header, 0xC), + Version = version, + FrameCount = LittleEndian.ToInt32 (header, 0x10), + Shuffled = header[0x15] != 0, + Compression = header[0x16], + Mask = LittleEndian.ToUInt32 (header, 0x18), + BlockWidth = LittleEndian.ToInt32 (header, 0x1C), + BlockHeight = LittleEndian.ToInt32 (header, 0x20), + DataOffset = header_size + 11, + }; + } + + uint? LastKey = null; + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = (GalMetaData)info; + uint key = 0; + if (meta.Shuffled) + { + if (LastKey != null) + key = LastKey.Value; + else + key = QueryKey(); + } + try + { + using (var reader = new GalReader (stream, meta, key)) + { + reader.Unpack(); + if (meta.Shuffled) + LastKey = key; + return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride); + } + } + catch + { + LastKey = null; + throw; + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GalFormat.Write not implemented"); + } + + public override ResourceOptions GetDefaultOptions () + { + return new GalOptions { Key = Settings.Default.GALKey }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetGAL(); + } + + uint QueryKey () + { + if (!KnownKeys.Any()) + return 0; + var options = Query (arcStrings.ArcImageEncrypted); + return options.Key; + } + } + + internal sealed class GalReader : IDisposable + { + BinaryReader m_input; + GalMetaData m_info; + byte[] m_output; + List m_frames; + uint m_key; + + public byte[] Data { get { return m_output; } } + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + public int Stride { get; private set; } + + public GalReader (Stream input, GalMetaData info, uint key) + { + m_info = info; + if (m_info.Compression < 0 || m_info.Compression > 2) + throw new InvalidFormatException(); + m_frames = new List (m_info.FrameCount); + m_key = key; + m_input = new ArcView.Reader (input); + } + + internal class Frame + { + public int Width; + public int Height; + public int BPP; + public int Stride; + public int AlphaStride; + public List Layers; + public Color[] Palette; + + public Frame (int layer_count) + { + Layers = new List (layer_count); + } + } + + internal class Layer + { + public byte[] Pixels; + public byte[] Alpha; + } + + public void Unpack () + { + m_input.BaseStream.Position = m_info.DataOffset; + uint name_length = m_input.ReadUInt32(); + m_input.BaseStream.Seek (name_length, SeekOrigin.Current); + uint mask = m_input.ReadUInt32(); + m_input.BaseStream.Seek (9, SeekOrigin.Current); + int layer_count = m_input.ReadInt32(); + if (layer_count < 1) + throw new InvalidFormatException(); + if (layer_count > 1) + throw new NotImplementedException(); + + // XXX only first frame is interpreted. + + var frame = new Frame (layer_count); + frame.Width = m_input.ReadInt32(); + frame.Height = m_input.ReadInt32(); + frame.BPP = m_input.ReadInt32(); + if (frame.BPP <= 0) + throw new InvalidFormatException(); + if (frame.BPP <= 8) + frame.Palette = ReadPalette (1 << frame.BPP); + frame.Stride = (frame.Width * frame.BPP + 7) / 8; + frame.AlphaStride = (frame.Width + 3) & ~3; + if (frame.BPP >= 8) + frame.Stride = (frame.Stride + 3) & ~3; + m_frames.Add (frame); + for (int i = 0; i < layer_count; ++i) + { + m_input.ReadInt32(); + m_input.ReadInt32(); + m_input.ReadByte(); // visibility + m_input.ReadInt32(); // -1 + m_input.ReadInt32(); // 0xFF + m_input.ReadByte(); + name_length = m_input.ReadUInt32(); + m_input.BaseStream.Seek (name_length, SeekOrigin.Current); + if (m_info.Version >= 107) + m_input.ReadByte(); + var layer = new Layer(); + int layer_size = m_input.ReadInt32(); + long layer_end = m_input.BaseStream.Position + layer_size; + layer.Pixels = UnpackLayer (frame, layer_size); + m_input.BaseStream.Position = layer_end; + int alpha_size = m_input.ReadInt32(); + if (alpha_size != 0) + { + layer.Alpha = UnpackLayer (frame, alpha_size, true); + } + frame.Layers.Add (layer); + } + Flatten (0); + } + + byte[] UnpackLayer (Frame frame, int length, bool is_alpha = false) + { + using (var packed = new StreamRegion (m_input.BaseStream, m_input.BaseStream.Position, length, true)) + { + if (0 == m_info.Compression || 2 == m_info.Compression && is_alpha) + return ReadZlib (frame, packed, is_alpha); + if (2 == m_info.Compression) + return ReadJpeg (frame, packed); + return ReadBlocks (frame, packed, is_alpha); + } + } + + byte[] ReadBlocks (Frame frame, Stream packed, bool is_alpha) + { + if (m_info.BlockWidth <= 0 || m_info.BlockHeight <= 0) + return ReadRaw (frame, packed, is_alpha); + int blocks_w = (frame.Width + m_info.BlockWidth - 1) / m_info.BlockWidth; + int blocks_h = (frame.Height + m_info.BlockHeight - 1) / m_info.BlockHeight; + int blocks_count = blocks_w * blocks_h; + var data = new byte[blocks_count * 8]; + packed.Read (data, 0, data.Length); + var refs = new int[blocks_count * 2]; + Buffer.BlockCopy (data, 0, refs, 0, data.Length); + if (m_info.Shuffled) + ShuffleBlocks (refs, blocks_count); + + int bpp = is_alpha ? 8 : frame.BPP; + int stride = is_alpha ? frame.AlphaStride : frame.Stride; + var pixels = new byte[stride * frame.Height]; + int i = 0; + for (int y = 0; y < frame.Height; y += m_info.BlockHeight) + { + int height = Math.Min (m_info.BlockHeight, frame.Height - y); + for (int x = 0; x < frame.Width; x += m_info.BlockWidth) + { + int dst = y * stride + (x * bpp + 7) / 8; + int width = Math.Min (m_info.BlockWidth, frame.Width - x); + int chunk_size = (width * bpp + 7) / 8; + if (-1 == refs[i]) + { + for (int j = 0; j < height; ++j) + { + packed.Read (pixels, dst, chunk_size); + dst += stride; + } + } + else if (-2 == refs[i]) + { + int src_x = m_info.BlockWidth * (refs[i+1] % blocks_w); + int src_y = m_info.BlockHeight * (refs[i+1] / blocks_w); + int src = src_y * stride + (src_x * bpp + 7) / 8; + for (int j = 0; j < height; ++j) + { + Buffer.BlockCopy (pixels, src, pixels, dst, chunk_size); + src += stride; + dst += stride; + } + } + else + { + int frame_ref = refs[i]; + int layer_ref = refs[i+1]; + if (frame_ref > m_frames.Count || layer_ref > m_frames[frame_ref].Layers.Count) + throw new InvalidFormatException(); + var layer = m_frames[frame_ref].Layers[layer_ref]; + byte[] src = is_alpha ? layer.Alpha : layer.Pixels; + for (int j = 0; j < height; ++j) + { + Buffer.BlockCopy (src, dst, pixels, dst, chunk_size); + dst += stride; + } + } + i += 2; + } + } + return pixels; + } + + byte[] ReadRaw (Frame frame, Stream packed, bool is_alpha) + { + int stride = is_alpha ? frame.AlphaStride : frame.Stride; + var pixels = new byte[frame.Height * stride]; + if (m_info.Shuffled) + { + foreach (var dst in RandomSequence (frame.Height, m_key)) + { + packed.Read (pixels, dst*stride, stride); + } + } + else + { + packed.Read (pixels, 0, pixels.Length); + } + return pixels; + } + + byte[] ReadZlib (Frame frame, Stream packed, bool is_alpha) + { + using (var zs = new ZLibStream (packed, CompressionMode.Decompress)) + return ReadBlocks (frame, zs, is_alpha); + } + + byte[] ReadJpeg (Frame frame, Stream packed) + { + var decoder = new JpegBitmapDecoder (packed, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + var bitmap = decoder.Frames[0]; + frame.BPP = bitmap.Format.BitsPerPixel; + int stride = bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8; + var pixels = new byte[bitmap.PixelHeight * stride]; + bitmap.CopyPixels (pixels, stride, 0); + return pixels; + } + + void Flatten (int frame_num) + { + // XXX only first layer is considered. + + var frame = m_frames[frame_num]; + var layer = frame.Layers[0]; + if (null == layer.Alpha) + { + m_output = layer.Pixels; + if (null != frame.Palette) + Palette = new BitmapPalette (frame.Palette); + if (8 == frame.BPP) + Format = PixelFormats.Indexed8; + else if (16 == frame.BPP) + Format = PixelFormats.Bgr565; + else if (24 == frame.BPP) + Format = PixelFormats.Bgr24; + else if (32 == frame.BPP) + Format = PixelFormats.Bgr32; + else if (4 == frame.BPP) + Format = PixelFormats.Indexed4; + else + throw new NotSupportedException(); + Stride = frame.Stride; + } + else + { + m_output = new byte[frame.Width * frame.Height * 4]; + switch (frame.BPP) + { + case 4: Flatten4bpp (frame, layer); break; + case 8: Flatten8bpp (frame, layer); break; + case 16: Flatten16bpp (frame, layer); break; + case 24: Flatten24bpp (frame, layer); break; + case 32: Flatten32bpp (frame, layer); break; + default: throw new NotSupportedException ("Not supported color depth"); + } + Format = PixelFormats.Bgra32; + Stride = frame.Width * 4; + } + } + + void Flatten4bpp (Frame frame, Layer layer) + { + int dst = 0; + int src = 0; + int a = 0; + for (int y = 0; y < frame.Height; ++y) + { + for (int x = 0; x < frame.Width; ++x) + { + byte pixel = layer.Pixels[src + x/2]; + int index = 0 == (x & 1) ? (pixel & 0xF) : (pixel >> 4); + var color = frame.Palette[index]; + m_output[dst++] = color.B; + m_output[dst++] = color.G; + m_output[dst++] = color.R; + m_output[dst++] = layer.Alpha[a+x]; + } + src += frame.Stride; + a += frame.AlphaStride; + } + } + + void Flatten8bpp (Frame frame, Layer layer) + { + int dst = 0; + int src = 0; + int a = 0; + for (int y = 0; y < frame.Height; ++y) + { + for (int x = 0; x < frame.Width; ++x) + { + var color = frame.Palette[ layer.Pixels[src+x] ]; + m_output[dst++] = color.B; + m_output[dst++] = color.G; + m_output[dst++] = color.R; + m_output[dst++] = layer.Alpha[a+x]; + } + src += frame.Stride; + a += frame.AlphaStride; + } + } + + void Flatten16bpp (Frame frame, Layer layer) + { + int src = 0; + int dst = 0; + int a = 0; + for (int y = 0; y < frame.Height; ++y) + { + for (int x = 0; x < frame.Width; ++x) + { + int pixel = LittleEndian.ToUInt16 (layer.Pixels, src + x*2); + m_output[dst++] = (byte)((pixel & 0x001F) * 0xFF / 0x001F); + m_output[dst++] = (byte)((pixel & 0x07E0) * 0xFF / 0x07E0); + m_output[dst++] = (byte)((pixel & 0xF800) * 0xFF / 0xF800); + m_output[dst++] = layer.Alpha[a+x]; + } + src += frame.Stride; + a += frame.AlphaStride; + } + } + + void Flatten24bpp (Frame frame, Layer layer) + { + int src = 0; + int dst = 0; + int a = 0; + int gap = frame.Stride - frame.Width * 3; + for (int y = 0; y < frame.Height; ++y) + { + for (int x = 0; x < frame.Width; ++x) + { + m_output[dst++] = layer.Pixels[src++]; + m_output[dst++] = layer.Pixels[src++]; + m_output[dst++] = layer.Pixels[src++]; + m_output[dst++] = layer.Alpha[a+x]; + } + src += gap; + a += frame.AlphaStride; + } + } + + void Flatten32bpp (Frame frame, Layer layer) + { + int src = 0; + int dst = 0; + int a = 0; + for (int y = 0; y < frame.Height; ++y) + { + for (int x = 0; x < frame.Width; ++x) + { + m_output[dst++] = layer.Pixels[src]; + m_output[dst++] = layer.Pixels[src+1]; + m_output[dst++] = layer.Pixels[src+2]; + m_output[dst++] = layer.Alpha[a+x]; + src += 4; + } + a += frame.AlphaStride; + } + } + + Color[] ReadPalette (int colors) + { + var palette_data = m_input.ReadBytes (4 * colors); + if (palette_data.Length != 4 * colors) + throw new EndOfStreamException(); + var palette = new Color[colors]; + for (int i = 0; i < colors; ++i) + { + int c = i * 4; + palette[i] = Color.FromRgb (palette_data[c+2], palette_data[c+1], palette_data[c]); + } + return palette; + } + + void ShuffleBlocks (int[] refs, int count) + { + var copy = refs.Clone() as int[]; + int src = 0; + foreach (var index in RandomSequence (count, m_key)) + { + refs[index*2] = copy[src++]; + refs[index*2+1] = copy[src++]; + } + } + + static IEnumerable RandomSequence (int count, uint seed) + { + var tp = new TpRandom (seed); + var order = Enumerable.Range (0, count).ToList(); + for (int i = 0; i < count; ++i) + { + int n = (int)(tp.GetRand32() % (uint)order.Count); + yield return order[n]; + order.RemoveAt (n); + } + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/ArcFormats/LiveMaker/WidgetGAL.xaml b/ArcFormats/LiveMaker/WidgetGAL.xaml new file mode 100644 index 00000000..98a6efe5 --- /dev/null +++ b/ArcFormats/LiveMaker/WidgetGAL.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ArcFormats/LiveMaker/WidgetGAL.xaml.cs b/ArcFormats/LiveMaker/WidgetGAL.xaml.cs new file mode 100644 index 00000000..2ea1c943 --- /dev/null +++ b/ArcFormats/LiveMaker/WidgetGAL.xaml.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Controls; +using System.Windows.Data; +using GameRes.Formats.LiveMaker; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetGAL.xaml + /// + public partial class WidgetGAL : StackPanel + { + public WidgetGAL() + { + InitializeComponent(); + var first_item = new KeyValuePair (arcStrings.ArcIgnoreEncryption, 0u); + var items = new KeyValuePair[] { first_item }; + this.Title.ItemsSource = items.Concat (GalFormat.KnownKeys.OrderBy (x => x.Key)); + } + } + + [ValueConversion(typeof(uint), typeof(string))] + public class GaleKeyConverter : IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is uint) + return ((uint)value).ToString ("X"); + else if (value is string) + return ConvertBack (value, targetType, parameter, culture); + else + return ""; + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is uint) + return Convert (value, targetType, parameter, culture); + string strValue = value as string; + uint result_key; + if (!string.IsNullOrWhiteSpace (strValue) + && uint.TryParse (strValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result_key)) + return result_key; + else + return null; + } + } + + public class GaleKeyRule : ValidationRule + { + public override ValidationResult Validate (object value, CultureInfo cultureInfo) + { + uint key = 0; + try + { + if (value is uint) + key = (uint)value; + else if (!string.IsNullOrWhiteSpace (value as string)) + key = UInt32.Parse ((string)value, NumberStyles.HexNumber); + } + catch + { + return new ValidationResult (false, arcStrings.INTKeyRequirement); + } + return new ValidationResult (true, null); + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index de96d561..33527eb1 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -549,5 +549,17 @@ namespace GameRes.Formats.Properties { this["MEDScriptScheme"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public uint GALKey { + get { + return ((uint)(this["GALKey"])); + } + set { + this["GALKey"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 1abee176..cfbeabf8 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -134,5 +134,8 @@ + + 0 + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index d5a6b979..314db7e0 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -188,6 +188,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Choose title or enter 32-bit hex key. + /// + public static string GALChoose { + get { + return ResourceManager.GetString("GALChoose", resourceCulture); + } + } + /// /// Looks up a localized string similar to âge proprietary image format. /// diff --git a/ArcFormats/Strings/arcStrings.ko-KR.resx b/ArcFormats/Strings/arcStrings.ko-KR.resx index 04d137bd..3b5f99b7 100644 --- a/ArcFormats/Strings/arcStrings.ko-KR.resx +++ b/ArcFormats/Strings/arcStrings.ko-KR.resx @@ -372,4 +372,8 @@ 아카이브 내용이 암호화 되었을 수 있습니다. 적절한 암호체계를 선택하세요. + + Choose title or enter 32-bit hex key + translation pending + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index 4ca4169f..5d53d179 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -375,4 +375,7 @@ choose appropriate encryption scheme. Archive content could be encrypted. Choose appropriate encryption scheme. + + Choose title or enter 32-bit hex key + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index 3008e40f..24908644 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -158,6 +158,9 @@ Ключи шифрования + + Выберите наименование или введите 32-битный ключ + Выберите исполняемый файл diff --git a/ArcFormats/Strings/arcStrings.zh-Hans.resx b/ArcFormats/Strings/arcStrings.zh-Hans.resx index b76f2066..96d3659f 100644 --- a/ArcFormats/Strings/arcStrings.zh-Hans.resx +++ b/ArcFormats/Strings/arcStrings.zh-Hans.resx @@ -377,4 +377,8 @@ choose appropriate encryption scheme. Choose appropriate encryption scheme. translation pending + + Choose title or enter 32-bit hex key + translation pending + \ No newline at end of file diff --git a/ArcFormats/app.config b/ArcFormats/app.config index ce5836a9..872810cf 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -136,6 +136,9 @@ + + 0 + diff --git a/supported.html b/supported.html index 036683ee..8c9bedf1 100644 --- a/supported.html +++ b/supported.html @@ -474,6 +474,7 @@ Eve to Iu Na no Omocha
Hissatsu Chikannin II
Otomegari
Ryoujoku Gojuusou
+Shinjin Kangofu Miho
Tokumei Sentai Sirenger
Tokumei Sentai Yuzu Ranger
Zetsuboushi
@@ -580,6 +581,7 @@ Vampire Crusaders
*.gpk+*.gtb
*.vpk+*.vtb-NoBlack Cyc Before Dawn Daybreak ~Shinen no Utahime~
Gun-Katana
+Hana Goyomi
Jishou Seirei Majutsushi vs Shinsei Daiikkyuu Akuma
Kurogane no Tsubasa ~The Alchemist's Story~
Mushitsukai
@@ -874,6 +876,11 @@ Taiiku Souko ~Shoujotachi no Sange~
Big Magnum Harimoto-sensei
*.mfcMFCNo +game.datvffNoLiveMaker +Grope ~Yami no Naka no Kotori-tachi~
+Ryoujoku Seifuku Jogakuen ~Chimitsu ni Nureta Seifuku~
+ +*.galGale105
Gale106No

1 Non-encrypted only