[comp.lang.pascal] A program for patching strings inside object files

ajayshah@aludra.usc.edu (Ajay Shah) (05/06/90)

Briefly, the command

	patch -v command.com DIR @#$

finds every occurence of DIR inside command.com, shows you the
context on screen, and replaces by @#$ if you say yes.  Skip the
-v flag and the replacement is automatic.

It is pretty useful as compared with the standard
alternative, which is usually hunting for strings with nu and
precariously editing on screen there...  full information on how to
use it is embedded in the code (procedure help).

---------------------------------------------------------------------------
program PatchFiles;

uses crt, dos;

const
     MaxTableEntries = 1000;

type
    fnstring = string[65];
    rawtable = array[1..MaxTableEntries] of longint;
    tabletype = ^rawtable;
    ByteFile = file of byte;
    CharFile = file of char;

var
   verbose : boolean;

   function exist(fn:fnstring):boolean;
   begin
        exist := fsearch(fn, '.') <> ''
   end;

   procedure Patch(var f:CharFile;
                       where:longint;
                       replacestring:string);
   var
      i:byte;
   begin
        writeln('Patching at ', where);
        seek(f, where);
        for i := 1 to length(replacestring) do
            write(f, replacestring[i])
   end;

   procedure SilentPatch(fname:fnstring;
                         table:tabletype;
                         entries:integer;
                         rs:string);
   var i:1..MaxTableEntries;
       inf:CharFile;
   begin
        assign(inf, fname); reset(inf);
        for i := 1 to entries do
            Patch(inf, table^[i], rs);
        close(inf)
   end;

   function max(i,j:longint):longint;
   begin
        if i >= j then max := i
                  else max := j
   end;

   function min(i,j:longint):longint;
   begin
        if i <= j then min := i
                  else min := j
   end;

   function printable(c:char):boolean;
   const
        PrintableCharacters : set of char
                            = [#32..#255];
   begin
        printable := c in PrintableCharacters
   end;

   procedure Display(var f:CharFile;
                     rmin, rmax, focus : longint;
                     highlightlength:byte);
   var i:longint;
       outc, c:char;
   begin
        seek(f, rmin);
        for i := rmin to rmax do
        begin
             read(f, c);
             if printable(c) then outc := c
                             else outc := #254;

             if (i >= focus) and (i <= (focus+highlightlength))
             then textattr := 15
             else textattr := 7;
             write(outc)
        end;
   end;

   procedure InteractivePatch(fname:fnstring;
                              table:tabletype;
                              entries : integer;
                              rs:string);
   var
      inf:CharFile;
      rmin, rmax, UpperLimit : longint;
      i : 1..MaxTableEntries;

   begin
        assign(inf, fname); reset(inf);
        Upperlimit := filesize(inf);
        for i := 1 to entries do
        begin
             rmin := max (0, table^[i] - 30);
             rmax := min (Upperlimit, table^[i] + 30);
             Display(inf, rmin, rmax, table^[i], length(rs)-1);
             writeln;
             write('Replace? ');
             if upcase(readkey) = 'Y' then
                Patch(inf, table^[i], rs);
             writeln
        end;
   end;

   procedure Work(fname:fnstring;
                  sstring, rstring:string;
                  verbose:boolean);

   label done;

   var inf:CharFile;
       entries : integer;
       table : tabletype;
       address : longint;
       i : byte;
       c : char;
       destruct : boolean;

   begin
        write('Searching...');
        entries := 0; new(table);
        assign(inf, fname); reset(inf);
        repeat
              repeat
                    if eof(inf) then goto done;
                    read(inf, c);
              until c = sstring[1];
              address := filepos(inf);

              {We'll now "try out" that chappie.}
              destruct := false;
              i := 2;
              repeat
                   if eof(inf) then goto done;
                   read(inf, c);
                   if c <> sstring[i] then destruct := true;
                   inc(i);
              until (i > length(sstring)) or destruct;

              if destruct
               then seek(inf, address)
              else {we have a occurence of searchstring}
               begin
                    inc(entries); write('.');
                    table^[entries] := address - 1
               end
        until eof(inf);

done:
    close(inf);
    if entries = 0 then
    begin
         writeln('No occurences of ', sstring, ' found.');
         halt(0)
    end;
    writeln('Finished searching.');
    if verbose then InteractivePatch(fname, table, entries, rstring)
               else SilentPatch(fname, table, entries, rstring)
                    {talk to stdout, though}
   end;

    procedure help;
    const
         NumStrings = 11;
         Strings : array[1..NumStrings] of string
                 = ('Usage:',
                    '        patch [-v] filename string1 string2',
                    '',
                    'filename is the file which is patched.',
                    'You must have length(string1) = length(string2).',
                    '',
                    'Without the verbose flag, every occurence of string1 is replaced by string2.',
                    '',
                    'With verbose on:',
                    'Every occurence of string1 is displayed on screen, along with it''s context.',
                    'Iff you give a goahead, then the patch is made.');
    var i:byte;
    begin
         for i := 1 to NumStrings do writeln(Strings[i]);
         halt(1)
    end;

    procedure courtesy;
    begin
         writeln('Say');
         writeln('      patch');
         writeln('for more help.');
         halt(1)
    end;

var
   firstparam : string;
   filename : fnstring;
   searchstring, replacestring : string;
   
   i : byte;

begin
     if (paramcount = 0) or (paramcount > 4) then help;
     verbose := false;
     firstparam := paramstr(1);
     if firstparam[1] = '-' then {might have a -v here}
     begin
          if upcase(firstparam[2]) = 'V'
             then verbose := true
             else help;
          filename := paramstr(2);
          searchstring := paramstr(3);
          replacestring := paramstr(4);

     end
     else {first parameter isn't -*}
     begin
          filename := paramstr(1);
          searchstring := paramstr(2);
          replacestring := paramstr(3)
     end;

     if length(searchstring) <> length(replacestring) then
     begin
          writeln('Searchstring and Replacestring must be of same length.');
          courtesy
     end;
     if length(searchstring) = 0 then
     begin
          writeln('You have to specify some searchstring.'); courtesy
     end;
     if not exist(filename) then
     begin
          writeln('File ', filename, ' not found.'); courtesy
     end;

     {Now we have all the raw materials.}
     Work(filename, searchstring, replacestring, verbose)
end.
_______________________________________________________________________________
Ajay Shah, (213)747-9991, ajayshah@usc.edu
                              The more things change, the more they stay insane.
_______________________________________________________________________________