vendredi 18 septembre 2009

RubyPatchMaker

Lorsque vous apportez des modifications à un fichier binaire, il peut être intéressant de stocker celles-ci pour les réutiliser :
Binaire Original + Patch = Binaire modifié

RubyPatchMaker permet de créer un patch en ruby, utilisable sur le Binaire original. Il existe en deux versions: ligne de commande, et GUI.





  • Prérequis:
- Ruby
- ruby-gtk2 (ruby-gtk2-0.16.0-1-i386-msvcrt-1.8.zip , également pour la version GUI)

  • Mode d'emploi:
1) Sélectionnez le binaire original, le binaire modifié, et le répertoire de destination pour le patch:



2) Un patch est alors créé. Il enregistre:
- l'empreinte MD5 de l'original
- l'ensemble des différences entre le binaire original et celui modifié.


3) Pour patcher ultérieurement, placez le binaire et le patch dans le même répertoire, et lancez le patch. Une copie de l'orignal est sauvegardée.



article rédigé par t0ka7a 2009


******************************************************************************
  • Code source version GUI:
#!/usr/bin/env ruby

# Ruby Patch Maker Tool 1.0
# created by t0ka7a 2009

# this program stores differences between two files:
# - original file: file before patch
# - new file: file patched
# Then, it returns a patch in ruby.

require 'gtk2'
require 'digest/md5'


class Modifications
# differences between 2 files
  def initialize
    @data=[]
    @counters=[]
    @modif_text = ""
  end
  
  def push(obj1,obj2)
    @data.push(obj1)
    @counters.push(obj2)
  end
 
  def data
    @data
  end

  def counters
    @counters
  end

  def extract_modifications(name_f_old,name_f_new)
    #returns an array with differences between original and new file.
  
    #open original and patched files
    f_old = File.open(name_f_old.to_s,'r').binmode
    f_new = File.open(name_f_new.to_s,'r').binmode
  
    # test if f_new and f_old have same size
    return nil if test_error(File.size(name_f_old)!=File.size(name_f_new),"Original and patched files don't have same size")
  
    #declaration
    i=0
    a,b=""
  
    # compares byte number i of original file to byte number i of patched file. Returns an array with differences found
    while b=f_old.getc do
      a=f_new.getc
      if a!=b then
        push(a,i)
      end
      i+=1
    end
  
    #close files
    f_old.close
    f_new.close
  
    # puts lines of program used to load modifications in @modif
    i=0
    while @counters[i] do
      @modif_text << " @data.push(#{@data[i]})\n@counters.push(#{@counters[i]})\n"
      i+=1
    end
  
  end
  def write_program_to_file(name_f_save,name_f_old,md5_f_old)
    # creates a file named xxx_patch.rb with the program of the patch
    file = File.open("#{name_f_save}.rb",'w')
  
    # creates a string with lines of program for the patch
    program = "
#!/usr/bin/env ruby
# created with Ruby Patch Maker Tool by t0ka7a 2009

require 'gtk2'
require 'digest/md5'
require 'ftools'

#----------------------------------------------------------------------------------------------------------------------------------------
# differences between 2 files
class Modifications
  
    def initialize
      @data=[]
      @counters=[]
      @name_p=#{name_f_save.dump}
      @name_f_to_patch=#{name_f_old.dump}
      @md5_f_old=#{md5_f_old.dump}
      @md5_f_new=Digest::MD5.file(@name_f_to_patch).hexdigest if @name_f_to_patch.to_s != \"\"
    
      #{@modif_text}
    
      #-----------------------------------------------------------------------
      #compare md5 of file to patch with md5 calculated when patch was created.
      if @md5_f_old != @md5_f_new then
        dialog = Gtk::MessageDialog.new(
          nil,
          Gtk::Dialog::DESTROY_WITH_PARENT,
          Gtk::MessageDialog::WARNING,
          Gtk::MessageDialog::BUTTONS_CLOSE,
          \"File to patch is not valid. Good version?\"
        )
        dialog.run
        dialog.destroy
        return nil
      end
    
      #-----------------------------------------------------------------------
      # copy original_file to original_file.old
      File.copy(@name_f_to_patch.to_s,@name_f_to_patch.to_s+\".old\")
    
      #-----------------------------------------------------------------------
      #patch the file
      f = File.open(@name_f_to_patch.to_s,\'r+\').binmode
      for i in 0...@counters.length
        f.pos=@counters[i].to_i
        f.write(@data[i].chr.to_s)
        end
      f.close
    
      #-----------------------------------------------------------------------
      #show dialog success
      dialog = Gtk::MessageDialog.new(
        nil,
        Gtk::Dialog::DESTROY_WITH_PARENT,
        Gtk::MessageDialog::INFO,
        Gtk::MessageDialog::BUTTONS_CLOSE,
        \"file \#{@name_f_to_patch} patched!\"
        )
      dialog.run
      dialog.destroy
    
    end  
end

#----------------------------------------------------------------------------------------------------------------------------------------
def messagebox2
# shows a dialog of confirmation of patch
 
  window = Gtk::Window.new
  table = Gtk::Table.new(3,2,true)
  label1 = Gtk::Label.new(\"Patch for \#{#{name_f_save.dump}}\")
  label2 = Gtk::Label.new(\"generated by t0ka7a's Patcher Tool\")
  button_cancel = Gtk::Button.new(\"cancel\")
  button_ok = Gtk::Button.new(\"Patch\")
 
  table.attach_defaults(label1,0,2,0,1)
  table.attach_defaults(label2,0,2,1,2)
  table.attach_defaults(button_cancel,0,1,2,3)
  table.attach_defaults(button_ok,1,2,2,3)
 
  button_cancel.signal_connect(\"clicked\") { Gtk.main_quit }
  button_ok.signal_connect(\"clicked\") { modif=Modifications.new;Gtk.main_quit}
 
  window.add(table)
  window.show_all
  Gtk.main
end

#----------------------------------------------------------------------------------------------------------------------------------------
# main
messagebox2
"
    file.write(program)
    file.close
  end
end

def md5(file)
  # returns md5 of file
  digest = Digest::MD5.file(file).hexdigest if file.to_s != ""
  return digest
end

def dialog_bravo(message)
# dialog bravo
  dialog = Gtk::MessageDialog.new(
    nil,
      Gtk::Dialog::DESTROY_WITH_PARENT,
      Gtk::MessageDialog::INFO,
      Gtk::MessageDialog::BUTTONS_CLOSE,
      message
    )
    dialog.run
    dialog.destroy
end

def test_error(condition,message)
# shows an error dialog with message if condition false
 if condition then
    dialog = Gtk::MessageDialog.new(
      nil,
      Gtk::Dialog::DESTROY_WITH_PARENT,
      Gtk::MessageDialog::WARNING,
      Gtk::MessageDialog::BUTTONS_CLOSE,
      message
    )
    dialog.run
    dialog.destroy
    return true
  else
    return false
  end
end
 
def next_function(name_f_old,name_f_new,name_f_save)
md5_f_old=md5(name_f_old)
modif=Modifications.new
modif.extract_modifications(name_f_old,name_f_new)
modif.write_program_to_file(name_f_save,name_f_old,md5_f_old)
dialog_bravo("patch successfully created: #{name_f_save}")
Gtk.main_quit
end

def messagebox1
# show main dialog
  window = Gtk::Window.new
  window.set_window_position Gtk::Window::POS_CENTER # center window
  filled = [false,false,false] # = true when all dialog filled by user
  modif = [] # array with differences between original and patched files
  table = Gtk::Table.new(7,3,true) #table for position of the buttons and labels  #rows, columns, homogenous
  options = Gtk::EXPAND|Gtk::FILL
 
  # line 7
  button_cancel = Gtk::Button.new("cancel")
  button_ok = Gtk::Button.new("OK")
 
  # line 0
  label = Gtk::Label.new("Ruby Patch Maker Tool by t0ka7a")
 
  # line 1
  label2 = Gtk::Label.new("original file: ")
  button_file2 = Gtk::FileChooserButton.new("original file", Gtk::FileChooser::ACTION_OPEN)
 
  # line 2
  label_file3 = Gtk::Label.new("")
  
  # line 3
  label4 = Gtk::Label.new("patched file: ")
  button_file4 = Gtk::FileChooserButton.new("patched file",Gtk::FileChooser::ACTION_OPEN)
  
  # line 4
  label_file5 = Gtk::Label.new("")
  
  #line 5
  label_folder = Gtk::Label.new("")
  
  # line 6
  button_folder = Gtk::Button.new("destinatation for patch")
  
                # child, left_attach, right_attach, top_attach, bottom_attach, x-opt, y-opt, xpad, ypad
  table.attach(label,0,3,0,1,options,options,0,0) #line 0
  table.attach(label2,0,1,1,2,options,options,0,0) # line 1
  table.attach(button_file2,1,3,1,2,options,options,0,0)
  table.attach(label_file3,0,3,2,3,options,options,0,0) # line 2
  table.attach(label4,0,1,3,4,options,options,0,0) # line 3
  table.attach(button_file4,1,3,3,4,options,options,0,0)
  table.attach(label_file5,0,3,4,5,options,options,0,0) # line 4
  table.attach(button_folder,0,3,5,6,options,options,0,0) # line 5
  table.attach(label_folder,0,3,6,7,options,options,0,0) # line 6
  table.attach(button_cancel,0,1,7,8,options,options,0,0) # line 7
  table.attach(button_ok,2,3,7,8,options,options,0,0)
 
  window.add(table)
  window.border_width = 10
  window.title = "Ruby Patch Maker Tool 1.0 by t0ka7a"

  #events
  button_file2.signal_connect('selection_changed') do # old file
    label_file3.text = button_file2.filename
    filled[0]=true
  end
  button_file4.signal_connect('selection_changed') do #patched file
    label_file5.text = button_file4.filename
    filled[1]=true
  end
  button_folder.signal_connect('clicked') do #destination for patch
    filled[2]=true
    dialog = Gtk::FileChooserDialog.new(
      "Save Patch As ...",
      window,
      Gtk::FileChooser::ACTION_SAVE,
      nil,
      [ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ],
      [ Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT ]
    )
    dialog.run do |response|
      if response == Gtk::Dialog::RESPONSE_ACCEPT then
        label_folder.text = dialog.filename
        dialog.destroy      
      end
    end
  end
  button_cancel.signal_connect("clicked") { Gtk.main_quit }
  button_ok.signal_connect("clicked") { next_function(label_file3.text,label_file5.text,label_folder.text) if filled[0] and filled[1] and filled[2]}
  window.signal_connect("destroy") { Gtk.main_quit }
  window.show_all
  Gtk.main
end
def compil
 
end
#main
messagebox1
******************************************************************************
  • Code source version ligne de commandes
#!/usr/bin/env ruby

# Ruby Patch Maker Tool 1.0
# created by t0ka7a 2009

# this program stores differences between two files:
# - original file: file before patch
# - new file: file patched
# Then, it returns a patch in ruby.

# ***********************************
# *                                                  *
#     Ruby Patch Maker Tool by t0ka7a 
# *                                                  *
# ***********************************"
#
# How to use:"
# Install ruby from http://www.ruby-lang.org/
# put original file, patched file, and this program in the same directory."
# In Windows, go to \"Start->Execute\""
# Write \"cmd\"."
# Write \"ruby RubyPatchMaker1.0.rb\". Enjoy!"require 'digest/md5'

require 'ftools'

class Modifications
# differences between 2 files
  def initialize
    @data=[]
    @counters=[]
    @modif_text = ""
  end
  
  def push(obj1,obj2)
    @data.push(obj1)
    @counters.push(obj2)
  end
 
  def data
    @data
  end

  def counters
    @counters
  end

  def extract_modifications(name_f_old,name_f_new)
    #returns an array with differences between original and new file.
  
    #open original and patched files
    f_old = File.open(name_f_old.to_s,'r').binmode
    f_new = File.open(name_f_new.to_s,'r').binmode
  
    # test if f_new and f_old have same size
    if File.size(name_f_old)!=File.size(name_f_new) then
      puts "Original and patched files don't have same size. Quit!"
      exit
    end
  
    #declaration
    i=0
    a,b=""
  
    # compares byte number i of original file to byte number i of patched file. Returns an array with differences found
    while b=f_old.getc do
      a=f_new.getc
      if a!=b then
        push(a,i)
      end
      i+=1
    end
  
    #close files
    f_old.close
    f_new.close
  
    # puts lines of program used to load modifications in @modif
    i=0
    while @counters[i] do
      @modif_text << " @data.push(#{@data[i]})\n@counters.push(#{@counters[i]})\n"
      i+=1
    end
  
  end
  def write_program_to_file(name_f_save,name_f_old,md5_f_old)
    # creates a file named xxx_patch.rb with the program of the patch
    file = File.open("#{name_f_save}.rb",'w')
  
    # creates a string with lines of program for the patch
    program = "
#!/usr/bin/env ruby
# created with Ruby Patch Maker Tool by t0ka7a 2009

require 'digest/md5'
require 'ftools'

#----------------------------------------------------------------------------------------------------------------------------------------
# differences between 2 files
class Modifications
  
    def initialize
      @data=[]
      @counters=[]
      @name_p=#{name_f_save.dump}
      @name_f_to_patch=#{name_f_old.dump}
      @md5_f_old=#{md5_f_old.dump}
      @md5_f_new=Digest::MD5.file(@name_f_to_patch).hexdigest if @name_f_to_patch.to_s != \"\"
    
      #{@modif_text}
    
      #-----------------------------------------------------------------------
      #compare md5 of file to patch with md5 calculated when patch was created.
      if @md5_f_old != @md5_f_new then
        puts \"File to patch is not valid. Good version?\"
        return nil
      end
    
      #-----------------------------------------------------------------------
      # copy original_file to original_file.old
      File.copy(@name_f_to_patch.to_s,@name_f_to_patch.to_s+\".old\")
    
      #-----------------------------------------------------------------------
      #patch the file
      f = File.open(@name_f_to_patch.to_s,\'r+\').binmode
      for i in 0...@counters.length
        f.pos=@counters[i].to_i
        f.write(@data[i].chr.to_s)
        end
      f.close
      puts \"File Patched successfully. Enjoy!\"
end

#----------------------------------------------------------------------------------------------------------------------------------------
# main
  puts \"\"
  puts \"\"
  puts \"\"
  puts \"\"
  puts \"\"
  puts \"*************************************************\"
  puts \"*                                               *\"
  puts \"  Patch for \#{#{name_f_save.dump}}\ \"
  puts \"*                                               *\"
  puts \"*************************************************\"
  puts \"\"
  puts \"\"
  puts \"\"
  puts \"\"
  puts \"How to use:\"
  puts \"    put patch in the folder of \#{#{name_f_save.dump}}\.\"
  puts \"    In Windows, go to \\\"Start->Execute\\\"\"
  puts \"    Write \\\"cmd\\\".\"
  puts \"    Write ruby \\\"\#{#{name_f_save.dump}}\.rb\\\". Enjoy!\"
  puts \"\"
  puts \"ready (Y/n)?\"
  exit if gets.chomp != \"Y\"
  modif=Modifications.new
end"
    file.write(program)
    file.close
  end
end

def md5(file)
  # returns md5 of file
  digest = Digest::MD5.file(file).hexdigest if file.to_s != ""
  return digest
end
def main
  puts ""
  puts ""
  puts ""
  puts ""
  puts ""
  puts "***********************************"
  puts "*                                 *"
  puts "* Ruby Patch Maker Tool by t0ka7a *"
  puts "*                                 *"
  puts "***********************************"
  puts ""
  puts ""
  puts ""
  puts ""
  puts "How to use:"
  puts "    put original file, patched file, and this program in the same directory."
  puts "    In Windows, go to \"Start->Execute\""
  puts "    Write \"cmd\"."
  puts "    Write \"ruby RubyPatchMaker1.0.rb\". Enjoy!"
  puts ""
  puts "ready (Y/n)?"
  exit if gets.chomp != "Y"
  puts ""
  puts "original file name?"
  name_f_old=gets.chomp
  if !File.exist?(name_f_old) then
    puts "File doesn't exist. Quit!"
    exit
  end
  puts ""
  puts "patched file name?"
  name_f_new=gets.chomp
  puts ""
  if !File.exist?(name_f_new) then
    puts "File doesn't exist. Quit!"
    return nil
  end
  name_f_save = name_f_old + "_patch"
 
  md5_f_old=md5(name_f_old)
  modif=Modifications.new
  modif.extract_modifications(name_f_old,name_f_new)
  modif.write_program_to_file(name_f_save,name_f_old,md5_f_old)
  puts "patch successfully created: #{name_f_save}.rb."
end
main



Aucun commentaire:

Enregistrer un commentaire