unit USmartPatcher; interface uses Windows, SysUtils, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Classes, Graphics, StrUtils; type TFPatcher = class(TForm) Label1: TLabel; Label2: TLabel; GroupOrigFileset: TGroupBox; Label3: TLabel; Label4: TLabel; OpenDialog: TOpenDialog; EditOrigFile: TEdit; EditModFile: TEdit; ButBrowseOrig: TButton; ButBrowseMod: TButton; GroupNewFile: TGroupBox; Label5: TLabel; EditNewFile: TEdit; ButtBrowseNew: TButton; ImgLogoRiddickTop: TImage; ButtSmartPatch: TButton; LogBox: TListBox; Label6: TLabel; CheckBackup: TCheckBox; ButtExit: TButton; ImgLogoRiddickLeft: TImage; CheckExtSearch: TCheckBox; procedure ButBrowseOrigClick(Sender: TObject); procedure ButtExitClick(Sender: TObject); procedure ButBrowseModClick(Sender: TObject); procedure ButtBrowseNewClick(Sender: TObject); procedure ButtSmartPatchClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; var FPatcher: TFPatcher; implementation {$R *.dfm} const PreAftLen: integer = 10; PreAftSrc: integer = $4000; type TModRec = record Offset: longint; Length: longint; Orig: AnsiString; New: AnsiString; Pre: AnsiString; Aft: AnsiString; end; TModRecA = array of TModRec; var Mods: TModRecA; procedure Log(x: string); var i: integer; begin while (FPatcher.LogBox.Count>500) do begin FPatcher.LogBox.Items.Delete(0); end; i := FPatcher.LogBox.Items.Add(x); FPatcher.LogBox.TopIndex := i; end; function Dec2Hex(d: longint): string; const hexset: string[16] = '0123456789abcdef'; begin Result := ''; while (d>=16) do begin Result := hexset[(d MOD 16)+1] + Result; d := d DIV 16; end; Result := hexset[d+1] + Result; if (Length(Result) MOD 2=1) then Result := '0'+Result; end; function FindModsInFiles(g1,g2: string): TModRecA; var f1,f2: file of char; Buf1, Buf2: array[1..64] of char; i, i1, i2: integer; o: longint; lastinequal: boolean; resi: longint; Last10, Next10: AnsiString; begin try AssignFile(f1,g1); AssignFile(f2,g2); FileMode := fmOpenRead; Reset(f1); Reset(f2); repeat if (FilePos(f1)<>FilePos(f2)) then begin Log('ERROR while reading! File positions are different. Aborting.'); CloseFile(f1); CloseFile(f2); Exit; end; o := FilePos(f1); BlockRead(f1,Buf1,SizeOf(Buf1),i1); BlockRead(f2,Buf2,SizeOf(Buf2),i2); if (i2<>i1) then begin Log('ERROR while reading! Different sized blocks were read. Aborting.'); CloseFile(f1); CloseFile(f2); Exit; end; lastinequal := false; for i:=1 to i1 do begin if (Buf1[i]<>Buf2[i]) then begin if (lastinequal) then begin resi := Length(Result)-1; Inc(Result[resi].Length); Result[resi].Orig := Result[resi].Orig + Buf1[i]; Result[resi].New := Result[resi].New + Buf2[i]; end else begin resi := Length(Result); SetLength(Result, resi+1); Result[resi].Offset := o+i-1; Result[resi].Length := 1; Result[resi].Orig := Buf1[i]; Result[resi].New := Buf2[i]; Result[resi].Pre := Last10; // Result[resi].Aft := Next10; end; lastinequal := true; end else begin if (lastinequal) then begin Last10 := ''; resi := Length(Result)-1; Log('Found '+IntToStr(Result[resi].Length)+' different Byte(s) at Offset 0x'+UpperCase(Dec2Hex(Result[resi].Offset))); end; lastinequal := false; end; if (Length(Last10)>> Found '+IntToStr(Length(Result))+' different areas.'); except on e: Exception do Log('Exception: '+e.Message); end; end; function ChangeNewFile(g: string; m: TModRecA): boolean; var f: file of char; i,j: integer; o, lo, mo: longint; Last: AnsiString; Buf: char; foundit, missedany: boolean; label NextTurn; begin try missedany := false; AssignFile(f, g); FileMode := fmOpenReadWrite; Reset(f); Last := ''; o := Mods[0].Offset - PreAftSrc; lo := 0; if (o<0) then o := 0; Seek(f,o); for i:=0 to Length(Mods)-1 do begin foundit := false; NextTurn: if (FPatcher.CheckExtSearch.Checked) then mo := FileSize(f) else mo := Mods[i].Offset+2*PreAftSrc; while (FilePos(f)2) then begin Mods[i].Pre := RightBStr(Mods[i].Pre,Length(Mods[i].Pre)-1); Log('Pre not found. Dropping one byte... (now '+IntToStr(Length(Mods[i].Pre))+' bytes)'); if (lo>0) then o := lo else o := Mods[i].Offset - PreAftSrc; if (o<0) then o:=0; Seek(f,o); goto NextTurn; end else if (NOT foundit) then begin Log('Couldn''t find this mod. Trying next one...'); missedany := true; end; end; CloseFile(f); if (NOT missedany) then Result := true else Result := false; except on e: Exception do begin Log('Exception: '+e.Message); Result := falsE; end; end; end; function CheckFileSizes(g1,g2: string): boolean; var f1,f2: file of char; s1,s2: longint; begin try AssignFile(f1,g1); AssignFile(f2,g2); FileMode := fmOpenRead; Reset(f1); Reset(f2); s1 := FileSize(f1); s2 := FileSize(f2); CloseFile(f1); CloseFile(f2); if (s1=s2) then Result := true else Result := false; except on e: Exception do begin Log('Exception: '+e.Message); Result := false; end; end; end; procedure TFPatcher.ButBrowseOrigClick(Sender: TObject); begin if (Length(ExtractFilePath(EditOrigFile.Text))>0) then OpenDialog.InitialDir := ExtractFilePath(EditOrigFile.Text); OpenDialog.Title := 'Select original file from old version'; if (OpenDialog.Execute) then begin EditOrigFile.Text := OpenDialog.FileName; Log('Original file: '+ExtractFilename(OpenDialog.FileName)); end else Log('Aborted OpenDialog. (Original file)'); end; procedure TFPatcher.ButtExitClick(Sender: TObject); begin Application.Terminate; end; procedure TFPatcher.ButBrowseModClick(Sender: TObject); begin if (Length(ExtractFilePath(EditModFile.Text))>0) then OpenDialog.InitialDir := ExtractFilePath(EditModFile.Text); OpenDialog.Title := 'Select modified file from old version'; if (OpenDialog.Execute) then begin EditModFile.Text := OpenDialog.FileName; Log('Original modified file: '+ExtractFilename(OpenDialog.FileName)); end else Log('Aborted OpenDialog. (Original mod file)'); end; procedure TFPatcher.ButtBrowseNewClick(Sender: TObject); begin if (Length(ExtractFilePath(EditNewFile.Text))>0) then OpenDialog.InitialDir := ExtractFilePath(EditNewFile.Text); OpenDialog.Title := 'Select file with new version'; if (OpenDialog.Execute) then begin EditNewFile.Text := OpenDialog.FileName; Log('New version file: '+ExtractFilename(OpenDialog.FileName)); end else Log('Aborted OpenDialog. (New ver file)'); end; function Backup(f: string): boolean; var t: string; begin t := ChangeFileExt(f,'.bak'); Log('Backup to: '+t); if (FileExists(t)) then begin Log('File exists! Let''s hope that this is an old backup of this file. Skipping backup.'); Result := true; end else Result := CopyFile(PChar(f),PChar(t),true); end; procedure TFPatcher.ButtSmartPatchClick(Sender: TObject); begin if (ExtractFileName(EditOrigFile.Text)='') then begin Log('ERROR! Please select the original file of the OLD version!'); Exit; end; if (ExtractFileName(EditModFile.Text)='') then begin Log('ERROR! Please select the modified file of the OLD version!'); Exit; end; if (ExtractFileName(EditNewFile.Text)='') then begin Log('ERROR! Please select the original file of the NEW version!'); Exit; end; if (EditOrigFile.Text = EditModFile.Text) then begin Log('ERROR! Please select different original files!'); Exit; end; if (EditOrigFile.Text = EditNewFile.Text) OR (EditModFile.Text = EditNewFile.Text) then begin Log('ERROR! New version file can''t be the same as old version file!'); Exit; end; if (NOT CheckFileSizes(EditOrigFile.Text, EditModFile.Text)) then begin Log('ERROR! File sizes of original files differ!'); Exit; end; Log('No errors until here. Start modification parsing...'); Mods := FindModsInFiles(EditOrigFile.Text, EditModFile.Text); Log('Done finding differences in original fileset.'); if (CheckBackup.Checked) then begin if (Backup(EditNewFile.Text)) then begin Log('Backup done.'); end else begin Log('ERROR! Could not backup file. Aborting...'); Exit; end; end else Log('Skipping backup of new file.'); if (ChangeNewFile(EditNewFile.Text, Mods)) then begin Log('Done making changes to new file. File should work now!'); end else begin Log('ERROR! Could not apply all changes to new file.'); end; end; procedure TFPatcher.FormCreate(Sender: TObject); begin Log('Welcome to SmartPatcher by riddick'); Log(''); Log('NOTE: You can damage your files - use this app wisely!'); Log(DupeString('-',140)); end; end.