GLS.FileVfsPAK.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.FileVfsPAK;
  5. (*
  6. Support-code for loading files from Quake II PAK Files.
  7. When instance is created all LoadFromFile methods using
  8. GLS.ApplicationFileIO mechanism will be pointed into PAK file.
  9. You can change current PAK file by ActivePak variable.
  10. *)
  11. interface
  12. {$I GLScene.inc}
  13. uses
  14. System.Classes,
  15. System.Contnrs,
  16. System.SysUtils,
  17. GLS.Strings,
  18. GLS.ApplicationFileIO;
  19. const
  20. SIGN = 'PACK'; // Signature for uncompressed - raw pak.
  21. SIGN_COMPRESSED = 'PACZ'; // Signature for compressed pak.
  22. type
  23. TZCompressedMode = (Good, Fast, Auto, None);
  24. TPakHeader = record
  25. Signature: array [0 .. 3] of AnsiChar;
  26. DirOffset: integer;
  27. DirLength: integer;
  28. end;
  29. TFileSection = record
  30. FileName: array [0 .. 119] of AnsiChar;
  31. FilePos: integer;
  32. FileLength: integer;
  33. end;
  34. TGLVfsPAK = class(TComponent)
  35. private
  36. FPakFiles: TStringList;
  37. FHeader: TPakHeader;
  38. FHeaderList: array of TPakHeader;
  39. FStream: TFileStream;
  40. FStreamList: TObjectList;
  41. FFiles: TStrings;
  42. FFilesLists: TObjectList;
  43. FFileName: string;
  44. FCompressionLevel: TZCompressedMode;
  45. FCompressed: Boolean;
  46. function GetFileCount: integer;
  47. procedure MakeFileList;
  48. function GetStreamNumber: integer;
  49. procedure SetStreamNumber(i: integer);
  50. public
  51. property PakFiles: TStringList read FPakFiles;
  52. property Files: TStrings read FFiles;
  53. property ActivePakNum: integer read GetStreamNumber write SetStreamNumber;
  54. property FileCount: integer Read GetFileCount;
  55. property PakFileName: string Read FFileName;
  56. property Compressed: Boolean read FCompressed;
  57. property CompressionLevel: TZCompressedMode read FCompressionLevel;
  58. constructor Create(AOwner: TComponent); overload; override;
  59. constructor Create(AOwner: TComponent; const CbrMode: TZCompressedMode);
  60. reintroduce; overload;
  61. destructor Destroy; override;
  62. // for Mode value search Delphi Help for "File open mode constants"
  63. procedure LoadFromFile(const FileName: string; Mode: word);
  64. procedure ClearPakFiles;
  65. function FileExists(const FileName: string): Boolean;
  66. function GetFile(index: integer): TStream; overload;
  67. function GetFile(const FileName: string): TStream; overload;
  68. function GetFileSize(index: integer): integer; overload;
  69. function GetFileSize(const FileName: string): integer; overload;
  70. procedure AddFromStream(const FileName, Path: string; F: TStream);
  71. procedure AddFromFile(const FileName, Path: string);
  72. procedure AddEmptyFile(const FileName, Path: string);
  73. procedure RemoveFile(index: integer); overload;
  74. procedure RemoveFile(const FileName: string); overload;
  75. procedure Extract(index: integer; const NewName: string); overload;
  76. procedure Extract(const FileName, NewName: string); overload;
  77. end;
  78. function PAKCreateFileStream(const FileName: string; Mode: word): TStream;
  79. function PAKFileStreamExists(const FileName: string): Boolean;
  80. var
  81. ActiveVfsPAK: TGLVfsPAK;
  82. // ---------------------------------------------------------------------
  83. implementation
  84. // ---------------------------------------------------------------------
  85. var
  86. Dir: TFileSection;
  87. function BackToSlash(const s: string): string;
  88. var
  89. i: integer;
  90. begin
  91. SetLength(Result, Length(s));
  92. for i := 1 to Length(s) do
  93. if s[i] = '\' then
  94. Result[i] := '/'
  95. else
  96. Result[i] := s[i];
  97. end;
  98. function PAKCreateFileStream(const FileName: string; Mode: word): TStream;
  99. var
  100. i: integer;
  101. begin
  102. with ActiveVfsPAK do
  103. for i := FStreamList.Count - 1 downto 0 do
  104. begin
  105. FFiles := TStrings(FFilesLists[i]);
  106. if FileExists(BackToSlash(FileName)) then
  107. begin
  108. FHeader := FHeaderList[i];
  109. FStream := TFileStream(FStreamList[i]);
  110. Result := GetFile(BackToSlash(FileName));
  111. Exit;
  112. end
  113. else
  114. begin
  115. if FileExists(FileName) then
  116. begin
  117. Result := TFileStream.Create(FileName, fmOpenReadWrite or
  118. fmShareDenyWrite);
  119. Exit;
  120. end
  121. else
  122. begin
  123. Result := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
  124. Exit;
  125. end;
  126. end;
  127. end;
  128. if FileExists(FileName) then
  129. begin
  130. Result := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite);
  131. Exit;
  132. end
  133. else
  134. begin
  135. Result := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
  136. Exit;
  137. end;
  138. Result.Free;
  139. end;
  140. function PAKFileStreamExists(const FileName: string): Boolean;
  141. var
  142. i: integer;
  143. begin
  144. with ActiveVfsPAK do
  145. for i := 0 to FStreamList.Count - 1 do
  146. begin
  147. FFiles := TStrings(FFilesLists[i]);
  148. if FileExists(BackToSlash(FileName)) then
  149. begin
  150. Result := True;
  151. Exit;
  152. end;
  153. end;
  154. Result := FileExists(FileName);
  155. end;
  156. // --------------------------
  157. // TGLVfsPAK
  158. // --------------------------
  159. function TGLVfsPAK.GetStreamNumber: integer;
  160. begin
  161. Result := FStreamList.IndexOf(FStream);
  162. end;
  163. procedure TGLVfsPAK.SetStreamNumber(i: integer);
  164. begin
  165. FStream := TFileStream(FStreamList[i]);
  166. end;
  167. constructor TGLVfsPAK.Create(AOwner: TComponent);
  168. begin
  169. inherited Create(AOwner);
  170. FPakFiles := TStringList.Create;
  171. FStreamList := TObjectList.Create(True);
  172. FFilesLists := TObjectList.Create(True);
  173. ActiveVfsPAK := Self;
  174. vAFIOCreateFileStream := PAKCreateFileStream;
  175. vAFIOFileStreamExists := PAKFileStreamExists;
  176. FCompressionLevel := None;
  177. FCompressed := False;
  178. end;
  179. constructor TGLVfsPAK.Create(AOwner: TComponent;
  180. const CbrMode: TZCompressedMode);
  181. begin
  182. Self.Create(AOwner);
  183. FCompressionLevel := None;
  184. FCompressed := FCompressionLevel <> None;
  185. end;
  186. destructor TGLVfsPAK.Destroy;
  187. begin
  188. vAFIOCreateFileStream := nil;
  189. vAFIOFileStreamExists := nil;
  190. SetLength(FHeaderList, 0);
  191. FPakFiles.Free;
  192. // Objects are automatically freed by TObjectList
  193. FStreamList.Free;
  194. FFilesLists.Free;
  195. ActiveVfsPAK := nil;
  196. inherited Destroy;
  197. end;
  198. function TGLVfsPAK.GetFileCount: integer;
  199. begin
  200. Result := FHeader.DirLength div SizeOf(TFileSection);
  201. end;
  202. procedure TGLVfsPAK.MakeFileList;
  203. var
  204. i: integer;
  205. begin
  206. FStream.Seek(FHeader.DirOffset, soFromBeginning);
  207. FFiles.Clear;
  208. for i := 0 to FileCount - 1 do
  209. begin
  210. FStream.ReadBuffer(Dir, SizeOf(TFileSection));
  211. FFiles.Add(string(Dir.FileName));
  212. end;
  213. end;
  214. procedure TGLVfsPAK.LoadFromFile(const FileName: string; Mode: word);
  215. var
  216. l: integer;
  217. begin
  218. FFileName := FileName;
  219. FPakFiles.Clear;
  220. FPakFiles.Add(FileName);
  221. FFiles := TStringList.Create;
  222. FStream := TFileStream.Create(FileName, Mode);
  223. if FStream.Size = 0 then
  224. begin
  225. if FCompressed then
  226. FHeader.Signature := SIGN_COMPRESSED
  227. else
  228. FHeader.Signature := SIGN;
  229. FHeader.DirOffset := SizeOf(TPakHeader);
  230. FHeader.DirLength := 0;
  231. if FHeader.Signature = SIGN_COMPRESSED then
  232. begin
  233. FStream.Free;
  234. raise Exception.Create
  235. (FileName +
  236. ' - This is a compressed PAK file. This version of software does not support Compressed Pak files.');
  237. Exit;
  238. end;
  239. FStream.WriteBuffer(FHeader, SizeOf(TPakHeader));
  240. FStream.Position := 0;
  241. end;
  242. FStream.ReadBuffer(FHeader, SizeOf(TPakHeader));
  243. if (FHeader.Signature <> SIGN) and (FHeader.Signature <> SIGN_COMPRESSED) then
  244. begin
  245. FStream.Free;
  246. raise Exception.Create(FileName + ' - This is not PAK file');
  247. Exit;
  248. end;
  249. // Set the compression flag property.
  250. FCompressed := FHeader.Signature = SIGN_COMPRESSED;
  251. if FCompressed then
  252. begin
  253. FStream.Free;
  254. raise Exception.Create
  255. (FileName +
  256. ' - This is a compressed PAK file. This version of software does not support Compressed Pak files.');
  257. Exit;
  258. end;
  259. if FileCount <> 0 then
  260. MakeFileList;
  261. l := Length(FHeaderList);
  262. SetLength(FHeaderList, l + 1);
  263. FHeaderList[l] := FHeader;
  264. FFilesLists.Add(FFiles);
  265. FStreamList.Add(FStream);
  266. end;
  267. procedure TGLVfsPAK.ClearPakFiles;
  268. begin
  269. SetLength(FHeaderList, 0);
  270. FPakFiles.Clear;
  271. // Objects are automatically freed by TObjectList
  272. FStreamList.Clear;
  273. FFilesLists.Clear;
  274. ActiveVfsPAK := nil;
  275. end;
  276. function TGLVfsPAK.GetFile(index: integer): TStream;
  277. begin
  278. FStream.Seek(FHeader.DirOffset + SizeOf(TFileSection) * index,
  279. soFromBeginning);
  280. FStream.Read(Dir, SizeOf(TFileSection));
  281. FStream.Seek(Dir.FilePos, soFromBeginning);
  282. Result := TMemoryStream.Create;
  283. Result.CopyFrom(FStream, Dir.FileLength);
  284. Result.Position := 0;
  285. end;
  286. function TGLVfsPAK.FileExists(const FileName: string): Boolean;
  287. begin
  288. Result := (FFiles.IndexOf(FileName) > -1);
  289. end;
  290. function TGLVfsPAK.GetFile(const FileName: string): TStream;
  291. begin
  292. Result := nil;
  293. if Self.FileExists(FileName) then
  294. Result := GetFile(FFiles.IndexOf(FileName));
  295. end;
  296. function TGLVfsPAK.GetFileSize(index: integer): integer;
  297. begin
  298. FStream.Seek(FHeader.DirOffset + SizeOf(TFileSection) * index,
  299. soFromBeginning);
  300. FStream.Read(Dir, SizeOf(Dir));
  301. Result := Dir.FileLength;
  302. end;
  303. function TGLVfsPAK.GetFileSize(const FileName: string): integer;
  304. begin
  305. Result := -1;
  306. if Self.FileExists(FileName) then
  307. Result := GetFileSize(FFiles.IndexOf(FileName));
  308. end;
  309. {$WARNINGS OFF}
  310. procedure TGLVfsPAK.AddFromStream(const FileName, Path: string; F: TStream);
  311. var
  312. Temp: TMemoryStream;
  313. begin
  314. FStream.Position := FHeader.DirOffset;
  315. if FHeader.DirLength > 0 then
  316. begin
  317. Temp := TMemoryStream.Create;
  318. Temp.CopyFrom(FStream, FHeader.DirLength);
  319. Temp.Position := 0;
  320. FStream.Position := FHeader.DirOffset;
  321. end;
  322. Dir.FilePos := FHeader.DirOffset;
  323. Dir.FileLength := F.Size;
  324. FStream.CopyFrom(F, 0);
  325. FHeader.DirOffset := FStream.Position;
  326. if FHeader.DirLength > 0 then
  327. begin
  328. FStream.CopyFrom(Temp, 0);
  329. Temp.Free;
  330. end;
  331. StrPCopy(Dir.FileName, Path + ExtractFileName(FileName));
  332. FStream.WriteBuffer(Dir, SizeOf(TFileSection));
  333. FHeader.DirLength := FHeader.DirLength + SizeOf(TFileSection);
  334. FStream.Position := 0;
  335. FStream.WriteBuffer(FHeader, SizeOf(TPakHeader));
  336. FFiles.Add(Dir.FileName);
  337. end;
  338. {$WARNINGS ON}
  339. procedure TGLVfsPAK.AddFromFile(const FileName, Path: string);
  340. var
  341. F: TFileStream;
  342. begin
  343. if not FileExists(FileName) then
  344. Exit;
  345. F := TFileStream.Create(FileName, fmOpenRead);
  346. try
  347. AddFromStream(FileName, Path, F);
  348. finally
  349. F.Free;
  350. end;
  351. end;
  352. procedure TGLVfsPAK.AddEmptyFile(const FileName, Path: string);
  353. var
  354. F: TMemoryStream;
  355. begin
  356. F := TMemoryStream.Create;
  357. try
  358. AddFromStream(FileName, Path, F);
  359. finally
  360. F.Free;
  361. end;
  362. end;
  363. procedure TGLVfsPAK.RemoveFile(index: integer);
  364. var
  365. Temp: TMemoryStream;
  366. i: integer;
  367. F: TFileSection;
  368. begin
  369. Temp := TMemoryStream.Create;
  370. FStream.Seek(FHeader.DirOffset + SizeOf(TFileSection) * index,
  371. soFromBeginning);
  372. FStream.ReadBuffer(Dir, SizeOf(TFileSection));
  373. FStream.Seek(Dir.FilePos + Dir.FileLength, soFromBeginning);
  374. Temp.CopyFrom(FStream, FStream.Size - FStream.Position);
  375. FStream.Position := Dir.FilePos;
  376. FStream.CopyFrom(Temp, 0);
  377. FHeader.DirOffset := FHeader.DirOffset - Dir.FileLength;
  378. Temp.Clear;
  379. for i := 0 to FileCount - 1 do
  380. if i > index then
  381. begin
  382. FStream.Seek(FHeader.DirOffset + SizeOf(TFileSection) * i,
  383. soFromBeginning);
  384. FStream.ReadBuffer(F, SizeOf(TFileSection));
  385. FStream.Position := FStream.Position - SizeOf(TFileSection);
  386. F.FilePos := F.FilePos - Dir.FileLength;
  387. FStream.WriteBuffer(F, SizeOf(TFileSection));
  388. end;
  389. i := FHeader.DirOffset + SizeOf(TFileSection) * index;
  390. FStream.Position := i + SizeOf(TFileSection);
  391. if FStream.Position < FStream.Size then
  392. begin
  393. Temp.CopyFrom(FStream, FStream.Size - FStream.Position);
  394. FStream.Position := i;
  395. FStream.CopyFrom(Temp, 0);
  396. end;
  397. Temp.Free;
  398. FHeader.DirLength := FHeader.DirLength - SizeOf(TFileSection);
  399. FStream.Position := 0;
  400. FStream.WriteBuffer(FHeader, SizeOf(TPakHeader));
  401. FStream.Size := FStream.Size - Dir.FileLength - SizeOf(TFileSection);
  402. MakeFileList;
  403. end;
  404. procedure TGLVfsPAK.RemoveFile(const FileName: string);
  405. begin
  406. if Self.FileExists(FileName) then
  407. RemoveFile(FFiles.IndexOf(FileName));
  408. end;
  409. procedure TGLVfsPAK.Extract(index: integer; const NewName: string);
  410. var
  411. s: TFileStream;
  412. begin
  413. if NewName = '' then
  414. Exit;
  415. if (index < 0) or (index >= FileCount) then
  416. Exit;
  417. s := TFileStream.Create(NewName, fmCreate);
  418. s.CopyFrom(GetFile(index), 0);
  419. s.Free;
  420. end;
  421. procedure TGLVfsPAK.Extract(const FileName, NewName: string);
  422. begin
  423. if Self.FileExists(FileName) then
  424. Extract(FFiles.IndexOf(FileName), NewName);
  425. end;
  426. end.