Setup.InstFunc.pas 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. unit Setup.InstFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Misc. installation functions. Used only by the Setup project.
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Diagnostics, SHA256,
  12. Shared.CommonFunc, Shared.FileClass,
  13. Setup.DownloadFileFunc, Compression.SevenZipDecoder;
  14. type
  15. PSimpleStringListArray = ^TSimpleStringListArray;
  16. TSimpleStringListArray = array[0..$7FFFFFFF div SizeOf(String) - 1] of String;
  17. TSimpleStringList = class
  18. private
  19. FList: PSimpleStringListArray;
  20. FCount, FCapacity: Integer;
  21. function Get(Index: Integer): String;
  22. procedure SetCapacity(NewCapacity: Integer);
  23. public
  24. destructor Destroy; override;
  25. procedure Add(const S: String);
  26. procedure AddIfDoesntExist(const S: String);
  27. procedure Clear;
  28. function IndexOf(const S: String): Integer;
  29. property Count: Integer read FCount;
  30. property Items[Index: Integer]: String read Get; default;
  31. end;
  32. TDeleteDirProc = function(const DisableFsRedir: Boolean; const DirName: String;
  33. const Param: Pointer): Boolean;
  34. TDeleteFileProc = function(const DisableFsRedir: Boolean; const FileName: String;
  35. const Param: Pointer): Boolean;
  36. TEnumFROFilenamesProc = procedure(const Filename: String; Param: Pointer);
  37. { Must keep this in synch with Compiler.ScriptFunc.pas: }
  38. TExecWait = (ewNoWait, ewWaitUntilTerminated, ewWaitUntilIdle);
  39. { Only reports progress at start or finish, or if at least 50 ms passed since last report }
  40. TProgressThrottler = class
  41. private
  42. FOnDownloadProgress: TOnDownloadProgress;
  43. FOnExtractionProgress: TOnExtractionProgress;
  44. FStopWatch: TStopWatch;
  45. FLastOkProgress: Int64;
  46. function ThrottleOk(const Progress, ProgressMax: Int64): Boolean;
  47. public
  48. constructor Create(const OnDownloadProgress: TOnDownloadProgress); overload;
  49. constructor Create(const OnExtractionProgress: TOnExtractionProgress); overload;
  50. procedure Reset;
  51. function OnDownloadProgress(const Url, BaseName: string; const Progress, ProgressMax: Int64): Boolean;
  52. function OnExtractionProgress(const ArchiveName, FileName: string; const Progress, ProgressMax: Int64): Boolean;
  53. end;
  54. function CheckForMutexes(const Mutexes: String): Boolean;
  55. procedure CreateMutexes(const Mutexes: String);
  56. function DecrementSharedCount(const RegView: TRegView; const Filename: String): Boolean;
  57. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  58. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  59. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  60. const Param: Pointer): Boolean;
  61. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  62. Param: Pointer);
  63. function GetComputerNameString: String;
  64. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  65. var DateTime: TFileTime): Boolean;
  66. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest; overload;
  67. function GetSHA256OfFile(const F: TFile): TSHA256Digest; overload;
  68. function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
  69. function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
  70. function GetRegRootKeyName(const RootKey: HKEY): String;
  71. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  72. var FreeBytes, TotalBytes: Int64): Boolean;
  73. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  74. const StartDir: String; var FreeBytes, TotalBytes: Int64): Boolean;
  75. function GetUserNameString: String;
  76. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  77. const AlreadyExisted: Boolean);
  78. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  79. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  80. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  81. var ResultCode: DWORD): Boolean;
  82. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  83. const Wait: TExecWait; const ShowCmd: Integer;
  84. const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
  85. procedure InternalError(const Id: String);
  86. procedure InternalErrorFmt(const S: String; const Args: array of const);
  87. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  88. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  89. const Filename: String): Boolean;
  90. function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
  91. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  92. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  93. procedure RefreshEnvironment;
  94. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  95. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  96. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  97. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  98. procedure Win32ErrorMsg(const FunctionName: String);
  99. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  100. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  101. procedure AddAttributesToFile(const DisableFsRedir: Boolean; const Filename: String; Attribs: Integer);
  102. implementation
  103. uses
  104. Messages, ShellApi, Classes, RegStr, Math,
  105. PathFunc, UnsignedFunc,
  106. Shared.SetupTypes, Shared.SetupMessageIDs,
  107. SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages, Setup.RedirFunc;
  108. procedure InternalError(const Id: String);
  109. begin
  110. raise Exception.Create(FmtSetupMessage1(msgErrorInternal2, Id));
  111. end;
  112. procedure InternalErrorFmt(const S: String; const Args: array of const);
  113. begin
  114. InternalError(Format(S, Args));
  115. end;
  116. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  117. begin
  118. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  119. [FunctionName, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  120. end;
  121. procedure Win32ErrorMsg(const FunctionName: String);
  122. begin
  123. Win32ErrorMsgEx(FunctionName, GetLastError);
  124. end;
  125. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  126. begin
  127. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  128. [FunctionName, IntToHexStr8(ResultCode), Win32ErrorString(DWORD(ResultCode))]));
  129. end;
  130. function GetRegRootKeyName(const RootKey: HKEY): String;
  131. begin
  132. case UInt32(RootKey) of
  133. UInt32(HKEY_AUTO): InternalError('GetRegRootKeyName called for HKEY_AUTO');
  134. UInt32(HKEY_CLASSES_ROOT): Result := 'HKEY_CLASSES_ROOT';
  135. UInt32(HKEY_CURRENT_USER): Result := 'HKEY_CURRENT_USER';
  136. UInt32(HKEY_LOCAL_MACHINE): Result := 'HKEY_LOCAL_MACHINE';
  137. UInt32(HKEY_USERS): Result := 'HKEY_USERS';
  138. UInt32(HKEY_PERFORMANCE_DATA): Result := 'HKEY_PERFORMANCE_DATA';
  139. UInt32(HKEY_CURRENT_CONFIG): Result := 'HKEY_CURRENT_CONFIG';
  140. UInt32(HKEY_DYN_DATA): Result := 'HKEY_DYN_DATA';
  141. else
  142. { unknown - shouldn't get here }
  143. Result := Format('[%x]', [UInt32(RootKey)]);
  144. end;
  145. end;
  146. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  147. { If the user is running 64-bit Windows and Path begins with
  148. 'x:\windows\system32' it replaces it with 'x:\windows\syswow64', like the
  149. file system redirector would do. Otherwise, Path is returned unchanged. }
  150. var
  151. SysWow64Dir, SysDir: String;
  152. L: Integer;
  153. begin
  154. SysWow64Dir := GetSysWow64Dir;
  155. if SysWow64Dir <> '' then begin
  156. SysDir := GetSystemDir;
  157. { x:\windows\system32 -> x:\windows\syswow64
  158. x:\windows\system32\ -> x:\windows\syswow64\
  159. x:\windows\system32\filename -> x:\windows\syswow64\filename
  160. x:\windows\system32x -> x:\windows\syswow64x <- yes, like Windows! }
  161. L := Length(SysDir);
  162. if (Length(Path) = L) or
  163. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  164. { ^ avoid splitting a double-byte character }
  165. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  166. Result := SysWow64Dir + Copy(Path, L+1, Maxint);
  167. Exit;
  168. end;
  169. end;
  170. end;
  171. Result := Path;
  172. end;
  173. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  174. { If Path begins with 'x:\windows\system32\' it replaces it with
  175. 'x:\windows\sysnative\' and if Path equals 'x:\windows\system32'
  176. it replaces it with 'x:\windows\sysnative'. Otherwise, Path is
  177. returned unchanged. }
  178. var
  179. SysNativeDir, SysDir: String;
  180. L: Integer;
  181. begin
  182. SysNativeDir := GetSysNativeDir(IsWin64);
  183. if SysNativeDir <> '' then begin
  184. SysDir := GetSystemDir;
  185. if PathCompare(Path, SysDir) = 0 then begin
  186. { x:\windows\system32 -> x:\windows\sysnative }
  187. Result := SysNativeDir;
  188. Exit;
  189. end else begin
  190. { x:\windows\system32\ -> x:\windows\sysnative\
  191. x:\windows\system32\filename -> x:\windows\sysnative\filename }
  192. SysDir := AddBackslash(SysDir);
  193. L := Length(SysDir);
  194. if (Length(Path) = L) or
  195. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  196. { ^ avoid splitting a double-byte character }
  197. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  198. Result := SysNativeDir + Copy(Path, L, Maxint);
  199. Exit;
  200. end;
  201. end;
  202. end;
  203. end;
  204. Result := Path;
  205. end;
  206. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  207. { Renames TempFile to DestFile the next time Windows is started. If DestFile
  208. already existed, it will be overwritten. If DestFile is '' then TempFile
  209. will be deleted.. }
  210. begin
  211. TempFile := PathExpand(TempFile);
  212. if DestFile <> '' then
  213. DestFile := PathExpand(DestFile);
  214. if not DisableFsRedir then begin
  215. { Work around WOW64 bug present in the IA64 and x64 editions of Windows
  216. XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
  217. to the registry verbatim without mapping system32->syswow64. }
  218. TempFile := ReplaceSystemDirWithSysWow64(TempFile);
  219. if DestFile <> '' then
  220. DestFile := ReplaceSystemDirWithSysWow64(DestFile);
  221. end;
  222. if not MoveFileExRedir(DisableFsRedir, TempFile, DestFile,
  223. MOVEFILE_DELAY_UNTIL_REBOOT or MOVEFILE_REPLACE_EXISTING) then
  224. Win32ErrorMsg('MoveFileEx');
  225. end;
  226. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  227. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  228. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  229. const Param: Pointer): Boolean;
  230. { Deletes the specified directory including all files and subdirectories in
  231. it (including those with hidden, system, and read-only attributes). Returns
  232. True if it was able to successfully remove everything. If BreakOnError is
  233. set to True it will stop and return False the first time a delete failed or
  234. DeleteDirProc/DeleteFileProc returned False. }
  235. var
  236. BasePath, FindSpec: String;
  237. H: THandle;
  238. FindData: TWin32FindData;
  239. S: String;
  240. begin
  241. Result := True;
  242. if DeleteFiles and
  243. (not IsDir or IsDirectoryAndNotReparsePointRedir(DisableFsRedir, Path)) then begin
  244. if IsDir then begin
  245. BasePath := AddBackslash(Path);
  246. FindSpec := BasePath + '*';
  247. end
  248. else begin
  249. BasePath := PathExtractPath(Path);
  250. FindSpec := Path;
  251. end;
  252. H := FindFirstFileRedir(DisableFsRedir, FindSpec, FindData);
  253. if H <> INVALID_HANDLE_VALUE then begin
  254. try
  255. repeat
  256. S := FindData.cFileName;
  257. if (S <> '.') and (S <> '..') then begin
  258. if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then begin
  259. { Strip the read-only attribute if this is a file, or if it's a
  260. directory and we're deleting subdirectories also }
  261. if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0) or DeleteSubdirsAlso then
  262. SetFileAttributesRedir(DisableFsRedir, BasePath + S,
  263. FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
  264. end;
  265. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  266. if Assigned(DeleteFileProc) then begin
  267. if not DeleteFileProc(DisableFsRedir, BasePath + S, Param) then
  268. Result := False;
  269. end
  270. else begin
  271. if not DeleteFileRedir(DisableFsRedir, BasePath + S) then
  272. Result := False;
  273. end;
  274. end
  275. else begin
  276. if DeleteSubdirsAlso then
  277. if not DelTree(DisableFsRedir, BasePath + S, True, True, True, BreakOnError,
  278. DeleteDirProc, DeleteFileProc, Param) then
  279. Result := False;
  280. end;
  281. end;
  282. until (BreakOnError and not Result) or not FindNextFile(H, FindData);
  283. finally
  284. Windows.FindClose(H);
  285. end;
  286. end;
  287. end;
  288. if (not BreakOnError or Result) and IsDir then begin
  289. if Assigned(DeleteDirProc) then begin
  290. if not DeleteDirProc(DisableFsRedir, Path, Param) then
  291. Result := False;
  292. end
  293. else begin
  294. if not RemoveDirectoryRedir(DisableFsRedir, Path) then
  295. Result := False;
  296. end;
  297. end;
  298. end;
  299. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  300. { Returns True if Dir contains no files or subdirectories.
  301. Note: If Dir does not exist or lacks list permission, False will be
  302. returned. }
  303. var
  304. H: THandle;
  305. FindData: TWin32FindData;
  306. begin
  307. H := FindFirstFileRedir(DisableFsRedir, AddBackslash(Dir) + '*', FindData);
  308. if H <> INVALID_HANDLE_VALUE then begin
  309. try
  310. Result := True;
  311. while True do begin
  312. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  313. { Found a file }
  314. Result := False;
  315. Break;
  316. end;
  317. if (StrComp(FindData.cFileName, '.') <> 0) and
  318. (StrComp(FindData.cFileName, '..') <> 0) then begin
  319. { Found a subdirectory }
  320. Result := False;
  321. Break;
  322. end;
  323. if not FindNextFile(H, FindData) then begin
  324. if GetLastError <> ERROR_NO_MORE_FILES then begin
  325. { Exited the loop early due to some unexpected error. The directory
  326. might not be empty, so return False }
  327. Result := False;
  328. end;
  329. Break;
  330. end;
  331. end;
  332. finally
  333. Windows.FindClose(H);
  334. end;
  335. end
  336. else begin
  337. { The directory may not exist, or it may lack list permission }
  338. Result := False;
  339. end;
  340. end;
  341. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  342. const AlreadyExisted: Boolean);
  343. const
  344. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  345. var
  346. K: HKEY;
  347. Disp, Size, CurType, NewType: DWORD;
  348. CountStr: String;
  349. FilenameP: PChar;
  350. begin
  351. const ErrorCode = Cardinal(RegCreateKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0, nil,
  352. REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE, nil, K, @Disp));
  353. if ErrorCode <> ERROR_SUCCESS then
  354. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  355. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  356. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  357. ['RegCreateKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  358. FilenameP := PChar(Filename);
  359. var Count := 0;
  360. NewType := REG_DWORD;
  361. try
  362. if RegQueryValueEx(K, FilenameP, nil, @CurType, nil, @Size) = ERROR_SUCCESS then
  363. case CurType of
  364. REG_SZ:
  365. if RegQueryStringValue(K, FilenameP, CountStr) then begin
  366. Count := StrToInt(CountStr);
  367. NewType := REG_SZ;
  368. end;
  369. REG_BINARY: begin
  370. if (Size >= 1) and (Size <= 4) then begin
  371. if RegQueryValueEx(K, FilenameP, nil, nil, PByte(@Count), @Size) <> ERROR_SUCCESS then
  372. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  373. Abort;
  374. NewType := REG_BINARY;
  375. end;
  376. end;
  377. REG_DWORD: begin
  378. Size := SizeOf(DWORD);
  379. if RegQueryValueEx(K, FilenameP, nil, nil, PByte(@Count), @Size) <> ERROR_SUCCESS then
  380. Abort;
  381. end;
  382. end;
  383. except
  384. Count := 0;
  385. end;
  386. if Count < 0 then
  387. Count := 0; { just in case... }
  388. if (Count = 0) and AlreadyExisted then
  389. Inc(Count);
  390. Inc(Count);
  391. case NewType of
  392. REG_SZ: begin
  393. CountStr := IntToStr(Count);
  394. RegSetValueEx(K, FilenameP, 0, NewType, PChar(CountStr), (ULength(CountStr)+1)*SizeOf(CountStr[1]));
  395. end;
  396. REG_BINARY, REG_DWORD:
  397. RegSetValueEx(K, FilenameP, 0, NewType, @Count, SizeOf(Count));
  398. end;
  399. RegCloseKey(K);
  400. end;
  401. function DecrementSharedCount(const RegView: TRegView;
  402. const Filename: String): Boolean;
  403. { Attempts to decrement the shared file reference count of Filename. Returns
  404. True if the count reached zero (meaning it's OK to delete the file). }
  405. const
  406. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  407. var
  408. K: HKEY;
  409. CountRead: Boolean;
  410. CurType, Size: DWORD;
  411. CountStr: String;
  412. begin
  413. Result := False;
  414. const ErrorCode = Cardinal(RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0,
  415. KEY_QUERY_VALUE or KEY_SET_VALUE, K));
  416. if ErrorCode = ERROR_FILE_NOT_FOUND then
  417. Exit;
  418. if ErrorCode <> ERROR_SUCCESS then
  419. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  420. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  421. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  422. ['RegOpenKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  423. try
  424. if RegQueryValueEx(K, PChar(Filename), nil, @CurType, nil, @Size) <> ERROR_SUCCESS then
  425. Exit;
  426. CountRead := False;
  427. var Count := 0;
  428. try
  429. case CurType of
  430. REG_SZ:
  431. if RegQueryStringValue(K, PChar(Filename), CountStr) then begin
  432. Count := StrToInt(CountStr);
  433. CountRead := True;
  434. end;
  435. REG_BINARY: begin
  436. if (Size >= 1) and (Size <= 4) then begin
  437. if RegQueryValueEx(K, PChar(Filename), nil, nil, PByte(@Count), @Size) = ERROR_SUCCESS then
  438. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  439. CountRead := True;
  440. end;
  441. end;
  442. REG_DWORD: begin
  443. Size := SizeOf(DWORD);
  444. if RegQueryValueEx(K, PChar(Filename), nil, nil, PByte(@Count), @Size) = ERROR_SUCCESS then
  445. CountRead := True;
  446. end;
  447. end;
  448. except
  449. { don't propagate exceptions (e.g. from StrToInt) }
  450. end;
  451. { If we failed to read the count, or it's in some type we don't recognize,
  452. don't touch it }
  453. if not CountRead then
  454. Exit;
  455. Dec(Count);
  456. if Count <= 0 then begin
  457. Result := True;
  458. RegDeleteValue(K, PChar(Filename));
  459. end
  460. else begin
  461. case CurType of
  462. REG_SZ: begin
  463. CountStr := IntToStr(Count);
  464. RegSetValueEx(K, PChar(Filename), 0, REG_SZ, PChar(CountStr), (ULength(CountStr)+1)*SizeOf(Char));
  465. end;
  466. REG_BINARY, REG_DWORD:
  467. RegSetValueEx(K, PChar(Filename), 0, CurType, @Count, SizeOf(Count));
  468. end;
  469. end;
  470. finally
  471. RegCloseKey(K);
  472. end;
  473. end;
  474. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  475. var DateTime: TFileTime): Boolean;
  476. var
  477. Handle: THandle;
  478. FindData: TWin32FindData;
  479. begin
  480. Handle := FindFirstFileRedir(DisableFsRedir, Filename, FindData);
  481. if Handle <> INVALID_HANDLE_VALUE then begin
  482. Windows.FindClose(Handle);
  483. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  484. DateTime := FindData.ftLastWriteTime;
  485. Result := True;
  486. Exit;
  487. end;
  488. end;
  489. Result := False;
  490. DateTime.dwLowDateTime := 0;
  491. DateTime.dwHighDateTime := 0;
  492. end;
  493. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest;
  494. { Gets SHA-256 sum as a string of the file Filename. An exception will be raised upon
  495. failure. }
  496. begin
  497. const F = TFileRedir.Create(DisableFsRedir, Filename, fdOpenExisting, faRead, fsReadWrite);
  498. try
  499. Result := GetSHA256OfFile(F);
  500. finally
  501. F.Free;
  502. end;
  503. end;
  504. function GetSHA256OfFile(const F: TFile): TSHA256Digest;
  505. { Gets SHA-256 sum as a string of the file F. An exception will be raised upon
  506. failure. }
  507. var
  508. Buf: array[0..65535] of Byte;
  509. begin
  510. F.Seek(0);
  511. var Context: TSHA256Context;
  512. SHA256Init(Context);
  513. while True do begin
  514. var NumRead := F.Read(Buf, SizeOf(Buf));
  515. if NumRead = 0 then
  516. Break;
  517. SHA256Update(Context, Buf, NumRead);
  518. end;
  519. Result := SHA256Final(Context);
  520. end;
  521. function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
  522. begin
  523. Result := SHA256Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  524. end;
  525. function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
  526. begin
  527. Result := SHA256Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  528. end;
  529. var
  530. SFCInitialized: Boolean;
  531. SfcIsFileProtectedFunc: function(RpcHandle: THandle; ProtFileName: PWideChar): BOOL; stdcall;
  532. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  533. const Filename: String): Boolean;
  534. { Returns True if the specified file is protected by Windows File Protection
  535. (and therefore can't be replaced). }
  536. var
  537. M: HMODULE;
  538. FN: String;
  539. begin
  540. if not SFCInitialized then begin
  541. M := SafeLoadLibrary(PChar(AddBackslash(GetSystemDir) + 'sfc.dll'),
  542. SEM_NOOPENFILEERRORBOX);
  543. if M <> 0 then
  544. SfcIsFileProtectedFunc := GetProcAddress(M, 'SfcIsFileProtected');
  545. SFCInitialized := True;
  546. end;
  547. if Assigned(SfcIsFileProtectedFunc) then begin
  548. { The function only accepts fully qualified paths. Also, as of
  549. IA-64 2003 SP1 and x64 XP, it does not respect file system redirection,
  550. so a call to ReplaceSystemDirWithSysWow64 is needed. }
  551. FN := PathExpand(Filename);
  552. if not DisableFsRedir then
  553. FN := ReplaceSystemDirWithSysWow64(FN);
  554. Result := SfcIsFileProtectedFunc(0, PChar(FN));
  555. end
  556. else
  557. Result := False; { Should never happen }
  558. end;
  559. procedure HandleProcessWait(ProcessHandle: THandle; const Wait: TExecWait;
  560. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  561. var ResultCode: DWORD);
  562. begin
  563. try
  564. if Wait = ewWaitUntilIdle then begin
  565. repeat
  566. ProcessMessagesProc;
  567. until WaitForInputIdle(ProcessHandle, 50) <> WAIT_TIMEOUT;
  568. end;
  569. if Wait = ewWaitUntilTerminated then begin
  570. { Wait until the process returns, but still process any messages that
  571. arrive and read the output if requested. }
  572. var WaitMilliseconds := Cardinal(IfThen(OutputReader <> nil, 50, INFINITE));
  573. var WaitResult: DWORD := 0;
  574. repeat
  575. { Process any pending messages first because MsgWaitForMultipleObjects
  576. (called below) only returns when *new* messages arrive, unless there's
  577. a timeout }
  578. if WaitResult <> WAIT_TIMEOUT then
  579. ProcessMessagesProc;
  580. if OutputReader <> nil then
  581. OutputReader.Read(False);
  582. WaitResult := MsgWaitForMultipleObjects(1, ProcessHandle, False,
  583. WaitMilliseconds, QS_ALLINPUT);
  584. until (WaitResult <> WAIT_OBJECT_0+1) and (WaitResult <> WAIT_TIMEOUT);
  585. { Process messages once more in case MsgWaitForMultipleObjects saw the
  586. process terminate and new messages arrive simultaneously. (Can't leave
  587. unprocessed messages waiting, or a subsequent call to WaitMessage
  588. won't see them.) }
  589. ProcessMessagesProc;
  590. if OutputReader <> nil then
  591. OutputReader.Read(True);
  592. end;
  593. { Get the exit code. Will be set to STILL_ACTIVE if not yet available }
  594. if not GetExitCodeProcess(ProcessHandle, DWORD(ResultCode)) then
  595. ResultCode := DWORD(-1); { just in case }
  596. finally
  597. CloseHandle(ProcessHandle);
  598. end;
  599. end;
  600. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  601. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  602. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  603. var ResultCode: DWORD): Boolean;
  604. var
  605. CmdLine: String;
  606. StartupInfo: TStartupInfo;
  607. ProcessInfo: TProcessInformation;
  608. begin
  609. {Also see IsppFuncs' Exec }
  610. if Filename = '>' then
  611. CmdLine := Params
  612. else begin
  613. CmdLine := '"' + Filename + '"';
  614. if Params <> '' then
  615. CmdLine := CmdLine + ' ' + Params;
  616. if SameText(PathExtractExt(Filename), '.bat') or
  617. SameText(PathExtractExt(Filename), '.cmd') then begin
  618. { Use our own handling for .bat and .cmd files since passing them straight
  619. to CreateProcess on Windows NT 4.0 has problems: it doesn't properly
  620. quote the command line it passes to cmd.exe. This didn't work before:
  621. Filename: "c:\batch.bat"; Parameters: """abc"""
  622. And other Windows versions might have unknown quirks too, since
  623. CreateProcess isn't documented to accept .bat files in the first place. }
  624. { With cmd.exe, the whole command line must be quoted for quoted
  625. parameters to work. For example, this fails:
  626. cmd.exe /c "z:\blah.bat" "test"
  627. But this works:
  628. cmd.exe /c ""z:\blah.bat" "test""
  629. }
  630. CmdLine := '"' + AddBackslash(GetSystemDir) + 'cmd.exe" /C "' + CmdLine + '"'
  631. end;
  632. if WorkingDir = '' then
  633. WorkingDir := PathExtractDir(Filename);
  634. end;
  635. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  636. StartupInfo.cb := SizeOf(StartupInfo);
  637. StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  638. StartupInfo.wShowWindow := Word(ShowCmd);
  639. if WorkingDir = '' then
  640. WorkingDir := GetSystemDir;
  641. var InheritHandles := False;
  642. var dwCreationFlags: DWORD := CREATE_DEFAULT_ERROR_MODE;
  643. if (OutputReader <> nil) and (Wait = ewWaitUntilTerminated) then begin
  644. OutputReader.UpdateStartupInfo(StartupInfo);
  645. InheritHandles := True;
  646. dwCreationFlags := dwCreationFlags or CREATE_NO_WINDOW;
  647. end;
  648. Result := CreateProcessRedir(DisableFsRedir, nil, PChar(CmdLine), nil, nil,
  649. InheritHandles, dwCreationFlags, nil, PChar(WorkingDir),
  650. StartupInfo, ProcessInfo);
  651. if not Result then begin
  652. ResultCode := GetLastError;
  653. Exit;
  654. end;
  655. { Don't need the thread handle, so close it now }
  656. CloseHandle(ProcessInfo.hThread);
  657. if OutputReader <> nil then
  658. OutputReader.NotifyCreateProcessDone;
  659. HandleProcessWait(ProcessInfo.hProcess, Wait, ProcessMessagesProc,
  660. OutputReader, ResultCode);
  661. end;
  662. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  663. const Wait: TExecWait; const ShowCmd: Integer;
  664. const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
  665. var
  666. Info: TShellExecuteInfo;
  667. begin
  668. if WorkingDir = '' then begin
  669. WorkingDir := PathExtractDir(Filename);
  670. if WorkingDir = '' then
  671. WorkingDir := GetSystemDir;
  672. end;
  673. FillChar(Info, SizeOf(Info), 0);
  674. Info.cbSize := SizeOf(Info);
  675. Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
  676. SEE_MASK_NOCLOSEPROCESS;
  677. if Verb <> '' then
  678. Info.lpVerb := PChar(Verb);
  679. Info.lpFile := PChar(Filename);
  680. Info.lpParameters := PChar(Params);
  681. Info.lpDirectory := PChar(WorkingDir);
  682. Info.nShow := ShowCmd;
  683. Result := ShellExecuteEx(@Info);
  684. if not Result then begin
  685. ResultCode := GetLastError;
  686. Exit;
  687. end;
  688. ResultCode := STILL_ACTIVE;
  689. { A process handle won't always be returned, e.g. if DDE was used }
  690. if Info.hProcess <> 0 then
  691. HandleProcessWait(Info.hProcess, Wait, ProcessMessagesProc, nil, ResultCode);
  692. end;
  693. function CheckForOrCreateMutexes(Mutexes: String; const Create: Boolean): Boolean;
  694. function MutexPos(const S: String): Integer;
  695. begin
  696. for var I := 1 to Length(S) do
  697. if (S[I] = ',') and ((I = 1) or (S[I-1] <> '\')) then
  698. Exit(I);
  699. Result := 0;
  700. end;
  701. { Returns True if any of the mutexes in the comma-separated Mutexes string
  702. exist and Create is False }
  703. var
  704. I: Integer;
  705. M: String;
  706. H: THandle;
  707. begin
  708. Result := False;
  709. repeat
  710. I := MutexPos(Mutexes);
  711. if I = 0 then I := Maxint;
  712. M := Trim(Copy(Mutexes, 1, I-1));
  713. if M <> '' then begin
  714. StringChange(M, '\,', ',');
  715. if Create then begin
  716. CreateMutex(M)
  717. end else begin
  718. H := OpenMutex(SYNCHRONIZE, False, PChar(M));
  719. if H <> 0 then begin
  720. CloseHandle(H);
  721. Result := True;
  722. Break;
  723. end;
  724. end;
  725. end;
  726. Delete(Mutexes, 1, I);
  727. until Mutexes = '';
  728. end;
  729. function CheckForMutexes(const Mutexes: String): Boolean;
  730. begin
  731. Result := CheckForOrCreateMutexes(Mutexes, False);
  732. end;
  733. procedure CreateMutexes(const Mutexes: String);
  734. begin
  735. CheckForOrCreateMutexes(Mutexes, True);
  736. end;
  737. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  738. { Changes the "Close on exit" setting of a .pif file. Returns True if it was
  739. able to make the change. }
  740. var
  741. F: TFile;
  742. B: Byte;
  743. begin
  744. { Note: Specs on the .pif format were taken from
  745. http://smsoft.chat.ru/en/pifdoc.htm }
  746. Result := False;
  747. F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  748. try
  749. { Is it a valid .pif file? }
  750. if F.Size >= $171 then begin
  751. F.Seek($63);
  752. F.ReadBuffer(B, SizeOf(B));
  753. { Toggle the "Close on exit" bit }
  754. if (B and $10 <> 0) <> CloseOnExit then begin
  755. B := B xor $10;
  756. F.Seek($63);
  757. F.WriteBuffer(B, SizeOf(B));
  758. end;
  759. Result := True;
  760. end;
  761. finally
  762. F.Free;
  763. end;
  764. end;
  765. function GetComputerNameString: String;
  766. var
  767. Buf: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
  768. Size: DWORD;
  769. begin
  770. Size := SizeOf(Buf) div SizeOf(Buf[0]);
  771. if GetComputerName(Buf, Size) then
  772. Result := Buf
  773. else
  774. Result := '';
  775. end;
  776. function GetUserNameString: String;
  777. var
  778. Buf: array[0..256] of Char; { 256 = UNLEN }
  779. BufSize: DWORD;
  780. begin
  781. BufSize := SizeOf(Buf) div SizeOf(Buf[0]);
  782. if GetUserName(Buf, BufSize) then
  783. Result := Buf
  784. else
  785. Result := '';
  786. end;
  787. function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
  788. { Calculates a checksum of the current PendingFileRenameOperations registry
  789. value The caller can use this checksum to determine if
  790. PendingFileRenameOperations was changed (perhaps by another program). }
  791. var
  792. Context: TSHA256Context;
  793. K: HKEY;
  794. S: String;
  795. begin
  796. SHA256Init(Context);
  797. try
  798. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  799. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  800. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations', S) then
  801. SHA256Update(Context, S[1], ULength(S)*SizeOf(S[1]));
  802. { When "PendingFileRenameOperations" is full, it spills over into
  803. "PendingFileRenameOperations2" }
  804. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations2', S) then
  805. SHA256Update(Context, S[1], ULength(S)*SizeOf(S[1]));
  806. RegCloseKey(K);
  807. end;
  808. except
  809. { don't propagate exceptions }
  810. end;
  811. Result := SHA256Final(Context);
  812. end;
  813. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  814. Param: Pointer);
  815. { Enumerates all the filenames in the current PendingFileRenameOperations
  816. registry value or WININIT.INI file. The function does not distinguish between
  817. source and destination filenames; it enumerates both. }
  818. procedure DoValue(const K: HKEY; const ValueName: PChar);
  819. var
  820. S: String;
  821. P, PEnd: PChar;
  822. begin
  823. if not RegQueryMultiStringValue(K, ValueName, S) then
  824. Exit;
  825. P := PChar(S);
  826. PEnd := P + Length(S);
  827. while P < PEnd do begin
  828. if P[0] = '!' then
  829. { Note: '!' means that MoveFileEx was called with the
  830. MOVEFILE_REPLACE_EXISTING flag }
  831. Inc(P)
  832. else if (P[0] = '*') and CharInSet(P[1], ['1', '2']) then
  833. { Note: '*1' and '*2' _seem_ to mean
  834. - On the source filename, *1 means the file exists, *2 otherwise
  835. - On the destination filename, *1 means the path exists, *2 otherwise }
  836. Inc(P, 2);
  837. if StrLComp(P, '\??\', 4) = 0 then begin
  838. Inc(P, 4);
  839. if P[0] <> #0 then
  840. EnumFunc(P, Param);
  841. end;
  842. Inc(P, StrLen(P) + 1);
  843. end;
  844. end;
  845. var
  846. K: HKEY;
  847. begin
  848. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  849. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  850. try
  851. DoValue(K, 'PendingFileRenameOperations');
  852. { When "PendingFileRenameOperations" is full, it spills over into
  853. "PendingFileRenameOperations2" }
  854. DoValue(K, 'PendingFileRenameOperations2');
  855. finally
  856. RegCloseKey(K);
  857. end;
  858. end;
  859. end;
  860. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  861. var
  862. RootKey, K: HKEY;
  863. begin
  864. if PerUserFont then
  865. RootKey := HKEY_CURRENT_USER
  866. else
  867. RootKey := HKEY_LOCAL_MACHINE;
  868. if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
  869. 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  870. RegDeleteValue(K, PChar(FontName));
  871. RegCloseKey(K);
  872. end;
  873. if RemoveFontResource(PChar(FontFilename)) then
  874. SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  875. end;
  876. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  877. var FreeBytes, TotalBytes: Int64): Boolean;
  878. var
  879. GetDiskFreeSpaceExFunc: function(lpDirectoryName: PChar;
  880. lpFreeBytesAvailable: PLargeInteger; lpTotalNumberOfBytes: PLargeInteger;
  881. lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
  882. PrevState: TPreviousFsRedirectionState;
  883. SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: Cardinal;
  884. begin
  885. { NOTE: The docs claim that GetDiskFreeSpace supports UNC paths on
  886. Windows 95 OSR2 and later. But that does not seem to be the case in my
  887. tests; it fails with error 50 on Windows 95 through Me.
  888. GetDiskFreeSpaceEx, however, *does* succeed with UNC paths, so use it
  889. if available. }
  890. GetDiskFreeSpaceExFunc := GetProcAddress(GetModuleHandle(kernel32),
  891. 'GetDiskFreeSpaceExW');
  892. if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
  893. Result := False;
  894. Exit;
  895. end;
  896. try
  897. if Assigned(@GetDiskFreeSpaceExFunc) then begin
  898. Result := GetDiskFreeSpaceExFunc(PChar(AddBackslash(PathExpand(DriveRoot))),
  899. @FreeBytes, @TotalBytes, nil);
  900. end
  901. else begin
  902. Result := GetDiskFreeSpace(PChar(AddBackslash(PathExtractDrive(PathExpand(DriveRoot)))),
  903. SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters);
  904. if Result then begin
  905. { The result of GetDiskFreeSpace does not cap at 2GB, so we must use a
  906. 64-bit multiply operation to avoid an overflow. }
  907. FreeBytes := Int64(BytesPerSector * SectorsPerCluster) * FreeClusters;
  908. TotalBytes := Int64(BytesPerSector * SectorsPerCluster) * TotalClusters;
  909. end;
  910. end;
  911. finally
  912. RestoreFsRedirection(PrevState);
  913. end;
  914. end;
  915. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  916. const StartDir: String; var FreeBytes, TotalBytes: Int64): Boolean;
  917. { Gets the free and total space available on the specified directory. If that
  918. fails (e.g. if the directory does not exist), then it strips off the last
  919. component of the path and tries again. This repeats until it reaches the
  920. root. Returns True if successful. }
  921. var
  922. Dir: String;
  923. LastLen: Integer;
  924. begin
  925. Result := False;
  926. Dir := RemoveBackslashUnlessRoot(StartDir);
  927. LastLen := 0;
  928. while Length(Dir) <> LastLen do begin
  929. if GetSpaceOnDisk(DisableFsRedir, Dir, FreeBytes, TotalBytes) then begin
  930. Result := True;
  931. Break;
  932. end;
  933. LastLen := Length(Dir);
  934. Dir := PathExtractDir(Dir);
  935. end;
  936. end;
  937. procedure RefreshEnvironment;
  938. { Notifies other applications (Explorer) that environment variables have
  939. changed. Based on code from KB article 104011. }
  940. var
  941. MsgResult: DWORD_PTR;
  942. begin
  943. { Note: We originally used SendNotifyMessage to broadcast the message but it
  944. turned out that while it worked fine on NT 4 and 2000 it didn't work on XP
  945. -- the string "Environment" in lParam would be garbled on the receiving
  946. end (why I'm not exactly sure). We now use SendMessageTimeout as directed
  947. in the KB article 104011. It isn't as elegant since it could cause us to
  948. be delayed if another app is hung, but it'll have to do. }
  949. SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
  950. LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @MsgResult);
  951. end;
  952. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  953. begin
  954. Dir := RemoveBackslashUnlessRoot(Dir);
  955. if (PathExtractPath(Dir) = Dir) or DirExistsRedir(DisableFsRedir, Dir) then
  956. Result := True
  957. else
  958. Result := ForceDirectories(DisableFsRedir, PathExtractPath(Dir)) and
  959. CreateDirectoryRedir(DisableFsRedir, Dir);
  960. end;
  961. procedure AddAttributesToFile(const DisableFsRedir: Boolean;
  962. const Filename: String; Attribs: Integer);
  963. var
  964. ExistingAttr: DWORD;
  965. begin
  966. if Attribs <> 0 then begin
  967. ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
  968. if ExistingAttr <> INVALID_FILE_ATTRIBUTES then
  969. SetFileAttributesRedir(DisableFsRedir, Filename,
  970. (ExistingAttr and not FILE_ATTRIBUTE_NORMAL) or DWORD(Attribs));
  971. end;
  972. end;
  973. { TSimpleStringList }
  974. procedure TSimpleStringList.Add(const S: String);
  975. var
  976. Delta: Integer;
  977. begin
  978. if FCount = FCapacity then begin
  979. if FCapacity > 64 then Delta := FCapacity div 4 else
  980. if FCapacity > 8 then Delta := 16 else
  981. Delta := 4;
  982. SetCapacity(FCapacity + Delta);
  983. end;
  984. FList^[FCount] := S;
  985. Inc(FCount);
  986. end;
  987. procedure TSimpleStringList.AddIfDoesntExist(const S: String);
  988. begin
  989. if IndexOf(S) = -1 then
  990. Add(S);
  991. end;
  992. procedure TSimpleStringList.SetCapacity(NewCapacity: Integer);
  993. begin
  994. ReallocMem(FList, NewCapacity * SizeOf(Pointer));
  995. if NewCapacity > FCapacity then
  996. FillChar(FList^[FCapacity], (NewCapacity - FCapacity) * SizeOf(Pointer), 0);
  997. FCapacity := NewCapacity;
  998. end;
  999. procedure TSimpleStringList.Clear;
  1000. begin
  1001. if FCount <> 0 then Finalize(FList^[0], FCount);
  1002. FCount := 0;
  1003. SetCapacity(0);
  1004. end;
  1005. function TSimpleStringList.Get(Index: Integer): String;
  1006. begin
  1007. Result := FList^[Index];
  1008. end;
  1009. function TSimpleStringList.IndexOf(const S: String): Integer;
  1010. { Note: This is case-sensitive, unlike TStringList.IndexOf }
  1011. var
  1012. I: Integer;
  1013. begin
  1014. Result := -1;
  1015. for I := 0 to FCount-1 do
  1016. if FList^[I] = S then begin
  1017. Result := I;
  1018. Break;
  1019. end;
  1020. end;
  1021. destructor TSimpleStringList.Destroy;
  1022. begin
  1023. Clear;
  1024. inherited Destroy;
  1025. end;
  1026. { TProgressThrottler }
  1027. constructor TProgressThrottler.Create(const OnDownloadProgress: TOnDownloadProgress);
  1028. begin
  1029. inherited Create;
  1030. FOnDownloadProgress := OnDownloadProgress;
  1031. end;
  1032. constructor TProgressThrottler.Create(const OnExtractionProgress: TOnExtractionProgress);
  1033. begin
  1034. inherited Create;
  1035. FOnExtractionProgress := OnExtractionProgress;
  1036. end;
  1037. procedure TProgressThrottler.Reset;
  1038. begin
  1039. FStopWatch.Stop;
  1040. end;
  1041. function TProgressThrottler.ThrottleOk(const Progress, ProgressMax: Int64): Boolean;
  1042. begin
  1043. if FStopWatch.IsRunning then begin
  1044. Result := ((Progress = ProgressMax) and (FLastOkProgress <> ProgressMax)) or (FStopWatch.ElapsedMilliseconds >= 50);
  1045. if Result then
  1046. FStopWatch.Reset;
  1047. end else begin
  1048. Result := True;
  1049. FStopWatch := TStopwatch.StartNew;
  1050. end;
  1051. if Result then
  1052. FLastOkProgress := Progress;
  1053. end;
  1054. function TProgressThrottler.OnDownloadProgress(const Url, BaseName: string; const Progress,
  1055. ProgressMax: Int64): Boolean;
  1056. begin
  1057. if Assigned(FOnDownloadProgress) and ThrottleOk(Progress, ProgressMax) then begin
  1058. Result := FOnDownloadProgress(Url, BaseName, Progress, ProgressMax)
  1059. end else
  1060. Result := True;
  1061. end;
  1062. function TProgressThrottler.OnExtractionProgress(const ArchiveName, FileName: string;
  1063. const Progress, ProgressMax: Int64): Boolean;
  1064. begin
  1065. if Assigned(FOnExtractionProgress) and ThrottleOk(Progress, ProgressMax) then
  1066. Result := FOnExtractionProgress(ArchiveName, FileName, Progress, ProgressMax)
  1067. else
  1068. Result := True;
  1069. end;
  1070. end.