diff --git a/Rakefile b/Rakefile index 3cf51d69..f8410fc3 100644 --- a/Rakefile +++ b/Rakefile @@ -16,7 +16,7 @@ STUB_DIR = "share/ocran" BUILD_DIR = "src" if WINDOWS - STUB_NAMES = %w[stub stubw edicon] + STUB_NAMES = %w[stub stubw] STUB_EXE_EXT = ".exe" else STUB_NAMES = %w[stub] diff --git a/lib/ocran/ed_icon.rb b/lib/ocran/ed_icon.rb new file mode 100644 index 00000000..365d30cd --- /dev/null +++ b/lib/ocran/ed_icon.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true +require "fiddle/import" +require "fiddle/types" + +module Ocran + # Changes the Icon in a PE executable. + module EdIcon + extend Fiddle::Importer + dlload "kernel32.dll" + + include Fiddle::Win32Types + typealias "LPVOID", "void*" + typealias "LPCWSTR", "char*" + + module Successive + include Enumerable + + def each + return to_enum(__method__) unless block_given? + + entry = self + while true + yield(entry) + entry = self.class.new(tail) + end + end + + def tail + to_ptr + self.class.size + end + end + + # Icon file header + IconHeader = struct( + [ + "WORD Reserved", + "WORD ResourceType", + "WORD ImageCount" + ] + ).include(Successive) + + icon_info = [ + "BYTE Width", + "BYTE Height", + "BYTE Colors", + "BYTE Reserved", + "WORD Planes", + "WORD BitsPerPixel", + "DWORD ImageSize" + ] + + # Icon File directory entry structure + IconDirectoryEntry = struct(icon_info + ["DWORD ImageOffset"]).include(Successive) + + # Group Icon Resource directory entry structure + IconDirResEntry = struct(icon_info + ["WORD ResourceID"]).include(Successive) + + class IconFile < IconHeader + def initialize(icon_filename) + @data = File.binread(icon_filename) + super(Fiddle::Pointer.to_ptr(@data)) + + entries_end = IconHeader.size + self.ImageCount * IconDirectoryEntry.size + if entries_end > @data.bytesize + raise "Icon file too small for declared ImageCount" + end + + entries.each_with_index do |entry, i| + if entry.ImageOffset + entry.ImageSize > @data.bytesize + raise "Icon entry #{i} exceeds file bounds" + end + end + end + + def entries + IconDirectoryEntry.new(self.tail).take(self.ImageCount) + end + end + + class GroupIcon < IconHeader + attr_reader :size + + def initialize(image_count, resource_type) + @size = IconHeader.size + image_count * IconDirResEntry.size + super(Fiddle.malloc(@size), Fiddle::RUBY_FREE) + self.Reserved = 0 + self.ResourceType = resource_type + self.ImageCount = image_count + end + + def entries + IconDirResEntry.new(self.tail).take(self.ImageCount) + end + end + + MAKEINTRESOURCE = -> (i) { Fiddle::Pointer.new(i) } + RT_ICON = MAKEINTRESOURCE.(3) + RT_GROUP_ICON = MAKEINTRESOURCE.(RT_ICON.to_i + 11) + + MAKELANGID = -> (p, s) { s << 10 | p } + LANG_NEUTRAL = 0x00 + SUBLANG_DEFAULT = 0x01 + LANGID = MAKELANGID.(LANG_NEUTRAL, SUBLANG_DEFAULT) + + extern "DWORD GetLastError()" + extern "HANDLE BeginUpdateResourceW(LPCWSTR, BOOL)" + extern "BOOL EndUpdateResourceW(HANDLE, BOOL)" + extern "BOOL UpdateResourceW(HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD)" + + class << self + def update_icon(executable_filename, icon_filename) + update_resource(executable_filename) do |handle| + icon_file = IconFile.new(icon_filename) + icon_entries = icon_file.entries + + # Create the RT_ICON resources + icon_entries.each_with_index do |entry, i| + if UpdateResourceW(handle, RT_ICON, 101 + i, LANGID, icon_file.to_i + entry.ImageOffset, entry.ImageSize) == 0 + raise "failed to UpdateResource(#{GetLastError()})" + end + end + + # Create the RT_GROUP_ICON structure + group_icon = GroupIcon.new(icon_file.ImageCount, icon_file.ResourceType) + group_icon.entries.zip(icon_entries).each_with_index do |(res, icon), i| + res.Width = icon.Width + res.Height = icon.Height + res.Colors = icon.Colors + res.Reserved = icon.Reserved + res.Planes = icon.Planes + res.BitsPerPixel = icon.BitsPerPixel + res.ImageSize = icon.ImageSize + res.ResourceID = 101 + i + end + + # Save the RT_GROUP_ICON resource + if UpdateResourceW(handle, RT_GROUP_ICON, 100, LANGID, group_icon, group_icon.size) == 0 + raise "Failed to create group icon(#{GetLastError()})" + end + end + end + + def update_resource(executable_filename) + handle = BeginUpdateResourceW(executable_filename.encode("UTF-16LE"), 0) + if handle == Fiddle::NULL + raise "Failed to BeginUpdateResourceW(#{GetLastError()})" + end + + yield(handle) + + if EndUpdateResourceW(handle, 0) == 0 + raise "Failed to EndUpdateResourceW(#{GetLastError()})" + end + end + end + end +end diff --git a/lib/ocran/stub_builder.rb b/lib/ocran/stub_builder.rb index dffc5a23..d6af333b 100644 --- a/lib/ocran/stub_builder.rb +++ b/lib/ocran/stub_builder.rb @@ -27,7 +27,6 @@ class StubBuilder STUB_PATH = File.expand_path(WINDOWS ? "stub.exe" : "stub", base_dir) STUBW_PATH = WINDOWS ? File.expand_path("stubw.exe", base_dir) : nil LZMA_PATH = WINDOWS ? File.expand_path("lzma.exe", base_dir) : nil - EDICON_PATH = WINDOWS ? File.expand_path("edicon.exe", base_dir) : nil def self.find_posix_lzma_cmd if system("which lzma > /dev/null 2>&1") @@ -122,7 +121,8 @@ def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil, # Embed icon resource (Windows only) if icon_path && WINDOWS - system(EDICON_PATH, stub, icon_path.to_s, exception: true) + require_relative "ed_icon" + EdIcon.update_icon(stub, icon_path.to_s) end File.open(stub, "ab") do |of| diff --git a/src/Makefile b/src/Makefile index 51b16460..75a6937f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -21,7 +21,7 @@ else STUB_CFLAGS := $(CFLAGS) -D_CONSOLE STUBW_CFLAGS := $(CFLAGS) SYSTEM_UTILS_SRC := system_utils.c - PROG_NAMES := stub stubw edicon + PROG_NAMES := stub stubw RESOURCE_OBJ := stub.res endif @@ -62,13 +62,11 @@ ifeq ($(IS_POSIX),) stubw$(EXEEXT): $(COMMON_OBJS) $(WINDOW_OBJS) $(CC) $(LDFLAGS) $(GUI_LDFLAGS) $^ $(LDLIBS) -o $@ -edicon$(EXEEXT): edicon.o - $(CC) $(LDFLAGS) $^ -o $@ endif clean: rm -f $(BINARIES) $(COMMON_OBJS) $(CONSOLE_OBJS) $(WINDOW_OBJS) \ - edicon.o $(RESOURCE_OBJ) + $(RESOURCE_OBJ) install: $(BINARIES) mkdir -p $(BINDIR) diff --git a/src/edicon.c b/src/edicon.c deleted file mode 100644 index aee18d1e..00000000 --- a/src/edicon.c +++ /dev/null @@ -1,161 +0,0 @@ -/** - Changes the Icon in a PE executable. -*/ - -#include -#include - -#pragma pack(push, 2) - -/* Icon file header */ -typedef struct -{ - WORD Reserved; - WORD ResourceType; - WORD ImageCount; -} IconFileHeader; - -/* Icon File directory entry structure */ -typedef struct -{ - BYTE Width; - BYTE Height; - BYTE Colors; - BYTE Reserved; - WORD Planes; - WORD BitsPerPixel; - DWORD ImageSize; - DWORD ImageOffset; -} IconDirectoryEntry; - -/* Group Icon Resource directory entry structure */ -typedef struct -{ - BYTE Width; - BYTE Height; - BYTE Colors; - BYTE Reserved; - WORD Planes; - WORD BitsPerPixel; - DWORD ImageSize; - WORD ResourceID; -} IconDirResEntry, *PIconDirResEntry; - -/* Group Icon Structore (RT_GROUP_ICON) */ -typedef struct -{ - WORD Reserved; - WORD ResourceType; - WORD ImageCount; - IconDirResEntry Enries[0]; /* Number of these is in ImageCount */ -} GroupIcon; - -#pragma pack(pop) - -BOOL UpdateIcon(LPTSTR ExecutableFileName, LPTSTR IconFileName) -{ - HANDLE h = BeginUpdateResource(ExecutableFileName, FALSE); - if (h == INVALID_HANDLE_VALUE) - { - printf("Failed to BeginUpdateResource\n"); - return FALSE; - } - - /* Read the Icon file */ - HANDLE hIconFile = CreateFile(IconFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); - if (hIconFile == INVALID_HANDLE_VALUE) - { - fprintf(stderr, "Failed to open icon file.\n"); - return FALSE; - } - DWORD Size = GetFileSize(hIconFile, NULL); - BYTE* Data = LocalAlloc(LMEM_FIXED, Size); - DWORD BytesRead; - if (!ReadFile(hIconFile, Data, Size, &BytesRead, NULL)) - { - fprintf(stderr, "Failed to read icon file.\n"); - return FALSE; - } - CloseHandle(hIconFile); - - IconFileHeader* header = (IconFileHeader*)Data; - IconDirectoryEntry* entries = (IconDirectoryEntry*)(header + 1); - - /* Validate that all directory entries fit within the file */ - DWORD entriesEnd = sizeof(IconFileHeader) + header->ImageCount * sizeof(IconDirectoryEntry); - if (entriesEnd > Size) - { - fprintf(stderr, "Icon file too small for declared ImageCount.\n"); - LocalFree(Data); - return FALSE; - } - - /* Create the RT_ICON resources */ - int i; - for (i = 0; i < header->ImageCount; ++i) - { - if (entries[i].ImageOffset + entries[i].ImageSize > Size) - { - fprintf(stderr, "Icon entry %d exceeds file bounds.\n", i); - LocalFree(Data); - return FALSE; - } - BOOL b = UpdateResource(h, MAKEINTRESOURCE(RT_ICON), MAKEINTRESOURCE(101 + i), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), Data + entries[i].ImageOffset, entries[i].ImageSize); - if (!b) - { - fprintf(stderr, "failed to UpdateResource %lu\n", GetLastError()); - return FALSE; - } - } - - /* Create the RT_GROUP_ICON structure */ - DWORD GroupIconSize = sizeof(GroupIcon) + header->ImageCount * sizeof(IconDirectoryEntry); - GroupIcon* gi = (GroupIcon*)LocalAlloc(LMEM_FIXED, GroupIconSize); - gi->Reserved = 0; - gi->ResourceType = header->ResourceType; - gi->ImageCount = header->ImageCount; - for (i = 0; i < header->ImageCount; ++i) - { - IconDirResEntry* e = &gi->Enries[i]; - e->Width = entries[i].Width; - e->Height = entries[i].Height; - e->Colors = entries[i].Colors; - e->Reserved = entries[i].Reserved; - e->Planes = entries[i].Planes; - e->BitsPerPixel = entries[i].BitsPerPixel; - e->ImageSize = entries[i].ImageSize; - e->ResourceID = 101 + i; - } - - /* Save the RT_GROUP_ICON resource */ - BOOL b = UpdateResource(h, MAKEINTRESOURCE(RT_GROUP_ICON), MAKEINTRESOURCE(100), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), gi, GroupIconSize); - if (!b) - { - fprintf(stderr, "Failed to create group icon.\n"); - return FALSE; - } - - if (!EndUpdateResource(h, FALSE)) - { - fprintf(stderr, "Failed to EndUpdateResource.\n"); - return FALSE; - } - - return TRUE; -} - -int main(int argc, char* argv[]) -{ - if (argc == 3) - { - if (UpdateIcon(argv[1], argv[2])) - return 0; - else - return -1; - } - else - { - fprintf(stderr, "Usage: edicon.exe \n"); - return -1; - } -}