# encoding: utf-8

require 'rubygems'
require 'bundler'
begin
  Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
  $stderr.puts e.message
  $stderr.puts "Run `bundle install` to install missing gems"
  exit e.status_code
end
require 'rake'

require 'juwelier'
Juwelier::Tasks.new do |gem|
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
  gem.name = "pedump"
  gem.homepage = "http://github.com/zed-0xff/pedump"
  gem.license = "MIT"
  gem.summary = %Q{dump win32 PE executable files with a pure ruby}
  gem.description = %Q{dump headers, sections, extract resources of win32 PE exe,dll,etc}
  gem.email = "zed.0xff@gmail.com"
  gem.authors = ["Andrey \"Zed\" Zaikin"]
  gem.executables = %w'pedump'
  gem.files.include "lib/**/*.rb"
  gem.files.exclude %w'samples/**/* spec/**/* tmp/**/* tmp/.keep .* README.md.tpl .github/**/*'
  gem.extra_rdoc_files.exclude 'README.md.tpl'
  # dependencies defined in Gemfile
end
Juwelier::RubygemsDotOrgTasks.new

require 'rspec/core'
require 'rspec/core/rake_task'

desc "run specs"
RSpec::Core::RakeTask.new

task :default => [:spec, :readme]

namespace :test do
  desc "test on all files in given path"
  task :all_files do
    require './lib/pedump'
    require './lib/pedump/cli'
    path = ENV['path'] || raise("run me with path=...")
    `find #{path} -type f`.split("\n").each do |fname|
      puts "\n### #{fname}\n"
      PEdump::CLI.new(fname).run
    end
  end

  namespace :all_files do
    desc "output file name to stderr, use with stdout redirection"
    task :stderr do
      require './lib/pedump'
      require './lib/pedump/cli'
      path = ENV['path'] || raise("run me with path=...")
      `find #{path} -type f`.split("\n").each do |fname|
        STDERR.puts "\n### #{fname}\n"
        PEdump::CLI.new(fname).run
      end
    end
  end

  desc "test on corkami binaries"
  task :corkami do
      require './lib/pedump'
      require './lib/pedump/cli'
      path = "samples/corkami"
      `find #{path} -type f`.split("\n").each do |fname|
        STDERR.puts "\n### #{fname}\n"
        PEdump::CLI.new(fname).run
      end
  end
end

def check_file url, params = {}
  require 'digest/md5'
  require 'open-uri'

  params[:min_size] ||= 80_000

  STDOUT.sync = true
  prefix = params[:prefix]
  fname = File.join 'data', (prefix ? "#{prefix}-" : '') + File.basename(url)
  existing_md5 = File.exist?(fname) ? Digest::MD5.file(fname).hexdigest : ''
  print "[.] fetching #{url} .. "
  remote_data = URI.open(url).read.force_encoding('cp1252').encode('utf-8')
  puts "#{remote_data.size} bytes"
  raise "too small remote data (#{remote_data.size})" if remote_data.size < params[:min_size]
  remote_md5   = Digest::MD5.hexdigest(remote_data)
  if remote_md5 == existing_md5
    puts "[.] same as local"
  else
    existing_size = File.exist?(fname) ? File.size(fname) : 0
    File.open(fname,"wb"){ |f| f << remote_data }
    puts "[*] updated: #{existing_size} -> #{remote_data.size}"
  end
end

RICH_IDS_URL = "https://raw.githubusercontent.com/dishather/richprint/master/comp_id.txt"

namespace :rich do
  desc "update rich comp_id db from net"
  task :update do
    check_file RICH_IDS_URL, :min_size => 30_000
  end

  desc "convert"
  task :convert do
    result = [
      "class PEdump",
      "  # data from #{RICH_IDS_URL}",
      "  RICH_IDS = {"
    ]
    n = 0
    t0 = Time.now
    File.readlines(File.join("data", File.basename(RICH_IDS_URL))).each do |line|
      line.strip!
      next if line.empty? || line[0] == '#'
      comp_id, desc = line.split(nil, 2)
      raise unless comp_id =~ /\A[0-9a-fA-F]+\Z/
      result << "    0x#{comp_id} => #{desc.inspect},"
      n += 1
    end
    result << "  }"
    result << "end"
    printf "[.] parsed %d definitions in %6.3fs\n", n, Time.now-t0
    File.write("lib/pedump/rich.rb", result.join("\n") + "\n")
  end
end

namespace :sigs do
  desc "update packers db from net"
  task :update do
    require './lib/pedump/packer'
    check_file "http://research.pandasecurity.com/blogs/images/userdb.txt"
    check_file "http://fuu.googlecode.com/svn/trunk/src/x86/Tools/Signaturesdb/signatures.txt"
    check_file "http://handlers.sans.edu/jclausing/userdb.txt", :prefix => "jc"
  end

  desc "convert txt2bin"
  task :convert do
    require './lib/pedump/packer'
    t0 = Time.now
    sigs = PEdump::SigParser.parse :optimize => true, :verbose => true
    printf "[.] parsed %d definitions in %6.3fs\n", sigs.size, Time.now-t0
    File.open(PEdump::Packer::BIN_SIGS_FILE,"wb"){ |f| Marshal.dump(sigs,f) }
  end

  desc "dump"
  task :dump do
    require './lib/pedump/packer'
    require 'awesome_print'
    PEdump::Packer.all.
      group_by{ |sig| sig.name }.
      sort_by{|name,sigs| name }.
      each do |name,sigs|
        next if sigs.size == 1
        puts name.green
        sigs.each do |sig|
          printf "    %-5s  %s\n", sig.ep_only, sig.re.source.inspect
        end
      end
  end
end

desc "build readme"
task :readme do
  require 'erb'
  tpl = File.read('README.md.tpl').gsub(/^%\s+(.+)/) do |x|
    x.sub! /^%/,''
    "<%= run(\"#{x}\") %>"
  end
  def run cmd
    cmd.strip!
    puts "[.] #{cmd} ..."
    r = "    # #{cmd}\n\n"
    cmd.sub! /^pedump/,"../bin/pedump"
    lines = `#{cmd}`.sub(/\A\n+/m,'').sub(/\s+\Z/,'').split("\n")
    lines = lines[0,25] + ['...'] if lines.size > 50
    r << lines.map{|x| "    #{x}"}.join("\n")
    r << "\n"
  end
  Dir.chdir 'samples'
  result = ERB.new(tpl,nil,'%>').result
  Dir.chdir '..'
  File.open('README.md','w'){ |f| f << result }
end

namespace :console do
  desc "start console with PEdump::Loader with loaded file"
  task :load do
    raise "gimme a fname" unless fname = ENV['fname']
    require './lib/pedump'
    require './lib/pedump/loader'
    require 'pp'
    File.open(fname,"rb") do |f|
      @ldr = PEdump::Loader.new f
      puts "[.] loader is at @ldr"
      pp @ldr.sections
      Rake::Task["console"].execute
    end
  end
end

desc "compare two PE files"
task :cmp do
  raise "gimme a f1" unless f1 = ENV['f1']
  raise "gimme a f2" unless f2 = ENV['f2']
  require './lib/pedump'
  require './lib/pedump/comparer'
  PEdump::Comparer.cmp(f1,f2)
end
