Hace algún tiempo hablé de como recuperar imágenes jpg de un disco o archivo (aquí y aquí), ahora, además de depurar un poco el código de ambos programas, voy a aprovechar para unirlos en uno solo. El código resultante se puede compilar tanto en delphi como en freepascal, por lo que puede ser usado perfectamente en windows o en linux.
El funcionamiento del programa sigue siendo el mismo que el de los dos anteriores, buscamos la marca que indica el comienzo de un archivo jpeg, es decir, los bytes FFD8FF. Una vez encontrada, intentamos obtener de los bytes que siguen a esos tres una imagen jpeg, para luego continuar la búsqueda en el bloque siguiente.
El código es el siguiente (es una aplicación de consola):
program recover; {$APPTYPE CONSOLE} uses SysUtils, Classes; type EGetOptError = class(Exception); var Options: record BlockSize: Integer; InputFile: String; FormatStr: String; Index: Integer; end; procedure CopyJpegFrom(Src, Dst: TStream); var Buffer: PByteArray; Size: Integer; begin GetMem(Buffer,$10000); try while TRUE do begin FillChar(Buffer^,4,#0); Src.Read(Buffer^,4); Size:= ((Buffer[2] shl 8) + Buffer[3]) - 2; case Buffer[1] of $01,$D0..$D8: begin Dst.WriteBuffer(Buffer^,2); Src.Seek(-2,soFromCurrent); end; $D9: begin Dst.WriteBuffer(Buffer^,2); // Salimos del procedure Exit; end; $DA: begin Dst.WriteBuffer(Buffer^,2); Src.Seek(-2,soFromCurrent); while TRUE do begin Src.ReadBuffer(Buffer^,1); if Buffer[0] = $FF then begin Src.ReadBuffer(Buffer^,1); if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then begin Src.Seek(-2,soFromCurrent); break; end else begin Buffer[1]:= Buffer[0]; Buffer[0]:= $FF; Dst.WriteBuffer(Buffer^,2); end; end else Dst.WriteBuffer(Buffer^,1); end; end else begin Dst.WriteBuffer(Buffer^,4); Src.ReadBuffer(Buffer^,Size); Dst.WriteBuffer(Buffer^,Size); end; end; end; finally FreeMem(Buffer); end; end; procedure SaveJpegToFile(Src: TStream; Filename: String); var Stream: TMemoryStream; begin Stream:= TMemoryStream.Create; try CopyJpegFrom(Src, Stream); Stream.SaveToFile(Filename); finally Stream.Free; end; end; procedure ScanFile(Stream: TStream; FormatStr: String; Index: Integer); overload; var B: Byte; Leidos: Integer; Posicion: int64; Str: String; begin // Vamos leyendo byte a byte el archivo Leidos:= Stream.Read(B,1); while Leidos = 1 do if B = $FF then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $D8 then begin Leidos:= Stream.Read(B,1); if Leidos = 1 then if B = $FF then begin // Hasta encontrar la combinacion $FFD8FF Writeln('Encontrada una posible imagen'); // Guardamos la posicion Posicion:= Stream.Position; Stream.Seek(-3,soFromCurrent); Str:= Format(FormatStr,[index]); try SaveJpegToFile(Stream,Str); inc(index); Writeln('Recuperada: ' + Str); except // Si se produce un error lo mostramos, pero no salimos del bucle On E: Exception do Writeln('Error: ' + E.Message); end; // Volvemos a la posicion en la que estabamos Stream.Position:= Posicion; end; end; end else Leidos:= Stream.Read(B,1); end; const MAXSIZE = 8*1024*1024; procedure ScanFile(Stream: TStream; FormatStr: String; Index: Integer; BlockSize: Integer); overload; var Block: PChar; Leidos: Integer; Posicion: int64; Str: String; TmpStream: TMemorystream; begin GetMem(Block,BlockSize); try // Vamos leyendo bloque a bloque el archivo Leidos:= Stream.Read(Block^,BlockSize); while Leidos = BlockSize do begin // Hasta encontrar la combinacion $FFD8FF if CompareMem(Block,PChar(#$FF#$D8#$FF),3) then begin Posicion:= Stream.Position; TmpStream:= TMemoryStream.Create; try TmpStream.Write(Block^,Leidos); TmpStream.CopyFrom(Stream,MAXSIZE - (MAXSIZE mod BlockSize)); TmpStream.Position:= 0; Writeln('Encontrada una posible imagen.'); Str:= Format(FormatStr,[index]); // Intentamos recuperar la imagen try SaveJpegToFile(TmpStream,Str); inc(index); Writeln('Recuperada: ' + Str); except // Si se produce un error lo mostramos, pero no salimos del bucle On E: Exception do Writeln('Error: ' + E.Message); end; finally TmpStream.Free; end; // Nos movemos hasta el siguiente cluster Stream.Position:= Posicion; end; Leidos:= Stream.Read(Block^,BlockSize); end; finally FreeMem(Block); end; end; procedure WriteHelp; begin Writeln; Writeln('Uso: recover [opciones]'); Writeln(' --bs'); Writeln(' Medida del bloque en el archivo de entrada'); Writeln(' --fs'); Writeln(' Cadena para dar formato a las imagenes recuperadas.'); Writeln(' --help -h'); Writeln(' Muestra este mensaje de ayuda'); Writeln(' --id'); Writeln(' Valor inicial del indice'); Writeln(' --if'); Writeln(' Nombre del archivo o disco a explorar'); Writeln; Writeln('Ejemplos:'); Writeln(' recover --if=\\.\F: --bs=4096 --fs=%d.jpg --id=5'); Writeln(' Busca y repuepar imagenes del disco F, y las guarda'); Writeln(' como 5.jpg, 6.jpg, ...'); Writeln(' recover --if=thumb.db --fs=%d.jpg'); Writeln(' Busca y repuepar imagenes en el archivo "thumb.db", y'); Writeln(' las guarda como 0.jpg, 1.jpg, ...'); Writeln; end; function Starts(Sub, Str: String): Boolean; begin Result:= SameText(Sub,Copy(Str,1,Length(sub))); end; procedure Getopt; var i: Integer; Str: String; begin FillChar(Options,Sizeof(Options),#0); if ParamCount > 0 then for i:= 1 to ParamCount do begin Str:= ParamStr(i); if (Copy(Str,1,2) = '--') and (Length(Str) > 2) then begin if Starts('--if=',Str) then begin if Options.InputFile = EmptyStr then Options.InputFile:= Copy(Str,Length('--if=') + 1,MAXINT) else raise EGetOptError.Create(Str); end else if Starts('--id=',Str) then begin if Options.Index = 0 then Options.Index:= StrToInt(Copy(Str,Length('--id=') + 1,MAXINT)) else raise EGetOptError.Create(Str); end else if Starts('--fs=',Str) then begin if Options.FormatStr = EmptyStr then Options.FormatStr:= Copy(Str,Length('--fs=') + 1,MAXINT) else raise EGetOptError.Create(Str); end else if Starts('--bs=',Str) then begin if Options.BlockSize = 0 then Options.BlockSize:= StrToInt(Copy(Str,Length('--bs=') + 1,MAXINT)) else raise EGetOptError.Create(Str); end else if SameText('--help',Str) then begin WriteHelp; Halt; end else raise EGetOptError.Create(Str); end else if (Copy(Str,1,1) = '-') and (Length(Str) > 1) then begin if SameText('-h',Str) then begin WriteHelp; Halt; end else raise EGetOptError.Create(Str); end else begin raise EGetOptError.Create(Str); end; end else begin WriteHelp; Halt; end; end; procedure WriteHeader; begin Writeln; Writeln('Recover 0.2 - Copyright (c) 2007 Domingo Seoane'); Writeln('<a href="http://delphi.jmrds.com'">http://delphi.jmrds.com'</a>); Writeln; end; var Stream: TFileStream; begin WriteHeader; try GetOpt; TFileStream.Create(Format(Options.FormatStr,[0]),fmCreate).Free; DeleteFile(Format(Options.FormatStr,[0])); Stream:= TFileStream.Create(Options.InputFile,fmOpenRead or fmShareDenyNone); try Writeln('Explorando el archivo: ' + Options.InputFile); if Options.BlockSize > 0 then ScanFile(Stream,Options.FormatStr,Options.Index,Options.BlockSize) else ScanFile(Stream,Options.FormatStr,Options.Index); finally Stream.Free; end; except // Si se produce un error, lo mostramos On E: Exception do Writeln('Error: ' + E.Message); end; end.
El código completo y el programa ya compilado para windows, los puedes bajar de aquí
Para compilar el código en linux con freepascal utiliza un comando como este:
fpc -XX -Sd recover.dpr
El programa recibe cuatro parámetros de los que solo dos son obligatorios, --if y --fs. El archivo de entrada (--if) es el archivo o disco donde se buscaran las imágenes, puede ser una archivo normal, por ejemplo "thumb.db", o un disco, por ejemplo "\\.\F:" en windows o "/dev/sda" en linux. El parámetro --fs sirve para generar el nombre con el que se guardaran las imágenes recuperadas, debe contener la cadena %d que sera sustituida por un numero creciente. Los otros dos parámetros (--bs y --id) son opcionales, y sirven para indicar el tamaño del bloque (necesario para los discos duros) y el numero por el que se empiezan a numerar las imágenes recuperadas.
Algunos ejemplo de uso:
recover --if=\\.\F: --bs=4096 --fs=%d.jpg
recover --if=/dev/sda --bs=4096 --fs=%d.jpg
recover --if=thumb.db --fs=%d.jpg --id=100
Comentarios
I think that this program
I think that this program also recover actual (non deleted) images. In fact it recover all possible jpg images from disk. On a disk i have about 3000 jpg images and it recover all of them + many miniatures. Am i right?
Thanks. nh.
Tienes razón, el programa
Tienes razón, el programa recupera todas las imágenes jpg que encuentra en el disco, sin importar si las imágenes han sido borradas o no. Esto es un problema cuando tienes muchas imágenes en un disco, una posible mejora sería descartar las que ya existen, o incluso hacer algún tipo de vista previa para poder seleccionar las que se quieren recuperar, pero eso lo dejo para un futuro.
gracias - a screening or
gracias - a screening or filter would be not easy to do since we have no name on the files. Regular restoration programs recover deleted file by name i guess. Here it is different. Thank again - from montreal canada
Cordial saludo Domingo. Ojalá
Cordial saludo Domingo.
Ojalá aún sigas atendiendo tu sitio.
Estas a punto de resolverme una necesidad grande que tengo, o tenía.
Estaba ampliando un partición de mi disco y hubo un corte de energía, cuando volví a tratar de ver su contenido (al rededor de 10000 fotos y videos), no pude verlas. Intenté recuperarlas con varios programas que hay en el mercado (Easyrecory, GetdataBack) y ecuperó una buena parte, pero tengo pendiente recuperara al rededor de 3000.
Tu programa me viene como anillo al dedo. Sin embargo tengo algunas inquietudes al respecto:
1. Dado que se trata de hacer un barrido de un dd de 250G, consideras viable utilizarlo ?. No importa que se demore días en lograrlo.
2. Revisé los fuentes y se ocurre que utilizarlo para otro tipo de archivos sólo requiere conocer la "firma" del tipo de archivo que se desea recuperar y hacer los ajustes a tu fuente. Es correcto ?
Es inmensa la gratitud que te manifiesto, sólo por darme la esperanza de poder recuperar los archivo.
Te felicito por el programa,
Te felicito por el programa, lo que no entiendo si detecta los distintos formatos de disco (Fat32,nfts,...). Aunque veo que aguanta los formatos de linux.
Supongo que según el formato se tendría que reajustar los parámetros.
Que al final se pueda usar a base de comandos linux, lo hace una herramienta muy eficaz. Por ejemplo en php podemos recurrir a este programa en la consola.
Lee cualquier formato de
Lee cualquier formato de disco ya que lee "físicamente" el disco, independientemente del sistema de ficheros que se este utilizando.