CompExeUpdate.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. unit CompExeUpdate;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2020 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. PE header and resource update functions used by the compiler only
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, FileClass, VerInfo;
  12. {$I VERSION.INC}
  13. procedure UpdateSetupPEHeaderFields(const F: TCustomFile;
  14. const IsVistaCompatible, IsTSAware, IsDEPCompatible, IsASLRCompatible: Boolean);
  15. procedure UpdateIcons(const FileName, IcoFileName: String; const DeleteUninstallIcon: Boolean);
  16. procedure UpdateVersionInfo(const F: TCustomFile;
  17. const NewBinaryFileVersion, NewBinaryProductVersion: TFileVersionNumbers;
  18. const NewCompanyName, NewFileDescription, NewTextFileVersion, NewLegalCopyright,
  19. NewProductName, NewTextProductVersion, NewOriginalFileName: String;
  20. const SetFileVersionAndDescription: Boolean);
  21. procedure RemoveManifestDllHijackProtection(const F: TCustomFile; const TestBlockOnly: Boolean);
  22. implementation
  23. uses
  24. ResUpdate{$IFDEF UNICODE}, Math{$ENDIF}, Int64Em;
  25. procedure UpdateSetupPEHeaderFields(const F: TCustomFile;
  26. const IsVistaCompatible, IsTSAware, IsDEPCompatible, IsASLRCompatible: Boolean);
  27. function SeekToPEHeader(const F: TCustomFile): Boolean;
  28. var
  29. DosHeader: packed record
  30. Sig: array[0..1] of AnsiChar;
  31. Other: array[0..57] of Byte;
  32. PEHeaderOffset: LongWord;
  33. end;
  34. Sig: DWORD;
  35. begin
  36. Result := False;
  37. F.Seek(0);
  38. if F.Read(DosHeader, SizeOf(DosHeader)) = SizeOf(DosHeader) then begin
  39. if (DosHeader.Sig[0] = 'M') and (DosHeader.Sig[1] = 'Z') and
  40. (DosHeader.PEHeaderOffset <> 0) then begin
  41. F.Seek(DosHeader.PEHeaderOffset);
  42. if F.Read(Sig, SizeOf(Sig)) = SizeOf(Sig) then
  43. if Sig = IMAGE_NT_SIGNATURE then
  44. Result := True;
  45. end;
  46. end;
  47. end;
  48. const
  49. IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = $0040;
  50. IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;
  51. IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = $8000;
  52. OffsetOfOperatingSystemVersion = $28;
  53. OffsetOfImageVersion = $2C;
  54. OffsetOfSubsystemVersion = $30;
  55. OffsetOfDllCharacteristics = $46;
  56. var
  57. Header: TImageFileHeader;
  58. Ofs: Cardinal;
  59. OptMagic, DllChars, OrigDllChars: Word;
  60. VersionRecord: packed record
  61. Major, Minor: Word;
  62. end;
  63. begin
  64. if SeekToPEHeader(F) then begin
  65. if (F.Read(Header, SizeOf(Header)) = SizeOf(Header)) and
  66. (Header.SizeOfOptionalHeader = 224) then begin
  67. Ofs := F.Position.Lo;
  68. if (F.Read(OptMagic, SizeOf(OptMagic)) = SizeOf(OptMagic)) and
  69. (OptMagic = IMAGE_NT_OPTIONAL_HDR32_MAGIC) then begin
  70. if IsVistaCompatible then begin
  71. { Update OS/Subsystem version }
  72. VersionRecord.Major := 6;
  73. VersionRecord.Minor := 0;
  74. F.Seek(Ofs + OffsetOfOperatingSystemVersion);
  75. F.WriteBuffer(VersionRecord, SizeOf(VersionRecord));
  76. F.Seek(Ofs + OffsetOfSubsystemVersion);
  77. F.WriteBuffer(VersionRecord, SizeOf(VersionRecord));
  78. end;
  79. { Update MajorImageVersion and MinorImageVersion to 6.0.
  80. Works around apparent bug in Vista (still present in Vista SP1;
  81. not reproducible on Server 2008): When UAC is turned off,
  82. launching an uninstaller (as admin) from ARP and answering No at the
  83. ConfirmUninstall message box causes a "This program might not have
  84. uninstalled correctly" dialog to be displayed, even if the EXE
  85. has a proper "Vista-aware" manifest. I discovered that if the EXE's
  86. image version is set to 6.0, like the EXEs that ship with Vista
  87. (notepad.exe), the dialog does not appear. (This is reproducible
  88. with notepad.exe too if its image version is changed to anything
  89. other than 6.0 exactly.) }
  90. VersionRecord.Major := 6;
  91. VersionRecord.Minor := 0;
  92. F.Seek(Ofs + OffsetOfImageVersion);
  93. F.WriteBuffer(VersionRecord, SizeOf(VersionRecord));
  94. { Update DllCharacteristics }
  95. F.Seek(Ofs + OffsetOfDllCharacteristics);
  96. if F.Read(DllChars, SizeOf(DllChars)) = SizeOf(DllChars) then begin
  97. OrigDllChars := DllChars;
  98. if IsTSAware then
  99. DllChars := DllChars or IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
  100. else
  101. DllChars := DllChars and not IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE;
  102. if IsDEPCompatible then
  103. DllChars := DllChars or IMAGE_DLLCHARACTERISTICS_NX_COMPAT
  104. else
  105. DllChars := DllChars and not IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
  106. { Note: because we stripped relocations from Setup(Ldr).e32 during
  107. compilation IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE won't actually
  108. enable ASLR, but allow setting it anyway to make checkers happy. }
  109. if IsASLRCompatible then
  110. DllChars := DllChars or IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
  111. else
  112. DllChars := DllChars and not IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
  113. if DllChars <> OrigDllChars then begin
  114. F.Seek(Ofs + OffsetOfDllCharacteristics);
  115. F.WriteBuffer(DllChars, SizeOf(DllChars));
  116. end;
  117. Exit;
  118. end;
  119. end;
  120. end;
  121. end;
  122. raise Exception.Create('UpdateSetupPEHeaderFields failed');
  123. end;
  124. procedure ResUpdateError(const Msg: String);
  125. begin
  126. raise Exception.Create('Resource update error: ' + Msg);
  127. end;
  128. procedure ResUpdateErrorWithLastError(const Msg: String);
  129. begin
  130. ResUpdateError(Msg + ' (' + IntToStr(GetLastError) + ')');
  131. end;
  132. procedure UpdateVersionInfo(const F: TCustomFile;
  133. const NewBinaryFileVersion, NewBinaryProductVersion: TFileVersionNumbers;
  134. const NewCompanyName, NewFileDescription, NewTextFileVersion, NewLegalCopyright,
  135. NewProductName, NewTextProductVersion, NewOriginalFileName: String;
  136. const SetFileVersionAndDescription: Boolean);
  137. function WideStrsEqual(P1, P2: PWideChar): Boolean;
  138. function WideUpCase(C: WideChar): WideChar;
  139. begin
  140. Result := C;
  141. if (Result >= 'a') and (Result <= 'z') then
  142. Dec(Result, Ord('a') - Ord('A'));
  143. end;
  144. begin
  145. while True do begin
  146. if WideUpCase(P1^) <> WideUpCase(P2^) then begin
  147. Result := False;
  148. Exit;
  149. end;
  150. if P1^ = #0 then
  151. Break;
  152. Inc(P1);
  153. Inc(P2);
  154. end;
  155. Result := True;
  156. end;
  157. procedure BumpToDWordBoundary(var P: Pointer);
  158. begin
  159. if Cardinal(P) and 3 <> 0 then
  160. Cardinal(P) := (Cardinal(P) or 3) + 1;
  161. end;
  162. function QueryValue(P: Pointer; Path: PWideChar; var Buf: Pointer;
  163. var BufLen: Cardinal): Boolean;
  164. var
  165. EndP: Pointer;
  166. ValueLength: Cardinal;
  167. begin
  168. Result := False;
  169. Cardinal(EndP) := Cardinal(P) + PWord(P)^;
  170. Inc(PWord(P));
  171. ValueLength := PWord(P)^;
  172. Inc(PWord(P));
  173. Inc(PWord(P));
  174. if WideStrsEqual(PWideChar(P), Path) then begin
  175. Inc(PWideChar(P), lstrlenW(P) + 1);
  176. BumpToDWordBoundary(P);
  177. Inc(Path, lstrlenW(Path) + 1);
  178. if Path^ = #0 then begin
  179. { Found the requested value }
  180. Buf := P;
  181. BufLen := ValueLength;
  182. Result := True;
  183. end
  184. else begin
  185. { Handle children.
  186. Note: Like VerQueryValue, we always treat ValueLength as a byte count
  187. when looking for child nodes. Many resource compilers, including
  188. Borland's, wrongly set ValueLength to a *character* count on string
  189. nodes. But since we never try to query for a child of a string node,
  190. that doesn't matter here. }
  191. Inc(Cardinal(P), ValueLength);
  192. BumpToDWordBoundary(P);
  193. while Cardinal(P) < Cardinal(EndP) do begin
  194. Result := QueryValue(P, Path, Buf, BufLen);
  195. if Result then
  196. Exit;
  197. Inc(Cardinal(P), PWord(P)^);
  198. BumpToDWordBoundary(P);
  199. end;
  200. end;
  201. end;
  202. end;
  203. procedure ReplaceWithRealCopyrightSymbols(const Value: PWideChar);
  204. var
  205. Len, I, J: Integer;
  206. begin
  207. Len := lstrlenW(Value);
  208. for I := 0 to Len-3 do begin
  209. if (Value[I] = '(') and (Value[I+1] = 'C') and (Value[I+2] = ')') then begin
  210. Value[I] := WideChar($00A9);
  211. { Shift back two characters }
  212. for J := I+1 to Len-3 do
  213. Value[J] := Value[J+2];
  214. Value[Len-2] := ' ';
  215. Value[Len-1] := ' ';
  216. end;
  217. end;
  218. end;
  219. procedure UpdateStringValue(P: Pointer; const Path: PWideChar; NewValue: String);
  220. var
  221. Value: PWideChar;
  222. ValueLen: Cardinal;
  223. begin
  224. if not QueryValue(P, Path, Pointer(Value), ValueLen) then
  225. ResUpdateError('Unexpected version resource format (1)');
  226. {$IFDEF UNICODE}
  227. Move(Pointer(NewValue)^, Value^, (Min(Length(NewValue), lstrlenW(Value)))*SizeOf(Char));
  228. {$ELSE}
  229. MultiByteToWideChar(CP_ACP, 0, PChar(NewValue), Length(NewValue), Value, lstrlenW(Value));
  230. {$ENDIF}
  231. ReplaceWithRealCopyrightSymbols(Value);
  232. end;
  233. procedure UpdateFixedFileInfo(P: Pointer; const Path: PWideChar;
  234. const NewFileVersion, NewProductVersion: TFileVersionNumbers;
  235. const SetFileVersion: Boolean);
  236. var
  237. FixedFileInfo: PVSFixedFileInfo;
  238. ValueLen: Cardinal;
  239. begin
  240. if not QueryValue(P, Path, Pointer(FixedFileInfo), ValueLen) then
  241. ResUpdateError('Unexpected version resource format (2)');
  242. if FixedFileInfo.dwSignature <> $FEEF04BD then
  243. ResUpdateError('Unexpected version resource format (3)');
  244. if SetFileVersion then begin
  245. FixedFileInfo.dwFileVersionLS := NewFileVersion.LS;
  246. FixedFileInfo.dwFileVersionMS := NewFileVersion.MS;
  247. end;
  248. FixedFileInfo.dwProductVersionLS := NewProductVersion.LS;
  249. FixedFileInfo.dwProductVersionMS := NewProductVersion.MS;
  250. end;
  251. var
  252. ResOffset, ResSize: Cardinal;
  253. VersRes: Pointer;
  254. begin
  255. { Locate the resource }
  256. ResSize := SeekToResourceData(F, Cardinal(RT_VERSION), 1);
  257. ResOffset := F.Position.Lo;
  258. GetMem(VersRes, ResSize);
  259. try
  260. { Read the resource }
  261. F.ReadBuffer(VersRes^, ResSize);
  262. { Update the resource }
  263. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'CompanyName'#0, NewCompanyName);
  264. if SetFileVersionAndDescription then begin
  265. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'FileDescription'#0, NewFileDescription);
  266. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'FileVersion'#0, NewTextFileVersion);
  267. end;
  268. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'LegalCopyright'#0, NewLegalCopyright);
  269. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'ProductName'#0, NewProductName);
  270. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'OriginalFileName'#0, NewOriginalFileName);
  271. UpdateStringValue(VersRes, 'VS_VERSION_INFO'#0'StringFileInfo'#0'000004b0'#0'ProductVersion'#0, NewTextProductVersion);
  272. UpdateFixedFileInfo(VersRes, 'VS_VERSION_INFO'#0, NewBinaryFileVersion, NewBinaryProductVersion, SetFileVersionAndDescription);
  273. { Write the updated resource }
  274. F.Seek(ResOffset);
  275. F.WriteBuffer(VersRes^, ResSize);
  276. finally
  277. FreeMem(VersRes);
  278. end;
  279. end;
  280. function EnumLangsFunc(hModule: Cardinal; lpType, lpName: PAnsiChar; wLanguage: Word; lParam: Integer): BOOL; stdcall;
  281. begin
  282. PWord(lParam)^ := wLanguage;
  283. Result := False;
  284. end;
  285. function GetResourceLanguage(hModule: Cardinal; lpType, lpName: PChar; var wLanguage: Word): Boolean;
  286. begin
  287. wLanguage := 0;
  288. EnumResourceLanguages(hModule, lpType, lpName, @EnumLangsFunc, Integer(@wLanguage));
  289. Result := True;
  290. end;
  291. procedure UpdateIcons(const FileName, IcoFileName: String; const DeleteUninstallIcon: Boolean);
  292. type
  293. PIcoItemHeader = ^TIcoItemHeader;
  294. TIcoItemHeader = packed record
  295. Width: Byte;
  296. Height: Byte;
  297. Colors: Byte;
  298. Reserved: Byte;
  299. Planes: Word;
  300. BitCount: Word;
  301. ImageSize: DWORD;
  302. end;
  303. PIcoItem = ^TIcoItem;
  304. TIcoItem = packed record
  305. Header: TIcoItemHeader;
  306. Offset: DWORD;
  307. end;
  308. PIcoHeader = ^TIcoHeader;
  309. TIcoHeader = packed record
  310. Reserved: Word;
  311. Typ: Word;
  312. ItemCount: Word;
  313. Items: array [0..MaxInt shr 4 - 1] of TIcoItem;
  314. end;
  315. PGroupIconDirItem = ^TGroupIconDirItem;
  316. TGroupIconDirItem = packed record
  317. Header: TIcoItemHeader;
  318. Id: Word;
  319. end;
  320. PGroupIconDir = ^TGroupIconDir;
  321. TGroupIconDir = packed record
  322. Reserved: Word;
  323. Typ: Word;
  324. ItemCount: Word;
  325. Items: array [0..MaxInt shr 4 - 1] of TGroupIconDirItem;
  326. end;
  327. function IsValidIcon(P: Pointer; Size: Cardinal): Boolean;
  328. var
  329. ItemCount: Cardinal;
  330. begin
  331. Result := False;
  332. if Size < Cardinal(SizeOf(Word) * 3) then
  333. Exit;
  334. if (PChar(P)[0] = 'M') and (PChar(P)[1] = 'Z') then
  335. Exit;
  336. ItemCount := PIcoHeader(P).ItemCount;
  337. if Size < Cardinal((SizeOf(Word) * 3) + (ItemCount * SizeOf(TIcoItem))) then
  338. Exit;
  339. P := @PIcoHeader(P).Items;
  340. while ItemCount > Cardinal(0) do begin
  341. if (Cardinal(PIcoItem(P).Offset + PIcoItem(P).Header.ImageSize) < Cardinal(PIcoItem(P).Offset)) or
  342. (Cardinal(PIcoItem(P).Offset + PIcoItem(P).Header.ImageSize) > Cardinal(Size)) then
  343. Exit;
  344. Inc(PIcoItem(P));
  345. Dec(ItemCount);
  346. end;
  347. Result := True;
  348. end;
  349. function DeleteIcon(const H: THandle; const M: HMODULE; const ResourceName: PChar): PGroupIconDir;
  350. var
  351. R: HRSRC;
  352. Res: HGLOBAL;
  353. GroupIconDir: PGroupIconDir;
  354. I: Integer;
  355. wLanguage: Word;
  356. begin
  357. { Load the group icon resource }
  358. R := FindResource(M, ResourceName, RT_GROUP_ICON);
  359. if R = 0 then
  360. ResUpdateErrorWithLastError('FindResource failed (1)');
  361. Res := LoadResource(M, R);
  362. if Res = 0 then
  363. ResUpdateErrorWithLastError('LoadResource failed (1)');
  364. GroupIconDir := LockResource(Res);
  365. if GroupIconDir = nil then
  366. ResUpdateErrorWithLastError('LockResource failed (1)');
  367. { Delete the group icon resource }
  368. if not GetResourceLanguage(M, RT_GROUP_ICON, ResourceName, wLanguage) then
  369. ResUpdateError('GetResourceLanguage failed (1)');
  370. if not UpdateResource(H, RT_GROUP_ICON, ResourceName, wLanguage, nil, 0) then
  371. ResUpdateErrorWithLastError('UpdateResource failed (1)');
  372. { Delete the icon resources that belonged to the group }
  373. for I := 0 to GroupIconDir.ItemCount-1 do begin
  374. if not GetResourceLanguage(M, RT_ICON, MakeIntResource(GroupIconDir.Items[I].Id), wLanguage) then
  375. ResUpdateError('GetResourceLanguage failed (2)');
  376. if not UpdateResource(H, RT_ICON, MakeIntResource(GroupIconDir.Items[I].Id), wLanguage, nil, 0) then
  377. ResUpdateErrorWithLastError('UpdateResource failed (2)');
  378. end;
  379. Result := GroupIconDir;
  380. end;
  381. var
  382. H: THandle;
  383. M: HMODULE;
  384. OldGroupIconDir, NewGroupIconDir: PGroupIconDir;
  385. I: Integer;
  386. F: TFile;
  387. Ico: PIcoHeader;
  388. N: Cardinal;
  389. NewGroupIconDirSize: LongInt;
  390. begin
  391. if Win32Platform <> VER_PLATFORM_WIN32_NT then
  392. ResUpdateError('Only supported on Windows NT and above');
  393. Ico := nil;
  394. try
  395. { Load the icons }
  396. F := TFile.Create(IcoFileName, fdOpenExisting, faRead, fsRead);
  397. try
  398. N := F.CappedSize;
  399. if Cardinal(N) > Cardinal($100000) then { sanity check }
  400. ResUpdateError('Icon file is too large');
  401. GetMem(Ico, N);
  402. F.ReadBuffer(Ico^, N);
  403. finally
  404. F.Free;
  405. end;
  406. { Ensure the icon is valid }
  407. if not IsValidIcon(Ico, N) then
  408. ResUpdateError('Icon file is invalid');
  409. { Update the resources }
  410. H := BeginUpdateResource(PChar(FileName), False);
  411. if H = 0 then
  412. ResUpdateErrorWithLastError('BeginUpdateResource failed (1)');
  413. try
  414. M := LoadLibraryEx(PChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
  415. if M = 0 then
  416. ResUpdateErrorWithLastError('LoadLibraryEx failed (1)');
  417. try
  418. { Delete default icons }
  419. OldGroupIconDir := DeleteIcon(H, M, 'MAINICON');
  420. if DeleteUninstallIcon then
  421. DeleteIcon(H, M, 'Z_UNINSTALLICON');
  422. { Build the new group icon resource }
  423. NewGroupIconDirSize := 3*SizeOf(Word)+Ico.ItemCount*SizeOf(TGroupIconDirItem);
  424. GetMem(NewGroupIconDir, NewGroupIconDirSize);
  425. try
  426. { Build the new group icon resource }
  427. NewGroupIconDir.Reserved := OldGroupIconDir.Reserved;
  428. NewGroupIconDir.Typ := OldGroupIconDir.Typ;
  429. NewGroupIconDir.ItemCount := Ico.ItemCount;
  430. for I := 0 to NewGroupIconDir.ItemCount-1 do begin
  431. NewGroupIconDir.Items[I].Header := Ico.Items[I].Header;
  432. NewGroupIconDir.Items[I].Id := I+100; //start at 100 to avoid overwriting other icons that may exist
  433. end;
  434. { Update 'MAINICON' }
  435. for I := 0 to NewGroupIconDir.ItemCount-1 do
  436. if not UpdateResource(H, RT_ICON, MakeIntResource(NewGroupIconDir.Items[I].Id), 1033, Pointer(DWORD(Ico) + Ico.Items[I].Offset), Ico.Items[I].Header.ImageSize) then
  437. ResUpdateErrorWithLastError('UpdateResource failed (3)');
  438. { Update the icons }
  439. if not UpdateResource(H, RT_GROUP_ICON, 'MAINICON', 1033, NewGroupIconDir, NewGroupIconDirSize) then
  440. ResUpdateErrorWithLastError('UpdateResource failed (4)');
  441. finally
  442. FreeMem(NewGroupIconDir);
  443. end;
  444. finally
  445. FreeLibrary(M);
  446. end;
  447. except
  448. EndUpdateResource(H, True); { discard changes }
  449. raise;
  450. end;
  451. if not EndUpdateResource(H, False) then
  452. ResUpdateErrorWithLastError('EndUpdateResource failed');
  453. finally
  454. FreeMem(Ico);
  455. end;
  456. end;
  457. procedure RemoveManifestDllHijackProtection(const F: TCustomFile; const TestBlockOnly: Boolean);
  458. const
  459. BlockStartText: AnsiString = '<file name="';
  460. BlockLength = 380;
  461. var
  462. S: AnsiString;
  463. Offset: Integer64;
  464. P: Integer;
  465. begin
  466. { Read the manifest resource into a string }
  467. SetString(S, nil, SeekToResourceData(F, 24, 1));
  468. Offset := F.Position;
  469. F.ReadBuffer(S[1], Length(S));
  470. { Locate and update the block with file elements }
  471. P := Pos(BlockStartText, S);
  472. if P = 0 then
  473. ResUpdateError('Block not found');
  474. if Copy(S, P+BlockLength, 11) <> '</assembly>' then
  475. ResUpdateError('Block too short (BlockLength should be '+string(IntToStr(Pos('</assembly>', string(S))-P)+'): '+string(Copy(S, P+BlockLength, 11))));
  476. if TestBlockOnly then
  477. Exit;
  478. Inc64(Offset, P-1);
  479. F.Seek64(Offset);
  480. F.WriteAnsiString(AnsiString(Format('%*s', [BlockLength, ' '])));
  481. end;
  482. end.