NSIS と VPatch を利用したバイナリパッチの作成方法

バイナリパッチの作成には WDiffudm などが使用されていますが、WDiff は拡張性が低くかったり、udm は割と自由だけど有料、といった感じですので、拡張性が高く無料なパッチを NSISVPatch で作成してみたいと思います。NSIS をインストールすると、VPatch が付属してきます。
パッチ自体は VPatch のみで作成できますが、VPatch で作成されたパッチは自己展開パッチですが、パッチファイル名、対象ファイル名、出力ファイル名を指定してあげなければならず、WDiff のパッチのように、フォルダに突っ込んで実行という手軽さがありません。そこで NSIS と組み合わせる必要があります。

パッチファイルの作成

"C:\Program Files\NSIS\Bin" に "GenPat.exe" があります。パッチを作成するには

genpat oldfile.txt newfile.txt patch.pat

という風に実行します。出力される patch.pat ファイルが VPatch のパッチファイルです。VPatch の最新版*1には VPatchGUI.exe が付属していて、そちらは GUI でパッチの作成ができます。

VPatch の example.nsi を改造

NSIS + VPatch の使用方法は、"C:\Program Files\NSIS\Examples\VPatch\example.nsi" を参考にします。そのままでは旧式のインストーラーが作成されるので、これを Modern UI に変更してみました。動作は変わらないので無理に変更する必要もありませんが見た目も大事だと思います。以下ソースファイル。

;VPatch example
;Written by wwwcfe

;--------------------------------
;Include Modern UI

  !include "MUI2.nsh"

;--------------------------------
;General
  ; The name of the installer
  Name "Installer Name"
  ; The file to write
  OutFile "patcher.exe"
  ; The default installation directory
  InstallDir "$PROGRAMFILES\TargetFolder"
  ; Show details
  ShowInstDetails show

;--------------------------------
;Pages

  !insertmacro MUI_PAGE_LICENSE "readme.txt"
  !insertmacro MUI_PAGE_DIRECTORY
  !insertmacro MUI_PAGE_INSTFILES

;--------------------------------
;Languages
 
  !insertmacro MUI_LANGUAGE "Japanese"

;--------------------------------
;  The normal way to use VPatch
;--------------------------------
!include "VPatchLib.nsh"

Section "Update file"
  ; Set output path to the installation directory
  SetOutPath $INSTDIR
  
  ; Update the file - it will be replaced with the new version
  DetailPrint "Updating updatefile.txt using patch..."
  !insertmacro VPatchFile "patch.pat" "$INSTDIR\target.txt" "$INSTDIR\temporaryfile.txt"
  
SectionEnd

後はpatch.pat, readme.txt, example.nsi をフォルダにまとめて、example.nsi を右クリックして "Compile NSIS Script" でコンパイルします。
試しに "C:\Program Folder\TargetFolder\target.txt" (中身は oldfile.txt) を配置して実行すると無事 newfile.txt の内容に更新されます。

バックアップの作成機能を追加する

しかしそのままではバックアップが作成されないので元に戻すことができません。なので、!include している "C:\Program Files\NSIS\Include\VPatchLib.nsh" を改造してみます。といっても一行書き換えるだけです。

; PatchLib v3.0
; =============
;
; Library with macro for use with VPatch (DLL version) in NSIS 2.0.5+
; Created by Koen van de Sande

!include LogicLib.nsh

!macro VPatchFile PATCHDATA SOURCEFILE TEMPFILE

  Push $1
  Push $2
  Push $3
  Push $4

  Push ${SOURCEFILE}
  Push ${TEMPFILE}

  Pop $2 # temp file
  Pop $3 # source file

  InitPluginsDir
  GetTempFileName $1 $PLUGINSDIR
  File /oname=$1 ${PATCHDATA}

  vpatch::vpatchfile $1 $3 $2
  Pop $4
  DetailPrint $4

  StrCpy $4 $4 2
  ${Unless} $4 == "OK"
    SetErrors
  ${EndIf}

  ${If} ${FileExists} $2
    ; ここを書き換えます。
    Rename /REBOOTOK $3 $3.bak ; original: Delete$3
    Rename /REBOOTOK $2 $3
  ${EndIf}

  Delete $1

  Pop $4
  Pop $3
  Pop $2
  Pop $1

!macroend

これでパッチが正常に当てられた場合に、元ファイルを 元ファイル名.bak に変更するようになります。オリジナルではこの部分で元ファイルが削除されていました。

あとがき

NSIS は自由度が高くて便利です。しかし、スクリプトが独特なので覚えるのが少し面倒ではあります。それに一応 VPatch のみでパッチファイルの作成を完結させることができるので、手間が増えたといえます。VPatch が NSIS プラグインに対応しているので VPatch を使用しましたが、Inno Setup に VPatch を入れてもいいわけですし、単にバッチファイルを使ったり、自己展開書庫などを使用してもいいわけです。なので NSIS + VPatch という組み合わせにこだわらず、NSIS + bsdiff だとか、InnoSetup + VPatch だとかでもいいとおもいます。正直なところ WDiff で十分なので、WDiff って良くできているなぁと改めて思いました。
それから新しいパッチ作成ソフト File Diff Patcher を発見しました (id:shiku_otomiya:20090516)。 複数ファイルへのパッチなどには対応していませんが、今後に期待しています。
なお、ディレクトリを比較してパッチを作成したい場合は、nsisPatchGenerator を使用するといいかもしれません(未確認)。

*1:NSIS 付属でなく、VPatch 公式からダウンロード