Skip to content

Commit 23b6597

Browse files
committed
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 201f51a commit 23b6597

File tree

5 files changed

+154
-157
lines changed

5 files changed

+154
-157
lines changed

Rakefile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,16 @@ task :build_stub do
1616
sh "ridk exec make -C src"
1717
cp "src/stub.exe", "share/ocran/stub.exe"
1818
cp "src/stubw.exe", "share/ocran/stubw.exe"
19-
cp "src/edicon.exe", "share/ocran/edicon.exe"
2019
end
2120

2221
file "share/ocran/stub.exe" => :build_stub
2322
file "share/ocran/stubw.exe" => :build_stub
24-
file "share/ocran/edicon.exe" => :build_stub
2523

2624
task :test => :build_stub
2725

2826
task :clean do
2927
rm_f Dir["{bin,samples}/*.exe"]
30-
rm_f Dir["share/ocran/{stub,stubw,edicon}.exe"]
28+
rm_f Dir["share/ocran/{stub,stubw}.exe"]
3129
sh "ridk exec make -C src clean"
3230
end
3331

lib/ocran/ed_icon.rb

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
end
63+
64+
def entries
65+
IconDirectoryEntry.new(self.tail).take(self.ImageCount)
66+
end
67+
end
68+
69+
class GroupIcon < IconHeader
70+
attr_reader :size
71+
72+
def initialize(image_count, resource_type)
73+
@size = IconHeader.size + image_count * IconDirResEntry.size
74+
super(Fiddle.malloc(@size), Fiddle::RUBY_FREE)
75+
self.Reserved = 0
76+
self.ResourceType = resource_type
77+
self.ImageCount = image_count
78+
end
79+
80+
def entries
81+
IconDirResEntry.new(self.tail).take(self.ImageCount)
82+
end
83+
end
84+
85+
INVALID_HANDLE_VALUE = Fiddle::Pointer.new(-1)
86+
87+
MAKEINTRESOURCE = -> (i) { Fiddle::Pointer.new(i) }
88+
RT_ICON = MAKEINTRESOURCE.(3)
89+
RT_GROUP_ICON = MAKEINTRESOURCE.(RT_ICON.to_i + 11)
90+
91+
MAKELANGID = -> (p, s) { s << 10 | p }
92+
LANG_NEUTRAL = 0x00
93+
SUBLANG_DEFAULT = 0x01
94+
LANGID = MAKELANGID.(LANG_NEUTRAL, SUBLANG_DEFAULT)
95+
96+
extern "DWORD GetLastError()"
97+
extern "HANDLE BeginUpdateResourceW(LPCWSTR, BOOL)"
98+
extern "BOOL EndUpdateResourceW(HANDLE, BOOL)"
99+
extern "BOOL UpdateResourceW(HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD)"
100+
101+
class << self
102+
def update_icon(executable_filename, icon_filename)
103+
update_resource(executable_filename) do |handle|
104+
icon_file = IconFile.new(icon_filename)
105+
icon_entries = icon_file.entries
106+
107+
# Create the RT_ICON resources
108+
icon_entries.each_with_index do |entry, i|
109+
if UpdateResourceW(handle, RT_ICON, 101 + i, LANGID, icon_file.to_i + entry.ImageOffset, entry.ImageSize) == 0
110+
raise "failed to UpdateResource(#{GetLastError()})"
111+
end
112+
end
113+
114+
# Create the RT_GROUP_ICON structure
115+
group_icon = GroupIcon.new(icon_file.ImageCount, icon_file.ResourceType)
116+
group_icon.entries.zip(icon_entries).each_with_index do |(res, icon), i|
117+
res.Width = icon.Width
118+
res.Height = icon.Height
119+
res.Colors = icon.Colors
120+
res.Reserved = icon.Reserved
121+
res.Planes = icon.Planes
122+
res.BitsPerPixel = icon.BitsPerPixel
123+
res.ImageSize = icon.ImageSize
124+
res.ResourceID = 101 + i
125+
end
126+
127+
# Save the RT_GROUP_ICON resource
128+
if UpdateResourceW(handle, RT_GROUP_ICON, 100, LANGID, group_icon, group_icon.size) == 0
129+
raise "Failed to create group icon(#{GetLastError()})"
130+
end
131+
end
132+
end
133+
134+
def update_resource(executable_filename)
135+
handle = BeginUpdateResourceW(executable_filename.encode("UTF-16LE"), 0)
136+
if handle == Fiddle::NULL
137+
raise "Failed to BeginUpdateResourceW(#{GetLastError()})"
138+
end
139+
140+
yield(handle)
141+
142+
if EndUpdateResourceW(handle, 0) == 0
143+
raise "Failed to EndUpdateResourceW(#{GetLastError()})"
144+
end
145+
end
146+
end
147+
end
148+
end

lib/ocran/stub_builder.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
6666
stub.close
6767

6868
if icon_path
69-
system(EDICON_PATH, stub.path, icon_path.to_s, exception: true)
69+
require_relative "ed_icon"
70+
EdIcon.update_icon(stub.path, icon_path.to_s)
7071
end
7172

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

src/Makefile

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ STUB_CFLAGS = -D_CONSOLE $(CFLAGS)
1010
STUBW_CFLAGS = -mwindows $(CFLAGS)
1111
# -D_MBCS
1212

13-
all: stub.exe stubw.exe edicon.exe
13+
all: stub.exe stubw.exe
1414

1515
stubicon.o: stub.rc
1616
windres -i $< -o $@
@@ -21,9 +21,6 @@ stub.exe: $(OBJS) stub.o $(CONSOLE_OBJS)
2121
stubw.exe: $(OBJS) stubw.o $(WINDOW_OBJS)
2222
$(CC) $(STUBW_CFLAGS) $(OBJS) stubw.o $(WINDOW_OBJS) -o $@
2323

24-
edicon.exe: edicon.o
25-
$(CC) $(CFLAGS) edicon.o -o edicon
26-
2724
error_console.o: error.c error.h
2825
$(CC) $(STUB_CFLAGS) -o $@ -c $<
2926

@@ -61,10 +58,9 @@ script_info_window.o: script_info.c script_info.h
6158
$(CC) $(STUBW_CFLAGS) -o $@ -c $<
6259

6360
clean:
64-
rm -f $(OBJS) stub.exe stubw.exe edicon.exe edicon.o stubw.o stub.o \
61+
rm -f $(OBJS) stub.exe stubw.exe stubw.o stub.o \
6562
$(CONSOLE_OBJS) $(WINDOW_OBJS)
6663

67-
install: stub.exe stubw.exe edicon.exe
64+
install: stub.exe stubw.exe
6865
cp -f stub.exe $(BINDIR)/stub.exe
6966
cp -f stubw.exe $(BINDIR)/stubw.exe
70-
cp -f edicon.exe $(BINDIR)/edicon.exe

src/edicon.c

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

0 commit comments

Comments
 (0)