Setup.InstFunc.pas 41 KB

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