diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index ee48ddc7..dbefab9f 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -104,6 +104,7 @@
+
diff --git a/ArcFormats/CatSystem/ArcINT.cs b/ArcFormats/CatSystem/ArcINT.cs
index 8718747e..808750a5 100644
--- a/ArcFormats/CatSystem/ArcINT.cs
+++ b/ArcFormats/CatSystem/ArcINT.cs
@@ -363,15 +363,12 @@ namespace GameRes.Formats.CatSystem
///
public static string GetPassFromExe (string filename)
{
- var exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, 0x20); // LOAD_LIBRARY_AS_IMAGE_RESOURCE
- if (IntPtr.Zero == exe)
- throw new Win32Exception (Marshal.GetLastWin32Error());
- try
+ using (var exe = new ExeFile.ResourceAccessor (filename))
{
- var code = GetResource (exe, "DATA", "V_CODE2");
+ var code = exe.GetResource ("DATA", "V_CODE2");
if (null == code || code.Length < 8)
return null;
- var key = GetResource (exe, "KEY", "KEY_CODE");
+ var key = exe.GetResource ("KEY", "KEY_CODE");
if (null != key)
{
for (int i = 0; i < key.Length; ++i)
@@ -388,50 +385,6 @@ namespace GameRes.Formats.CatSystem
length = code.Length;
return Encodings.cp932.GetString (code, 0, length);
}
- finally
- {
- NativeMethods.FreeLibrary (exe);
- }
}
-
- static byte[] GetResource (IntPtr exe, string name, string type)
- {
- var res = NativeMethods.FindResource (exe, name, type);
- if (IntPtr.Zero == res)
- return null;
- var glob = NativeMethods.LoadResource (exe, res);
- if (IntPtr.Zero == glob)
- return null;
- uint size = NativeMethods.SizeofResource (exe, res);
- var src = NativeMethods.LockResource (glob);
- if (IntPtr.Zero == src)
- return null;
-
- var dst = new byte[size];
- Marshal.Copy (src, dst, 0, dst.Length);
- return dst;
- }
- }
-
- static internal class NativeMethods
- {
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- static internal extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hReservedNull, uint dwFlags);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- static internal extern bool FreeLibrary (IntPtr hModule);
-
- [DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- static internal extern IntPtr FindResource (IntPtr hModule, string lpName, string lpType);
-
- [DllImport("Kernel32.dll", SetLastError = true)]
- static internal extern IntPtr LoadResource (IntPtr hModule, IntPtr hResource);
-
- [DllImport("Kernel32.dll", SetLastError = true)]
- static internal extern uint SizeofResource (IntPtr hModule, IntPtr hResource);
-
- [DllImport("kernel32.dll")]
- static internal extern IntPtr LockResource (IntPtr hResData);
}
}
diff --git a/ArcFormats/ExeFile.cs b/ArcFormats/ExeFile.cs
new file mode 100644
index 00000000..d55993d3
--- /dev/null
+++ b/ArcFormats/ExeFile.cs
@@ -0,0 +1,270 @@
+//! \file ExeFile.cs
+//! \date Mon Jan 23 05:12:50 2017
+//! \brief Win32 EXE file parser/accessor.
+//
+// Copyright (C) 2017 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;
+using System.Runtime.InteropServices;
+using System.Text;
+using GameRes.Utility;
+
+namespace GameRes.Formats
+{
+ public class ExeFile
+ {
+ ArcView m_file;
+ Dictionary m_section_table;
+ Section m_overlay;
+
+ public ExeFile (ArcView file)
+ {
+ if (!file.View.AsciiEqual (0, "MZ"))
+ throw new InvalidFormatException ("File is not a valid win32 executable.");
+ m_file = file;
+ Whole = new Section { Offset = 0, Size = (uint)Math.Min (m_file.MaxOffset, uint.MaxValue) };
+ }
+
+ public ArcView.Frame View { get { return m_file.View; } }
+
+ ///
+ /// Section representing the whole file.
+ ///
+ public Section Whole { get; private set; }
+
+ ///
+ /// Dictionary of executable file sections.
+ ///
+ public IReadOnlyDictionary Sections
+ {
+ get
+ {
+ if (null == m_section_table)
+ InitSectionTable();
+ return m_section_table;
+ }
+ }
+
+ ///
+ /// Overlay section of executable file.
+ ///
+ public Section Overlay
+ {
+ get
+ {
+ if (null == m_section_table)
+ InitSectionTable();
+ return m_overlay;
+ }
+ }
+
+ ///
+ /// Structure representing section of executable file in the form of its offset and size.
+ ///
+ public struct Section
+ {
+ public long Offset;
+ public uint Size;
+ }
+
+ ///
+ /// Returns true if executable file contains section .
+ ///
+ public bool ContainsSection (string name)
+ {
+ return Sections.ContainsKey (name);
+ }
+
+ ///
+ /// Search for byte sequence within specified section.
+ ///
+ /// Offset of byte sequence, if found, -1 otherwise.
+ public long FindString (Section section, byte[] seq, int step = 1)
+ {
+ if (step <= 0)
+ throw new ArgumentOutOfRangeException ("step", "Search step should be positive integer.");
+ long offset = section.Offset;
+ if (offset < 0 || offset > m_file.MaxOffset)
+ throw new ArgumentOutOfRangeException ("section", "Invalid executable file section specified.");
+ uint seq_length = (uint)seq.Length;
+ if (0 == seq_length || section.Size < seq_length)
+ return -1;
+ long end_offset = Math.Min (m_file.MaxOffset, offset + section.Size);
+ unsafe
+ {
+ while (offset < end_offset)
+ {
+ uint page_size = (uint)Math.Min (0x10000L, end_offset - offset);
+ if (page_size < seq_length)
+ break;
+ using (var view = m_file.CreateViewAccessor (offset, page_size))
+ using (var ptr = new ViewPointer (view, offset))
+ {
+ byte* page_begin = ptr.Value;
+ byte* page_end = page_begin + page_size - seq_length;
+ byte* p;
+ for (p = page_begin; p <= page_end; p += step)
+ {
+ int i = 0;
+ while (p[i] == seq[i])
+ {
+ if (++i == seq.Length)
+ return offset + (p - page_begin);
+ }
+ }
+ offset += p - page_begin;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public long FindAsciiString (Section section, string seq, int step = 1)
+ {
+ return FindString (section, Encoding.ASCII.GetBytes (seq), step);
+ }
+
+ public long FindSignature (Section section, uint signature, int step = 4)
+ {
+ var bytes = new byte[4];
+ LittleEndian.Pack (signature, bytes, 0);
+ return FindString (section, bytes, step);
+ }
+
+ private void InitSectionTable ()
+ {
+ long pe_offset = m_file.View.ReadUInt32 (0x3C);
+ if (pe_offset >= m_file.MaxOffset-0x58 || !m_file.View.AsciiEqual (pe_offset, "PE\0\0"))
+ throw new InvalidFormatException ("File is not a valid win32 executable.");
+
+ int opt_header = m_file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
+ long offset = m_file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders
+ long section_table = pe_offset+opt_header+0x18;
+ int count = m_file.View.ReadUInt16 (pe_offset+6); // NumberOfSections
+ var table = new Dictionary (count);
+ if (section_table + 0x28*count < m_file.MaxOffset)
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ var name = m_file.View.ReadString (section_table, 0x10);
+ var section = new Section {
+ Size = m_file.View.ReadUInt32 (section_table+0x10),
+ Offset = m_file.View.ReadUInt32 (section_table+0x14)
+ };
+ if (!table.ContainsKey (name))
+ table.Add (name, section);
+ if (0 != section.Size)
+ offset = Math.Max (section.Offset + section.Size, offset);
+ section_table += 0x28;
+ }
+ }
+ offset = Math.Min ((offset + 0xF) & ~0xFL, m_file.MaxOffset);
+ m_overlay.Offset = offset;
+ m_overlay.Size = (uint)(m_file.MaxOffset - offset);
+ m_section_table = table;
+ }
+
+ ///
+ /// Helper class for executable file resources access.
+ ///
+ public sealed class ResourceAccessor : IDisposable
+ {
+ IntPtr m_exe;
+
+ public ResourceAccessor (string filename)
+ {
+ const uint LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20;
+
+ m_exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
+ if (IntPtr.Zero == m_exe)
+ throw new Win32Exception (Marshal.GetLastWin32Error());
+ }
+
+ public byte[] GetResource (string name, string type)
+ {
+ if (m_disposed)
+ throw new ObjectDisposedException ("Access to disposed ResourceAccessor object failed.");
+ var res = NativeMethods.FindResource (m_exe, name, type);
+ if (IntPtr.Zero == res)
+ return null;
+ var glob = NativeMethods.LoadResource (m_exe, res);
+ if (IntPtr.Zero == glob)
+ return null;
+ uint size = NativeMethods.SizeofResource (m_exe, res);
+ var src = NativeMethods.LockResource (glob);
+ if (IntPtr.Zero == src)
+ return null;
+
+ var dst = new byte[size];
+ Marshal.Copy (src, dst, 0, dst.Length);
+ return dst;
+ }
+
+ #region IDisposable implementation
+ bool m_disposed = false;
+ public void Dispose ()
+ {
+ Dispose (true);
+ GC.SuppressFinalize (this);
+ }
+
+ ~ResourceAccessor ()
+ {
+ Dispose (false);
+ }
+
+ void Dispose (bool disposing)
+ {
+ if (!m_disposed)
+ {
+ NativeMethods.FreeLibrary (m_exe);
+ m_disposed = true;
+ }
+ }
+ #endregion
+ }
+ }
+
+ static internal class NativeMethods
+ {
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ static internal extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hReservedNull, uint dwFlags);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static internal extern bool FreeLibrary (IntPtr hModule);
+
+ [DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ static internal extern IntPtr FindResource (IntPtr hModule, string lpName, string lpType);
+
+ [DllImport("Kernel32.dll", SetLastError = true)]
+ static internal extern IntPtr LoadResource (IntPtr hModule, IntPtr hResource);
+
+ [DllImport("Kernel32.dll", SetLastError = true)]
+ static internal extern uint SizeofResource (IntPtr hModule, IntPtr hResource);
+
+ [DllImport("kernel32.dll")]
+ static internal extern IntPtr LockResource (IntPtr hResData);
+ }
+}
diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs
index 82569689..1fc1e0ad 100644
--- a/ArcFormats/KiriKiri/ArcXP3.cs
+++ b/ArcFormats/KiriKiri/ArcXP3.cs
@@ -295,67 +295,23 @@ NextEntry:
private long SkipExeHeader (ArcView file)
{
- long offset = 0x10;
- long pe_offset = file.View.ReadUInt32 (0x3c);
- if (pe_offset < file.MaxOffset && 0x4550 == file.View.ReadUInt32 (pe_offset)) // 'PE'
+ var exe = new ExeFile (file);
+ if (exe.ContainsSection (".rsrc"))
{
- int opt_header = file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
- offset = file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders
- long section_table = pe_offset+opt_header+0x18;
- int count = file.View.ReadUInt16 (pe_offset+6); // NumberOfSections
- if (section_table + 0x28*count < file.MaxOffset)
- {
- for (int i = 0; i < count; ++i)
- {
- uint size = file.View.ReadUInt32 (section_table+0x10);
- uint addr = file.View.ReadUInt32 (section_table+0x14);
- if (file.View.AsciiEqual (section_table, ".rsrc\0"))
- {
- // look within EXE resource section
- offset = addr;
- break;
- }
- section_table += 0x28;
- if (0 != size)
- offset = Math.Max ((long)addr + size, offset);
- }
- }
+ var offset = exe.FindString (exe.Sections[".rsrc"], s_xp3_header);
+ if (offset != -1 && 0 != file.View.ReadUInt32 (offset+s_xp3_header.Length))
+ return offset;
}
- unsafe
+ var section = exe.Overlay;
+ while (section.Offset < file.MaxOffset)
{
- while (offset < file.MaxOffset)
- {
- uint page_size = (uint)Math.Min (0x10000L, file.MaxOffset - offset);
- if (page_size < 0x20)
- break;
- using (var view = file.CreateViewAccessor (offset, page_size))
- {
- byte* page_begin = view.GetPointer (offset);
- byte* page_end = page_begin + page_size - 0x10;
- try {
- for (byte* ptr = page_begin; ptr != page_end; ++ptr)
- {
- // TODO: search every byte only when inside resource section,
- // otherwise stick to paragraph boundary.
- int i = 0;
- while (ptr[i] == s_xp3_header[i])
- {
- if (++i == s_xp3_header.Length)
- {
- // check whether index offset is non-zero
- if (0 == *(uint*)(ptr+i))
- break;
- return offset + (ptr - page_begin);
- }
- }
- }
- }
- finally {
- view.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
- offset += page_size - 0x10;
- }
+ var offset = exe.FindString (section, s_xp3_header, 0x10);
+ if (-1 == offset)
+ break;
+ if (0 != file.View.ReadUInt32 (offset+s_xp3_header.Length))
+ return offset;
+ section.Offset = offset + 0x10;
+ section.Size = (uint)(file.MaxOffset - section.Offset);
}
return 0;
}
diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs
index 474ce7d2..7feaab70 100644
--- a/ArcFormats/LiveMaker/ArcVF.cs
+++ b/ArcFormats/LiveMaker/ArcVF.cs
@@ -190,27 +190,8 @@ namespace GameRes.Formats.LiveMaker
uint SkipExeData (ArcView file)
{
- uint offset = 0;
- uint pe_offset = file.View.ReadUInt32 (0x3c);
- if (pe_offset < file.MaxOffset && 0x4550 == file.View.ReadUInt32 (pe_offset)) // 'PE'
- {
- int opt_header = file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
- offset = file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders
- long section_table = pe_offset+opt_header+0x18;
- int count = file.View.ReadUInt16 (pe_offset+6); // NumberOfSections
- if (section_table + 0x28*count < file.MaxOffset)
- {
- for (int i = 0; i < count; ++i)
- {
- uint size = file.View.ReadUInt32 (section_table+0x10);
- uint addr = file.View.ReadUInt32 (section_table+0x14);
- section_table += 0x28;
- if (0 != size)
- offset = Math.Max (addr + size, offset);
- }
- }
- }
- return offset;
+ var exe = new ExeFile (file);
+ return (uint)exe.Overlay.Offset;
}
}