ocran 1.4.0-x86_64-linux
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 +7 -0
- data/CHANGELOG.txt +306 -0
- data/LICENSE.txt +23 -0
- data/README.md +549 -0
- data/exe/ocran +5 -0
- data/lib/ocran/build_constants.rb +16 -0
- data/lib/ocran/build_facade.rb +17 -0
- data/lib/ocran/build_helper.rb +110 -0
- data/lib/ocran/command_output.rb +22 -0
- data/lib/ocran/dir_builder.rb +162 -0
- data/lib/ocran/direction.rb +623 -0
- data/lib/ocran/empty_source +0 -0
- data/lib/ocran/file_path_set.rb +69 -0
- data/lib/ocran/gem_spec_queryable.rb +172 -0
- data/lib/ocran/host_config_helper.rb +57 -0
- data/lib/ocran/inno_setup_script_builder.rb +111 -0
- data/lib/ocran/launcher_batch_builder.rb +85 -0
- data/lib/ocran/library_detector.rb +61 -0
- data/lib/ocran/library_detector_posix.rb +55 -0
- data/lib/ocran/option.rb +323 -0
- data/lib/ocran/refine_pathname.rb +104 -0
- data/lib/ocran/runner.rb +115 -0
- data/lib/ocran/runtime_environment.rb +46 -0
- data/lib/ocran/stub_builder.rb +298 -0
- data/lib/ocran/version.rb +5 -0
- data/lib/ocran/windows_command_escaping.rb +15 -0
- data/lib/ocran.rb +7 -0
- data/share/ocran/lzma.exe +0 -0
- data/share/ocran/stub +0 -0
- metadata +109 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "tempfile"
|
|
3
|
+
require_relative "file_path_set"
|
|
4
|
+
|
|
5
|
+
module Ocran
|
|
6
|
+
# Utility class that produces the actual executable. Opcodes
|
|
7
|
+
# (create_file, mkdir etc) are added by invoking methods on an
|
|
8
|
+
# instance of OcranBuilder.
|
|
9
|
+
class StubBuilder
|
|
10
|
+
Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
|
|
11
|
+
|
|
12
|
+
OP_CREATE_DIRECTORY = 1
|
|
13
|
+
OP_CREATE_FILE = 2
|
|
14
|
+
OP_SETENV = 3
|
|
15
|
+
OP_SET_SCRIPT = 4
|
|
16
|
+
OP_CREATE_SYMLINK = 5
|
|
17
|
+
|
|
18
|
+
DEBUG_MODE = 0x01
|
|
19
|
+
EXTRACT_TO_EXE_DIR = 0x02
|
|
20
|
+
AUTO_CLEAN_INST_DIR = 0x04
|
|
21
|
+
CHDIR_BEFORE_SCRIPT = 0x08
|
|
22
|
+
DATA_COMPRESSED = 0x10
|
|
23
|
+
|
|
24
|
+
WINDOWS = Gem.win_platform?
|
|
25
|
+
|
|
26
|
+
base_dir = File.expand_path("../../share/ocran", File.dirname(__FILE__))
|
|
27
|
+
STUB_PATH = File.expand_path(WINDOWS ? "stub.exe" : "stub", base_dir)
|
|
28
|
+
STUBW_PATH = WINDOWS ? File.expand_path("stubw.exe", base_dir) : nil
|
|
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
|
+
|
|
32
|
+
def self.find_posix_lzma_cmd
|
|
33
|
+
if system("which lzma > /dev/null 2>&1")
|
|
34
|
+
["lzma", "--compress", "--stdout"]
|
|
35
|
+
elsif system("which xz > /dev/null 2>&1")
|
|
36
|
+
["xz", "--format=lzma", "--compress", "--stdout"]
|
|
37
|
+
elsif File.exist?("/opt/homebrew/bin/lzma")
|
|
38
|
+
["/opt/homebrew/bin/lzma", "--compress", "--stdout"]
|
|
39
|
+
else
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
LZMA_CMD = WINDOWS ? [LZMA_PATH, "e", "-si", "-so"] : find_posix_lzma_cmd
|
|
45
|
+
|
|
46
|
+
attr_reader :data_size
|
|
47
|
+
|
|
48
|
+
# Clear invalid security directory entries from PE executables
|
|
49
|
+
# This is necessary because some linkers may set non-zero values in the
|
|
50
|
+
# security directory even when there is no actual digital signature
|
|
51
|
+
def self.clear_invalid_security_entry(file_path)
|
|
52
|
+
data = File.binread(file_path)
|
|
53
|
+
return unless data.size > 64 # Minimum PE header size
|
|
54
|
+
|
|
55
|
+
# Read DOS header to find PE header offset
|
|
56
|
+
e_lfanew_offset = 60
|
|
57
|
+
pe_offset = data[e_lfanew_offset, 4].unpack1("L")
|
|
58
|
+
return if pe_offset + 160 > data.size # Not enough room for headers
|
|
59
|
+
|
|
60
|
+
# Calculate security directory offset
|
|
61
|
+
# PE signature (4) + FILE_HEADER (20) + partial OPTIONAL_HEADER to DataDirectory
|
|
62
|
+
security_entry_offset = pe_offset + 4 + 20 + 128
|
|
63
|
+
|
|
64
|
+
# Read security directory entry (VirtualAddress and Size)
|
|
65
|
+
sec_addr = data[security_entry_offset, 4].unpack1("L")
|
|
66
|
+
sec_size = data[security_entry_offset + 4, 4].unpack1("L")
|
|
67
|
+
|
|
68
|
+
# Check if security entry is invalid (points beyond file or size is 0)
|
|
69
|
+
if sec_size != 0 && (sec_addr == 0 || sec_addr >= data.size || sec_addr + sec_size > data.size)
|
|
70
|
+
# Clear the invalid security entry
|
|
71
|
+
data[security_entry_offset, 8] = "\x00" * 8
|
|
72
|
+
File.binwrite(file_path, data)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# chdir_before:
|
|
77
|
+
# When set to true, the working directory is changed to the application's
|
|
78
|
+
# deployment location at runtime.
|
|
79
|
+
#
|
|
80
|
+
# debug_mode:
|
|
81
|
+
# When the debug_mode option is set to true, the stub will output debug information
|
|
82
|
+
# when the exe file is executed. Debug mode can also be enabled within the directive
|
|
83
|
+
# code using the enable_debug_mode method. This option is provided to transition to
|
|
84
|
+
# debug mode from the initialization point of the stub.
|
|
85
|
+
#
|
|
86
|
+
# debug_extract:
|
|
87
|
+
# When set to true, the runtime file is extracted to the directory where the executable resides,
|
|
88
|
+
# and the extracted files remain even after the application exits.
|
|
89
|
+
# When set to false, the runtime file is extracted to the system's temporary directory,
|
|
90
|
+
# and the extracted files are deleted after the application exits.
|
|
91
|
+
#
|
|
92
|
+
# gui_mode:
|
|
93
|
+
# When set to true, the stub does not display a console window at startup. Errors are shown in a dialog window.
|
|
94
|
+
# When set to false, the stub reports errors through the console window.
|
|
95
|
+
#
|
|
96
|
+
# icon_path:
|
|
97
|
+
# Specifies the path to the icon file to be embedded in the stub's resources.
|
|
98
|
+
#
|
|
99
|
+
def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
|
|
100
|
+
enable_compression: nil, gui_mode: nil, icon_path: nil)
|
|
101
|
+
@dirs = FilePathSet.new
|
|
102
|
+
@files = FilePathSet.new
|
|
103
|
+
@data_size = 0
|
|
104
|
+
|
|
105
|
+
if icon_path && !File.exist?(icon_path)
|
|
106
|
+
raise "Icon file #{icon_path} not found"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
output_dir = File.dirname(path)
|
|
110
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
111
|
+
stub_tmp = File.join(output_dir, ".ocran_stub_#{$$}_#{Time.now.to_i}")
|
|
112
|
+
stub_src = if gui_mode && WINDOWS
|
|
113
|
+
STUBW_PATH
|
|
114
|
+
else
|
|
115
|
+
STUB_PATH
|
|
116
|
+
end
|
|
117
|
+
IO.copy_stream(stub_src, stub_tmp)
|
|
118
|
+
stub = stub_tmp
|
|
119
|
+
|
|
120
|
+
# Clear any invalid security directory entries from the stub (Windows only)
|
|
121
|
+
self.class.clear_invalid_security_entry(stub) if WINDOWS
|
|
122
|
+
|
|
123
|
+
# Embed icon resource (Windows only)
|
|
124
|
+
if icon_path && WINDOWS
|
|
125
|
+
system(EDICON_PATH, stub, icon_path.to_s, exception: true)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
File.open(stub, "ab") do |of|
|
|
129
|
+
@of = of
|
|
130
|
+
@opcode_offset = @of.size
|
|
131
|
+
|
|
132
|
+
write_header(debug_mode, debug_extract, chdir_before, enable_compression)
|
|
133
|
+
|
|
134
|
+
b = proc {
|
|
135
|
+
yield(self)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if enable_compression && LZMA_CMD
|
|
139
|
+
compress(&b)
|
|
140
|
+
else
|
|
141
|
+
b.yield
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
write_footer
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
File.rename(stub, path)
|
|
148
|
+
File.chmod(0755, path) unless WINDOWS
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def mkdir(target)
|
|
152
|
+
return unless @dirs.add?("/", target)
|
|
153
|
+
|
|
154
|
+
write_opcode(OP_CREATE_DIRECTORY)
|
|
155
|
+
write_path(target)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def symlink(link_path, target)
|
|
159
|
+
write_opcode(OP_CREATE_SYMLINK)
|
|
160
|
+
write_path(link_path)
|
|
161
|
+
write_string(target.to_s)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cp(source, target)
|
|
165
|
+
unless File.exist?(source)
|
|
166
|
+
raise "The file does not exist (#{source})"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return unless @files.add?(source, target)
|
|
170
|
+
|
|
171
|
+
write_opcode(OP_CREATE_FILE)
|
|
172
|
+
write_path(target)
|
|
173
|
+
write_file(source)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Specifies the final application script to be launched, which can be called
|
|
177
|
+
# from any position in the data stream. It cannot be specified more than once.
|
|
178
|
+
#
|
|
179
|
+
# You can omit setting OP_SET_SCRIPT without issues, in which case
|
|
180
|
+
# the stub terminates without launching anything after performing other
|
|
181
|
+
# runtime operations.
|
|
182
|
+
def exec(image, script, *argv)
|
|
183
|
+
if @script_set
|
|
184
|
+
raise "Script is already set"
|
|
185
|
+
end
|
|
186
|
+
@script_set = true
|
|
187
|
+
|
|
188
|
+
write_opcode(OP_SET_SCRIPT)
|
|
189
|
+
write_string_array(convert_to_native(image), convert_to_native(script), *argv)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def export(name, value)
|
|
193
|
+
write_opcode(OP_SETENV)
|
|
194
|
+
write_string(name.to_s)
|
|
195
|
+
write_string(value.to_s)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def compress
|
|
199
|
+
raise "No LZMA compressor found" unless LZMA_CMD
|
|
200
|
+
|
|
201
|
+
IO.popen(LZMA_CMD, "r+b") do |lzma|
|
|
202
|
+
_of, @of = @of, lzma
|
|
203
|
+
Thread.new { yield(self); lzma.close_write }
|
|
204
|
+
IO.copy_stream(lzma, _of)
|
|
205
|
+
@of = _of
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Calculate the position to write the LZMA decompressed size (64-bit unsigned integer)
|
|
209
|
+
# @opcode_offset: start position of the data section
|
|
210
|
+
# 1: size of the header byte
|
|
211
|
+
# 5: size of the LZMA header in bytes
|
|
212
|
+
File.binwrite(@of.path, [@data_size].pack("Q<"), @opcode_offset + 1 + 5)
|
|
213
|
+
end
|
|
214
|
+
private :compress
|
|
215
|
+
|
|
216
|
+
def write_header(debug_mode, debug_extract, chdir_before, compressed)
|
|
217
|
+
next_to_exe, delete_after = debug_extract, !debug_extract
|
|
218
|
+
@of << [0 |
|
|
219
|
+
(debug_mode ? DEBUG_MODE : 0) |
|
|
220
|
+
(next_to_exe ? EXTRACT_TO_EXE_DIR : 0) |
|
|
221
|
+
(delete_after ? AUTO_CLEAN_INST_DIR : 0) |
|
|
222
|
+
(chdir_before ? CHDIR_BEFORE_SCRIPT : 0) |
|
|
223
|
+
(compressed ? DATA_COMPRESSED : 0)
|
|
224
|
+
].pack("C")
|
|
225
|
+
end
|
|
226
|
+
private :write_header
|
|
227
|
+
|
|
228
|
+
def write_opcode(op)
|
|
229
|
+
@of << [op].pack("C")
|
|
230
|
+
@data_size += 1
|
|
231
|
+
end
|
|
232
|
+
private :write_opcode
|
|
233
|
+
|
|
234
|
+
def write_size(i)
|
|
235
|
+
if i > 0xFFFF_FFFF
|
|
236
|
+
raise ArgumentError, "Size #{i} is too large: must be 32-bit unsigned integer (0 to 4294967295)"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
@of << [i].pack("V")
|
|
240
|
+
@data_size += 4
|
|
241
|
+
end
|
|
242
|
+
private :write_size
|
|
243
|
+
|
|
244
|
+
def write_string(str)
|
|
245
|
+
len = str.bytesize + 1 # +1 to account for the null terminator
|
|
246
|
+
|
|
247
|
+
if len > 0xFFFF
|
|
248
|
+
raise ArgumentError, "String length #{len} is too large: must be less than or equal to 65535 bytes including null terminator"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
write_size(len)
|
|
252
|
+
@of << [str].pack("Z*")
|
|
253
|
+
@data_size += len
|
|
254
|
+
end
|
|
255
|
+
private :write_string
|
|
256
|
+
|
|
257
|
+
def write_string_array(*str_array)
|
|
258
|
+
ary = str_array.map(&:to_s)
|
|
259
|
+
|
|
260
|
+
if ary.any?(&:empty?)
|
|
261
|
+
raise ArgumentError, "Argument list must not contain empty strings"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Append an empty string so that when joined with "\0", the final buffer
|
|
265
|
+
# ends in two consecutive NUL bytes (double–NUL terminator) to mark end-of-list.
|
|
266
|
+
ary << ""
|
|
267
|
+
|
|
268
|
+
size = ary.sum(0) { |s| s.bytesize + 1 }
|
|
269
|
+
write_size(size)
|
|
270
|
+
ary.each_slice(1) { |a| @of << a.pack("Z*") }
|
|
271
|
+
@data_size += size
|
|
272
|
+
end
|
|
273
|
+
private :write_string_array
|
|
274
|
+
|
|
275
|
+
def write_file(src)
|
|
276
|
+
size = File.size(src)
|
|
277
|
+
write_size(size)
|
|
278
|
+
IO.copy_stream(src, @of)
|
|
279
|
+
@data_size += size
|
|
280
|
+
end
|
|
281
|
+
private :write_file
|
|
282
|
+
|
|
283
|
+
def write_path(path)
|
|
284
|
+
write_string(convert_to_native(path))
|
|
285
|
+
end
|
|
286
|
+
private :write_path
|
|
287
|
+
|
|
288
|
+
def write_footer
|
|
289
|
+
@of << ([@opcode_offset] + Signature).pack("VC*")
|
|
290
|
+
end
|
|
291
|
+
private :write_footer
|
|
292
|
+
|
|
293
|
+
def convert_to_native(path)
|
|
294
|
+
WINDOWS ? path.to_s.tr(File::SEPARATOR, "\\") : path.to_s
|
|
295
|
+
end
|
|
296
|
+
private :convert_to_native
|
|
297
|
+
end
|
|
298
|
+
end
|
data/lib/ocran.rb
ADDED
|
Binary file
|
data/share/ocran/stub
ADDED
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ocran
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.4.0
|
|
5
|
+
platform: x86_64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Andi Idogawa
|
|
8
|
+
- shinokaro
|
|
9
|
+
- Lars Christensen
|
|
10
|
+
bindir: exe
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: fiddle
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '1.0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '1.0'
|
|
28
|
+
description: |
|
|
29
|
+
OCRAN (One-Click Ruby Application Next) packages Ruby applications for
|
|
30
|
+
distribution. It bundles your script, the Ruby interpreter, gems, and native
|
|
31
|
+
libraries into a self-contained artifact that runs without requiring Ruby to
|
|
32
|
+
be installed on the target machine.
|
|
33
|
+
|
|
34
|
+
Three output formats are supported on all platforms:
|
|
35
|
+
- Self-extracting executable (.exe on Windows, native binary on Linux/macOS)
|
|
36
|
+
- Directory with a launch script (--output-dir)
|
|
37
|
+
- Zip archive with a launch script (--output-zip)
|
|
38
|
+
|
|
39
|
+
This is a fork of OCRA maintained for Ruby 3.2+ compatibility.
|
|
40
|
+
Migration guide: replace OCRA_EXECUTABLE with OCRAN_EXECUTABLE in your code.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
ocran helloworld.rb # builds helloworld.exe / helloworld
|
|
44
|
+
ocran --output-dir out/ app.rb
|
|
45
|
+
ocran --output-zip app.zip app.rb
|
|
46
|
+
|
|
47
|
+
See readme at https://github.com/largo/ocran
|
|
48
|
+
Report problems at https://github.com/largo/ocran/issues
|
|
49
|
+
email:
|
|
50
|
+
- andi@idogawa.com
|
|
51
|
+
executables:
|
|
52
|
+
- ocran
|
|
53
|
+
extensions: []
|
|
54
|
+
extra_rdoc_files: []
|
|
55
|
+
files:
|
|
56
|
+
- CHANGELOG.txt
|
|
57
|
+
- LICENSE.txt
|
|
58
|
+
- README.md
|
|
59
|
+
- exe/ocran
|
|
60
|
+
- lib/ocran.rb
|
|
61
|
+
- lib/ocran/build_constants.rb
|
|
62
|
+
- lib/ocran/build_facade.rb
|
|
63
|
+
- lib/ocran/build_helper.rb
|
|
64
|
+
- lib/ocran/command_output.rb
|
|
65
|
+
- lib/ocran/dir_builder.rb
|
|
66
|
+
- lib/ocran/direction.rb
|
|
67
|
+
- lib/ocran/empty_source
|
|
68
|
+
- lib/ocran/file_path_set.rb
|
|
69
|
+
- lib/ocran/gem_spec_queryable.rb
|
|
70
|
+
- lib/ocran/host_config_helper.rb
|
|
71
|
+
- lib/ocran/inno_setup_script_builder.rb
|
|
72
|
+
- lib/ocran/launcher_batch_builder.rb
|
|
73
|
+
- lib/ocran/library_detector.rb
|
|
74
|
+
- lib/ocran/library_detector_posix.rb
|
|
75
|
+
- lib/ocran/option.rb
|
|
76
|
+
- lib/ocran/refine_pathname.rb
|
|
77
|
+
- lib/ocran/runner.rb
|
|
78
|
+
- lib/ocran/runtime_environment.rb
|
|
79
|
+
- lib/ocran/stub_builder.rb
|
|
80
|
+
- lib/ocran/version.rb
|
|
81
|
+
- lib/ocran/windows_command_escaping.rb
|
|
82
|
+
- share/ocran/lzma.exe
|
|
83
|
+
- share/ocran/stub
|
|
84
|
+
homepage: https://github.com/largo/ocran
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata:
|
|
88
|
+
homepage_uri: https://github.com/largo/ocran
|
|
89
|
+
source_code_uri: https://github.com/largo/ocran
|
|
90
|
+
changelog_uri: https://github.com/largo/ocran/CHANGELOG.txt
|
|
91
|
+
rdoc_options: []
|
|
92
|
+
require_paths:
|
|
93
|
+
- lib
|
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 3.2.0
|
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
requirements: []
|
|
105
|
+
rubygems_version: 4.0.3
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: OCRAN (One-Click Ruby Application Next) packages Ruby applications for distribution
|
|
108
|
+
on Windows, Linux, and macOS.
|
|
109
|
+
test_files: []
|