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);
+ }
+ }
+}