Skip to content

Instantly share code, notes, and snippets.

@dsisnero
Forked from aoitaku/rpk.rb
Created August 18, 2017 07:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsisnero/6a29b69c6c1fae6b8c4e4d09f075303b to your computer and use it in GitHub Desktop.
Save dsisnero/6a29b69c6c1fae6b8c4e4d09f075303b to your computer and use it in GitHub Desktop.
rpk
module Rpk
module Win32
class API
def self.setup
require 'fiddle/import'
const_set(:DLL, {}) unless defined? DLL
const_set(:TYPEMAP,
{
"0" => Fiddle::TYPE_VOID,
"S" => Fiddle::TYPE_VOIDP,
"I" => Fiddle::TYPE_LONG
}
) unless defined? TYPEMAP
const_set(:POINTER_TYPE,
Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
) unless defined? POINTER_TYPE
end
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
@proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1')
handle = DLL[dllname] ||= Fiddle.dlopen(dllname)
@func = Fiddle::Function.new(
handle[func],
import.chars.map { |win_type| TYPEMAP[win_type.tr("VPpNnLlIi", "0SSI")] },
TYPEMAP[export.tr("VPpNnLlIi", "0SSI")],
Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
)
rescue Fiddle::DLError => e
raise LoadError, e.message, e.backtrace
end
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
return ret || 0
end
end
def self.process_modules
API.setup
current_process = API.new('kernel32.dll', 'GetCurrentProcess', 'V', 'L')
enum_process_modules = API.new('psapi.dll', 'EnumProcessModules', 'LPLP', 'I')
module_file_name = API.new('kernel32.dll', 'GetModuleFileName', 'LPL', 'L')
process_handle = current_process.call(nil)
module_handle_buffer = nil
bytes_needed = 4 * 32
loop do
module_handle_buffer = "\x00" * bytes_needed
bytes_needed_buffer = [0].pack("I")
enum_process_modules.call(
process_handle,
module_handle_buffer,
module_handle_buffer.length,
bytes_needed_buffer
)
bytes_needed = bytes_needed_buffer.unpack("I")[0]
break if bytes_needed <= module_handle_buffer.size
end
module_handles = module_handle_buffer.unpack("I*")
module_handles.select {|handle| handle > 0 }.map do |handle|
name = "\x00" * 256
name_length = module_file_name.call(handle, name, name.size)
name[0, name_length]
end
end
end
def self.loaded_features ; @loaded_features end
def self.load_path ; @load_path end
def self.dir ; @dir end
def self.script ; @script end
def self.dist ; @dist end
def self.prefix ; @prefix end
def self.lib ; @lib end
def self.bin ; @bin end
def self.load_prefix ; @load_prefix end
def self.enumerate_features
loaded_features.lazy.select do |feat|
load_path.any? do |path|
File.dirname(feat).start_with?(path)
end
end
end
def self.features
enumerate_features.to_a
end
def self.enumerate_local_features
loaded_features.lazy.reject {|feat|
load_path.any? do |path|
File.dirname(feat).start_with?(path)
end
}.select {|feat|
feat.start_with?(dir)
}
end
def self.local_features
enumerate_local_features.to_a
end
def self.enumerate_dlls
Win32.process_modules.lazy.map {|dll|
dll.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
}.select {|dll|
File.dirname(dll).start_with?(bin)
}.select do |dll|
dll.end_with?('.dll')
end
end
def self.dlls
enumerate_dlls.to_a
end
def self.setup
@loaded_features = $LOADED_FEATURES.dup
@load_path = $LOAD_PATH.dup
require 'pathname'
require 'fileutils'
require 'json'
@prefix = Pathname.new(RbConfig.ruby).parent.parent
@dist = Pathname.new(dir) + "dist"
@lib = (prefix + "lib").to_s
@bin = (prefix + 'bin').to_s
@load_prefix = %w[
site_ruby/2.3.0
site_ruby/2.3.0/i386-msvcrt
site_ruby
vendor_ruby/2.3.0
vendor_ruby/2.3.0/i386-msvcrt
vendor_ruby
2.3.0
2.3.0/i386-mingw32
]
end
def self.copy_files
if (File.exists?(dist.to_s))
Dir.glob("#{dist.to_s}/*").each do |dir|
FileUtils.remove_entry_secure(dir)
end
else
FileUtils.mkdir("dist")
end
enumerate_features.each do |feat|
path = load_path.find {|path|
File.dirname(feat).start_with?(path)
}
if path.start_with?(prefix.to_s)
file = feat.slice(prefix.to_s.length + 1..feat.length)
FileUtils.mkdir_p((dist + File.dirname(file)).to_s)
FileUtils.copy(feat, (dist + file).to_s)
elsif path.start_with?(dir)
file = feat.slice(dir.length + 1..feat.length)
FileUtils.mkdir_p((dist + File.dirname(file)).to_s)
FileUtils.copy(feat, (dist + file).to_s)
end
end
enumerate_local_features.each do |feat|
file = feat.slice(dir.length + 1..feat.length)
FileUtils.mkdir_p((dist + File.dirname(file)).to_s)
FileUtils.copy(feat, (dist + file).to_s)
end
enumerate_dlls.each do |dll|
file = dll.slice(bin.length + 1..dll.length)
FileUtils.mkdir_p((dist + File.dirname(file)).to_s)
FileUtils.copy(dll, (dist + file).to_s)
end
recipe_file = "#{dir}/rpk.json"
if File.exist?(recipe_file)
recipe = JSON.parse(File.read("#{dir}/rpk.json"))
if recipe["extra"]
recipe["extra"].each do |entry|
FileUtils.copy_entry("#{dir}/#{entry}", (dist + entry).to_s)
end
end
end
app_name = File.basename(script, '.rb')
bootstrap = <<-'EOF'.gsub(/^[ ]{6}/, '')
$LOAD_PATH.clear
[
'lib/ruby/site_ruby/2.3.0',
'lib/ruby/site_ruby/2.3.0/i386-msvcrt',
'lib/ruby/site_ruby',
'lib/ruby/vendor_ruby/2.3.0',
'lib/ruby/vendor_ruby/2.3.0/i386-msvcrt',
'lib/ruby/vendor_ruby',
'lib/ruby/2.3.0',
'lib/ruby/2.3.0/i386-mingw32'
].each do |dir|
$LOAD_PATH << "#{__dir__}/#{dir}"
end
EOF
bootstrap.encode!('ASCII-8BIT')
main = File.binread(script)
rubyw = File.binread("#{bin}/rubyw.exe")
ruby = File.binread("#{bin}/ruby.exe")
File.binwrite("dist/#{app_name}.exe", rubyw+bootstrap+main)
File.binwrite("dist/#{app_name}_development.exe", ruby+bootstrap+main)
end
def self.run
@dir = Dir.pwd
@script = "#{dir}/#{ARGV.shift}"
$0 = @script
load @script
setup
copy_files
end
end
exit unless File.basename($0) == File.basename(__FILE__)
Rpk.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment