From a313a32c03b89a7de86e285cea0d57479e6753f8 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 15 May 2015 17:32:07 +0400 Subject: [PATCH] implemented EAGLS system resources. --- ArcFormats/ArcEAGLS.cs | 175 +++++++++++++++++++++++++++++++++++ ArcFormats/ArcFormats.csproj | 2 + ArcFormats/ImageGR.cs | 99 ++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 ArcFormats/ArcEAGLS.cs create mode 100644 ArcFormats/ImageGR.cs diff --git a/ArcFormats/ArcEAGLS.cs b/ArcFormats/ArcEAGLS.cs new file mode 100644 index 00000000..ead319a6 --- /dev/null +++ b/ArcFormats/ArcEAGLS.cs @@ -0,0 +1,175 @@ +//! \file ArcEAGLS.cs +//! \date Fri May 15 02:52:04 2015 +//! \brief EAGLS system resource archives. +// +// 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 GameRes.Utility; + +namespace GameRes.Formats.Eagls +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get { return "PAK/EAGLS"; } } + public override string Description { get { return "EAGLS engine resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public PakOpener () + { + Extensions = new string[] { "pak" }; + } + + static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; + static readonly string Key = "EAGLS_SYSTEM"; + + public override ArcFile TryOpen (ArcView file) + { + string idx_name = Path.ChangeExtension (file.Name, ".idx"); + var idx_info = new FileInfo (idx_name); + if (!idx_info.Exists || idx_info.Length > 0xfffff || idx_info.Length < 10000) + return null; + + byte[] index; + using (var idx = new ArcView (idx_name)) + index = DecryptIndex (idx); + int index_offset = 0; + int entry_size = index.Length / 10000; + bool long_offsets = 40 == entry_size; + int name_size = long_offsets ? 0x18 : 0x14; + long first_offset = LittleEndian.ToUInt32 (index, name_size); + var dir = new List(); + while (index_offset < index.Length) + { + if (0 == index[index_offset]) + break; + var name = Binary.GetCString (index, index_offset, name_size); + index_offset += name_size; + var entry = FormatCatalog.Instance.CreateEntry (name); + if (name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + entry.Type = "script"; + if (long_offsets) + { + entry.Offset = LittleEndian.ToInt64 (index, index_offset) - first_offset; + entry.Size = LittleEndian.ToUInt32 (index, index_offset+8); + index_offset += 0x10; + } + else + { + entry.Offset = LittleEndian.ToUInt32 (index, index_offset) - first_offset; + entry.Size = LittleEndian.ToUInt32 (index, index_offset+4); + index_offset += 8; + } + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (entry.Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) + return DecryptGr (arc, entry); + if (entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + return DecryptDat (arc, entry); + return arc.File.CreateStream (entry.Offset, entry.Size); + } + + Stream DecryptGr (ArcFile arc, Entry entry) + { + var input = new byte[entry.Size]; + arc.File.View.Read (entry.Offset, input, 0, entry.Size); + int seed = 0x75bd924 ^ input[input.Length-1]; + int limit = Math.Min (input.Length-1, 0x174b); + for (int i = 0; i < limit; ++i) + { + seed = LRand (seed); + int index = (int)(seed * 4.656612875245797e-10 * 256); + input[i] ^= (byte)Key[index % Key.Length]; + } + return new MemoryStream (input); + } + + Stream DecryptDat (ArcFile arc, Entry entry) + { + byte[] input = new byte[entry.Size]; + arc.File.View.Read (entry.Offset, input, 0, entry.Size); + int text_offset = 3600; + int text_length = (int)(entry.Size - 3600 - 2); + int seed = input[input.Length-1]; + for (int i = 0; i < text_length; i += 2) + { + seed = seed * 0x343FD + 0x269EC3; + int index = (seed >> 16) & 0x7fff; + input[text_offset + i] ^= (byte)Key[index % Key.Length]; + } + return new MemoryStream (input); + } + + byte[] DecryptIndex (ArcView idx) + { + int idx_size = (int)idx.MaxOffset-4; + byte[] output = new byte[idx_size]; + using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset)) + unsafe + { + uint seed = view.ReadUInt32 (idx_size); + byte* ptr = view.GetPointer (0); + try + { + for (int i = 0; i < idx_size; ++i) + { + seed = seed * 0x343FD + 0x269EC3; + int index = (int)(seed >> 16) & 0x7FFF; + output[i] = (byte)(ptr[i] ^ IndexKey[index % IndexKey.Length]); + } + return output; + } + finally + { + view.SafeMemoryMappedViewHandle.ReleasePointer(); + } + } + } + + int LRand (int seed) + { + const int A = 48271; + const int Q = 44488; + const int R = 3399; + const int M = 2147483647; + seed = A * (seed % Q) - R * (seed / Q); + if (seed < 0) + seed += M; + return seed; + } + } +} diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 1690dc4e..5f005651 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -76,6 +76,7 @@ + @@ -160,6 +161,7 @@ + diff --git a/ArcFormats/ImageGR.cs b/ArcFormats/ImageGR.cs new file mode 100644 index 00000000..8e053668 --- /dev/null +++ b/ArcFormats/ImageGR.cs @@ -0,0 +1,99 @@ +//! \file ImageGR.cs +//! \date Fri May 15 04:26:58 2015 +//! \brief EAGLS system compressed bitmap. +// +// 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.Windows.Media; +using System.IO; +using GameRes.Utility; + +namespace GameRes.Formats.Eagls +{ + internal class GrMetaData : ImageMetaData + { + public int UnpackedSize; + } + + [Export(typeof(ImageFormat))] + public class GrFormat : BmpFormat + { + public override string Tag { get { return "GR"; } } + public override string Description { get { return "EAGLS engine compressed bitmap"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + using (var reader = new LzssReader (stream, (int)stream.Length, 0x26)) // BMP header + { + reader.Unpack(); + var bmp = reader.Data; + if (bmp[0] != 'B' || bmp[1] != 'M') + return null; + int file_size = LittleEndian.ToInt32 (bmp, 2); + int width = LittleEndian.ToInt32 (bmp, 0x12); + int height = LittleEndian.ToInt32 (bmp, 0x16); + int bpp = LittleEndian.ToInt16 (bmp, 0x1c); + int image_size = LittleEndian.ToInt32 (bmp, 0x22); + return new GrMetaData + { + Width = (uint)width, + Height = (uint)height, + BPP = bpp, + UnpackedSize = 24 == bpp ? file_size : (image_size+0x36), + }; + } + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as GrMetaData; + if (null == meta) + throw new ArgumentException ("GrFormat.Read should be supplied with GrMetaData", "info"); + + using (var reader = new LzssReader (stream, (int)stream.Length, meta.UnpackedSize+2)) + { + reader.Unpack(); + if (32 != info.BPP) + using (var bmp = new MemoryStream (reader.Data)) + return base.Read (bmp, info); + int stride = (int)info.Width*4; + var pixels = new byte[stride*info.Height]; + int dst = 0; + int offset = 0x36; + for (int src = stride*((int)info.Height-1); src >= 0; src -= stride) + { + Buffer.BlockCopy (reader.Data, offset+src, pixels, dst, stride); + dst += stride; + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("GrFormat.Write not implemented"); + } + } +}