diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index 3240aae5..99177b1c 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -114,6 +114,8 @@ + + diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs new file mode 100644 index 00000000..336b46e8 --- /dev/null +++ b/Experimental/Microsoft/ArcEXE.cs @@ -0,0 +1,184 @@ +//! \file ArcEXE.cs +//! \date 2023 Aug 24 +//! \brief Access portable executable (PE) resources. +// +// Copyright (C) 2023 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; + +namespace GameRes.Formats.Microsoft +{ + [Export(typeof(ArchiveFormat))] + public class ExeOpener : ArchiveFormat + { + public override string Tag { get => "EXE"; } + public override string Description { get => "Windows executable resources"; } + public override uint Signature { get => 0; } + public override bool IsHierarchic { get => true; } + public override bool CanWrite { get => false; } + + public ExeOpener () + { + Extensions = new[] { "exe",/* "dll"*/ }; + } + + static readonly Dictionary RuntimeTypeMap = new Dictionary() { + { "#2", "RT_BITMAP" }, + { "#10", "RT_RCDATA" }, + }; + + static readonly Dictionary ExtensionTypeMap = new Dictionary() { + { "PNG", ".PNG" }, + { "WAVE", ".WAV" }, + { "MIDS", ".MID" }, + { "SCR", ".BIN" }, + { "#2", ".BMP" }, + { "#10", ".BIN" }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "MZ") || VFS.IsVirtual) + return null; + var res = new ExeFile.ResourceAccessor (file.Name); + try + { + var dir = new List(); + foreach (var type in res.EnumTypes()) + { + string dir_name = type; + if (type.StartsWith ("#") && !RuntimeTypeMap.TryGetValue (type, out dir_name)) + continue; + string ext; + if (!ExtensionTypeMap.TryGetValue (type, out ext)) + ext = ""; + foreach (var name in res.EnumNames (type)) + { + string full_name = name; + if (name.StartsWith ("#")) + full_name = IdToString (name); + full_name = string.Join ("/", dir_name, full_name) + ext; + var entry = Create (full_name); + entry.NativeName = name; + entry.NativeType = type; + entry.Offset = 0; + entry.Size = res.GetResourceSize (name, type); + dir.Add (entry); + } + } + if (0 == dir.Count) + { + res.Dispose(); + return null; + } + return new ResourcesArchive (file, this, dir, res); + } + catch + { + res.Dispose(); + throw; + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var rarc = (ResourcesArchive)arc; + var rent = (ResourceEntry)entry; + var data = rarc.Accessor.GetResource (rent.NativeName, rent.NativeType); + if (null == data) + return Stream.Null; + return new BinMemoryStream (data, rent.Name); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var rent = (ResourceEntry)entry; + if (rent.NativeType != "#2") + return base.OpenImage (arc, entry); + var rarc = (ResourcesArchive)arc; + var bitmap = new byte[14 + entry.Size]; + int length = rarc.Accessor.ReadResource (rent.NativeName, rent.NativeType, bitmap, 14); + + length += 14; + bitmap[0] = (byte)'B'; + bitmap[1] = (byte)'M'; + LittleEndian.Pack (length, bitmap, 2); + int bits_length = bitmap.ToInt32 (0x22); + int bits_pos = length - bits_length; + if (bits_length == 0) + bits_pos = bitmap.ToInt32 (14) + 22; + LittleEndian.Pack (bits_pos, bitmap, 10); + + var bm = new BinMemoryStream (bitmap, 0, length, entry.Name); + var info = ImageFormat.Bmp.ReadMetaData (bm); + if (null == info) + { + bm.Dispose(); + throw new InvalidFormatException ("Invalid bitmap resource."); + } + bm.Position = 0; + return new ImageFormatDecoder (bm, ImageFormat.Bmp, info); + } + + internal static string IdToString (string id) + { + if (id.Length > 1 && id[0] == '#' && char.IsDigit (id[1])) + id = id.Substring (1).PadLeft (5, '0'); + return id; + } + } + + internal class ResourceEntry : Entry + { + public string NativeName; + public string NativeType; + } + + internal class ResourcesArchive : ArcFile + { + public readonly ExeFile.ResourceAccessor Accessor; + + public ResourcesArchive (ArcView arc, ArchiveFormat impl, ICollection dir, ExeFile.ResourceAccessor acc) + : base (arc, impl, dir) + { + Accessor = acc; + } + + #region IDisposable Members + bool _acc_disposed = false; + protected override void Dispose (bool disposing) + { + if (_acc_disposed) + return; + if (disposing) + Accessor.Dispose(); + _acc_disposed = true; + base.Dispose (disposing); + } + #endregion + } +} diff --git a/Experimental/Microsoft/ArcNE.cs b/Experimental/Microsoft/ArcNE.cs new file mode 100644 index 00000000..ba795e57 --- /dev/null +++ b/Experimental/Microsoft/ArcNE.cs @@ -0,0 +1,103 @@ +//! \file ArcNE.cs +//! \date 2023 Aug 29 +//! \brief Access 16-bit NE executable resources. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.Microsoft +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get => "EXE/NE"; } + public override string Description { get => "Windows 16-bit executable resources"; } + public override uint Signature { get => 0; } + public override bool IsHierarchic { get => true; } + public override bool CanWrite { get => false; } + + static readonly Dictionary TypeMap = new Dictionary { + { 1, "RT_CURSOR" }, + { 2, "RT_BITMAP" }, + { 3, "RT_ICON" }, + { 4, "RT_MENU" }, + { 5, "RT_DIALOG" }, + { 6, "RT_STRING" }, + { 10, "RT_DATA" }, + { 11, "RT_MESSAGETABLE" }, + { 16, "RT_VERSION" }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "MZ")) + return null; + uint ne_offset = file.View.ReadUInt32 (0x3C); + if (!file.View.AsciiEqual (ne_offset, "NE")) + return null; + uint res_table_offset = file.View.ReadUInt16 (ne_offset+0x24) + ne_offset; + if (res_table_offset <= ne_offset || res_table_offset >= file.MaxOffset) + return null; + int shift = file.View.ReadUInt16 (res_table_offset); + res_table_offset += 2; + var dir = new List(); + while (res_table_offset + 1 < file.MaxOffset) + { + int type_id = file.View.ReadUInt16 (res_table_offset); + if (0 == type_id) + break; + string dir_name = null; + if ((type_id & 0x8000) != 0) + { + type_id &= 0x7FFF; + TypeMap.TryGetValue (type_id, out dir_name); + } + int count = file.View.ReadUInt16 (res_table_offset+2); + res_table_offset += 8; + if (null == dir_name) + dir_name = string.Format ("#{0}", type_id); + for (int i = 0; i < count; ++i) + { + int offset = file.View.ReadUInt16 (res_table_offset) << shift; + uint size = (uint)file.View.ReadUInt16 (res_table_offset+2) << shift; + int res_id = file.View.ReadUInt16 (res_table_offset+6); + res_table_offset += 12; + string name = res_id.ToString ("D5"); + name = string.Join ("/", dir_name, name); + var entry = new Entry { + Name = name, + Offset = offset, + Size = size, + }; + dir.Add (entry); + } + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + } +}