IDE.Wizard.WizardFormRegistryHelper.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. unit IDE.Wizard.WizardFormRegistryHelper;
  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. Helper to avoid duplicate code between WizardForm and RegistryDesignerForm
  8. }
  9. interface
  10. uses
  11. Forms, StdCtrls, ExtCtrls, BitmapButton;
  12. type
  13. TPrivilegesRequired = (prAdmin, prLowest, prDynamic);
  14. TWizardFormRegistryHelper = class
  15. private
  16. FForm: TForm;
  17. FFileEdit: TEdit;
  18. FUninsDeleteKeyCheck, FUninsDeleteKeyIfEmptyCheck,
  19. FUninsDeleteValueCheck, FMinVerCheck: TCheckBox;
  20. FMinVerEdit: TEdit;
  21. FMinVerDocBitBtn: TBitmapButton;
  22. FPrivilegesRequired: TPrivilegesRequired;
  23. procedure SetPrivilegesRequired(const Value: TPrivilegesRequired);
  24. procedure UpdateImages;
  25. procedure AfterMonitorDpiChanged(Sender: TObject; OldDPI: Integer; NewDPI: Integer);
  26. procedure FileButtonClick(Sender: TObject);
  27. procedure UninsDeleteKeyIfEmptyCheckClick(Sender: TObject);
  28. procedure MinVerCheckClick(Sender: TObject);
  29. procedure MinVerDocBitBtnClick(Sender: TObject);
  30. public
  31. constructor Create(const Form: TForm; const FileEdit: TEdit;
  32. const FileButton: TButton; const UninsDeleteKeyCheck,
  33. UninsDeleteKeyIfEmptyCheck, UninsDeleteValueCheck, MinVerCheck: TCheckBox;
  34. const MinVerEdit: TEdit; const MinVerDocBitBtn: TBitmapButton);
  35. procedure AddScript(var Registry: String; const AllowException: Boolean);
  36. property PrivilegesRequired: TPrivilegesRequired write SetPrivilegesRequired;
  37. end;
  38. implementation
  39. uses
  40. Windows, Classes, SysUtils, StrUtils, TypInfo, Graphics, UITypes,
  41. ComCtrls, BrowseFunc,
  42. IDE.MainForm, IDE.ImagesModule, IDE.HelperFunc, IDE.Messages, Shared.CommonFunc, IDE.HtmlHelpFunc;
  43. { TWizardFormRegistryHelper }
  44. procedure TWizardFormRegistryHelper.SetPrivilegesRequired(
  45. const Value: TPrivilegesRequired);
  46. begin
  47. FPrivilegesRequired := Value;
  48. end;
  49. procedure TWizardFormRegistryHelper.UpdateImages;
  50. function GetImage(const Button: TToolButton; const WH: Integer): TWICImage;
  51. begin
  52. Result := ImagesModule.LightToolBarImageCollection.GetSourceImage(Button.ImageIndex, WH, WH)
  53. end;
  54. begin
  55. { After a DPI change the button's Width and Height isn't yet updated, so calculate it ourselves }
  56. var WH := MulDiv(16, FForm.CurrentPPI, 96);
  57. FMinVerDocBitBtn.Bitmap.Assign(GetImage(MainForm.HelpButton, WH));
  58. end;
  59. constructor TWizardFormRegistryHelper.Create(const Form: TForm;
  60. const FileEdit: TEdit; const FileButton: TButton; const UninsDeleteKeyCheck,
  61. UninsDeleteKeyIfEmptyCheck, UninsDeleteValueCheck, MinVerCheck: TCheckBox;
  62. const MinVerEdit: TEdit; const MinVerDocBitBtn: TBitmapButton);
  63. begin
  64. FForm := Form;
  65. FFileEdit := FileEdit;
  66. FUninsDeleteKeyCheck := UninsDeleteKeyCheck;
  67. FUninsDeleteKeyIfEmptyCheck := UninsDeleteKeyIfEmptyCheck;
  68. FUninsDeleteValueCheck := UninsDeleteValueCheck;
  69. FMinVerCheck := MinVerCheck;
  70. FMinVerEdit := MinVerEdit;
  71. FMinVerDocBitBtn := MinVerDocBitBtn;
  72. FileButton.OnClick := FileButtonClick;
  73. UninsDeleteKeyIfEmptyCheck.OnClick := UninsDeleteKeyIfEmptyCheckClick;
  74. MinVerCheck.OnClick := MinVerCheckClick;
  75. MinVerCheck.OnClick(nil);
  76. MinVerDocBitBtn.OnClick := MinVerDocBitBtnClick;
  77. MinVerDocBitBtn.Cursor := crHandPoint;
  78. TryEnableAutoCompleteFileSystem(FileEdit.Handle);
  79. Form.OnAfterMonitorDpiChanged := AfterMonitorDpiChanged;
  80. UpdateImages;
  81. end;
  82. procedure TWizardFormRegistryHelper.AfterMonitorDpiChanged(Sender: TObject; OldDPI: Integer; NewDPI: Integer);
  83. begin
  84. UpdateImages;
  85. end;
  86. procedure TWizardFormRegistryHelper.FileButtonClick(Sender: TObject);
  87. begin
  88. var FileName: String := FFileEdit.Text;
  89. if NewGetOpenFileName('', FileName, '', SWizardAppRegFilter, SWizardAppRegDefaultExt, FForm.Handle) then
  90. FFileEdit.Text := FileName;
  91. end;
  92. procedure TWizardFormRegistryHelper.UninsDeleteKeyIfEmptyCheckClick(Sender: TObject);
  93. begin
  94. FUninsDeleteKeyCheck.Enabled := FUninsDeleteKeyIfEmptyCheck.Checked;
  95. if not FUninsDeleteKeyCheck.Enabled then
  96. FUninsDeleteKeyCheck.Checked := False;
  97. end;
  98. procedure TWizardFormRegistryHelper.MinVerCheckClick(Sender: TObject);
  99. begin
  100. FMinVerEdit.Enabled := FMinVerCheck.Checked;
  101. FMinVerDocBitBtn.Visible := FMinVerCheck.Checked;
  102. if FMinVerEdit.Enabled then
  103. FForm.ActiveControl := FMinVerEdit;
  104. end;
  105. procedure TWizardFormRegistryHelper.MinVerDocBitBtnClick(Sender: TObject);
  106. begin
  107. if Assigned(HtmlHelp) then
  108. HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, Cardinal(PChar('topic_winvernotes.htm')));
  109. end;
  110. procedure TWizardFormRegistryHelper.AddScript(var Registry: String;
  111. const AllowException: Boolean);
  112. function NextLine(const Lines: TStrings; var LineIndex: Integer): String;
  113. begin
  114. Inc(LineIndex);
  115. if LineIndex < Lines.Count then
  116. Result := Lines[LineIndex]
  117. else
  118. Result := ''; { Official .reg files must end with a blank line so should never get here but we support ones without }
  119. end;
  120. function CutStrBeginEnd(S: String; CharCount: Integer): String;
  121. begin
  122. Result := Copy(S, CharCount + 1, S.Length - 2 * CharCount);
  123. end;
  124. function StrRootRename(S: String): String;
  125. type
  126. TStrings = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_USERS, HKEY_CURRENT_CONFIG);
  127. begin
  128. var ARoot := TStrings(GetEnumValue(TypeInfo(TStrings), S));
  129. case ARoot of
  130. HKEY_CURRENT_USER: Result := 'HKCU';
  131. HKEY_LOCAL_MACHINE: Result := 'HKLM';
  132. HKEY_CLASSES_ROOT: Result := 'HKCR';
  133. HKEY_USERS: Result := 'HKU';
  134. HKEY_CURRENT_CONFIG: Result := 'HKCC';
  135. else
  136. raise Exception.CreateFmt('Unknown root %s', [S]);
  137. end;
  138. end;
  139. function UTF16LEHexStrToStr(HexStr: String): String;
  140. begin
  141. if HexStr.Length mod 4 <> 0 then
  142. HexStr := HexStr + '00'; { RegEdit does this as well on import }
  143. var UTF16LEBytes: TBytes;
  144. SetLength(UTF16LEBytes, HexStr.Length div 2);
  145. var i := 1;
  146. var idx := 0;
  147. while i <= HexStr.Length do
  148. begin
  149. UTF16LEBytes[idx] := StrToInt('$' + HexStr[i] + HexStr[i + 1]);
  150. i := i + 2;
  151. idx := idx + 1;
  152. end;
  153. Result := TEncoding.Unicode.GetString(UTF16LEBytes);
  154. end;
  155. type
  156. TValueType = (vtSz, vtSzAsList, vtExpandSz, vtMultiSz, vtBinary, vtDWord, vtDWordAsList, vtQWord, vtNone, vtDelete, vtUnsupported);
  157. function GetValueType(AStr: String): TValueType;
  158. { See https://en.wikipedia.org/wiki/Windows_Registry#.REG_files
  159. Value formats: (we don't support I/K/L and just ignore those)
  160. "Value A"="<REG_SZ String value data with escape characters>"
  161. "Value B"=hex:<REG_BINARY Binary data (as comma-delimited list of hexadecimal values)>
  162. "Value C"=dword:<REG_DWORD DWORD value integer>
  163. "Value D"=hex(0):<REG_NONE (as comma-delimited list of hexadecimal values)>
  164. "Value E"=hex(1):<REG_SZ (as comma-delimited list of hexadecimal values representing a UTF-16LE NUL-terminated string)>
  165. "Value F"=hex(2):<REG_EXPAND_SZ Expandable string value data (as comma-delimited list of hexadecimal values representing a UTF-16LE NUL-terminated string)>
  166. "Value G"=hex(3):<REG_BINARY Binary data (as comma-delimited list of hexadecimal values)> ; equal to "Value B"
  167. "Value H"=hex(4):<REG_DWORD DWORD value (as comma-delimited list of 4 hexadecimal values, in little endian byte order)>
  168. "Value I"=hex(5):<REG_DWORD_BIG_ENDIAN DWORD value (as comma-delimited list of 4 hexadecimal values, in big endian byte order)>
  169. "Value J"=hex(7):<RED_MULTISZ Multi-string value data (as comma-delimited list of hexadecimal values representing UTF-16LE NUL-terminated strings)>
  170. "Value K"=hex(8):<REG_RESOURCE_LIST (as comma-delimited list of hexadecimal values)>
  171. "Value L"=hex(a):<REG_RESOURCE_REQUIREMENTS_LIST (as comma-delimited list of hexadecimal values)>
  172. "Value M"=hex(b):<REG_QWORD QWORD value (as comma-delimited list of 8 hexadecimal values, in little endian byte order)>
  173. Other notes from the article:
  174. To remove a key (and all subkeys, values and data), the key name must be preceded by a minus sign ("-")
  175. To remove a value (and its data), the values to be removed must have a minus sign ("-") after the equal sign ("=")
  176. The Default Value of a key can be edited by using "@" instead of "Value Name"
  177. Lines beginning with a semicolon are considered comments
  178. BTW: Missing from the article is a note about multiline lists, these use "\" to continue }
  179. begin
  180. if Pos('"', AStr) <> 0 then
  181. Result := vtSz //Value A
  182. else if (Pos('hex:', AStr) <> 0) or
  183. (Pos('hex(3):', AStr) <> 0) then
  184. Result := vtBinary //Value B or G
  185. else if Pos('dword:', AStr) <> 0 then
  186. Result := vtDWord //Value C
  187. else if Pos('hex(0):', AStr) <> 0 then
  188. Result := vtNone //Value D
  189. else if Pos('hex(1):', AStr) <> 0 then
  190. Result := vtSzAsList //Value E
  191. else if Pos('hex(2):', AStr) <> 0 then
  192. Result := vtExpandSz //Value F
  193. else if Pos('hex(4):', AStr) <> 0 then
  194. Result := vtDWordAsList //Value H
  195. else if Pos('hex(7):', AStr) <> 0 then
  196. Result := vtMultiSz //Value J
  197. else if Pos('hex(b):', AStr) <> 0 then
  198. Result := vtQWord //Value M
  199. else if AStr.StartsWith('-') then
  200. Result := vtDelete
  201. else
  202. Result := vtUnsupported;
  203. end;
  204. type
  205. TRegistryEntry = record
  206. Root, Subkey, ValueName, ValueData, ValueType: String;
  207. end;
  208. function RequiresAdminInstallMode(AEntry: TRegistryEntry): Boolean;
  209. begin
  210. Result := (AEntry.Root = 'HKLM') or (AEntry.Root = 'HKCC') or
  211. ((AEntry.Root = 'HKU') and SameText(AEntry.Subkey, '.Default'));
  212. end;
  213. function RequiresNotAdminInstallMode(AEntry: TRegistryEntry): Boolean;
  214. begin
  215. Result := (AEntry.Root = 'HKCU');
  216. end;
  217. function TextCommon(AEntry: TRegistryEntry): String;
  218. begin
  219. Result := '';
  220. if FMinVerCheck.Checked and (FMinVerEdit.Text <> '') then
  221. Result := Result + '; MinVersion: ' + FMinVerEdit.Text;
  222. if (FPrivilegesRequired <> prAdmin) and RequiresAdminInstallMode(AEntry) then
  223. Result := Result + '; Check: IsAdminInstallMode'
  224. else if (FPrivilegesRequired <> prLowest) and RequiresNotAdminInstallMode(AEntry) then
  225. Result := Result + '; Check: not IsAdminInstallMode';
  226. end;
  227. function TextKeyEntry(AEntry: TRegistryEntry; ADeleteKey: Boolean): String;
  228. begin
  229. Result := 'Root: ' + AEntry.Root +
  230. '; Subkey: ' + AEntry.Subkey;
  231. if ADeleteKey then
  232. Result := Result + '; ValueType: none' +
  233. '; Flags: deletekey'
  234. else begin
  235. if FUninsDeleteKeyCheck.Checked then
  236. Result := Result + '; Flags: uninsdeletekey'
  237. else if FUninsDeleteKeyIfEmptyCheck.Checked then
  238. Result := Result + '; Flags: uninsdeletekeyifempty';
  239. end;
  240. Result := Result + TextCommon(AEntry);
  241. end;
  242. function TextValueEntry(AEntry: TRegistryEntry; AValueType: TValueType): String;
  243. begin
  244. Result := 'Root: ' + AEntry.Root +
  245. '; Subkey: ' + AEntry.Subkey +
  246. '; ValueType: ' + AEntry.ValueType +
  247. '; ValueName: ' + AEntry.ValueName;
  248. if AValueType = vtDelete then
  249. Result := Result + '; Flags: deletevalue'
  250. else begin
  251. if AValueType <> vtNone then
  252. Result := Result + '; ValueData: ' + AEntry.ValueData;
  253. if FUninsDeleteValueCheck.Checked then
  254. Result := Result + '; Flags: uninsdeletevalue';
  255. end;
  256. Result := Result + TextCommon(AEntry);
  257. end;
  258. function TextHeader: String;
  259. begin
  260. Result := ';Registry data from file ' + ExtractFileName(FFileEdit.Text);
  261. end;
  262. function TextBadHeader: String;
  263. begin
  264. Result := ';COULD NOT IMPORT ' + ExtractFileName(FFileEdit.Text);
  265. end;
  266. function TextFooter(const HadFilteredKeys, HadUnsupportedValueTypes: Boolean): String;
  267. begin
  268. Result := ';End of registry data from file ' + ExtractFileName(FFileEdit.Text);
  269. if HadFilteredKeys then
  270. Result := Result + SNewLine + ';SOME KEYS FILTERED DUE TO PRIVILEGESREQUIRED SETTINGS!';
  271. if HadUnsupportedValueTypes then
  272. Result := Result + SNewLine + ';SOME VALUES WITH UNSUPPORTED TYPES SKIPPED!'
  273. end;
  274. begin
  275. if FFileEdit.Text = '' then
  276. Exit;
  277. var Lines := TStringList.Create;
  278. var OutLines := TStringList.Create;
  279. try
  280. Lines.LoadFromFile(FFileEdit.Text);
  281. { Official .reg files must have blank lines as second and last lines but we
  282. don't require that so we just check for the header on the first line }
  283. const Header = 'Windows Registry Editor Version 5.00'; { don't localize }
  284. if (Lines.Count = 0) or (Lines[0] <> Header) then begin
  285. if AllowException then
  286. raise Exception.Create('Invalid file format.')
  287. else begin
  288. Registry := Registry + TextBadHeader + SNewLine;
  289. Exit;
  290. end;
  291. end;
  292. var LineIndex := 1;
  293. var HadFilteredKeys := False;
  294. var HadUnsupportedValueTypes := False;
  295. while LineIndex <= Lines.Count-1 do
  296. begin
  297. var Line := Lines[LineIndex];
  298. if (Length(Line) > 2) and (Line[1] = '[') and (Line[Line.Length] = ']') then
  299. begin
  300. { Got a new section, first handle the key }
  301. Line := CutStrBeginEnd(Line, 1);
  302. var DeleteKey := Line.StartsWith('-');
  303. if DeleteKey then
  304. Delete(Line, 1, 1);
  305. var P := Pos('\', Line);
  306. var Entry: TRegistryEntry;
  307. Entry.Root := StrRootRename(Copy(Line, 1, P - 1));
  308. Entry.Subkey := Copy(Line, P + 1, MaxInt);
  309. if Entry.Root = 'HKCR' then begin
  310. Entry.Root := 'HKA';
  311. Entry.Subkey := 'Software\Classes\' + Entry.Subkey;
  312. end;
  313. Entry.Subkey := Entry.Subkey.Replace('\WOW6432Node', '')
  314. .Replace('{', '{{')
  315. .QuotedString('"');
  316. var FilterKey := ((FPrivilegesRequired = prAdmin) and RequiresNotAdminInstallMode(Entry)) or
  317. ((FPrivilegesRequired = prLowest) and RequiresAdminInstallMode(Entry));
  318. if not FilterKey then
  319. OutLines.Add(TextKeyEntry(Entry, DeleteKey))
  320. else
  321. HadFilteredKeys := True;
  322. { Key done, handle values }
  323. Line := NextLine(Lines, LineIndex);
  324. while Line <> '' do begin
  325. if not FilterKey and not DeleteKey and (Line[1] <> ';') then begin
  326. P := Pos('=', Line);
  327. if (P = 2) and (Line[1] = '@') then
  328. Entry.ValueName := '""'
  329. else begin
  330. Entry.ValueName := CutStrBeginEnd(Copy(Line, 1, P - 1), 1);
  331. Entry.ValueName := Entry.ValueName.Replace('\\', '\')
  332. .Replace('{', '{{')
  333. .QuotedString('"');
  334. end;
  335. var ValueTypeAndData := Copy(Line, P + 1, MaxInt);
  336. var ValueType := GetValueType(ValueTypeAndData);
  337. case ValueType of
  338. vtSz:
  339. begin
  340. Entry.ValueData := CutStrBeginEnd(ValueTypeAndData, 1);
  341. Entry.ValueData := Entry.ValueData.Replace('\\', '\')
  342. .Replace('{', '{{')
  343. .QuotedString('"');
  344. Entry.ValueType := 'string';
  345. end;
  346. vtSzAsList, vtExpandSz, vtMultiSz, vtBinary:
  347. begin
  348. P := Pos(':', ValueTypeAndData);
  349. var ValueData := Copy(ValueTypeAndData, P + 1, MaxInt);
  350. var HasMoreLines := ValueData[ValueData.Length] = '\';
  351. if HasMoreLines then
  352. Delete(ValueData, ValueData.Length, 1);
  353. Entry.ValueData := ValueData;
  354. while HasMoreLines do
  355. begin
  356. ValueData := NextLine(Lines, LineIndex).TrimLeft;
  357. HasMoreLines := ValueData[ValueData.Length] = '\';
  358. if HasMoreLines then
  359. Delete(ValueData, ValueData.Length, 1);
  360. Entry.ValueData := Entry.ValueData + ValueData;
  361. end;
  362. Entry.ValueData := Entry.ValueData.Replace(',', ' ');
  363. if ValueType <> vtBinary then
  364. begin
  365. Entry.ValueData := Entry.ValueData.Replace(' ', '');
  366. Entry.ValueData := UTF16LEHexStrToStr(Entry.ValueData);
  367. end;
  368. if ValueType in [vtSzAsList, vtExpandSz] then
  369. begin
  370. Entry.ValueData := Entry.ValueData.Replace(#0, '');
  371. Entry.ValueType := IfThen(ValueType = vtSzAsList, 'string', 'expandsz');
  372. end else if ValueType = vtMultiSz then
  373. begin
  374. Entry.ValueData := Entry.ValueData.Replace(#0, '{break}');
  375. Entry.ValueType := 'multisz';
  376. end else
  377. Entry.ValueType := 'binary';
  378. Entry.ValueData := Entry.ValueData.QuotedString('"');
  379. end;
  380. vtDWord, vtDWordAsList, vtQWord:
  381. begin
  382. P := Pos(':', ValueTypeAndData);
  383. Entry.ValueData := Copy(ValueTypeAndData, P + 1, MaxInt);
  384. if ValueType in [vtDWordAsList, vtQWord] then
  385. begin
  386. { ValueData is in reverse order, fix this }
  387. var ReverseValueData := Entry.ValueData.Replace(',', '');
  388. Entry.ValueData := '';
  389. for var I := 0 to ReverseValueData.Length div 2 do
  390. Entry.ValueData := Copy(ReverseValueData, (I * 2) + 1, 2) + Entry.ValueData;
  391. Entry.ValueType := IfThen(ValueType = vtDWordAsList, 'dword', 'qword');
  392. end else
  393. Entry.ValueType := 'dword';
  394. Entry.ValueData := '$' + Entry.ValueData;
  395. end;
  396. vtNone, vtDelete:
  397. begin
  398. Entry.ValueType := 'none';
  399. Entry.ValueData := ''; { value doesn't matter }
  400. end;
  401. end;
  402. if ValueType <> vtUnsupported then
  403. OutLines.Add(TextValueEntry(Entry, ValueType))
  404. else
  405. HadUnsupportedValueTypes := True;
  406. end;
  407. Line := NextLine(Lines, LineIndex); { Go to the next line - should be the next value or a comment }
  408. end; { Out of values }
  409. end;
  410. Inc(LineIndex); { Go to the next line - should be the next key section or a comment }
  411. end;
  412. OutLines.Insert(0, TextHeader);
  413. OutLines.Add(TextFooter(HadFilteredKeys, HadUnsupportedValueTypes));
  414. Registry := Registry + OutLines.Text;
  415. finally
  416. Lines.Free;
  417. OutLines.Free;
  418. end;
  419. end;
  420. end.