ocran 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5b96c7de7250942c3f67b874546b10ff2ef0cb193e91f96c7c9736369937277
4
- data.tar.gz: ef03135551cce98a71c1eb546c0717ee82031e7a2b1eddf73bb63ba06017ed4e
3
+ metadata.gz: 23bee04c3290671dc55673fcb757a222e04af6977bd015a0791bab76ef492f1c
4
+ data.tar.gz: a4d437bda8e4bd00ea4a66c9c75c2aaa85b6904b8c7619e4f04a5e2b931688d5
5
5
  SHA512:
6
- metadata.gz: ba4c9a62f6a0c0cbcf88b6b7307df3777872e8cfb9a9b4e40352d0b8941dae0cf79d518c8ba4deea1eee2226137ecdcaf7cd2c575e0737f56db9f8e68907ab44
7
- data.tar.gz: a43d0bd6b7e17b2c8120182468ec9f594ec2707f2cef0a8e086c1c5e33646e3e76606d4b4d6a93725d0a0b1517f5a77c4b8f0391a6e2be6fc5bfb99507b20cec
6
+ metadata.gz: 0bd96938ab10206fe5009d93d9356034cbb9913159507b51f0df1fa4d46f428fbf7ae0c75ce1e8359079af70094ab8b68d64671064a59ec8bda313baf863dc79
7
+ data.tar.gz: 199615b364954980946418e4c850e9153cf2a2aabf4a5475143e08ff8bbeeda4dcb784a2fb578ef07b9559b8ae37b24103b36f99e4f86c96357674a115c371a6
data/CHANGELOG.txt CHANGED
@@ -1,3 +1,15 @@
1
+ === 1.4.3
2
+ - Migrate edicon from C to Ruby: replace edicon.exe with a pure-Ruby implementation (ed_icon.rb), fixing incorrect BeginUpdateResource error checking and wrong GroupIconSize calculation. Shortens build times and removes the C binary from the build.
3
+ - Include Gem.default_dir in GEM_PATH on all platforms: previously the exec_prefix gem directory was excluded on Windows, causing native-extension gems (e.g. fxruby/fox16) to fail at runtime with a LoadError even though they were bundled correctly.
4
+ - Add FXRuby (fox16) test: verify that native extension DLLs are bundled correctly so the packaged executable does not raise a LoadError at runtime.
5
+ - CI: start a virtual display (xpra + Xorg dummy driver with GLX) on Linux before running tests so FXRuby's X11 requirement is satisfied.
6
+ - CI: install XQuartz on macOS runners and add it to the portability test job so FXRuby can connect to a display on both test and portability jobs.
7
+
8
+ === 1.4.2
9
+ - Auto-bundle OpenSSL and all its transitive dependencies (openssl.rb, digest.so, etc.) when net/http is loaded but no HTTPS request was made during the dependency scan. OpenSSL is now required inside the OCRAN build process so every file it pulls in appears in the bundled executable.
10
+ - Add companion DLL scan on windows: when a native extension (.so) within the Ruby installation is loaded, all DLLs in the same directory are proactively included. This ensures libssl-3-x64.dll, libcrypto-3-x64.dll, libwinpthread-1.dll, and libyaml-0-2.dll are bundled even when no HTTPS connection is made during the build scan.
11
+ - Add `return if defined?(Ocran)` guards to test fixtures that have runtime-only side effects (file writes, Dir.chdir, file existence checks, network requests) to prevent them from running during the dependency scan, so we can check if the dependencies are included correctly.
12
+
1
13
  === 1.4.1
2
14
  - Fix kernel_require.rb packed at wrong path when rubygems-update is installed (e.g. via asdf on Linux/macOS). kernel_require.rb is now located relative to the actually-loaded rubygems.rb from $LOADED_FEATURES, falling back to rubylibdir for standard Ruby installations.
3
15
 
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # ocran
2
2
 
3
3
  home :: https://github.com/largo/ocran/
4
-
5
4
  issues :: http://github.com/largo/ocran/issues
6
5
 
7
6
  ## Description
@@ -23,18 +22,19 @@ Ruby 3.2+ compatibility.
23
22
 
24
23
  ## Recommended usage
25
24
 
26
- The most common use-case is shipping a program to Windows servers or users
27
- who do not have Ruby installed. By default, each time the `.exe` is opened it
25
+ The most common use-case is shipping a program to users running Windows / Linux / macOS
26
+ who do not have Ruby installed. By default, each time the `.exe` / executable is opened it
28
27
  extracts the Ruby interpreter and your code to a temporary directory and runs
29
28
  them from there.
30
29
 
31
- Because extraction takes time on each launch, consider using the Inno Setup
30
+ Because extraction takes time on each launch, use `--output-dir` or
31
+ `--output-zip` to produce a portable directory/archive that runs with the
32
+ bundled Ruby on Linux, macOS, or Windows.
33
+ If using Windows you can use the Inno Setup
32
34
  option (`--innosetup`) to produce a proper installer that extracts once to a
33
35
  permanent directory.
34
36
 
35
- For cross-platform packaging or CI artifacts, use `--output-dir` or
36
- `--output-zip` to produce a portable directory/archive that runs with the
37
- bundled Ruby on Linux, macOS, or Windows.
37
+ You can easily generate binaries for the supported Operating Systems with GitHub Actions.
38
38
 
39
39
  ## Features
40
40
 
@@ -74,12 +74,12 @@ https://github.com/largo/ocran/releases/.
74
74
 
75
75
  ## Synopsis
76
76
 
77
- ### Building a Windows executable:
77
+ ### Building an executable:
78
78
 
79
79
  ocran script.rb
80
80
 
81
81
  Packages `script.rb`, the Ruby interpreter, and all dependencies (gems and
82
- DLLs) into `script.exe`.
82
+ DLLs) into `script.exe` or `script` on Linux or macOS.
83
83
 
84
84
  ### Building a portable directory (Linux / macOS / Windows):
85
85
 
@@ -115,10 +115,11 @@ Same as `--output-dir`, but packages the result into a zip file. Requires
115
115
  ### Options:
116
116
 
117
117
  ocran --help
118
+ ocran -h
118
119
 
119
120
  #### General options:
120
121
 
121
- * `--help`: Display available command-line options.
122
+ * `--help`, `-h`: Display available command-line options.
122
123
  * `--quiet`: Suppress all output during the build process.
123
124
  * `--verbose`: Provide detailed output during the build process.
124
125
  * `--version`: Display the OCRAN version number and exit.
@@ -222,10 +223,28 @@ On Windows, run the batch file:
222
223
  * OCRAN-built executables correctly handle multibyte paths (e.g. Japanese, emoji) on Windows 10 1903+.
223
224
  * When using the executable from the console, run `chcp 65001` first to switch to UTF-8 on windows.
224
225
 
226
+ ## Limitations
227
+
228
+ ### No cross-platform building
229
+
230
+ OCRAN is not a cross-compiler. The executable or directory it produces bundles
231
+ the Ruby interpreter from the machine where OCRAN is run, so you must **run
232
+ OCRAN on the same platform (and architecture) as the intended target**:
233
+
234
+ * To produce a Windows `.exe`, run OCRAN on Windows.
235
+ * To produce a Linux binary, run OCRAN on Linux.
236
+ * To produce a macOS app bundle, run OCRAN on macOS.
237
+
238
+ There is no support for building a Windows `.exe` from a Linux or macOS host,
239
+ or vice versa. If you need builds for multiple platforms, run OCRAN in CI on
240
+ each target platform separately (e.g., a Windows runner for `.exe` builds and
241
+ a Linux runner for Linux builds).
242
+
225
243
  ## Requirements
226
244
 
227
245
  * Ruby 3.2+
228
- * For building Windows `.exe`: Windows with RubyInstaller DevKit (mingw-w64), or Wine on Linux/macOS
246
+ * For building Windows `.exe`: Windows with [RubyInstaller DevKit](https://rubyinstaller.org/downloads/) (mingw-w64), or Wine on Linux/macOS
247
+ * For building Linux and MacOS binaries: the respective build tools
229
248
  * For `--output-dir` / `--output-zip`: any platform with Ruby 3.2+
230
249
  * For `--output-zip` on Linux/macOS: the `zip` command must be available
231
250
  * For `--output-zip` on Windows: PowerShell (included in Windows 8+)
@@ -508,6 +527,11 @@ file:
508
527
  end
509
528
  end
510
529
 
530
+ ## See elsewhere
531
+
532
+ - [State of Ruby Packagers](https://gist.github.com/YOU54F/3775e66e6090e0371c11601e6b75c305)
533
+ - [Traveling Ruby](https://github.com/trubygems/traveling-ruby)
534
+
511
535
  ## Credits
512
536
 
513
537
  Lars Christensen and contributors for the OCRA project which this is forked from.
@@ -144,6 +144,24 @@ module Ocran
144
144
  # Store the currently loaded files
145
145
  features = normalized_features
146
146
 
147
+ # If net/http was loaded but openssl wasn't (it is only required lazily
148
+ # at the point of an actual HTTPS connection), require it now inside the
149
+ # OCRAN build process so that every transitive dependency — openssl.rb,
150
+ # digest.so, and any other files pulled in by the extension — appears in
151
+ # $LOADED_FEATURES and gets bundled alongside the application.
152
+ openssl_so = Pathname(RbConfig::CONFIG["archdir"]) / "openssl.so"
153
+ if openssl_so.exist? &&
154
+ features.any? { |f| f.to_posix.end_with?("/net/http.rb") } &&
155
+ features.none? { |f| f == openssl_so }
156
+ say "Auto-loading openssl (net/http loaded but openssl not yet required)"
157
+ before = $LOADED_FEATURES.dup
158
+ require "openssl"
159
+ ($LOADED_FEATURES - before).each do |f|
160
+ path = Pathname(f).cleanpath
161
+ features << path if path.absolute?
162
+ end
163
+ end
164
+
147
165
  say "Building #{@option.output_executable}"
148
166
  require_relative "build_helper"
149
167
  builder.extend(BuildHelper)
@@ -185,6 +203,22 @@ module Ocran
185
203
  builder.copy_to_bin(dll, dll.basename)
186
204
  end
187
205
  end
206
+
207
+ # Proactively include companion DLLs for loaded native extensions.
208
+ # Native extensions (.so) may depend on DLLs in the same archdir
209
+ # directory (e.g., libssl-3-x64.dll alongside openssl.so) that are
210
+ # loaded lazily on first use. Scanning .so directories ensures those
211
+ # DLLs are bundled even when the extension is required but not
212
+ # exercised during the OCRAN dependency scan.
213
+ features.select { |f| f.extname?(".so") && f.subpath?(exec_prefix) }
214
+ .map(&:dirname).uniq
215
+ .each do |dir|
216
+ dir.each_child do |path|
217
+ next unless path.file? && path.extname?(".dll")
218
+ say "Adding companion DLL #{path}"
219
+ builder.duplicate_to_exec_prefix(path)
220
+ end
221
+ end
188
222
  end
189
223
 
190
224
  # Windows-only: Add external manifest and builtin DLLs
@@ -480,14 +514,15 @@ module Ocran
480
514
  builder.set_env_path("GEM_HOME", GEMDIR)
481
515
 
482
516
  gem_paths = [GEMDIR]
483
- # On POSIX, default gems (e.g. error_highlight) are stored under the Ruby
484
- # installation's gem dir (Gem.default_dir), not in GEMDIR. Include it in
485
- # GEM_PATH so RubyGems can find and activate them in the extracted tree.
486
- unless Gem.win_platform?
487
- default_gem_dir = Pathname(Gem.default_dir)
488
- if default_gem_dir.subpath?(exec_prefix)
489
- gem_paths << default_gem_dir.relative_path_from(exec_prefix)
490
- end
517
+ # Gems installed under the Ruby prefix (exec_prefix) have their specs and
518
+ # extension dirs placed there via duplicate_to_exec_prefix. Include
519
+ # Gem.default_dir (relative to exec_prefix) in GEM_PATH so RubyGems can
520
+ # find and activate them at runtime. This is required on both Windows
521
+ # (e.g. fxruby/fox16 whose fox16_c.so lives in extension_dir under the
522
+ # Ruby prefix) and POSIX (e.g. error_highlight default gems).
523
+ default_gem_dir = Pathname(Gem.default_dir)
524
+ if default_gem_dir.subpath?(exec_prefix)
525
+ gem_paths << default_gem_dir.relative_path_from(exec_prefix)
491
526
  end
492
527
  builder.set_env_path("GEM_PATH", *gem_paths)
493
528
 
@@ -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
data/lib/ocran/option.rb CHANGED
@@ -46,7 +46,7 @@ ocran [options] script.rb
46
46
 
47
47
  Ocran options:
48
48
 
49
- --help Display this information.
49
+ --help, -h Display this information.
50
50
  --quiet Suppress output while building executable.
51
51
  --verbose Show extra output while building executable.
52
52
  --version Display version number and exit.
@@ -179,7 +179,7 @@ EOF
179
179
  when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
180
180
  negate, group, list = $1, $2, $3
181
181
  @options[:gem_options] << [negate, group.to_sym, list&.split(",")] if group
182
- when "--help", /\A--./
182
+ when "--help", "-h", /\A--./
183
183
  puts usage
184
184
  raise SystemExit
185
185
  else
@@ -27,7 +27,6 @@ module Ocran
27
27
  STUB_PATH = File.expand_path(WINDOWS ? "stub.exe" : "stub", base_dir)
28
28
  STUBW_PATH = WINDOWS ? File.expand_path("stubw.exe", base_dir) : nil
29
29
  LZMA_PATH = WINDOWS ? File.expand_path("lzma.exe", base_dir) : nil
30
- EDICON_PATH = WINDOWS ? File.expand_path("edicon.exe", base_dir) : nil
31
30
 
32
31
  def self.find_posix_lzma_cmd
33
32
  if system("which lzma > /dev/null 2>&1")
@@ -122,7 +121,8 @@ module Ocran
122
121
 
123
122
  # Embed icon resource (Windows only)
124
123
  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)
126
126
  end
127
127
 
128
128
  File.open(stub, "ab") do |of|
data/lib/ocran/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ocran
4
- VERSION = "1.4.1"
4
+ VERSION = "1.4.3"
5
5
  end
data/src/Makefile CHANGED
@@ -21,7 +21,7 @@ else
21
21
  STUB_CFLAGS := $(CFLAGS) -D_CONSOLE
22
22
  STUBW_CFLAGS := $(CFLAGS)
23
23
  SYSTEM_UTILS_SRC := system_utils.c
24
- PROG_NAMES := stub stubw edicon
24
+ PROG_NAMES := stub stubw
25
25
  RESOURCE_OBJ := stub.res
26
26
  endif
27
27
 
@@ -62,13 +62,11 @@ ifeq ($(IS_POSIX),)
62
62
  stubw$(EXEEXT): $(COMMON_OBJS) $(WINDOW_OBJS)
63
63
  $(CC) $(LDFLAGS) $(GUI_LDFLAGS) $^ $(LDLIBS) -o $@
64
64
 
65
- edicon$(EXEEXT): edicon.o
66
- $(CC) $(LDFLAGS) $^ -o $@
67
65
  endif
68
66
 
69
67
  clean:
70
68
  rm -f $(BINARIES) $(COMMON_OBJS) $(CONSOLE_OBJS) $(WINDOW_OBJS) \
71
- edicon.o $(RESOURCE_OBJ)
69
+ $(RESOURCE_OBJ)
72
70
 
73
71
  install: $(BINARIES)
74
72
  mkdir -p $(BINDIR)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ocran
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andi Idogawa
@@ -66,6 +66,7 @@ files:
66
66
  - lib/ocran/command_output.rb
67
67
  - lib/ocran/dir_builder.rb
68
68
  - lib/ocran/direction.rb
69
+ - lib/ocran/ed_icon.rb
69
70
  - lib/ocran/empty_source
70
71
  - lib/ocran/file_path_set.rb
71
72
  - lib/ocran/gem_spec_queryable.rb
@@ -83,7 +84,6 @@ files:
83
84
  - lib/ocran/windows_command_escaping.rb
84
85
  - share/ocran/lzma.exe
85
86
  - src/Makefile
86
- - src/edicon.c
87
87
  - src/error.c
88
88
  - src/error.h
89
89
  - src/inst_dir.c
@@ -125,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
127
  requirements: []
128
- rubygems_version: 4.0.3
128
+ rubygems_version: 4.0.6
129
129
  specification_version: 4
130
130
  summary: OCRAN (One-Click Ruby Application Next) packages Ruby applications for distribution
131
131
  on Windows, Linux, and macOS.
data/src/edicon.c DELETED
@@ -1,161 +0,0 @@
1
- /**
2
- Changes the Icon in a PE executable.
3
- */
4
-
5
- #include <windows.h>
6
- #include <stdio.h>
7
-
8
- #pragma pack(push, 2)
9
-
10
- /* Icon file header */
11
- typedef struct
12
- {
13
- WORD Reserved;
14
- WORD ResourceType;
15
- WORD ImageCount;
16
- } IconFileHeader;
17
-
18
- /* Icon File directory entry structure */
19
- typedef struct
20
- {
21
- BYTE Width;
22
- BYTE Height;
23
- BYTE Colors;
24
- BYTE Reserved;
25
- WORD Planes;
26
- WORD BitsPerPixel;
27
- DWORD ImageSize;
28
- DWORD ImageOffset;
29
- } IconDirectoryEntry;
30
-
31
- /* Group Icon Resource directory entry structure */
32
- typedef struct
33
- {
34
- BYTE Width;
35
- BYTE Height;
36
- BYTE Colors;
37
- BYTE Reserved;
38
- WORD Planes;
39
- WORD BitsPerPixel;
40
- DWORD ImageSize;
41
- WORD ResourceID;
42
- } IconDirResEntry, *PIconDirResEntry;
43
-
44
- /* Group Icon Structore (RT_GROUP_ICON) */
45
- typedef struct
46
- {
47
- WORD Reserved;
48
- WORD ResourceType;
49
- WORD ImageCount;
50
- IconDirResEntry Enries[0]; /* Number of these is in ImageCount */
51
- } GroupIcon;
52
-
53
- #pragma pack(pop)
54
-
55
- BOOL UpdateIcon(LPTSTR ExecutableFileName, LPTSTR IconFileName)
56
- {
57
- HANDLE h = BeginUpdateResource(ExecutableFileName, FALSE);
58
- if (h == INVALID_HANDLE_VALUE)
59
- {
60
- printf("Failed to BeginUpdateResource\n");
61
- return FALSE;
62
- }
63
-
64
- /* Read the Icon file */
65
- HANDLE hIconFile = CreateFile(IconFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
66
- if (hIconFile == INVALID_HANDLE_VALUE)
67
- {
68
- fprintf(stderr, "Failed to open icon file.\n");
69
- return FALSE;
70
- }
71
- DWORD Size = GetFileSize(hIconFile, NULL);
72
- BYTE* Data = LocalAlloc(LMEM_FIXED, Size);
73
- DWORD BytesRead;
74
- if (!ReadFile(hIconFile, Data, Size, &BytesRead, NULL))
75
- {
76
- fprintf(stderr, "Failed to read icon file.\n");
77
- return FALSE;
78
- }
79
- CloseHandle(hIconFile);
80
-
81
- IconFileHeader* header = (IconFileHeader*)Data;
82
- IconDirectoryEntry* entries = (IconDirectoryEntry*)(header + 1);
83
-
84
- /* Validate that all directory entries fit within the file */
85
- DWORD entriesEnd = sizeof(IconFileHeader) + header->ImageCount * sizeof(IconDirectoryEntry);
86
- if (entriesEnd > Size)
87
- {
88
- fprintf(stderr, "Icon file too small for declared ImageCount.\n");
89
- LocalFree(Data);
90
- return FALSE;
91
- }
92
-
93
- /* Create the RT_ICON resources */
94
- int i;
95
- for (i = 0; i < header->ImageCount; ++i)
96
- {
97
- if (entries[i].ImageOffset + entries[i].ImageSize > Size)
98
- {
99
- fprintf(stderr, "Icon entry %d exceeds file bounds.\n", i);
100
- LocalFree(Data);
101
- return FALSE;
102
- }
103
- BOOL b = UpdateResource(h, MAKEINTRESOURCE(RT_ICON), MAKEINTRESOURCE(101 + i), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), Data + entries[i].ImageOffset, entries[i].ImageSize);
104
- if (!b)
105
- {
106
- fprintf(stderr, "failed to UpdateResource %lu\n", GetLastError());
107
- return FALSE;
108
- }
109
- }
110
-
111
- /* Create the RT_GROUP_ICON structure */
112
- DWORD GroupIconSize = sizeof(GroupIcon) + header->ImageCount * sizeof(IconDirectoryEntry);
113
- GroupIcon* gi = (GroupIcon*)LocalAlloc(LMEM_FIXED, GroupIconSize);
114
- gi->Reserved = 0;
115
- gi->ResourceType = header->ResourceType;
116
- gi->ImageCount = header->ImageCount;
117
- for (i = 0; i < header->ImageCount; ++i)
118
- {
119
- IconDirResEntry* e = &gi->Enries[i];
120
- e->Width = entries[i].Width;
121
- e->Height = entries[i].Height;
122
- e->Colors = entries[i].Colors;
123
- e->Reserved = entries[i].Reserved;
124
- e->Planes = entries[i].Planes;
125
- e->BitsPerPixel = entries[i].BitsPerPixel;
126
- e->ImageSize = entries[i].ImageSize;
127
- e->ResourceID = 101 + i;
128
- }
129
-
130
- /* Save the RT_GROUP_ICON resource */
131
- BOOL b = UpdateResource(h, MAKEINTRESOURCE(RT_GROUP_ICON), MAKEINTRESOURCE(100), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), gi, GroupIconSize);
132
- if (!b)
133
- {
134
- fprintf(stderr, "Failed to create group icon.\n");
135
- return FALSE;
136
- }
137
-
138
- if (!EndUpdateResource(h, FALSE))
139
- {
140
- fprintf(stderr, "Failed to EndUpdateResource.\n");
141
- return FALSE;
142
- }
143
-
144
- return TRUE;
145
- }
146
-
147
- int main(int argc, char* argv[])
148
- {
149
- if (argc == 3)
150
- {
151
- if (UpdateIcon(argv[1], argv[2]))
152
- return 0;
153
- else
154
- return -1;
155
- }
156
- else
157
- {
158
- fprintf(stderr, "Usage: edicon.exe <exefile> <icofile>\n");
159
- return -1;
160
- }
161
- }