/FileUtils.pas

http://github.com/foxblock/PNDTools · Pascal · 211 lines · 135 code · 17 blank · 59 comment · 22 complexity · 64edd67c587b46ed004dd43a442ab284 MD5 · raw file

  1. {******************************************************************************}
  2. { }
  3. { Functions for byte-level file interaction (looking for and }
  4. { appending data/bytes) }
  5. { }
  6. { -------------------------------------------------------------------------- }
  7. { }
  8. { PNDTools is Copyright ©2011-2013 Janek Schäfer }
  9. { }
  10. { This file is part of PNDTools }
  11. { }
  12. { PNDTools is free software: you can redistribute it and/or modify }
  13. { it under the terms of the GNU General Public License as published by }
  14. { the Free Software Foundation, either version 3 of the License, or }
  15. { (at your option) any later version. }
  16. { }
  17. { PNDTools is distributed in the hope that it will be useful, }
  18. { but WITHOUT ANY WARRANTY; without even the implied warranty of }
  19. { MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the }
  20. { GNU General Public License for more details. }
  21. { }
  22. { You should have received a copy of the GNU General Public License }
  23. { along with this program. If not, see <http://www.gnu.org/licenses/>. }
  24. { }
  25. {******************************************************************************}
  26. unit FileUtils;
  27. {$IFDEF FPC}
  28. {$MODE DELPHI}
  29. {$ENDIF}
  30. interface
  31. uses Classes, SysUtils;
  32. { Appends the passed file (FileName) to the Stream, uses the current stream
  33. position if DontSeek is false (which may lead to overwrites), the end of the
  34. steam otherwise }
  35. procedure AppendDataToFileStream(Stream : TFileStream; const FileName : String;
  36. const DontSeek : Boolean = false);
  37. { Searches for a Data string in the passed byte-stream (will convert the string
  38. to a byte array)
  39. StartPos is the postition to start searching from, pass negative values for
  40. a offset from the end of the stream (passing 0 and Backwards=true will start
  41. at the end)
  42. Backwards specifies the direction in which to search, independant of StartPos
  43. You can pass an optional OutputStream in which all data from StartPos to the
  44. final end of the String to find will be written (only when Backwards is false)
  45. Uses a "rolling" buffer to eliminate the problem of the string to be split into two
  46. Because of that only the first 512 bytes of Data are used for comparison
  47. Returns the starting position of the match (first byte in Data) or -1 if no
  48. match could be found }
  49. function FindStringDataInStream(const Data : String; Stream : TFileStream;
  50. const StartPos : Int64 = 0; const Backwards : Boolean = false;
  51. OutputStream : TFileStream = nil) : Int64;
  52. { Looks for the ISO file header in the passed stream
  53. Returns true if found, false otherwise }
  54. function DetectIsoFormat(Stream : TFileStream) : Boolean;
  55. implementation
  56. uses Math, Forms;
  57. procedure AppendDataToFileStream(Stream : TFileStream; const FileName : String;
  58. const DontSeek : Boolean = false);
  59. var
  60. Buffer : Array [Word] of Byte; //64KB
  61. NumRead, NumWrite : Integer;
  62. Other : TFileStream;
  63. begin
  64. if NOT DontSeek then
  65. Stream.Seek(0,soFromEnd);
  66. Other := TFileStream.Create(FileName,fmOpenRead);
  67. try
  68. repeat
  69. NumRead := Other.Read(Buffer,SizeOf(Buffer));
  70. NumWrite := Stream.Write(Buffer,NumRead);
  71. until (NumRead = 0) OR (NumWrite <> NumRead);
  72. finally
  73. Other.Free;
  74. end;
  75. end;
  76. function FindStringDataInStream(const Data : String; Stream : TFileStream;
  77. const StartPos : Int64 = 0; const Backwards : Boolean = false;
  78. OutputStream : TFileStream = nil) : Int64;
  79. var
  80. NumRead : Word;
  81. DataArray : Array [0..511] of Byte;
  82. Buffer : Array [0..1023] of Byte; // 1KB
  83. I,K : Word;
  84. Size : Word;
  85. Found : Boolean;
  86. Pos : Int64;
  87. begin
  88. // convert string to byte array for comparison
  89. Size := Min(Length(Data) * SizeOf(Char),SizeOf(DataArray));
  90. Move(Data[1],DataArray,Size);
  91. // set-up stream correctly
  92. if StartPos < 0 then
  93. Stream.Seek(StartPos,soFromEnd)
  94. else if StartPos > 0 then
  95. Stream.Seek(StartPos,soFromBeginning)
  96. else
  97. begin
  98. if Backwards then
  99. Stream.Seek(0,soFromEnd)
  100. else
  101. Stream.Seek(0,soFromBeginning);
  102. end;
  103. if Backwards then
  104. begin
  105. // first seek here as we begin with a read operation
  106. Stream.Seek(-SizeOf(Buffer),soFromEnd);
  107. // stream output only supported in forwards mode
  108. OutputStream := nil;
  109. end;
  110. Result := -1;
  111. repeat
  112. // save current position for loop check
  113. Pos := Stream.Position;
  114. NumRead := Stream.Read(Buffer,SizeOf(Buffer));
  115. // check buffer against data
  116. for I := 0 to SizeOf(Buffer) - Size - 1 do
  117. begin
  118. if Buffer[I] = DataArray[0] then
  119. begin
  120. Found := true;
  121. for K := 0 to Size - 1 do
  122. begin
  123. if Buffer[I+K] <> DataArray[K] then
  124. begin
  125. Found := false;
  126. Break;
  127. end;
  128. end;
  129. if Found then
  130. begin
  131. Result := Stream.Position - NumRead + I;
  132. Break;
  133. end;
  134. end;
  135. end;
  136. // save buffer to output stream
  137. if OutputStream <> nil then
  138. begin
  139. if Result = -1 then // not found yet
  140. begin
  141. if NumRead < SizeOf(Buffer) then
  142. OutputStream.Write(Buffer,NumRead)
  143. else
  144. OutputStream.Write(Buffer,SizeOf(Buffer) div 2);
  145. end
  146. else
  147. OutputStream.Write(Buffer,Result + Size + NumRead - Stream.Position); // I + Size
  148. end;
  149. // position found -> exit
  150. if Result <> -1 then
  151. Exit;
  152. // use a "rolling" buffer to work around data beeing split on two buffers
  153. // this also is the reason the data array is half the size of the buffer
  154. if Backwards then
  155. Stream.Seek(Max(-SizeOf(Buffer) * 3 div 2,-Stream.Position),soFromCurrent)
  156. else
  157. Stream.Seek(-SizeOf(Buffer) div 2,soFromCurrent);
  158. // forward: read less bytes than buffer means end of file
  159. // backward: seeking does not change position means beginning of file
  160. until (NumRead < SizeOf(Buffer)) OR (Stream.Position = Pos);
  161. end;
  162. function DetectIsoFormat(Stream : TFileStream) : Boolean;
  163. const
  164. // Source: http://www.mactech.com/articles/develop/issue_03/high_sierra.html
  165. ISO_HEADER : Array [0..4] of Byte = (67,68,48,48,49); // CD001
  166. OFFSET_BYTES : Array [0..2] of Integer = (32769,34817,36865);
  167. var
  168. Buffer : Array [Word] of Byte;
  169. I,K : Integer;
  170. Found : Boolean;
  171. begin
  172. Result := false;
  173. for I := 0 to High(OFFSET_BYTES) do
  174. begin
  175. Stream.Seek(OFFSET_BYTES[I],soFromBeginning);
  176. Stream.Read(Buffer,Length(Buffer));
  177. Found := true;
  178. for K := 0 to High(ISO_HEADER) do
  179. begin
  180. if Buffer[K] <> ISO_HEADER[K] then
  181. begin
  182. Found := false;
  183. Break;
  184. end;
  185. end;
  186. if Found then
  187. begin
  188. Result := true;
  189. Exit;
  190. end;
  191. end;
  192. end;
  193. end.