From 42dce0e586404e57dd44b07f3627165aaec3c401 Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 23 Jan 2017 18:35:16 +0400 Subject: [PATCH] (ExeFile): new class to seek for resources within EXE files. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/CatSystem/ArcINT.cs | 53 +------ ArcFormats/ExeFile.cs | 270 +++++++++++++++++++++++++++++++++ ArcFormats/KiriKiri/ArcXP3.cs | 72 ++------- ArcFormats/LiveMaker/ArcVF.cs | 23 +-- 5 files changed, 290 insertions(+), 129 deletions(-) create mode 100644 ArcFormats/ExeFile.cs 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; } }