diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 733e79c3..409d9185 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -85,6 +85,7 @@ + @@ -122,6 +123,7 @@ + @@ -300,6 +302,7 @@ + @@ -722,6 +725,7 @@ + diff --git a/ArcFormats/Slg/ImageTIG.cs b/ArcFormats/Slg/ImageTIG.cs index 2b075f47..80e2b422 100644 --- a/ArcFormats/Slg/ImageTIG.cs +++ b/ArcFormats/Slg/ImageTIG.cs @@ -2,7 +2,7 @@ //! \date Sat Jun 04 18:00:15 2016 //! \brief SLG system encrypted PNG images. // -// Copyright (C) 2016 by morkt +// Copyright (C) 2016-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 @@ -30,7 +30,7 @@ using System.Security.Cryptography; namespace GameRes.Formats.Slg { [Export(typeof(ImageFormat))] - public class TigFormat : PngFormat + public class TigFormat : ImageFormat { public override string Tag { get { return "TIG"; } } public override string Description { get { return "SLG system encrypted PNG image"; } } @@ -39,18 +39,23 @@ namespace GameRes.Formats.Slg public override ImageMetaData ReadMetaData (IBinaryStream stream) { - using (var proxy = new ProxyStream (stream.AsStream, true)) - using (var crypt = new InputCryptoStream (proxy, new TigTransform())) - using (var input = new BinaryStream (crypt, stream.Name)) - return base.ReadMetaData (input); + using (var input = OpenEncrypted (stream)) + return Png.ReadMetaData (input); } public override ImageData Read (IBinaryStream stream, ImageMetaData info) { - using (var proxy = new ProxyStream (stream.AsStream, true)) - using (var crypt = new InputCryptoStream (proxy, new TigTransform())) - using (var input = new BinaryStream (crypt, stream.Name)) - return base.Read (input, info); + using (var input = OpenEncrypted (stream)) + return Png.Read (input, info); + } + + internal IBinaryStream OpenEncrypted (IBinaryStream stream, bool seekable = false) + { + Stream input = new ProxyStream (stream.AsStream, true); + input = new InputCryptoStream (input, new TigTransform()); + if (seekable) + input = new SeekableStream (input); + return new BinaryStream (input, stream.Name); } public override void Write (Stream file, ImageData image) @@ -59,19 +64,51 @@ namespace GameRes.Formats.Slg } } + [Export(typeof(ImageFormat))] + public class TicFormat : TigFormat + { + public override string Tag { get { return "TIC"; } } + public override string Description { get { return "SLG system encrypted JPEG image"; } } + public override uint Signature { get { return 0x15A44A01; } } + public override bool CanWrite { get { return false; } } + + public override ImageMetaData ReadMetaData (IBinaryStream stream) + { + using (var input = OpenEncrypted (stream, true)) + return Jpeg.ReadMetaData (input); + } + + public override ImageData Read (IBinaryStream stream, ImageMetaData info) + { + using (var input = OpenEncrypted (stream)) + return Jpeg.Read (input, info); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TicFormat.Write not implemented"); + } + } + internal sealed class TigTransform : ICryptoTransform { const int BlockSize = 1; - uint m_key; + const uint DefaultKey = 0x7F7F7F7F; - public bool CanReuseTransform { get { return true; } } + RandomGenerator m_rnd; + + public bool CanReuseTransform { get { return false; } } public bool CanTransformMultipleBlocks { get { return true; } } public int InputBlockSize { get { return BlockSize; } } public int OutputBlockSize { get { return BlockSize; } } - public TigTransform () + public TigTransform () : this (DefaultKey) { - m_key = 0x7F7F7F7F; + } + + public TigTransform (uint key) + { + m_rnd = new RandomGenerator (key); } public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, @@ -79,9 +116,7 @@ namespace GameRes.Formats.Slg { for (int i = 0; i < inputCount; ++i) { - m_key *= 0x343FD; - m_key += 0x269EC3; - outputBuffer[outputOffset+i] = (byte)(inputBuffer[inputOffset+i] - (m_key >> 16)); + outputBuffer[outputOffset+i] = (byte)(inputBuffer[inputOffset+i] - m_rnd.Next()); } return inputCount; } @@ -95,7 +130,30 @@ namespace GameRes.Formats.Slg public void Dispose () { - System.GC.SuppressFinalize (this); } } + + internal sealed class RandomGenerator + { + uint m_seed; + + public uint Seed { get { return m_seed; } } + + public RandomGenerator (uint seed) + { + SRand (seed); + } + + public void SRand (uint seed) + { + m_seed = seed; + } + + public uint Next () + { + m_seed *= 0x343FD; + m_seed += 0x269EC3; + return m_seed >> 16; + } + }; } diff --git a/ArcFormats/Slg/ImageTIM.cs b/ArcFormats/Slg/ImageTIM.cs new file mode 100644 index 00000000..9f773fc8 --- /dev/null +++ b/ArcFormats/Slg/ImageTIM.cs @@ -0,0 +1,95 @@ +//! \file ImageTIM.cs +//! \date 2018 Dec 04 +//! \brief SLG system encrypted image. +// +// Copyright (C) 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 +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.Slg +{ + internal class TimMetaData : ImageMetaData + { + public uint Seed; + public int Stride; + } + + [Export(typeof(ImageFormat))] + public class TimFormat : ImageFormat + { + public override string Tag { get { return "TIM/SLG"; } } + public override string Description { get { return "SLG system encrypted image"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".tim") || file.Length < 1024) + return null; + var header = file.ReadBytes (1024); + uint seed = (uint)(header[18] | header[42] << 8 | header[98] << 16 | header[118] << 24); + var rnd = new RandomGenerator (seed); + for (int i = 512; i < 1024; ++i) + { + header[i] -= (byte)rnd.Next(); + } + if (!Binary.AsciiEqual (header, 0x2DE, "TIM Data Ver 1.00\0")) + return null; + return new TimMetaData { + Width = header.ToUInt32 (0x248), + Height = header.ToUInt32 (0x29C), + Stride = header.ToInt32 (0x2BA), + Seed = rnd.Seed, + BPP = 24, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (TimMetaData)info; + file.Position = 1024; + var pixels = file.ReadBytes (meta.Stride * (int)meta.Height); + var key_table = GenerateDecryptTable (meta.Seed); + for (int i = 0; i < pixels.Length; ++i) + { + pixels[i] -= key_table[i & 0xFFF]; + } + return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, meta.Stride); + } + + internal byte[] GenerateDecryptTable (uint seed) + { + var rnd = new RandomGenerator (seed); + var table = new byte[0x1000]; + for (int i = 0; i < table.Length; ++i) + table[i] = (byte)rnd.Next(); + return table; + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TimFormat.Write not implemented"); + } + } +}