Class: Ocran::StubBuilder
- Inherits:
-
Object
- Object
- Ocran::StubBuilder
- Defined in:
- lib/ocran/stub_builder.rb
Overview
Utility class that produces the actual executable. Opcodes (create_file, mkdir etc) are added by invoking methods on an instance of OcranBuilder.
Constant Summary collapse
- Signature =
[0x41, 0xb6, 0xba, 0x4e].freeze
- OP_CREATE_DIRECTORY =
1- OP_CREATE_FILE =
2- OP_SETENV =
3- OP_SET_SCRIPT =
4- OP_CREATE_SYMLINK =
5- DEBUG_MODE =
0x01- EXTRACT_TO_EXE_DIR =
0x02- AUTO_CLEAN_INST_DIR =
0x04- CHDIR_BEFORE_SCRIPT =
0x08- DATA_COMPRESSED =
0x10- RUN_IN_EXE_DIR =
0x20- CHDIR_TO_EXE_DIR =
0x40- WINDOWS =
Gem.win_platform?
- STUB_PATH =
File.(WINDOWS ? "stub.exe" : "stub", base_dir)
- STUBW_PATH =
WINDOWS ? File.("stubw.exe", base_dir) : nil
- LZMA_PATH =
WINDOWS ? File.("lzma.exe", base_dir) : nil
- LZMA_CMD =
WINDOWS ? [LZMA_PATH, "e", "-si", "-so"] : find_posix_lzma_cmd
Instance Attribute Summary collapse
-
#data_size ⇒ Object
readonly
Returns the value of attribute data_size.
Class Method Summary collapse
-
.clear_invalid_security_entry(file_path) ⇒ Object
Clear invalid security directory entries from PE executables This is necessary because some linkers may set non-zero values in the security directory even when there is no actual digital signature.
- .find_posix_lzma_cmd ⇒ Object
Instance Method Summary collapse
- #cp(source, target) ⇒ Object
-
#exec(image, script, *argv) ⇒ Object
Specifies the final application script to be launched, which can be called from any position in the data stream.
- #export(name, value) ⇒ Object
-
#initialize(path, chdir_before: nil, chdir_to_exe_dir: nil, debug_extract: nil, debug_mode: nil, enable_compression: nil, gui_mode: nil, icon_path: nil, run_in_exe_dir: nil, stub_path: nil) ⇒ StubBuilder
constructor
chdir_before: When set to true, the working directory is changed to the application's deployment location at runtime.
- #mkdir(target) ⇒ Object
- #symlink(link_path, target) ⇒ Object
Constructor Details
#initialize(path, chdir_before: nil, chdir_to_exe_dir: nil, debug_extract: nil, debug_mode: nil, enable_compression: nil, gui_mode: nil, icon_path: nil, run_in_exe_dir: nil, stub_path: nil) ⇒ StubBuilder
chdir_before: When set to true, the working directory is changed to the application's deployment location at runtime.
chdir_to_exe_dir: When set to true, the working directory is changed to the directory containing the (stub) executable before the script starts. This makes relative file access resolve next to the executable regardless of how it was invoked. Mutually exclusive with chdir_before.
debug_mode: When the debug_mode option is set to true, the stub will output debug information when the exe file is executed. Debug mode can also be enabled within the directive code using the enable_debug_mode method. This option is provided to transition to debug mode from the initialization point of the stub.
debug_extract: When set to true, the runtime file is extracted to the directory where the executable resides, and the extracted files remain even after the application exits. When set to false, the runtime file is extracted to the system's temporary directory, and the extracted files are deleted after the application exits.
gui_mode: When set to true, the stub does not display a console window at startup. Errors are shown in a dialog window. When set to false, the stub reports errors through the console window.
icon_path: Specifies the path to the icon file to be embedded in the stub's resources.
run_in_exe_dir: When set to true, the stub runs the application directly from its own directory instead of extracting to a temporary directory. Used for installer (Inno Setup) wrapper executables where the application files are installed next to the stub (pre-1.4/OCRA behavior). The directory is never deleted on exit.
stub_path: Path to a stub binary to package with instead of the pre-built stub shipped with the gem (e.g. an APE stub freshly compiled with cosmocc, see --cosmo). When set, it takes precedence over both STUB_PATH and STUBW_PATH.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/ocran/stub_builder.rb', line 119 def initialize(path, chdir_before: nil, chdir_to_exe_dir: nil, debug_extract: nil, debug_mode: nil, enable_compression: nil, gui_mode: nil, icon_path: nil, run_in_exe_dir: nil, stub_path: nil) @dirs = FilePathSet.new @files = FilePathSet.new @data_size = 0 if icon_path && !File.exist?(icon_path) raise "Icon file #{icon_path} not found" end output_dir = File.dirname(path) FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir) stub_tmp = File.join(output_dir, ".ocran_stub_#{$$}_#{Time.now.to_i}") stub_src = if stub_path stub_path elsif gui_mode && WINDOWS STUBW_PATH else STUB_PATH end IO.copy_stream(stub_src, stub_tmp) stub = stub_tmp # Clear any invalid security directory entries from the stub (Windows only) self.class.clear_invalid_security_entry(stub) if WINDOWS # Embed icon resource (Windows only) if icon_path && WINDOWS require_relative "ed_icon" EdIcon.update_icon(stub, icon_path.to_s) end File.open(stub, "ab") do |of| @of = of @opcode_offset = @of.size write_header(debug_mode, debug_extract, chdir_before, enable_compression, run_in_exe_dir, chdir_to_exe_dir) b = proc { yield(self) } if enable_compression && LZMA_CMD compress(&b) else b.yield end end File.rename(stub, path) File.chmod(0755, path) unless WINDOWS end |
Instance Attribute Details
#data_size ⇒ Object (readonly)
Returns the value of attribute data_size.
47 48 49 |
# File 'lib/ocran/stub_builder.rb', line 47 def data_size @data_size end |
Class Method Details
.clear_invalid_security_entry(file_path) ⇒ Object
Clear invalid security directory entries from PE executables This is necessary because some linkers may set non-zero values in the security directory even when there is no actual digital signature
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/ocran/stub_builder.rb', line 52 def self.clear_invalid_security_entry(file_path) data = File.binread(file_path) return unless data.size > 64 # Minimum PE header size # Read DOS header to find PE header offset e_lfanew_offset = 60 pe_offset = data[e_lfanew_offset, 4].unpack1("L") return if pe_offset + 160 > data.size # Not enough room for headers # Calculate security directory offset # PE signature (4) + FILE_HEADER (20) + partial OPTIONAL_HEADER to DataDirectory security_entry_offset = pe_offset + 4 + 20 + 128 # Read security directory entry (VirtualAddress and Size) sec_addr = data[security_entry_offset, 4].unpack1("L") sec_size = data[security_entry_offset + 4, 4].unpack1("L") # Check if security entry is invalid (points beyond file or size is 0) if sec_size != 0 && (sec_addr == 0 || sec_addr >= data.size || sec_addr + sec_size > data.size) # Clear the invalid security entry data[security_entry_offset, 8] = "\x00" * 8 File.binwrite(file_path, data) end end |
.find_posix_lzma_cmd ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/ocran/stub_builder.rb', line 33 def self.find_posix_lzma_cmd if system("which lzma > /dev/null 2>&1") ["lzma", "--compress", "--stdout"] elsif system("which xz > /dev/null 2>&1") ["xz", "--format=lzma", "--compress", "--stdout"] elsif File.exist?("/opt/homebrew/bin/lzma") ["/opt/homebrew/bin/lzma", "--compress", "--stdout"] else nil end end |
Instance Method Details
#cp(source, target) ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/ocran/stub_builder.rb', line 189 def cp(source, target) unless File.exist?(source) raise "The file does not exist (#{source})" end return unless @files.add?(source, target) write_opcode(OP_CREATE_FILE) write_path(target) write_file(source) end |
#exec(image, script, *argv) ⇒ Object
Specifies the final application script to be launched, which can be called from any position in the data stream. It cannot be specified more than once.
You can omit setting OP_SET_SCRIPT without issues, in which case the stub terminates without launching anything after performing other runtime operations.
207 208 209 210 211 212 213 214 215 |
# File 'lib/ocran/stub_builder.rb', line 207 def exec(image, script, *argv) if @script_set raise "Script is already set" end @script_set = true write_opcode(OP_SET_SCRIPT) write_string_array(convert_to_native(image), convert_to_native(script), *argv) end |
#export(name, value) ⇒ Object
217 218 219 220 221 |
# File 'lib/ocran/stub_builder.rb', line 217 def export(name, value) write_opcode(OP_SETENV) write_string(name.to_s) write_string(value.to_s) end |
#mkdir(target) ⇒ Object
176 177 178 179 180 181 |
# File 'lib/ocran/stub_builder.rb', line 176 def mkdir(target) return unless @dirs.add?("/", target) write_opcode(OP_CREATE_DIRECTORY) write_path(target) end |
#symlink(link_path, target) ⇒ Object
183 184 185 186 187 |
# File 'lib/ocran/stub_builder.rb', line 183 def symlink(link_path, target) write_opcode(OP_CREATE_SYMLINK) write_path(link_path) write_string(target.to_s) end |