Setup.InstFunc.pas 38 KB

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