2
0

Setup.ScriptFunc.HelperFunc.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. unit Setup.ScriptFunc.HelperFunc;
  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. Helper types and functions for the script support functions (run time - used by Setup)
  8. Requires following globals to be set:
  9. -LangOptions.DialogFontName
  10. -LangOptions.DialogFontSize
  11. -LangOptions.DialogFontBaseScaleWidth
  12. -LangOptions.DialogFontBaseScaleHeight
  13. -shWizardKeepAspectRatio in SetupHeader.Options
  14. }
  15. interface
  16. uses
  17. Windows,
  18. uPSRuntime, MD5, SHA1,
  19. Shared.CommonFunc, Shared.FileClass, Setup.MainForm, Setup.WizardForm,
  20. Setup.UninstallProgressForm;
  21. type
  22. { Must keep this in synch with Compiler.ScriptFunc.pas }
  23. TOnLog = procedure(const S: String; const Error, FirstLine: Boolean) of object;
  24. { Must keep this in synch with Compiler.ScriptFunc.pas }
  25. TFindRec = record
  26. Name: String;
  27. Attributes: Cardinal;
  28. SizeHigh: Cardinal;
  29. SizeLow: Cardinal;
  30. CreationTime: TFileTime;
  31. LastAccessTime: TFileTime;
  32. LastWriteTime: TFileTime;
  33. AlternateName: String;
  34. FindHandle: THandle;
  35. end;
  36. { Must keep this in synch with Compiler.ScriptFunc.pas }
  37. TWindowsVersion = packed record
  38. Major: Cardinal;
  39. Minor: Cardinal;
  40. Build: Cardinal;
  41. ServicePackMajor: Cardinal;
  42. ServicePackMinor: Cardinal;
  43. NTPlatform: Boolean;
  44. ProductType: Byte;
  45. SuiteMask: Word;
  46. end;
  47. var
  48. OrigScaleBaseUnitX, OrigScaleBaseUnitY: Integer;
  49. ScaleBaseUnitX, ScaleBaseUnitY: Integer;
  50. procedure NoUninstallFuncError(const C: AnsiString); overload;
  51. procedure OnlyUninstallFuncError(const C: AnsiString); overload;
  52. function GetWizardForm: TWizardForm;
  53. function GetUninstallProgressForm: TUninstallProgressForm;
  54. procedure InitializeScaleBaseUnits;
  55. function IsProtectedSrcExe(const Filename: String): Boolean;
  56. function LogFmtHelper(const S: String; const Args: array of const): String;
  57. function FmtMessageHelper(const S: String; const Args: array of String): String;
  58. function FindFirstHelper(const FileName: String; var FindRec: TFindRec): Boolean;
  59. function FindNextHelper(var FindRec: TFindRec): Boolean;
  60. procedure FindCloseHelper(var FindRec: TFindRec);
  61. procedure GetWindowsVersionExHelper(var Version: TWindowsVersion);
  62. procedure CrackCodeRootKey(CodeRootKey: UInt32; var RegView: TRegView;
  63. var RootKey: HKEY);
  64. function GetSubkeyOrValueNames(const RegView: TRegView; const RootKey: HKEY;
  65. const SubKeyName: String; const Stack: TPSStack; const ItemNo: Longint; const Subkey: Boolean): Boolean;
  66. function GetMD5OfFile(const Filename: String): TMD5Digest;
  67. function GetSHA1OfFile(const Filename: String): TSHA1Digest;
  68. function GetMD5OfAnsiString(const S: AnsiString): TMD5Digest;
  69. function GetMD5OfUnicodeString(const S: UnicodeString): TMD5Digest;
  70. function GetSHA1OfAnsiString(const S: AnsiString): TSHA1Digest;
  71. function GetSHA1OfUnicodeString(const S: UnicodeString): TSHA1Digest;
  72. procedure ProcessMessagesProc; far;
  73. procedure ExecAndLogOutputLog(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  74. procedure ExecAndLogOutputLogCustom(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  75. function CustomMessage(const MsgName: String): String;
  76. function NewExtractRelativePath(BaseName, DestName: string): string;
  77. function NewFileSearch(const Name, DirList: String): String;
  78. function GetExceptionMessage(const Caller: TPSExec): String;
  79. function GetCodePreviousData(const ExpandedAppID, ValueName, DefaultValueData: String): String;
  80. function SetCodePreviousData(const PreviousDataKey: HKEY; const ValueName, ValueData: String): Boolean;
  81. function LoadStringFromFile(const FileName: String; var S: AnsiString;
  82. const Sharing: TFileSharing): Boolean;
  83. function LoadStringsFromFile(const FileName: String; const Stack: TPSStack;
  84. const ItemNo: Longint; const Sharing: TFileSharing): Boolean;
  85. function SaveStringToFile(const FileName: String; const S: AnsiString; Append: Boolean): Boolean;
  86. function SaveStringsToFile(const FileName: String; const Stack: TPSStack;
  87. const ItemNo: Longint; Append, UTF8, UTF8WithoutBOM: Boolean): Boolean;
  88. function GetShortName(const LongName: String): String;
  89. function CreateCallback(const Caller: TPSExec; const P: PPSVariantProcPtr): NativeInt;
  90. implementation
  91. uses
  92. Forms, SysUtils, Graphics,
  93. uPSUtils, PathFunc, ASMInline, PSStackHelper, UnsignedFunc,
  94. Setup.MainFunc, Setup.InstFunc,
  95. SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, Shared.Struct,
  96. Shared.SetupTypes, Shared.SetupSteps, Setup.LoggingFunc, Setup.SetupForm;
  97. procedure NoUninstallFuncError(const C: AnsiString); overload;
  98. begin
  99. InternalError(Format('Cannot call "%s" function during Uninstall', [C]));
  100. end;
  101. procedure OnlyUninstallFuncError(const C: AnsiString); overload;
  102. begin
  103. InternalError(Format('Cannot call "%s" function during Setup', [C]));
  104. end;
  105. function GetWizardForm: TWizardForm;
  106. begin
  107. Result := WizardForm;
  108. if Result = nil then
  109. InternalError('An attempt was made to access WizardForm before it has been created');
  110. end;
  111. function GetUninstallProgressForm: TUninstallProgressForm;
  112. begin
  113. Result := UninstallProgressForm;
  114. if Result = nil then
  115. InternalError('An attempt was made to access UninstallProgressForm before it has been created');
  116. end;
  117. var
  118. ScaleBaseUnitsInitialized: Boolean;
  119. procedure InitializeScaleBaseUnits;
  120. var
  121. Font: TFont;
  122. begin
  123. if ScaleBaseUnitsInitialized then
  124. Exit;
  125. Font := TFont.Create;
  126. try
  127. SetFontNameSize(Font, LangOptions.DialogFontName, LangOptions.DialogFontSize, '', 9);
  128. CalculateBaseUnitsFromFont(Font, ScaleBaseUnitX, ScaleBaseUnitY);
  129. OrigScaleBaseUnitX := LangOptions.DialogFontBaseScaleWidth;
  130. OrigScaleBaseUnitY := LangOptions.DialogFontBaseScaleHeight;
  131. if shWizardKeepAspectRatio in SetupHeader.Options then begin
  132. if ScaleBaseUnitX * OrigScaleBaseUnitY > ScaleBaseUnitY * OrigScaleBaseUnitX then begin
  133. ScaleBaseUnitY := ScaleBaseUnitX;
  134. OrigScaleBaseUnitY := OrigScaleBaseUnitX;
  135. end else begin
  136. ScaleBaseUnitX := ScaleBaseUnitY;
  137. OrigScaleBaseUnitX := OrigScaleBaseUnitY;
  138. end;
  139. end;
  140. finally
  141. Font.Free;
  142. end;
  143. ScaleBaseUnitsInitialized := True;
  144. end;
  145. function IsProtectedSrcExe(const Filename: String): Boolean;
  146. begin
  147. if (MainForm = nil) or (MainForm.CurStep < ssInstall) then begin
  148. var ExpandedFilename := PathExpand(PathConvertSuperToNormal(Filename));
  149. Result := PathSame(ExpandedFilename, SetupLdrOriginalFilename);
  150. end else
  151. Result := False;
  152. end;
  153. function LogFmtHelper(const S: String; const Args: array of const): String;
  154. begin
  155. LogFmt(S, Args);
  156. end;
  157. function FmtMessageHelper(const S: String; const Args: array of String): String;
  158. begin
  159. Result := FmtMessage(PChar(S), Args);
  160. end;
  161. procedure FindDataToFindRec(const FindData: TWin32FindData;
  162. var FindRec: TFindRec);
  163. begin
  164. FindRec.Name := FindData.cFileName;
  165. FindRec.Attributes := FindData.dwFileAttributes;
  166. FindRec.SizeHigh := FindData.nFileSizeHigh;
  167. FindRec.SizeLow := FindData.nFileSizeLow;
  168. FindRec.CreationTime := FindData.ftCreationTime;
  169. FindRec.LastAccessTime := FindData.ftLastAccessTime;
  170. FindRec.LastWriteTime := FindData.ftLastWriteTime;
  171. FindRec.AlternateName := FindData.cAlternateFileName;
  172. end;
  173. function FindFirstHelper(const FileName: String; var FindRec: TFindRec): Boolean;
  174. var
  175. FindHandle: THandle;
  176. FindData: TWin32FindData;
  177. begin
  178. FindHandle := FindFirstFile(PChar(FileName), FindData);
  179. if FindHandle <> INVALID_HANDLE_VALUE then begin
  180. FindRec.FindHandle := FindHandle;
  181. FindDataToFindRec(FindData, FindRec);
  182. Result := True;
  183. end
  184. else begin
  185. FindRec.FindHandle := 0;
  186. Result := False;
  187. end;
  188. end;
  189. function FindNextHelper(var FindRec: TFindRec): Boolean;
  190. var
  191. FindData: TWin32FindData;
  192. begin
  193. Result := (FindRec.FindHandle <> 0) and FindNextFile(FindRec.FindHandle, FindData);
  194. if Result then
  195. FindDataToFindRec(FindData, FindRec);
  196. end;
  197. procedure FindCloseHelper(var FindRec: TFindRec);
  198. begin
  199. if FindRec.FindHandle <> 0 then begin
  200. Windows.FindClose(FindRec.FindHandle);
  201. FindRec.FindHandle := 0;
  202. end;
  203. end;
  204. procedure GetWindowsVersionExHelper(var Version: TWindowsVersion);
  205. begin
  206. Version.Major := WindowsVersion shr 24;
  207. Version.Minor := (WindowsVersion shr 16) and $FF;
  208. Version.Build := WindowsVersion and $FFFF;
  209. Version.ServicePackMajor := Hi(NTServicePackLevel);
  210. Version.ServicePackMinor := Lo(NTServicePackLevel);
  211. Version.NTPlatform := True;
  212. Version.ProductType := WindowsProductType;
  213. Version.SuiteMask := WindowsSuiteMask;
  214. end;
  215. procedure CrackCodeRootKey(CodeRootKey: UInt32; var RegView: TRegView;
  216. var RootKey: HKEY);
  217. begin
  218. if (CodeRootKey and not CodeRootKeyValidFlags) = HKEY_AUTO then begin
  219. { Change HKA to HKLM or HKCU, keeping our special flag bits. }
  220. CodeRootKey := (CodeRootKey and CodeRootKeyValidFlags) or UInt32(InstallModeRootKey);
  221. end else begin
  222. { Allow only predefined key handles (8xxxxxxx). Can't accept handles to
  223. open keys because they might have our special flag bits set.
  224. Also reject unknown flags which may have a meaning in the future. }
  225. if (CodeRootKey shr 31 <> 1) or
  226. ((CodeRootKey and CodeRootKeyFlagMask) and not CodeRootKeyValidFlags <> 0) then
  227. InternalError('Invalid RootKey value');
  228. end;
  229. if CodeRootKey and CodeRootKeyFlag32Bit <> 0 then
  230. RegView := rv32Bit
  231. else if CodeRootKey and CodeRootKeyFlag64Bit <> 0 then begin
  232. if not IsWin64 then
  233. InternalError('Cannot access 64-bit registry keys on this version of Windows');
  234. RegView := rv64Bit;
  235. end
  236. else
  237. RegView := InstallDefaultRegView;
  238. RootKey := CodeRootKey and not CodeRootKeyFlagMask;
  239. end;
  240. function GetSubkeyOrValueNames(const RegView: TRegView; const RootKey: HKEY;
  241. const SubKeyName: String; const Stack: TPSStack; const ItemNo: Longint; const Subkey: Boolean): Boolean;
  242. const
  243. samDesired: array [Boolean] of REGSAM = (KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS);
  244. var
  245. K: HKEY;
  246. Buf, S: String;
  247. begin
  248. Result := False;
  249. SetString(Buf, nil, 512);
  250. if RegOpenKeyExView(RegView, RootKey, PChar(SubKeyName), 0, samDesired[Subkey], K) <> ERROR_SUCCESS then
  251. Exit;
  252. try
  253. var ArrayBuilder := Stack.InitArrayBuilder(ItemNo);
  254. while True do begin
  255. var BufSize := ULength(Buf);
  256. var R: Integer;
  257. if Subkey then
  258. R := RegEnumKeyEx(K, DWORD(ArrayBuilder.I), @Buf[1], BufSize, nil, nil, nil, nil)
  259. else
  260. R := RegEnumValue(K, DWORD(ArrayBuilder.I), @Buf[1], BufSize, nil, nil, nil, nil);
  261. case R of
  262. ERROR_SUCCESS: ;
  263. ERROR_NO_MORE_ITEMS: Break;
  264. ERROR_MORE_DATA:
  265. begin
  266. { Double the size of the buffer and try again }
  267. if Length(Buf) >= 65536 then begin
  268. { Sanity check: If we tried a 64 KB buffer and it's still saying
  269. there's more data, something must be seriously wrong. Bail. }
  270. Exit;
  271. end;
  272. SetString(Buf, nil, Length(Buf) * 2);
  273. Continue;
  274. end;
  275. else
  276. Exit; { unknown failure... }
  277. end;
  278. SetString(S, PChar(@Buf[1]), BufSize);
  279. ArrayBuilder.Add(S);
  280. end;
  281. finally
  282. RegCloseKey(K);
  283. end;
  284. Result := True;
  285. end;
  286. function GetMD5OfFile(const Filename: String): TMD5Digest;
  287. { Gets MD5 sum of the file Filename. An exception will be raised upon
  288. failure. }
  289. var
  290. Buf: array[0..65535] of Byte;
  291. begin
  292. var Context: TMD5Context;
  293. MD5Init(Context);
  294. var F := TFile.Create(Filename, fdOpenExisting, faRead, fsReadWrite);
  295. try
  296. while True do begin
  297. var NumRead := F.Read(Buf, SizeOf(Buf));
  298. if NumRead = 0 then
  299. Break;
  300. MD5Update(Context, Buf, NumRead);
  301. end;
  302. finally
  303. F.Free;
  304. end;
  305. Result := MD5Final(Context);
  306. end;
  307. function GetSHA1OfFile(const Filename: String): TSHA1Digest;
  308. { Gets SHA-1 sum of the file Filename. An exception will be raised upon
  309. failure. }
  310. var
  311. Buf: array[0..65535] of Byte;
  312. begin
  313. var Context: TSHA1Context;
  314. SHA1Init(Context);
  315. var F := TFile.Create(Filename, fdOpenExisting, faRead, fsReadWrite);
  316. try
  317. while True do begin
  318. var NumRead := F.Read(Buf, SizeOf(Buf));
  319. if NumRead = 0 then
  320. Break;
  321. SHA1Update(Context, Buf, NumRead);
  322. end;
  323. finally
  324. F.Free;
  325. end;
  326. Result := SHA1Final(Context);
  327. end;
  328. function GetMD5OfAnsiString(const S: AnsiString): TMD5Digest;
  329. begin
  330. Result := MD5Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  331. end;
  332. function GetMD5OfUnicodeString(const S: UnicodeString): TMD5Digest;
  333. begin
  334. Result := MD5Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  335. end;
  336. function GetSHA1OfAnsiString(const S: AnsiString): TSHA1Digest;
  337. begin
  338. Result := SHA1Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  339. end;
  340. function GetSHA1OfUnicodeString(const S: UnicodeString): TSHA1Digest;
  341. begin
  342. Result := SHA1Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
  343. end;
  344. procedure ProcessMessagesProc; far;
  345. begin
  346. Application.ProcessMessages;
  347. end;
  348. procedure ExecAndLogOutputLog(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  349. begin
  350. Log(S);
  351. end;
  352. procedure ExecAndLogOutputLogCustom(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  353. begin
  354. var OnLog := TOnLog(PMethod(Data)^);
  355. OnLog(S, Error, FirstLine);
  356. end;
  357. function CustomMessage(const MsgName: String): String;
  358. begin
  359. if not GetCustomMessageValue(MsgName, Result) then
  360. InternalError(Format('Unknown custom message name "%s"', [MsgName]));
  361. end;
  362. { ExtractRelativePath is not in Delphi 2's SysUtils. Use the one from Delphi 7.01. }
  363. function NewExtractRelativePath(BaseName, DestName: string): string;
  364. var
  365. BasePath, DestPath: string;
  366. BaseLead, DestLead: PChar;
  367. BasePtr, DestPtr: PChar;
  368. function ExtractFilePathNoDrive(const FileName: string): string;
  369. begin
  370. Result := PathExtractPath(FileName);
  371. Delete(Result, 1, Length(PathExtractDrive(FileName)));
  372. end;
  373. function Next(var Lead: PChar): PChar;
  374. begin
  375. Result := Lead;
  376. if Result = nil then Exit;
  377. Lead := PathStrScan(Lead, '\');
  378. if Lead <> nil then
  379. begin
  380. Lead^ := #0;
  381. Inc(Lead);
  382. end;
  383. end;
  384. begin
  385. { For consistency with the PathExtract* functions, normalize slashes so
  386. that forward slashes and multiple slashes work with this function also }
  387. BaseName := PathNormalizeSlashes(BaseName);
  388. DestName := PathNormalizeSlashes(DestName);
  389. if PathCompare(PathExtractDrive(BaseName), PathExtractDrive(DestName)) = 0 then
  390. begin
  391. BasePath := ExtractFilePathNoDrive(BaseName);
  392. UniqueString(BasePath);
  393. DestPath := ExtractFilePathNoDrive(DestName);
  394. UniqueString(DestPath);
  395. BaseLead := Pointer(BasePath);
  396. BasePtr := Next(BaseLead);
  397. DestLead := Pointer(DestPath);
  398. DestPtr := Next(DestLead);
  399. while (BasePtr <> nil) and (DestPtr <> nil) and (PathCompare(BasePtr, DestPtr) = 0) do
  400. begin
  401. BasePtr := Next(BaseLead);
  402. DestPtr := Next(DestLead);
  403. end;
  404. Result := '';
  405. while BaseLead <> nil do
  406. begin
  407. Result := Result + '..\'; { Do not localize }
  408. Next(BaseLead);
  409. end;
  410. if (DestPtr <> nil) and (DestPtr^ <> #0) then
  411. Result := Result + DestPtr + '\';
  412. if DestLead <> nil then
  413. Result := Result + DestLead; // destlead already has a trailing backslash
  414. Result := Result + PathExtractName(DestName);
  415. end
  416. else
  417. Result := DestName;
  418. end;
  419. { Use our own FileSearch function which includes these improvements over
  420. Delphi's version:
  421. - it supports MBCS and uses Path* functions
  422. - it uses NewFileExists instead of FileExists
  423. - it doesn't search the current directory unless it's told to
  424. - it always returns a fully-qualified path }
  425. function NewFileSearch(const Name, DirList: String): String;
  426. var
  427. I, P, L: Integer;
  428. begin
  429. { If Name is absolute, drive-relative, or root-relative, don't search DirList }
  430. if PathDrivePartLengthEx(Name, True) <> 0 then begin
  431. Result := PathExpand(Name);
  432. if NewFileExists(Result) then
  433. Exit;
  434. end
  435. else begin
  436. P := 1;
  437. L := Length(DirList);
  438. while True do begin
  439. while (P <= L) and (DirList[P] = ';') do
  440. Inc(P);
  441. if P > L then
  442. Break;
  443. I := P;
  444. while (P <= L) and (DirList[P] <> ';') do
  445. Inc(P, PathCharLength(DirList, P));
  446. Result := PathExpand(PathCombine(Copy(DirList, I, P - I), Name));
  447. if NewFileExists(Result) then
  448. Exit;
  449. end;
  450. end;
  451. Result := '';
  452. end;
  453. function GetExceptionMessage(const Caller: TPSExec): String;
  454. var
  455. Code: TPSError;
  456. E: TObject;
  457. begin
  458. Code := Caller.LastEx;
  459. if Code = erNoError then
  460. Result := '(There is no current exception)'
  461. else begin
  462. E := Caller.LastExObject;
  463. if Assigned(E) and (E is Exception) then
  464. Result := Exception(E).Message
  465. else
  466. Result := String(PSErrorToString(Code, Caller.LastExParam));
  467. end;
  468. end;
  469. function GetCodePreviousData(const ExpandedAppID, ValueName, DefaultValueData: String): String;
  470. begin
  471. { do not localize or change the following string }
  472. Result := GetPreviousData(ExpandedAppId, 'Inno Setup CodeFile: ' + ValueName, DefaultValueData);
  473. end;
  474. { Also see RegisterUninstallInfo in Install.pas }
  475. function SetCodePreviousData(const PreviousDataKey: HKEY; const ValueName, ValueData: String): Boolean;
  476. begin
  477. if ValueData <> '' then begin
  478. { do not localize or change the following string }
  479. Result := RegSetValueEx(PreviousDataKey, PChar('Inno Setup CodeFile: ' + ValueName), 0, REG_SZ, PChar(ValueData), (ULength(ValueData)+1)*SizeOf(ValueData[1])) = ERROR_SUCCESS
  480. end else
  481. Result := True;
  482. end;
  483. function LoadStringFromFile(const FileName: String; var S: AnsiString;
  484. const Sharing: TFileSharing): Boolean;
  485. var
  486. F: TFile;
  487. N: Cardinal;
  488. begin
  489. try
  490. F := TFile.Create(FileName, fdOpenExisting, faRead, Sharing);
  491. try
  492. N := F.CappedSize;
  493. SetLength(S, N);
  494. F.ReadBuffer(S[1], N);
  495. finally
  496. F.Free;
  497. end;
  498. Result := True;
  499. except
  500. Result := False;
  501. end;
  502. end;
  503. function LoadStringsFromFile(const FileName: String; const Stack: TPSStack;
  504. const ItemNo: Longint; const Sharing: TFileSharing): Boolean;
  505. var
  506. F: TTextFileReader;
  507. begin
  508. try
  509. F := TTextFileReader.Create(FileName, fdOpenExisting, faRead, Sharing);
  510. try
  511. var ArrayBuilder := Stack.InitArrayBuilder(ItemNo);
  512. while not F.Eof do
  513. ArrayBuilder.Add(F.ReadLine);
  514. finally
  515. F.Free;
  516. end;
  517. Result := True;
  518. except
  519. Result := False;
  520. end;
  521. end;
  522. function SaveStringToFile(const FileName: String; const S: AnsiString; Append: Boolean): Boolean;
  523. var
  524. F: TFile;
  525. begin
  526. try
  527. if Append then
  528. F := TFile.Create(FileName, fdOpenAlways, faWrite, fsNone)
  529. else
  530. F := TFile.Create(FileName, fdCreateAlways, faWrite, fsNone);
  531. try
  532. F.SeekToEnd;
  533. F.WriteAnsiString(S);
  534. finally
  535. F.Free;
  536. end;
  537. Result := True;
  538. except
  539. Result := False;
  540. end;
  541. end;
  542. function SaveStringsToFile(const FileName: String; const Stack: TPSStack;
  543. const ItemNo: Longint; Append, UTF8, UTF8WithoutBOM: Boolean): Boolean;
  544. var
  545. F: TTextFileWriter;
  546. begin
  547. try
  548. if Append then
  549. F := TTextFileWriter.Create(FileName, fdOpenAlways, faWrite, fsNone)
  550. else
  551. F := TTextFileWriter.Create(FileName, fdCreateAlways, faWrite, fsNone);
  552. try
  553. if UTF8 and UTF8WithoutBOM then
  554. F.UTF8WithoutBOM := UTF8WithoutBOM;
  555. var ArrayEnumerator := Stack.InitArrayEnumerator(ItemNo);
  556. while ArrayEnumerator.HasNext do begin
  557. var S := ArrayEnumerator.Next;
  558. if not UTF8 then
  559. F.WriteAnsiLine(AnsiString(S))
  560. else
  561. F.WriteLine(S);
  562. end;
  563. finally
  564. F.Free;
  565. end;
  566. Result := True;
  567. except
  568. Result := False;
  569. end;
  570. end;
  571. function GetShortName(const LongName: String): String;
  572. { Gets the short version of the specified long filename. If the file does not
  573. exist, or some other error occurs, it returns LongName. }
  574. var
  575. Res: DWORD;
  576. begin
  577. SetLength(Result, MAX_PATH);
  578. repeat
  579. Res := GetShortPathName(PChar(LongName), PChar(Result), ULength(Result));
  580. if Res = 0 then begin
  581. Result := LongName;
  582. Break;
  583. end;
  584. until AdjustLength(Result, Res);
  585. end;
  586. var
  587. ASMInliners: array of Pointer;
  588. function CreateCallback(const Caller: TPSExec; const P: PPSVariantProcPtr): NativeInt;
  589. begin
  590. { ProcNo 0 means nil was passed by the script }
  591. if P.ProcNo = 0 then
  592. InternalError('Invalid Method value');
  593. { Calculate parameter count of our proc, will need this later. }
  594. const ProcRec = Caller.GetProcNo(P.ProcNo) as TPSInternalProcRec;
  595. var S := ProcRec.ExportDecl;
  596. GRFW(S);
  597. var ParamCount := 0;
  598. while S <> '' do begin
  599. Inc(ParamCount);
  600. GRFW(S);
  601. end;
  602. { Turn our proc into a callable TMethod - its Code will point to
  603. ROPS' MyAllMethodsHandler and its Data to a record identifying our proc.
  604. When called, MyAllMethodsHandler will use the record to call our proc. }
  605. const Method = MkMethod(Caller, P.ProcNo);
  606. { Wrap our TMethod with a dynamically generated stdcall callback which will
  607. do two things:
  608. -Remember the Data pointer which MyAllMethodsHandler needs.
  609. -Handle the calling convention mismatch.
  610. Based on InnoCallback by Sherlock Software, see
  611. http://www.sherlocksoftware.org/page.php?id=54 and
  612. https://github.com/thenickdude/InnoCallback. }
  613. const Inliner = TASMInline.create;
  614. try
  615. {$IFDEF CPUX86}
  616. Inliner.Pop(EAX); //get the retptr off the stack
  617. var SwapFirst := 2;
  618. var SwapLast := ParamCount-1;
  619. //Reverse the order of parameters from param3 onwards in the stack
  620. while SwapLast > SwapFirst do begin
  621. Inliner.Mov(ECX, Inliner.Addr(ESP, SwapFirst * 4)); //load the first item of the pair
  622. Inliner.Mov(EDX, Inliner.Addr(ESP, SwapLast * 4)); //load the last item of the pair
  623. Inliner.Mov(Inliner.Addr(ESP, SwapFirst * 4), EDX);
  624. Inliner.Mov(Inliner.Addr(ESP, SwapLast * 4), ECX);
  625. Inc(SwapFirst);
  626. Dec(SwapLast);
  627. end;
  628. if ParamCount >= 1 then
  629. Inliner.Pop(EDX); //load param1
  630. if ParamCount >= 2 then
  631. Inliner.Pop(ECX); //load param2
  632. Inliner.Push(EAX); //put the retptr back onto the stack
  633. Inliner.Mov(EAX, Cardinal(Method.Data)); //Load the self ptr
  634. Inliner.Jmp(Method.Code); //jump to the wrapped proc
  635. {$ELSE}
  636. { RCX, RDX, R8, R9 carry the first 4 params and 32 bytes of shadow space
  637. belong to the caller. ROPS' MyAllMethodsHandler expects RCX=Self/Data,
  638. RDX/R8/R9=param1..param3 and the rest packed on the stack. }
  639. { Keep values for later }
  640. Inliner.MovRegReg(R11, RCX);
  641. Inliner.MovRegReg(R10, RDX);
  642. Inliner.MovRegReg(RAX, R8);
  643. Inliner.MovRegReg(RDX, R9);
  644. { Make our own shadow space + spill area to re-stack params for ROPS. }
  645. var ExtraParams := ParamCount - 3;
  646. if ExtraParams < 0 then
  647. ExtraParams := 0;
  648. var FrameSize := 32 + ExtraParams * SizeOf(Pointer);
  649. if (FrameSize and $F) = 0 then
  650. Inc(FrameSize, 8); { keep RSP 16-byte aligned at call site }
  651. Inliner.SubRsp(FrameSize);
  652. if ParamCount >= 4 then
  653. Inliner.MovMemRSPReg(32, RDX); { param4: top of shadow space }
  654. { Copy remaining params (5+) from the caller's stack into our spill
  655. area so they follow shadow space, matching the order ROPS expects. }
  656. if ParamCount > 4 then begin
  657. for var I := 0 to ParamCount - 5 do begin
  658. const SrcOffset = FrameSize + 40 + I * SizeOf(Pointer); { 40 = return address (8) + caller shadow space (32) }
  659. const DestOffset = 32 + (I + 1) * SizeOf(Pointer); { 32 = callee shadow space, + 1 to skip param4 }
  660. Inliner.MovRegMemRSP(RDX, SrcOffset);
  661. Inliner.MovMemRSPReg(DestOffset, RDX);
  662. end;
  663. end;
  664. { Put the original params back in the order MyAllMethodsHandler wants. }
  665. Inliner.MovRegImm64(RCX, NativeUInt(Method.Data)); { Self/Data }
  666. Inliner.MovRegReg(RDX, R11); { param1 }
  667. if ParamCount >= 2 then
  668. Inliner.MovRegReg(R8, R10); { param2 }
  669. if ParamCount >= 3 then
  670. Inliner.MovRegReg(R9, RAX); { param3 }
  671. Inliner.MovRegImm64(R10, NativeUInt(Method.Code));
  672. Inliner.CallReg(R10); { Call the wrapped proc }
  673. Inliner.AddRsp(FrameSize);
  674. Inliner.Ret;
  675. {$ENDIF}
  676. SetLength(ASMInliners, Length(ASMInliners) + 1);
  677. ASMInliners[High(ASMInliners)] := Inliner.SaveAsMemory;
  678. Result := NativeInt(ASMInliners[High(ASMInliners)]);
  679. finally
  680. Inliner.Free;
  681. end;
  682. end;
  683. procedure FreeASMInliners;
  684. begin
  685. for var I := 0 to High(ASMInliners) do
  686. FreeMem(ASMInliners[I]);
  687. SetLength(ASMInliners, 0);
  688. end;
  689. initialization
  690. finalization
  691. FreeASMInliners;
  692. end.