Recuperar imagenes jpeg

El siguiente programa sirve para recuperar imágenes jpeg borradas. Se basa en la suposición de que la imagen que se quiere recuperar esta guardada en clusteres consecutivos dentro del disco duro. Esta suposición no siempre tiene que ser cierta, pero si lo es en la mayoría de los casos. Así que si hay suerte, podremos recuperar la imagen.

El proceso es sencillo, examinamos cluster a cluster, buscando uno cuyos tres primeros bytes coincidan con los tres primeros bytes de un archivo jpeg. Estos bytes, para cualquier archivo jpeg, son FFD8FF. Una vez que encontramos esa coincidencia intentamos reconstruir la imagen a partir de ese cluster y los siguientes. Una vez finalizada la reconstrucción, tanto si tuvo éxito como si no, regresamos hasta el cluster siguiente y continuamos la búsqueda.

El programa necesita que se le pasen al menos dos parámetros, el archivo de entrada y la plantilla que sirve para dar nombre a las imágenes recuperadas. También se puede incluir un tercer parámetro que indica el tamaño del cluster a utilizar.

El archivo de entrada puede ser la imagen de un disco (una imagen .iso, o una imagen de un disco duro), o puede ser directamente el propio disco. Para este ultimo caso utilizaremos el siguiente parámetro \\.\F: donde F es la letra que identifica el disco.

La plantilla tiene que incluir "%d", esta variable sera sustituida por un valor que se va incrementando. Por ejemplo, si queremos guardar las imágenes en C:\Temp utilizaremos C:\Temp\%d.jpg

El ultimo parámetro sirve para determinar el tamaño del cluster. El tamaño que se utilizara es igual al valor del parámetro multiplicado por 512. El valor mínimo es 1, en algunas memorias usb deberías de usar 2, en los discos duros un valor de 8 podría ser el adecuado. Cuanto mayor es el tamaño mas rápida es la búsqueda, pero si indicamos un tamaño demasiado grande el programa no funciona. Así que lo mejor es averiguar el tamaño del cluster del disco duro a examinar, o utilizar 1 que siempre funciona. Si no se usa este parámetro el valor por defecto es 1.

El código es el siguiente (es una aplicación de consola):

program recover;
 
{$APPTYPE CONSOLE}
 
uses Windows, SysUtils, Classes;
 
procedure RecuperarJpeg(Origen: TStream; FormatStr: String; var Index: Integer);
var
  Destino: TMemoryStream;
  Buffer: array[0..$FFFF] of Byte;
  Header: array[1..4] of Byte;
  Size: Integer;
begin
  Destino:= TMemoryStream.Create;
  try
    // En este bucle vamos copiando segmento a segmento
    while TRUE do
    begin
      Origen.ReadBuffer(Header,4);
      Size:= ((Header[3] shl 8) + Header[4]) - 2;
      case Header[2] of
        $01,$D0..$D8: begin
                        // Estos segmentos solo miden dos bytes,
                        // asi que escribimos 2 bytes y
                        Destino.WriteBuffer(Header,2);
                        // retrocedemos otros dos hacia atras.
                        Origen.Seek(-2,soFromCurrent);
                      end;
        $D9: begin
               // Este indica que llegamos al final del archivo jpeg
               Destino.WriteBuffer(Header,2);
               // Guardamos el archivo usando la plantilla
               Destino.SaveToFile(Format(FormatStr,[Index]));
               Writeln('Recuperada: ' + Format(FormatStr,[Index]));
               // Incrementamos el indice
               inc(Index);
               // Salimos del procedure
               Exit;
             end;
        $DA: begin
               // Este segmento no tiene tamaño
               Destino.WriteBuffer(Header,2);
               Origen.Seek(-2,soFromCurrent);
               // Buscamos el final byte a byte
               while TRUE do
               begin
                 Origen.ReadBuffer(Buffer,1);
                 // Posible comienzo de otro segmento
                 if Buffer[0] = $FF then
                 begin
                   Origen.ReadBuffer(Buffer,1);
                   // $FF00 = Secuencia de escape
                   // $FFD0 .. $FFD7 = Se deben de ignorar
                   if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then
                   begin
                     // Si encontramos el comienzo de otro segmento,
                     // retrocedemos dos bytes, y volvemos al bucle principal
                     Origen.Seek(-2,soFromCurrent);
                     break;
                   end else
                   begin
                     // Si no es el comienzo de otro segmento, escribimos
                     // y continuamos
                     Header[1]:= $FF;
                     Header[2]:= Buffer[0];
                     Destino.WriteBuffer(Header,2);
                   end;  
                 end else
                   Destino.WriteBuffer(Buffer,1);
               end;
             end
        else
        begin
          // Cualquier otro segmento lo copiamos directamente
          Origen.ReadBuffer(Buffer,Size);
          Destino.WriteBuffer(Header,4);
          Destino.WriteBuffer(Buffer,Size);
        end;
      end;
    end;
  finally
    Destino.Free;
  end;
end;
 
procedure Scan(Stream: TStream; ClusterSize: Integer; FormatStr: String);
var
  Buffer: PChar;
  Indice: Integer;
  Leidos: Integer;
  MemStream: TMemoryStream;
  Posicion: int64;
begin
  Indice:= 0;
  // Reservamos memoria
  GetMem(Buffer,ClusterSize);
  try
    // Leemos un cluster completo
    Leidos:= Stream.Read(Buffer^,ClusterSize);
    while Leidos > 0 do
    begin
      // Comprobamos si los primeros bytes del cluter coinciden con
      // el inicio de un archivo jpeg.
      if CompareMem(Buffer,PChar(#$FF#$D8#$FF),3) then
      begin
        // Guardamos la posicion
        Posicion:= Stream.Position;
        try
          // Cargamos en un stream este cluster y los siguientes 8 Megas
          MemStream:= TMemoryStream.Create;
          try
            MemStream.Write(Buffer^,Leidos);
            MemStream.CopyFrom(Stream,8*1024*1024);
            MemStream.Position:= 0;
            Writeln('Encontrada una posible imagen.');
            // Intentamos recuperar la imagen
            RecuperarJpeg(MemStream,FormatStr,Indice);
          finally
            MemStream.Free;
          end;
        except
          // Si se produce un error lo mostramos, pero no salimos del bucle
          On E: Exception do
             Writeln('Error: ' + E.Message);
        end;
        // Nos movemos hasta el siguiente cluster
        Stream.Position:= Posicion + ClusterSize;
      end;
      Leidos:= Stream.Read(Buffer^,ClusterSize);
    end;
  finally
    FreeMem(Buffer);
  end;
end;
 
var
  TickCount: Cardinal;
  Stream: TFileStream;
begin
  try
    // Necesitamos al menos 2 parametros
    if ParamCount > 1 then
    begin
      // Comprobamos que se pueden crear archivos en la ruta indicada
      TFileStream.Create(Format(ParamStr(2),[0]),fmCreate).Free;
      // Borramos el archivo de prueba
      DeleteFile(Format(ParamStr(2),[0]));
      // Abrimos el archivo de origen
      Stream:= TFileStream.Create(ParamStr(1),fmOpenRead or fmShareDenyNone);
      try
        // Si llegamos hasta aqui podemos leer y escribir
        TickCount:= GetTickCount;
        // Iniciamos la busqueda
        Writeln('Comenzando la busqueda ...');
        Scan(Stream,StrToIntDef(ParamStr(3),1) * 512,ParamStr(2));
        // Mostramos el tiempo que hemos tardado
        Writeln(Format('Tiempo transcurrido: %d ms',[GetTickCount - TickCount]));
      finally
        Stream.Free;
      end;
    end else
      // Si no nos pasan dos parametros, imprimimos una pequeña ayuda
      Writeln('Help: ' + ExtractFilename(ParamStr(0)) + ' <File> <Format> [ClusterSize]');
  except
    On E: Exception do
      // Si algo sale mal, escribimos la descripcion del error
      Writeln('Error: ' + E.Message);
  end;
end.

Por ejemplo, para buscar imágenes en el disco F, guardarlas en C:\Temp y usar un tamaño de cluster de 1024 bytes, usaríamos el siguiente comando:

recover \\.\F: C:\Temp\%d.jpg 2

El código completo y programa, los puedes bajar de aquí

Comentarios

Gracias por este estupendo programa, me ha servido y mucho....

Saludos

Muy bueno !!!