From 40842076d91fd782962b19b8d46e099b95e5366f Mon Sep 17 00:00:00 2001 From: shinokaro Date: Sat, 10 Aug 2024 04:11:51 +0900 Subject: [PATCH] Migrate edicon to Ruby Implementation This pull request introduces a Ruby-based implementation of the previously C-based `edicon.exe`. Opting for a Ruby reimplement over fixing the existing C code will shorten the build times and make maintenance easier, leveraging widely accessible Microsoft documentation. Reference Information: The original `edicon.c` contained several issues that are being addressed by this reimplementation: - `BeginUpdateResource` was incorrectly compared to `INVALID_HANDLE_VALUE`. Proper error checking requires comparison to `NULL`. - The calculation of `GroupIconSize` was incorrect. It should have been calculated using the size of `IconDirResEntry` multiplied by the number of images, instead of using `IconDirectoryEntry`. This reimplement ensures that the functionality of `EdIcon` is preserved while simplifying future enhancements and maintenance. --- Rakefile | 4 +- lib/ocran/ed_icon.rb | 146 ++++++++++++++++++++++++++++++++++++++ lib/ocran/stub_builder.rb | 3 +- src/Makefile | 10 +-- src/edicon.c | 146 -------------------------------------- 5 files changed, 152 insertions(+), 157 deletions(-) create mode 100644 lib/ocran/ed_icon.rb delete mode 100644 src/edicon.c diff --git a/Rakefile b/Rakefile index f3e86dce..3fd3967e 100644 --- a/Rakefile +++ b/Rakefile @@ -16,18 +16,16 @@ task :build_stub do sh "ridk exec make -C src" cp "src/stub.exe", "share/ocran/stub.exe" cp "src/stubw.exe", "share/ocran/stubw.exe" - cp "src/edicon.exe", "share/ocran/edicon.exe" end file "share/ocran/stub.exe" => :build_stub file "share/ocran/stubw.exe" => :build_stub -file "share/ocran/edicon.exe" => :build_stub task :test => :build_stub task :clean do rm_f Dir["{bin,samples}/*.exe"] - rm_f Dir["share/ocran/{stub,stubw,edicon}.exe"] + rm_f Dir["share/ocran/{stub,stubw}.exe"] sh "ridk exec make -C src clean" end diff --git a/lib/ocran/ed_icon.rb b/lib/ocran/ed_icon.rb new file mode 100644 index 00000000..8ed98c01 --- /dev/null +++ b/lib/ocran/ed_icon.rb @@ -0,0 +1,146 @@ +# 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)) + 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 0f78bbb1..99009464 100644 --- a/lib/ocran/stub_builder.rb +++ b/lib/ocran/stub_builder.rb @@ -66,7 +66,8 @@ def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil, stub.close if icon_path - system(EDICON_PATH, stub.path, icon_path.to_s, exception: true) + require_relative "ed_icon" + EdIcon.update_icon(stub.path, icon_path.to_s) end File.open(stub, "ab") do |of| diff --git a/src/Makefile b/src/Makefile index 367de09b..4165363e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -10,7 +10,7 @@ STUB_CFLAGS = -D_CONSOLE $(CFLAGS) STUBW_CFLAGS = -mwindows $(CFLAGS) # -D_MBCS -all: stub.exe stubw.exe edicon.exe +all: stub.exe stubw.exe stubicon.o: stub.rc windres -i $< -o $@ @@ -21,9 +21,6 @@ stub.exe: $(OBJS) stub.o $(CONSOLE_OBJS) stubw.exe: $(OBJS) stubw.o $(WINDOW_OBJS) $(CC) $(STUBW_CFLAGS) $(OBJS) stubw.o $(WINDOW_OBJS) -o $@ -edicon.exe: edicon.o - $(CC) $(CFLAGS) edicon.o -o edicon - error_console.o: error.c error.h $(CC) $(STUB_CFLAGS) -o $@ -c $< @@ -61,10 +58,9 @@ script_info_window.o: script_info.c script_info.h $(CC) $(STUBW_CFLAGS) -o $@ -c $< clean: - rm -f $(OBJS) stub.exe stubw.exe edicon.exe edicon.o stubw.o stub.o \ + rm -f $(OBJS) stub.exe stubw.exe stubw.o stub.o \ $(CONSOLE_OBJS) $(WINDOW_OBJS) -install: stub.exe stubw.exe edicon.exe +install: stub.exe stubw.exe cp -f stub.exe $(BINDIR)/stub.exe cp -f stubw.exe $(BINDIR)/stubw.exe - cp -f edicon.exe $(BINDIR)/edicon.exe diff --git a/src/edicon.c b/src/edicon.c deleted file mode 100644 index 701d9829..00000000 --- a/src/edicon.c +++ /dev/null @@ -1,146 +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); - - /* Create the RT_ICON resources */ - int i; - for (i = 0; i < header->ImageCount; ++i) - { - 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; - } -}