Skip to content

Commit 7da70cf

Browse files
shinokaroLargo
authored andcommitted
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.
1 parent 7e9d67f commit 7da70cf

File tree

5 files changed

+162
-168
lines changed

5 files changed

+162
-168
lines changed

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ STUB_DIR = "share/ocran"
1616
BUILD_DIR = "src"
1717

1818
if WINDOWS
19-
STUB_NAMES = %w[stub stubw edicon]
19+
STUB_NAMES = %w[stub stubw]
2020
STUB_EXE_EXT = ".exe"
2121
else
2222
STUB_NAMES = %w[stub]

lib/ocran/ed_icon.rb

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# frozen_string_literal: true
2+
require "fiddle/import"
3+
require "fiddle/types"
4+
5+
module Ocran
6+
# Changes the Icon in a PE executable.
7+
module EdIcon
8+
extend Fiddle::Importer
9+
dlload "kernel32.dll"
10+
11+
include Fiddle::Win32Types
12+
typealias "LPVOID", "void*"
13+
typealias "LPCWSTR", "char*"
14+
15+
module Successive
16+
include Enumerable
17+
18+
def each
19+
return to_enum(__method__) unless block_given?
20+
21+
entry = self
22+
while true
23+
yield(entry)
24+
entry = self.class.new(tail)
25+
end
26+
end
27+
28+
def tail
29+
to_ptr + self.class.size
30+
end
31+
end
32+
33+
# Icon file header
34+
IconHeader = struct(
35+
[
36+
"WORD Reserved",
37+
"WORD ResourceType",
38+
"WORD ImageCount"
39+
]
40+
).include(Successive)
41+
42+
icon_info = [
43+
"BYTE Width",
44+
"BYTE Height",
45+
"BYTE Colors",
46+
"BYTE Reserved",
47+
"WORD Planes",
48+
"WORD BitsPerPixel",
49+
"DWORD ImageSize"
50+
]
51+
52+
# Icon File directory entry structure
53+
IconDirectoryEntry = struct(icon_info + ["DWORD ImageOffset"]).include(Successive)
54+
55+
# Group Icon Resource directory entry structure
56+
IconDirResEntry = struct(icon_info + ["WORD ResourceID"]).include(Successive)
57+
58+
class IconFile < IconHeader
59+
def initialize(icon_filename)
60+
@data = File.binread(icon_filename)
61+
super(Fiddle::Pointer.to_ptr(@data))
62+
63+
entries_end = IconHeader.size + self.ImageCount * IconDirectoryEntry.size
64+
if entries_end > @data.bytesize
65+
raise "Icon file too small for declared ImageCount"
66+
end
67+
68+
entries.each_with_index do |entry, i|
69+
if entry.ImageOffset + entry.ImageSize > @data.bytesize
70+
raise "Icon entry #{i} exceeds file bounds"
71+
end
72+
end
73+
end
74+
75+
def entries
76+
IconDirectoryEntry.new(self.tail).take(self.ImageCount)
77+
end
78+
end
79+
80+
class GroupIcon < IconHeader
81+
attr_reader :size
82+
83+
def initialize(image_count, resource_type)
84+
@size = IconHeader.size + image_count * IconDirResEntry.size
85+
super(Fiddle.malloc(@size), Fiddle::RUBY_FREE)
86+
self.Reserved = 0
87+
self.ResourceType = resource_type
88+
self.ImageCount = image_count
89+
end
90+
91+
def entries
92+
IconDirResEntry.new(self.tail).take(self.ImageCount)
93+
end
94+
end
95+
96+
MAKEINTRESOURCE = -> (i) { Fiddle::Pointer.new(i) }
97+
RT_ICON = MAKEINTRESOURCE.(3)
98+
RT_GROUP_ICON = MAKEINTRESOURCE.(RT_ICON.to_i + 11)
99+
100+
MAKELANGID = -> (p, s) { s << 10 | p }
101+
LANG_NEUTRAL = 0x00
102+
SUBLANG_DEFAULT = 0x01
103+
LANGID = MAKELANGID.(LANG_NEUTRAL, SUBLANG_DEFAULT)
104+
105+
extern "DWORD GetLastError()"
106+
extern "HANDLE BeginUpdateResourceW(LPCWSTR, BOOL)"
107+
extern "BOOL EndUpdateResourceW(HANDLE, BOOL)"
108+
extern "BOOL UpdateResourceW(HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD)"
109+
110+
class << self
111+
def update_icon(executable_filename, icon_filename)
112+
update_resource(executable_filename) do |handle|
113+
icon_file = IconFile.new(icon_filename)
114+
icon_entries = icon_file.entries
115+
116+
# Create the RT_ICON resources
117+
icon_entries.each_with_index do |entry, i|
118+
if UpdateResourceW(handle, RT_ICON, 101 + i, LANGID, icon_file.to_i + entry.ImageOffset, entry.ImageSize) == 0
119+
raise "failed to UpdateResource(#{GetLastError()})"
120+
end
121+
end
122+
123+
# Create the RT_GROUP_ICON structure
124+
group_icon = GroupIcon.new(icon_file.ImageCount, icon_file.ResourceType)
125+
group_icon.entries.zip(icon_entries).each_with_index do |(res, icon), i|
126+
res.Width = icon.Width
127+
res.Height = icon.Height
128+
res.Colors = icon.Colors
129+
res.Reserved = icon.Reserved
130+
res.Planes = icon.Planes
131+
res.BitsPerPixel = icon.BitsPerPixel
132+
res.ImageSize = icon.ImageSize
133+
res.ResourceID = 101 + i
134+
end
135+
136+
# Save the RT_GROUP_ICON resource
137+
if UpdateResourceW(handle, RT_GROUP_ICON, 100, LANGID, group_icon, group_icon.size) == 0
138+
raise "Failed to create group icon(#{GetLastError()})"
139+
end
140+
end
141+
end
142+
143+
def update_resource(executable_filename)
144+
handle = BeginUpdateResourceW(executable_filename.encode("UTF-16LE"), 0)
145+
if handle == Fiddle::NULL
146+
raise "Failed to BeginUpdateResourceW(#{GetLastError()})"
147+
end
148+
149+
yield(handle)
150+
151+
if EndUpdateResourceW(handle, 0) == 0
152+
raise "Failed to EndUpdateResourceW(#{GetLastError()})"
153+
end
154+
end
155+
end
156+
end
157+
end

lib/ocran/stub_builder.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class StubBuilder
2727
STUB_PATH = File.expand_path(WINDOWS ? "stub.exe" : "stub", base_dir)
2828
STUBW_PATH = WINDOWS ? File.expand_path("stubw.exe", base_dir) : nil
2929
LZMA_PATH = WINDOWS ? File.expand_path("lzma.exe", base_dir) : nil
30-
EDICON_PATH = WINDOWS ? File.expand_path("edicon.exe", base_dir) : nil
3130

3231
def self.find_posix_lzma_cmd
3332
if system("which lzma > /dev/null 2>&1")
@@ -122,7 +121,8 @@ def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
122121

123122
# Embed icon resource (Windows only)
124123
if icon_path && WINDOWS
125-
system(EDICON_PATH, stub, icon_path.to_s, exception: true)
124+
require_relative "ed_icon"
125+
EdIcon.update_icon(stub, icon_path.to_s)
126126
end
127127

128128
File.open(stub, "ab") do |of|

src/Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ else
2121
STUB_CFLAGS := $(CFLAGS) -D_CONSOLE
2222
STUBW_CFLAGS := $(CFLAGS)
2323
SYSTEM_UTILS_SRC := system_utils.c
24-
PROG_NAMES := stub stubw edicon
24+
PROG_NAMES := stub stubw
2525
RESOURCE_OBJ := stub.res
2626
endif
2727

@@ -62,13 +62,11 @@ ifeq ($(IS_POSIX),)
6262
stubw$(EXEEXT): $(COMMON_OBJS) $(WINDOW_OBJS)
6363
$(CC) $(LDFLAGS) $(GUI_LDFLAGS) $^ $(LDLIBS) -o $@
6464

65-
edicon$(EXEEXT): edicon.o
66-
$(CC) $(LDFLAGS) $^ -o $@
6765
endif
6866

6967
clean:
7068
rm -f $(BINARIES) $(COMMON_OBJS) $(CONSOLE_OBJS) $(WINDOW_OBJS) \
71-
edicon.o $(RESOURCE_OBJ)
69+
$(RESOURCE_OBJ)
7270

7371
install: $(BINARIES)
7472
mkdir -p $(BINDIR)

src/edicon.c

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)