hydrablack

ObfPas --only-gui

Aug 29th, 2025
12
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 59.44 KB | None | 0 0
  1. program PasObf;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6. SysUtils, Classes, StrUtils, Math, Windows;
  7.  
  8. const
  9. NEVER_TOUCH_PROGRAMS = True;
  10. ENABLE_POSTPROCESS = False; // Gate cosmetic rewrite passes
  11. SKIP_BETTERCONTROLS = True; // Skip betterControls/* units
  12. SKIP_DBK32 = True; // Skip dbk32/* units
  13.  
  14.  
  15.  
  16. type
  17. TTokenKind = (tkIdent, tkString, tkWhitespace, tkComment, tkSymbol, tkNumber, tkOther);
  18.  
  19. TToken = record
  20. Kind: TTokenKind;
  21. Text: string;
  22. InInterface: Boolean;
  23. InUses: Boolean;
  24. InConstBlock: Boolean;
  25. end;
  26.  
  27. TTokenArray = array of TToken;
  28. TStrArray = array of string;
  29.  
  30. TStrSet = class
  31. private
  32. S: TStringList;
  33. public
  34. constructor Create;
  35. destructor Destroy; override;
  36. procedure Add(const v: string);
  37. function Has(const v: string): Boolean;
  38. procedure Clear;
  39. end;
  40.  
  41. var
  42. GXorStrFuncName: string;
  43. GPolymorphicFuncCode: string;
  44. GHostUnitName: string;
  45. GHostUnitFileName: string;
  46. GRoot: string = '';
  47. GInplace: Boolean = False;
  48. GOnlyGUI: Boolean = False;
  49. GSeed: LongInt = 0;
  50. GSeedGiven: Boolean = False;
  51. GXorKey: string = '';
  52. PublicNames : TStrSet = nil;
  53. DeclaredHere: TStrSet = nil;
  54. SkipNames : TStrSet = nil;
  55. PascalKeywords: TStrArray;
  56.  
  57. constructor TStrSet.Create;
  58. begin
  59. S := TStringList.Create;
  60. S.CaseSensitive := False;
  61. S.Sorted := True;
  62. S.Duplicates := dupIgnore;
  63. end;
  64.  
  65. destructor TStrSet.Destroy;
  66. begin
  67. S.Free;
  68. inherited Destroy;
  69. end;
  70.  
  71. procedure TStrSet.Add(const v: string);
  72. begin
  73. if v <> '' then S.Add(v);
  74. end;
  75.  
  76. function TStrSet.Has(const v: string): Boolean;
  77. begin
  78. Result := (v <> '') and (S.IndexOf(v) >= 0);
  79. end;
  80.  
  81. procedure TStrSet.Clear;
  82. begin
  83. S.Clear;
  84. end;
  85.  
  86. function MakeStrArray(const A: array of string): TStrArray;
  87. var
  88. i: Integer;
  89. begin
  90. Result := nil;
  91. SetLength(Result, Length(A));
  92. for i := Low(A) to High(A) do
  93. Result[i] := LowerCase(A[i]);
  94. end;
  95.  
  96. procedure InitKeywords;
  97. begin
  98. PascalKeywords := MakeStrArray([
  99. 'and','array','as','asm','begin','case','class','const','constructor',
  100. 'destructor','div','do','downto','else','end','except','exports','file',
  101. 'finalization','finally','for','function','goto','if','implementation',
  102. 'in','inherited','initialization','inline','interface','is','label','library',
  103. 'mod','nil','not','object','of','or','out','packed','procedure','program',
  104. 'property','raise','record','repeat','resourcestring','set','shl','shr',
  105. 'string','then','threadvar','to','try','type','unit','until','uses',
  106. 'var','while','with','xor','specialize','generic','on','helper','result',
  107. 'true','false','default','abstract','overload','override','reintroduce',
  108. 'virtual','cdecl','stdcall','safecall','register','far','near','private',
  109. 'protected','public','published','pchar','forspecificcpu','foreachcpu',
  110. 'input','PBOOL','integer','PDWORD',
  111. 'absolute','external','operator','deprecated','platform','experimental',
  112. 'strict','sealed','final','static','message','pascal','export'
  113. ]);
  114. end;
  115.  
  116. function IsKeyword(const S: string): Boolean;
  117. var
  118. L: string;
  119. i: Integer;
  120. begin
  121. L := LowerCase(S);
  122. for i := Low(PascalKeywords) to High(PascalKeywords) do
  123. if L = PascalKeywords[i] then
  124. Exit(True);
  125. Result := False;
  126. end;
  127.  
  128. function IsIdentStart(const C: Char): Boolean; inline;
  129. begin
  130. Result := (C = '_') or (C in ['A'..'Z','a'..'z']);
  131. end;
  132.  
  133. function IsIdentChar(const C: Char): Boolean; inline;
  134. begin
  135. Result := IsIdentStart(C) or (C in ['0'..'9']);
  136. end;
  137.  
  138. procedure SplitExtPasFiles(const Root: string; List: TStrings; Recursive: Boolean = True);
  139. var
  140. SR: TSearchRec;
  141. Sub: string;
  142. begin
  143. if FindFirst(IncludeTrailingPathDelimiter(Root) + '*', faAnyFile, SR) = 0 then
  144. try
  145. repeat
  146. if (SR.Name = '.') or (SR.Name = '..') then Continue;
  147. if (SR.Attr and faDirectory) <> 0 then
  148. begin
  149. if Recursive then
  150. begin
  151. Sub := IncludeTrailingPathDelimiter(Root) + SR.Name;
  152. SplitExtPasFiles(Sub, List, True);
  153. end;
  154. end
  155. else
  156. begin
  157. if AnsiEndsText('.pas', SR.Name) or AnsiEndsText('.pp', SR.Name) or AnsiEndsText('.lpr', SR.Name) then
  158. List.Add(IncludeTrailingPathDelimiter(Root) + SR.Name);
  159. end;
  160. until FindNext(SR) <> 0;
  161. finally
  162. SysUtils.FindClose(SR);
  163. end;
  164. end;
  165.  
  166. procedure AddToken(var Tokens: TTokenArray; var Count: Integer; Kind: TTokenKind;
  167. const Text: string; InInterface, InUses, InConstBlock: Boolean);
  168. begin
  169. if Count >= Length(Tokens) then
  170. SetLength(Tokens, Length(Tokens) * 2 + 256);
  171. Tokens[Count].Kind := Kind;
  172. Tokens[Count].Text := Text;
  173. Tokens[Count].InInterface := InInterface;
  174. Tokens[Count].InUses := InUses;
  175. Tokens[Count].InConstBlock := InConstBlock;
  176. Inc(Count);
  177. end;
  178.  
  179. procedure LexFile(const Content: string; out Tokens: TTokenArray; out Count: Integer);
  180. var
  181. i, n: Integer;
  182. ch: Char;
  183. buf: string;
  184. inInterface, inUses, inConstBlock, inResourceStringBlock: Boolean;
  185. isConstContext: Boolean;
  186.  
  187. function Peek(offset: Integer): Char;
  188. var idx: Integer;
  189. begin
  190. idx := i + offset;
  191. if (idx >= 1) and (idx <= n) then Result := Content[idx] else Result := #0;
  192. end;
  193.  
  194. procedure FlushBuf(kind: TTokenKind);
  195. begin
  196. if buf <> '' then
  197. begin
  198. isConstContext := inConstBlock or inResourceStringBlock;
  199. AddToken(Tokens, Count, kind, buf, inInterface, inUses, isConstContext);
  200. buf := '';
  201. end;
  202. end;
  203.  
  204. procedure AddSym(const s: string);
  205. begin
  206. FlushBuf(tkOther);
  207. isConstContext := inConstBlock or inResourceStringBlock;
  208. AddToken(Tokens, Count, tkSymbol, s, inInterface, inUses, isConstContext);
  209. end;
  210.  
  211. begin
  212. Count := 0;
  213. SetLength(Tokens, 1024);
  214. i := 1; n := Length(Content);
  215. buf := '';
  216. inInterface := False;
  217. inUses := False;
  218. inConstBlock := False;
  219. inResourceStringBlock := False;
  220. while i <= n do
  221. begin
  222. ch := Content[i];
  223. if ch in [#9, #10, #13, ' '] then
  224. begin
  225. FlushBuf(tkOther);
  226. buf := '';
  227. repeat
  228. buf := buf + ch;
  229. Inc(i);
  230. if i > n then Break;
  231. ch := Content[i];
  232. until not (ch in [#9,#10,#13,' ']);
  233. isConstContext := inConstBlock or inResourceStringBlock;
  234. AddToken(Tokens, Count, tkWhitespace, buf, inInterface, inUses, isConstContext);
  235. buf := '';
  236. Continue;
  237. end;
  238. if (ch = '/') and (Peek(1) = '/') then
  239. begin
  240. FlushBuf(tkOther);
  241. buf := '//';
  242. Inc(i, 2);
  243. while (i <= n) and not (Content[i] in [#10, #13]) do
  244. begin
  245. buf := buf + Content[i];
  246. Inc(i);
  247. end;
  248. isConstContext := inConstBlock or inResourceStringBlock;
  249. AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  250. buf := '';
  251. Continue;
  252. end;
  253. if ch = '{' then
  254. begin
  255. FlushBuf(tkOther);
  256. buf := '{';
  257. Inc(i);
  258. while (i <= n) and (Content[i] <> '}') do
  259. begin
  260. buf := buf + Content[i];
  261. Inc(i);
  262. end;
  263. if (i <= n) and (Content[i] = '}') then
  264. begin
  265. buf := buf + '}';
  266. Inc(i);
  267. end;
  268. isConstContext := inConstBlock or inResourceStringBlock;
  269. AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  270. buf := '';
  271. Continue;
  272. end;
  273. if (ch = '(') and (Peek(1) = '*') then
  274. begin
  275. FlushBuf(tkOther);
  276. buf := '(*';
  277. Inc(i, 2);
  278. while (i <= n) and not ((Content[i] = '*') and (Peek(1) = ')')) do
  279. begin
  280. buf := buf + Content[i];
  281. Inc(i);
  282. end;
  283. if (i <= n-1) then
  284. begin
  285. buf := buf + '*)';
  286. Inc(i, 2);
  287. end;
  288. isConstContext := inConstBlock or inResourceStringBlock;
  289. AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  290. buf := '';
  291. Continue;
  292. end;
  293. if ch = '''' then
  294. begin
  295. FlushBuf(tkOther);
  296. buf := '''';
  297. Inc(i);
  298. while i <= n do
  299. begin
  300. ch := Content[i];
  301. buf := buf + ch;
  302. Inc(i);
  303. if ch = '''' then
  304. begin
  305. if (i <= n) and (Content[i] = '''') then
  306. begin
  307. buf := buf + Content[i];
  308. Inc(i);
  309. Continue;
  310. end
  311. else
  312. Break;
  313. end;
  314. end;
  315. isConstContext := inConstBlock or inResourceStringBlock;
  316. AddToken(Tokens, Count, tkString, buf, inInterface, inUses, isConstContext);
  317. buf := '';
  318. Continue;
  319. end;
  320. if IsIdentStart(ch) then
  321. begin
  322. FlushBuf(tkOther);
  323. buf := '';
  324. while (i <= n) and IsIdentChar(Content[i]) do
  325. begin
  326. buf := buf + Content[i];
  327. Inc(i);
  328. end;
  329. if AnsiSameText(buf, 'interface') then
  330. begin
  331. inInterface := True; inUses := False;
  332. end
  333. else if AnsiSameText(buf, 'implementation') then
  334. begin
  335. inInterface := False; inUses := False; inConstBlock := False; inResourceStringBlock := False;
  336. end
  337. else if AnsiSameText(buf, 'uses') then
  338. inUses := True
  339. else if AnsiSameText(buf, 'const') then
  340. inConstBlock := True
  341. else if AnsiSameText(buf, 'resourcestring') then
  342. inResourceStringBlock := True
  343. else if AnsiSameText(buf, 'var') or AnsiSameText(buf, 'type') or AnsiSameText(buf, 'label') or
  344. AnsiSameText(buf, 'threadvar') or AnsiSameText(buf, 'class') or
  345. AnsiSameText(buf, 'procedure') or AnsiSameText(buf, 'function') then
  346. begin
  347. inConstBlock := False;
  348. inResourceStringBlock := False;
  349. end;
  350. isConstContext := inConstBlock or inResourceStringBlock;
  351. AddToken(Tokens, Count, tkIdent, buf, inInterface, inUses, isConstContext);
  352. buf := '';
  353. Continue;
  354. end;
  355. if (ch in ['0'..'9']) or ((ch = '$') and (Peek(1) in ['0'..'9','A'..'F','a'..'f'])) then
  356. begin
  357. FlushBuf(tkOther);
  358. buf := '';
  359. if ch = '$' then
  360. begin
  361. buf := buf + ch;
  362. Inc(i);
  363. while (i <= n) and (Content[i] in ['0'..'9','A'..'F','a'..'f']) do
  364. begin
  365. buf := buf + Content[i];
  366. Inc(i);
  367. end;
  368. end else
  369. begin
  370. while (i <= n) and (Content[i] in ['0'..'9']) do
  371. begin
  372. buf := buf + Content[i];
  373. Inc(i);
  374. end;
  375. if (i <= n) and (Content[i] = '.') and (Peek(1) <> '.') then
  376. begin
  377. buf := buf + Content[i];
  378. Inc(i);
  379. while (i <= n) and (Content[i] in ['0'..'9']) do
  380. begin
  381. buf := buf + Content[i];
  382. Inc(i);
  383. end;
  384. end;
  385. end;
  386. isConstContext := inConstBlock or inResourceStringBlock;
  387. AddToken(Tokens, Count, tkNumber, buf, inInterface, inUses, isConstContext);
  388. buf := '';
  389. Continue;
  390. end;
  391. if (ch = '.') and (Peek(1) = '.') then begin AddSym('..'); Inc(i,2); Continue; end;
  392. if (ch = '<') and (Peek(1) = '=') then begin AddSym('<='); Inc(i,2); Continue; end;
  393. if (ch = '>') and (Peek(1) = '=') then begin AddSym('>='); Inc(i,2); Continue; end;
  394. if (ch = '<') and (Peek(1) = '>') then begin AddSym('<>'); Inc(i,2); Continue; end;
  395. if (ch = ':') and (Peek(1) = '=') then begin AddSym(':='); Inc(i,2); Continue; end;
  396. if ch in [';', ':', '+','-','*','/','(',')','[',']','{','}','^','@',',','.','<','>','='] then
  397. begin
  398. AddSym(ch);
  399. Inc(i);
  400. Continue;
  401. end;
  402. buf := buf + ch;
  403. Inc(i);
  404. end;
  405. FlushBuf(tkOther);
  406. SetLength(Tokens, Count);
  407. end;
  408.  
  409. function EncryptStringForPascal(const s: string; const Key: string): string;
  410. var
  411. i: Integer;
  412. encryptedByte: Byte;
  413. begin
  414. if s = '' then Exit('[]');
  415. if Key = '' then
  416. begin
  417. Result := '[';
  418. for i := 1 to Length(s) do
  419. begin
  420. Result := Result + IntToStr(Ord(s[i]));
  421. if i < Length(s) then Result := Result + ', ';
  422. end;
  423. Result := Result + ']';
  424. Exit;
  425. end;
  426. Result := '[';
  427. for i := 1 to Length(s) do
  428. begin
  429. encryptedByte := Ord(s[i]) xor Ord(Key[((i-1) mod Length(Key)) + 1]);
  430. Result := Result + IntToStr(encryptedByte);
  431. if i < Length(s) then Result := Result + ', ';
  432. end;
  433. Result := Result + ']';
  434. end;
  435.  
  436. function LookupNewName(const OrigLower: string; MapOrig, MapNew: TStrings; out NewName: string): Boolean;
  437. var
  438. i: Integer;
  439. begin
  440. i := MapOrig.IndexOf(OrigLower);
  441. Result := (i >= 0);
  442. if Result then NewName := MapNew[i];
  443. end;
  444.  
  445. function IsCaseLabel(const Tokens: TTokenArray; idx: Integer): Boolean;
  446. var
  447. j: Integer;
  448. begin
  449. Result := False;
  450. if idx >= High(Tokens) then Exit(False);
  451. j := idx + 1;
  452. while j <= High(Tokens) do
  453. begin
  454. if Tokens[j].Kind in [tkWhitespace, tkComment] then
  455. begin
  456. Inc(j);
  457. continue;
  458. end;
  459.  
  460. if (Tokens[j].Kind = tkSymbol) and (Tokens[j].Text = ':') then
  461. Result := True;
  462.  
  463. break;
  464. end;
  465. end;
  466.  
  467. function FixDanglingConcats(const S: string): string;
  468. begin
  469. Result := S;
  470. // plus before delimiters -> add empty string
  471. Result := StringReplace(Result, ' + )', ' + '''' )', [rfReplaceAll]);
  472. Result := StringReplace(Result, ' + ,', ' + '''' ,', [rfReplaceAll]);
  473. Result := StringReplace(Result, ' + ;', ' + '''' ;', [rfReplaceAll]);
  474. Result := StringReplace(Result, ' + ]', ' + '''' ]', [rfReplaceAll]);
  475. Result := StringReplace(Result, ' + {$endif}', ' + '''' {$endif}', [rfReplaceAll]);
  476. Result := StringReplace(Result, ' + {$ELSE}', ' + '''' {$ELSE}', [rfReplaceAll]);
  477. Result := StringReplace(Result, ' + {$else}', ' + '''' {$else}', [rfReplaceAll]);
  478. Result := StringReplace(Result, ' + {$ENDIF}', ' + '''' {$ENDIF}', [rfReplaceAll]);
  479.  
  480. // plus right after opening delimiters or assignment -> inject empty string on left
  481. Result := StringReplace(Result, '( + ', '( '''' + ', [rfReplaceAll]);
  482. Result := StringReplace(Result, '[ + ', '[ '''' + ', [rfReplaceAll]);
  483. Result := StringReplace(Result, ':= + ', ':= '''' + ', [rfReplaceAll]);
  484. Result := StringReplace(Result, ' = + ', ' = '''' + ', [rfReplaceAll]);
  485.  
  486. // collapse any accidental '+ +'
  487. Result := StringReplace(Result, '+ +', '+', [rfReplaceAll]);
  488. Result := StringReplace(Result, '+ +', '+', [rfReplaceAll]);
  489. end;
  490.  
  491.  
  492. procedure SaveStringToFile(const APath, S: string);
  493. var
  494. fs: TFileStream;
  495. begin
  496. fs := TFileStream.Create(APath, fmCreate);
  497. try
  498. if Length(S) > 0 then
  499. fs.WriteBuffer(S[1], Length(S));
  500. finally
  501. fs.Free;
  502. end;
  503. end;
  504.  
  505. function LoadFileToString(const APath: string): string;
  506. var
  507. fs: TFileStream;
  508. begin
  509. Result := '';
  510. if not FileExists(APath) then Exit;
  511. fs := TFileStream.Create(APath, fmOpenRead or fmShareDenyNone);
  512. try
  513. SetLength(Result, fs.Size);
  514. if fs.Size > 0 then
  515. fs.ReadBuffer(Result[1], fs.Size);
  516. finally
  517. fs.Free;
  518. end;
  519. end;
  520.  
  521. function CastAPICalls(const S: string): string; forward;
  522. function BalanceParensPerStatement(const S: string): string; forward;
  523.  
  524.  
  525. function NeedsPCharWrap(const Arg: string): boolean;
  526. var t: string;
  527. begin
  528. t := Trim(Arg);
  529. Result := not (
  530. (Pos('PCHAR(', UpperCase(t)) = 1) or
  531. (Pos('PWIDECHAR(', UpperCase(t)) = 1) or
  532. (Pos('PANSICHAR(', UpperCase(t)) = 1)
  533. );
  534. end;
  535.  
  536. function WrapPChar(const Arg, ApiName: string): string;
  537. begin
  538. // Choose PWideChar for *W APIs, PAnsiChar for *A APIs, else PChar
  539. if (Length(ApiName) > 0) and (ApiName[Length(ApiName)] in ['W','w']) then
  540. Result := 'PWideChar(' + Arg + ')'
  541. else if (Length(ApiName) > 0) and (ApiName[Length(ApiName)] in ['A','a']) then
  542. Result := 'PAnsiChar(' + Arg + ')'
  543. else
  544. Result := 'PChar(' + Arg + ')';
  545. end;
  546.  
  547.  
  548. function ForcePCharOnAPILine(const Line: string): string;
  549.  
  550. function FindArgSpan(const L: string; openPos: Integer; out closePos: Integer): boolean;
  551. var j, depth: Integer; inS: Boolean;
  552. begin
  553. Result := False;
  554. depth := 0;
  555. inS := False;
  556. j := openPos;
  557. while j <= Length(L) do
  558. begin
  559. if L[j] = '''' then inS := not inS
  560. else if not inS then
  561. begin
  562. if L[j] = '(' then Inc(depth)
  563. else if L[j] = ')' then
  564. begin
  565. Dec(depth);
  566. if depth = 0 then
  567. begin
  568. closePos := j;
  569. Exit(True);
  570. end;
  571. end;
  572. end;
  573. Inc(j);
  574. end;
  575. end;
  576.  
  577. function FixSingleArg(const api, L: string): string;
  578. var u: string; i, openPos, closePos: Integer;
  579. before, args, after: string;
  580. begin
  581. Result := L;
  582. u := UpperCase(L);
  583. i := Pos(UpperCase(api) + '(', u);
  584. if i = 0 then Exit;
  585. openPos := i + Length(api);
  586. if (openPos > Length(L)) or (L[openPos] <> '(') then Exit;
  587. if not FindArgSpan(L, openPos, closePos) then Exit;
  588. before := Copy(L, 1, openPos);
  589. args := Copy(L, openPos+1, closePos-openPos-1);
  590. after := Copy(L, closePos, Length(L)-closePos+1);
  591. if NeedsPCharWrap(args) then
  592. Result := before + WrapPChar(Trim(args), api) + after
  593. else
  594. Result := L;
  595. end;
  596.  
  597. function FixGetProcAddress(const L: string): string;
  598. var u: string; i, openPos, closePos: Integer;
  599. before, args, after, a1, a2: string;
  600. k, depth: Integer; inS: Boolean; commaPos: Integer;
  601. begin
  602. Result := L;
  603. u := UpperCase(L);
  604. i := Pos('GETPROCADDRESS(', u);
  605. if i = 0 then Exit;
  606. openPos := i + Length('GetProcAddress');
  607. if (openPos > Length(L)) or (L[openPos] <> '(') then Exit;
  608. if not FindArgSpan(L, openPos, closePos) then Exit;
  609. before := Copy(L, 1, openPos);
  610. args := Copy(L, openPos+1, closePos-openPos-1);
  611. after := Copy(L, closePos, Length(L)-closePos+1);
  612.  
  613. // Find top-level comma in args
  614. commaPos := 0; depth := 0; inS := False;
  615. for k := 1 to Length(args) do
  616. begin
  617. if args[k] = '''' then inS := not inS
  618. else if not inS then
  619. begin
  620. if args[k] = '(' then Inc(depth)
  621. else if args[k] = ')' then Dec(depth)
  622. else if (args[k] = ',') and (depth = 0) then
  623. begin
  624. commaPos := k; Break;
  625. end;
  626. end;
  627. end;
  628. if commaPos = 0 then Exit; // malformed, skip
  629.  
  630. a1 := Trim(Copy(args, 1, commaPos-1));
  631. a2 := Trim(Copy(args, commaPos+1, MaxInt));
  632.  
  633. if NeedsPCharWrap(a2) then
  634. args := a1 + ', ' + WrapPChar(a2, 'GetProcAddress')
  635. else
  636. args := a1 + ', ' + a2;
  637.  
  638. Result := before + args + after;
  639. end;
  640.  
  641. var tmp: string;
  642. begin
  643. tmp := FixGetProcAddress(Line); if tmp <> Line then Exit(tmp);
  644. tmp := FixSingleArg('LoadLibrary', Line); if tmp <> Line then Exit(tmp);
  645. tmp := FixSingleArg('GetModuleHandle', Line); if tmp <> Line then Exit(tmp);
  646. tmp := FixSingleArg('OutputDebugString', Line); if tmp <> Line then Exit(tmp);
  647. Result := Line;
  648. end;
  649.  
  650. function ForcePCharOnAPI(const S: string): string;
  651. var sl: TStringList;
  652. k: Integer;
  653. begin
  654. sl := TStringList.Create;
  655. try
  656. sl.Text := S;
  657. for k := 0 to sl.Count-1 do
  658. sl[k] := ForcePCharOnAPILine(sl[k]);
  659. Result := sl.Text;
  660. finally
  661. sl.Free;
  662. end;
  663. end;
  664.  
  665.  
  666. function IsProgramOrLibraryFile(const APath: string): Boolean;
  667. var
  668. s, lower: string;
  669. i, n: Integer;
  670. begin
  671. Result := False;
  672. if (AnsiSameText(ExtractFileExt(APath), '.lpr')) or (AnsiSameText(ExtractFileExt(APath), '.dpr')) then
  673. Exit(True);
  674.  
  675. // Sniff first non-comment token (very lightweight)
  676. s := LoadFileToString(APath);
  677. lower := LowerCase(s);
  678. i := 1;
  679. while (i <= Length(s)) and (s[i] in [#0..#32]) do Inc(i);
  680. // skip line/block comments
  681. while (i <= Length(lower)) and ((Copy(lower,i,2)='//' ) or (Copy(lower,i,2)='(*') or (Copy(lower,i,1)='{')) do
  682. begin
  683. if Copy(lower,i,2)='//' then
  684. while (i<=Length(s)) and not (s[i] in [#10,#13]) do Inc(i)
  685. else
  686. begin
  687. n := Pos('*)', Copy(lower, i, MaxInt));
  688. if n=0 then Break else i := i + n + 1;
  689. end;
  690. while (i <= Length(s)) and (s[i] in [#0..#32]) do Inc(i);
  691. end;
  692. if Copy(lower,i,7)='program' then Exit(True);
  693. if Copy(lower,i,7)='library' then Exit(True);
  694. end;
  695.  
  696. function PosFrom(const SubStr, S: string; StartIndex: SizeInt): SizeInt;
  697. var
  698. tmp: SizeInt;
  699. begin
  700. if StartIndex<=1 then
  701. Exit(Pos(SubStr, S));
  702. tmp := Pos(SubStr, Copy(S, StartIndex, MaxInt));
  703. if tmp>0 then
  704. Result := StartIndex + tmp - 1
  705. else
  706. Result := 0;
  707. end;
  708.  
  709. function EnsureRuntimeInImplementationUses(const InTxt: string): string;
  710. var
  711. lower: string;
  712. implPos, usesPos, semiPos, checkPos: SizeInt;
  713. needInsert: Boolean;
  714. addStr: string;
  715. begin
  716. Result := InTxt;
  717. // Add only if the unit calls the generated function
  718. if Pos(GXorStrFuncName + '(', Result) = 0 then Exit;
  719.  
  720. addStr := GHostUnitName; // typically 'obf_runtime'
  721.  
  722. lower := LowerCase(Result);
  723.  
  724. implPos := Pos('implementation', lower);
  725. if implPos>0 then
  726. begin
  727. // find 'uses' after implementation
  728. usesPos := PosFrom('uses', lower, implPos + Length('implementation'));
  729. if usesPos>0 then
  730. begin
  731. semiPos := PosFrom(';', lower, usesPos);
  732. if semiPos>0 then
  733. begin
  734. // check if addStr already present between usesPos and semiPos
  735. checkPos := PosFrom(LowerCase(addStr), lower, usesPos);
  736. needInsert := not ((checkPos>0) and (checkPos<semiPos));
  737. if needInsert then
  738. begin
  739. Insert(', ' + addStr, Result, semiPos);
  740. end;
  741. Exit;
  742. end;
  743. end;
  744. // no implementation uses list; create one
  745. Insert(LineEnding + 'uses ' + addStr + ';' + LineEnding, Result, implPos + Length('implementation'));
  746. Exit;
  747. end;
  748.  
  749. // Fallback: try top-level uses
  750. usesPos := Pos('uses', lower);
  751. if usesPos>0 then
  752. begin
  753. semiPos := PosFrom(';', lower, usesPos);
  754. if semiPos>0 then
  755. begin
  756. checkPos := PosFrom(LowerCase(addStr), lower, usesPos);
  757. if not ((checkPos>0) and (checkPos<semiPos)) then
  758. Insert(', ' + addStr, Result, semiPos);
  759. end;
  760. end
  761. else
  762. begin
  763. // As last resort, add after unit header
  764. usesPos := Pos('unit', lower);
  765. if usesPos>0 then
  766. begin
  767. semiPos := PosFrom(';', lower, usesPos);
  768. if semiPos>0 then
  769. Insert(LineEnding + 'uses ' + addStr + ';' + LineEnding, Result, semiPos+1);
  770. end;
  771. end;
  772. end;
  773.  
  774. function EnsureUnitInUsesList(const InTxt, UnitName: string; StartFrom: SizeInt): string;
  775. var
  776. lower: string;
  777. usesPos, semiPos, checkPos: SizeInt;
  778. begin
  779. Result := InTxt;
  780. lower := LowerCase(Result);
  781. usesPos := PosFrom('uses', lower, StartFrom);
  782. if usesPos>0 then
  783. begin
  784. semiPos := PosFrom(';', lower, usesPos);
  785. if semiPos>0 then
  786. begin
  787. checkPos := PosFrom(LowerCase(UnitName), lower, usesPos);
  788. if not ((checkPos>0) and (checkPos<semiPos)) then
  789. Insert(', ' + UnitName, Result, semiPos);
  790. end;
  791. end;
  792. end;
  793.  
  794. function InjectRuntimeUsesEverywhere(const InTxt: string): string;
  795. var
  796. lower: string;
  797. implPos: SizeInt;
  798. begin
  799. Result := InTxt;
  800. lower := LowerCase(Result);
  801. // 1) Ensure interface-level uses includes runtime (if there is a top-level uses)
  802. Result := EnsureUnitInUsesList(Result, GHostUnitName, 1);
  803.  
  804. // 2) Ensure implementation-level uses includes runtime
  805. implPos := Pos('implementation', lower);
  806. if implPos>0 then
  807. Result := EnsureUnitInUsesList(Result, GHostUnitName, implPos + Length('implementation'));
  808. end;
  809. procedure PostProcessWrittenPas(const APath: string);
  810. var
  811. txt: string;
  812. begin
  813. if NEVER_TOUCH_PROGRAMS and IsProgramOrLibraryFile(APath) then Exit;
  814. if (ExtractFileExt(APath) <> '.pas') then Exit;
  815. txt := LoadFileToString(APath);
  816. if txt = '' then Exit;
  817. txt := FixDanglingConcats(txt);
  818. txt := CastAPICalls(txt);
  819. txt := BalanceParensPerStatement(txt);
  820. txt := ForcePCharOnAPI(txt);
  821. txt := InjectRuntimeUsesEverywhere(txt);
  822. SaveStringToFile(APath, txt);
  823. end;
  824.  
  825. function BalanceParensPerStatement(const S: string): string;
  826. var
  827. i, L, depth: Integer;
  828. res: string;
  829. inStr, inLC, inPC: Boolean; // string, { } comment, (* *) comment
  830. begin
  831. res := '';
  832. L := Length(S);
  833. depth := 0;
  834. inStr := False;
  835. inLC := False;
  836. inPC := False;
  837. i := 1;
  838. while i <= L do
  839. begin
  840. if not inStr then
  841. begin
  842. // line comments //
  843. if (S[i] = '/') and (i < L) and (S[i+1] = '/') and (not inLC) and (not inPC) then
  844. begin
  845. // copy until newline
  846. while (i <= L) and (S[i] <> #10) do
  847. begin
  848. res := res + S[i];
  849. Inc(i);
  850. end;
  851. Continue;
  852. end;
  853. // block comment { ... }
  854. if (S[i] = '{') and (not inPC) then
  855. begin
  856. inLC := True;
  857. res := res + S[i];
  858. Inc(i);
  859. Continue;
  860. end
  861. else if inLC then
  862. begin
  863. res := res + S[i];
  864. if S[i] = '}' then inLC := False;
  865. Inc(i);
  866. Continue;
  867. end;
  868. // block comment (* ... *)
  869. if (S[i] = '(') and (i < L) and (S[i+1] = '*') and (not inLC) then
  870. begin
  871. inPC := True;
  872. res := res + '(*';
  873. Inc(i,2);
  874. Continue;
  875. end
  876. else if inPC then
  877. begin
  878. res := res + S[i];
  879. if (S[i] = '*') and (i < L) and (S[i+1] = ')') then
  880. begin
  881. res := res + ')';
  882. Inc(i,2);
  883. inPC := False;
  884. Continue;
  885. end
  886. else
  887. begin
  888. Inc(i);
  889. Continue;
  890. end;
  891. end;
  892.  
  893. if S[i] = '''' then
  894. begin
  895. inStr := True;
  896. res := res + S[i];
  897. Inc(i);
  898. Continue;
  899. end;
  900.  
  901. if S[i] = '(' then
  902. begin
  903. Inc(depth);
  904. res := res + S[i];
  905. Inc(i);
  906. Continue;
  907. end
  908. else if S[i] = ')' then
  909. begin
  910. if depth > 0 then Dec(depth);
  911. res := res + S[i];
  912. Inc(i);
  913. Continue;
  914. end
  915. else if (S[i] = ';') and (depth > 0) then
  916. begin
  917. // close any unbalanced '(' before the end of statement
  918. res := res + StringOfChar(')', depth) + ';';
  919. depth := 0;
  920. Inc(i);
  921. Continue;
  922. end;
  923. end
  924. else // inStr
  925. begin
  926. res := res + S[i];
  927. if S[i] = '''' then
  928. begin
  929. if (i < L) and (S[i+1] = '''') then
  930. begin
  931. res := res + S[i+1];
  932. Inc(i,2);
  933. Continue;
  934. end
  935. else
  936. inStr := False;
  937. end;
  938. Inc(i);
  939. Continue;
  940. end;
  941.  
  942. res := res + S[i];
  943. Inc(i);
  944. end;
  945. Result := res;
  946. end;
  947.  
  948.  
  949. function CastAPICalls(const S: string): string;
  950. var
  951. i, L, depth, argStart, argEnd: Integer;
  952. res: string;
  953. inStr, inLC, inPC: Boolean;
  954. function AheadMatches(const kw: string): Boolean;
  955. var j: Integer;
  956. begin
  957. if i+Length(kw)-1 > L then exit(False);
  958. for j := 1 to Length(kw) do
  959. if LowerCase(S[i+j-1])<>LowerCase(kw[j]) then exit(False);
  960. Result := True;
  961. end;
  962. function IsIdentChar(c: Char): Boolean;
  963. begin
  964. Result := (c in ['A'..'Z','a'..'z','0'..'9','_']);
  965. end;
  966. procedure AppendChar(ch: Char);
  967. begin
  968. res := res + ch;
  969. end;
  970. procedure AppendStr(const str: string);
  971. begin
  972. res := res + str;
  973. end;
  974. // Find matching ')' at same call depth
  975. function FindMatchingParen(startPos: Integer): Integer;
  976. var d: Integer;
  977. k: Integer;
  978. sInStr, sInLC, sInPC: Boolean;
  979. begin
  980. d := 0;
  981. Result := -1;
  982. sInStr := False; sInLC := False; sInPC := False;
  983. k := startPos;
  984. while k <= L do
  985. begin
  986. if not sInStr then
  987. begin
  988. if (k<L) and (S[k]='/') and (S[k+1]='/') and (not sInLC) and (not sInPC) then
  989. begin
  990. while (k<=L) and (S[k]<>#10) do Inc(k);
  991. Continue;
  992. end;
  993. if (S[k]='{') and (not sInPC) then
  994. begin
  995. sInLC := True; Inc(k); Continue;
  996. end
  997. else if sInLC then
  998. begin
  999. if S[k]='}' then sInLC := False;
  1000. Inc(k); Continue;
  1001. end;
  1002. if (k<L) and (S[k]='(') and (S[k+1]='*') and (not sInLC) then
  1003. begin
  1004. sInPC := True; Inc(k,2); Continue;
  1005. end
  1006. else if sInPC then
  1007. begin
  1008. if (S[k]='*') and (k<L) and (S[k+1]=')') then
  1009. begin Inc(k,2); sInPC := False; Continue; end
  1010. else begin Inc(k); Continue; end;
  1011. end;
  1012. if S[k]='''' then begin sInStr := True; Inc(k); Continue; end;
  1013. if S[k]='(' then Inc(d)
  1014. else if S[k]=')' then
  1015. begin
  1016. if d=0 then begin Result := k; exit; end;
  1017. Dec(d);
  1018. end;
  1019. end
  1020. else
  1021. begin
  1022. if S[k]='''' then
  1023. begin
  1024. if (k<L) and (S[k+1]='''') then Inc(k,2) else begin sInStr:=False; Inc(k); end;
  1025. Continue;
  1026. end;
  1027. end;
  1028. Inc(k);
  1029. end;
  1030. end;
  1031. // Find start and end (exclusive) of second arg inside call at '('
  1032. procedure SecondArgBounds(openParenPos: Integer; out aStart, aEnd: Integer);
  1033. var d,k: Integer; comma1Pos: Integer;
  1034. sInStr, sInLC, sInPC: Boolean;
  1035. begin
  1036. aStart := -1; aEnd := -1;
  1037. d := 0; comma1Pos := -1;
  1038. k := openParenPos+1;
  1039. sInStr := False; sInLC := False; sInPC := False;
  1040. while k <= L do
  1041. begin
  1042. if not sInStr then
  1043. begin
  1044. if (k<L) and (S[k]='/') and (S[k+1]='/') and (not sInLC) and (not sInPC) then
  1045. begin while (k<=L) and (S[k]<>#10) do Inc(k); Continue; end;
  1046. if (S[k]='{') and (not sInPC) then begin sInLC := True; Inc(k); Continue; end
  1047. else if sInLC then begin if S[k]='}' then sInLC := False; Inc(k); Continue; end;
  1048. if (k<L) and (S[k]='(') and (S[k+1]='*') and (not sInLC) then begin sInPC := True; Inc(k,2); Continue; end
  1049. else if sInPC then begin if (S[k]='*') and (k<L) and (S[k+1]=')') then begin Inc(k,2); sInPC := False; Continue; end else begin Inc(k); Continue; end; end;
  1050.  
  1051. if S[k]='''' then begin sInStr := True; Inc(k); Continue; end;
  1052.  
  1053. if S[k]='(' then Inc(d)
  1054. else if S[k]=')' then
  1055. begin
  1056. if d=0 then
  1057. begin
  1058. if (comma1Pos<>-1) and (aStart<>-1) and (aEnd=-1) then aEnd := k;
  1059. exit;
  1060. end;
  1061. Dec(d);
  1062. end
  1063. else if (S[k]=',') and (d=0) then
  1064. begin
  1065. if comma1Pos=-1 then
  1066. begin
  1067. comma1Pos := k;
  1068. // second arg starts after comma and whitespace
  1069. aStart := k+1;
  1070. while (aStart<=L) and (S[aStart] in [#9,#10,#13,' ']) do Inc(aStart);
  1071. end
  1072. else if (comma1Pos<>-1) and (aEnd=-1) then
  1073. begin
  1074. aEnd := k;
  1075. exit;
  1076. end;
  1077. end;
  1078. end
  1079. else
  1080. begin
  1081. if S[k]='''' then
  1082. begin
  1083. if (k<L) and (S[k+1]='''') then Inc(k,2) else begin sInStr:=False; Inc(k); end;
  1084. Continue;
  1085. end;
  1086. end;
  1087. Inc(k);
  1088. end;
  1089. end;
  1090. begin
  1091. res := '';
  1092. L := Length(S);
  1093. i := 1;
  1094. inStr := False; inLC := False; inPC := False;
  1095. depth := 0;
  1096. while i <= L do
  1097. begin
  1098. if not inStr then
  1099. begin
  1100. // line comments
  1101. if (i<L) and (S[i]='/') and (S[i+1]='/') and (not inLC) and (not inPC) then
  1102. begin
  1103. while (i<=L) and (S[i]<>#10) do begin AppendChar(S[i]); Inc(i); end;
  1104. Continue;
  1105. end;
  1106. // { } comments
  1107. if (S[i]='{') and (not inPC) then
  1108. begin
  1109. inLC := True; AppendChar(S[i]); Inc(i); Continue;
  1110. end
  1111. else if inLC then
  1112. begin
  1113. AppendChar(S[i]);
  1114. if S[i]='}' then inLC := False;
  1115. Inc(i); Continue;
  1116. end;
  1117. // (* *) comments
  1118. if (i<L) and (S[i]='(') and (S[i+1]='*') and (not inLC) then
  1119. begin
  1120. inPC := True; AppendStr('(*'); Inc(i,2); Continue;
  1121. end
  1122. else if inPC then
  1123. begin
  1124. AppendChar(S[i]);
  1125. if (S[i]='*') and (i<L) and (S[i+1]=')') then
  1126. begin AppendChar(')'); Inc(i,2); inPC := False; Continue; end
  1127. else begin Inc(i); Continue; end;
  1128. end;
  1129.  
  1130. if S[i]='''' then begin inStr := True; AppendChar(S[i]); Inc(i); Continue; end;
  1131.  
  1132. // Detect LoadLibrary(
  1133. if AheadMatches('LoadLibrary') then
  1134. begin
  1135. // ensure not part of a longer identifier
  1136. if ((i=1) or (not IsIdentChar(S[i-1]))) and ((i+12>L) or (not IsIdentChar(S[i+12]))) then
  1137. begin
  1138. AppendStr(Copy(S, i, 12)); // 'LoadLibrary'
  1139. Inc(i,12);
  1140. // expect '('
  1141. if (i<=L) and (S[i]='(') then
  1142. begin
  1143. AppendChar('(');
  1144. Inc(i);
  1145. // Insert PChar(
  1146. AppendStr('PChar(');
  1147. // copy until matching ')' of the call, but we need to find it
  1148. argEnd := FindMatchingParen(i);
  1149. if argEnd<>-1 then
  1150. begin
  1151. AppendStr(Copy(S, i, argEnd - i)); // the original single arg
  1152. AppendChar(')'); // close PChar(
  1153. AppendChar(')'); // close LoadLibrary(
  1154. i := argEnd + 1;
  1155. Continue;
  1156. end;
  1157. end;
  1158. end;
  1159. end;
  1160.  
  1161. // Detect GetProcAddress(
  1162. if AheadMatches('GetProcAddress') then
  1163. begin
  1164. if ((i=1) or (not IsIdentChar(S[i-1]))) and ((i+14>L) or (not IsIdentChar(S[i+14]))) then
  1165. begin
  1166. AppendStr(Copy(S, i, 14)); // 'GetProcAddress'
  1167. Inc(i,14);
  1168. if (i<=L) and (S[i]='(') then
  1169. begin
  1170. // Copy '('
  1171. AppendChar('(');
  1172. Inc(i);
  1173. // We will copy the whole call then wrap the second arg
  1174. // Find matching ')'
  1175. argEnd := FindMatchingParen(i);
  1176. if argEnd<>-1 then
  1177. begin
  1178. // Within i..argEnd-1, find bounds of second arg
  1179. SecondArgBounds(i-1, argStart, argEnd); // we overloaded argEnd reuse; careful
  1180. // argStart/argEnd are relative to S, not to res
  1181. if (argStart<>-1) and (argEnd<>-1) then
  1182. begin
  1183. // copy first part up to second arg start
  1184. AppendStr(Copy(S, i, argStart - i));
  1185. // If the second arg already starts with PChar(, just copy it
  1186. if AheadMatches('PChar(') and False then ; // keep compiler happy
  1187. if (LowerCase(Copy(S, argStart, 6))='pchar(') then
  1188. begin
  1189. AppendStr(Copy(S, argStart, argEnd - argStart));
  1190. end
  1191. else
  1192. begin
  1193. AppendStr('PChar(');
  1194. AppendStr(Copy(S, argStart, argEnd - argStart));
  1195. AppendChar(')');
  1196. end;
  1197. // Copy the rest up to the closing ')'
  1198. i := argEnd;
  1199. // Copy any remaining chars inside the call until we hit the original ')'
  1200. // First find the true close again
  1201. depth := 0;
  1202. while (i<=L) and (S[i]<>')') do
  1203. begin
  1204. AppendChar(S[i]);
  1205. Inc(i);
  1206. end;
  1207. if (i<=L) and (S[i]=')') then
  1208. begin
  1209. AppendChar(')'); Inc(i);
  1210. Continue;
  1211. end;
  1212. end;
  1213. end;
  1214. end;
  1215. end;
  1216. end;
  1217. end
  1218. else
  1219. begin
  1220. AppendChar(S[i]);
  1221. if S[i]='''' then
  1222. begin
  1223. if (i<L) and (S[i+1]='''') then begin AppendChar(S[i+1]); Inc(i,2); Continue; end
  1224. else inStr := False;
  1225. end;
  1226. Inc(i);
  1227. Continue;
  1228. end;
  1229.  
  1230.  
  1231. // Detect OutputDebugString(
  1232. if AheadMatches('OutputDebugString') then
  1233. begin
  1234. if ((i=1) or (not IsIdentChar(S[i-1]))) and ((i+17>L) or (not IsIdentChar(S[i+17]))) then
  1235. begin
  1236. AppendStr(Copy(S, i, 17)); // 'OutputDebugString'
  1237. Inc(i,17);
  1238. if (i<=L) and (S[i]='(') then
  1239. begin
  1240. AppendChar('(');
  1241. Inc(i);
  1242. // Wrap the whole single argument with PChar(...)
  1243. argEnd := FindMatchingParen(i);
  1244. if argEnd<>-1 then
  1245. begin
  1246. AppendStr('PChar(');
  1247. AppendStr(Copy(S, i, argEnd - i));
  1248. AppendChar(')'); // close PChar
  1249. AppendChar(')'); // close OutputDebugString
  1250. i := argEnd + 1;
  1251. Continue;
  1252. end;
  1253. end;
  1254. end;
  1255. end;
  1256.  
  1257. // default copy
  1258. AppendChar(S[i]);
  1259. Inc(i);
  1260. end;
  1261. Result := res;
  1262. end;
  1263.  
  1264. procedure RewritePasFile(const InPath: string; const OutPath: string;
  1265. MapOrig, MapNew: TStrings);
  1266. var
  1267. Content: string;
  1268. SS: TStringList;
  1269. Tokens: TTokenArray;
  1270. Count, i, j, parenIdx, k, interfaceIdx, programLineEnd, usesPos: Integer;
  1271. outBuf, repl, rawString, actualString, prevIdent, lcText, replacement: string;
  1272. stringWasEncrypted, needsPWideChar, isExternalDecl, isConcatenated: Boolean;
  1273. IsProgramFile: Boolean;
  1274. begin
  1275. if GOnlyGUI then
  1276. begin
  1277. // GUI-only mode: do not touch PAS/LPR. Copy input to output.
  1278. if InPath <> OutPath then CopyFile(PChar(InPath), PChar(OutPath), False);
  1279. Exit;
  1280. end;
  1281. outBuf := '';
  1282. if NEVER_TOUCH_PROGRAMS and IsProgramOrLibraryFile(InPath) then
  1283. begin
  1284. if InPath <> OutPath then CopyFile(PChar(InPath), PChar(OutPath), False);
  1285. Exit;
  1286. end;
  1287.  
  1288. // Skip BetterControls component sources entirely (they're compiler-sensitive)
  1289. if (Pos('\bettercontrols\', LowerCase(InPath))>0) or (Pos('/bettercontrols/', LowerCase(InPath))>0) then
  1290. begin
  1291. if InPath <> OutPath then
  1292. CopyFile(PChar(InPath), PChar(OutPath), False);
  1293. Exit;
  1294. end;
  1295.  
  1296.  
  1297. // *** WORKAROUND: Skip cheatengine.lpr to avoid compilation errors ***
  1298. if AnsiSameText(ExtractFileName(InPath), 'cheatengine.lpr') then
  1299. begin
  1300. WriteLn('Skipping cheatengine.lpr...');
  1301. if InPath <> OutPath then
  1302. CopyFile(PChar(InPath), PChar(OutPath), False);
  1303. Exit;
  1304. end;
  1305. // *** END WORKAROUND ***
  1306.  
  1307. if SameText(ExtractFileName(InPath), 'LuaSyntax.pas') then
  1308. begin
  1309. if InPath <> OutPath then
  1310. CopyFile(PChar(InPath), PChar(OutPath), False);
  1311. Exit;
  1312. end;
  1313.  
  1314. // Use TStringList to handle file content to better respect original line endings
  1315. SS := TStringList.Create;
  1316. try
  1317. SS.LoadFromFile(InPath);
  1318. Content := SS.Text;
  1319. IsProgramFile := (Pos('unit', LowerCase(Content)) = 0) and (Pos('program', LowerCase(Content)) > 0);
  1320.  
  1321. if SKIP_BETTERCONTROLS then
  1322. begin
  1323. if (Pos('\\bettercontrols\\', LowerCase(InPath))>0) or
  1324. (Pos('/bettercontrols/', LowerCase(InPath))>0) then
  1325. begin
  1326. if InPath <> OutPath then
  1327. // Final cleanup: if this is a program (.lpr), remove any stray 'interface' lines
  1328. if IsProgramFile then
  1329. begin
  1330. outBuf := StringReplace(outBuf, LineEnding + 'interface' + LineEnding, LineEnding, [rfReplaceAll, rfIgnoreCase]);
  1331. // also handle potential Windows vs Unix endings
  1332. outBuf := StringReplace(outBuf, #13#10 + 'interface' + #13#10, #13#10, [rfReplaceAll, rfIgnoreCase]);
  1333. outBuf := StringReplace(outBuf, #10 + 'interface' + #10, #10, [rfReplaceAll, rfIgnoreCase]);
  1334. end;
  1335.  
  1336. SS.SaveToFile(OutPath);
  1337. Exit;
  1338. end;
  1339. end;
  1340.  
  1341. if SKIP_DBK32 then
  1342. begin
  1343. if (Pos('\\dbk32\\', LowerCase(InPath))>0) or
  1344. (Pos('/dbk32/', LowerCase(InPath))>0) then
  1345. begin
  1346. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1347. Exit;
  1348. end;
  1349. end;
  1350.  
  1351. if AnsiSameText(ExtractFileName(InPath), 'LuaSyntax.pas') or
  1352. AnsiSameText(ExtractFileName(InPath), 'mikmod.pas') or
  1353. AnsiSameText(ExtractFileName(InPath), 'betterdllsearchpath.pas') then
  1354. begin
  1355. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1356. Exit;
  1357. end;
  1358. if (Pos('unit luasyntax', LowerCase(Content)) > 0) or
  1359. (Pos('unit mikmod', LowerCase(Content)) > 0) or
  1360. (Pos('unit betterdllsearchpath', LowerCase(Content)) > 0) then
  1361. begin
  1362. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1363. Exit;
  1364. end;
  1365.  
  1366. if LowerCase(ExtractFileName(InPath)) = 'luasyntax.pas' then
  1367. begin
  1368. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1369. Exit;
  1370. end;
  1371.  
  1372. if Pos('unit luasyntax', LowerCase(Content)) > 0 then
  1373. begin
  1374. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1375. Exit;
  1376. end;
  1377.  
  1378. LexFile(Content, Tokens, Count);
  1379. stringWasEncrypted := False;
  1380. for i := 0 to Count - 1 do
  1381. begin
  1382. if IsProgramFile and (Tokens[i].Kind = tkIdent) and (LowerCase(Tokens[i].Text) = 'interface') then begin Continue; end;
  1383. if Tokens[i].Kind = tkString then
  1384. begin
  1385. rawString := Tokens[i].Text;
  1386. if (Length(rawString) > 2) and (rawString[1] = '''') and (not Tokens[i].InConstBlock) then
  1387. begin
  1388. actualString := StringReplace(Copy(rawString, 2, Length(rawString) - 2), '''''', '''', [rfReplaceAll]);
  1389. if (Length(actualString) > 1) and not ((Length(actualString) > 2) and (actualString[1] = '{') and (actualString[Length(actualString)] = '}')) then
  1390. begin
  1391. stringWasEncrypted := True;
  1392. break;
  1393. end;
  1394. end;
  1395. end;
  1396. end;
  1397.  
  1398. if not stringWasEncrypted then
  1399. begin
  1400. if InPath <> OutPath then SS.SaveToFile(OutPath);
  1401. Exit;
  1402. end;
  1403.  
  1404. // Check if our host unit is already used in this file
  1405. if Pos(LowerCase(GHostUnitName), LowerCase(Content)) = 0 then
  1406. begin
  1407. // It's a PROGRAM file (.lpr)
  1408. if IsProgramFile then
  1409. begin
  1410. usesPos := Pos('uses', LowerCase(Content));
  1411. // Found an existing 'uses' clause
  1412. if usesPos > 0 then
  1413. begin
  1414. Insert(' ' + GHostUnitName + ', ', Content, usesPos + Length('uses'));
  1415. end
  1416. // No 'uses' clause, so create one after 'program ...;'
  1417. else
  1418. begin
  1419. programLineEnd := Pos(';', LowerCase(Content));
  1420. if programLineEnd > 0 then
  1421. Insert(#13#10 + 'uses ' + GHostUnitName + ';' + #13#10, Content, programLineEnd + 1);
  1422. end;
  1423. end
  1424. // It's a UNIT file (.pas)
  1425. else
  1426. begin
  1427. interfaceIdx := Pos('interface', LowerCase(Content));
  1428. if interfaceIdx > 0 then
  1429. begin
  1430. usesPos := Pos('uses', Copy(LowerCase(Content), interfaceIdx, MaxInt));
  1431. // Found 'uses' in the interface section
  1432. if (usesPos > 0) and (Pos('implementation', Copy(LowerCase(Content), interfaceIdx, MaxInt)) > usesPos) then
  1433. begin
  1434. Insert(' ' + GHostUnitName + ', ', Content, interfaceIdx + usesPos - 1 + Length('uses'));
  1435. end
  1436. // No 'uses' in interface section, so add it
  1437. else
  1438. begin
  1439. Insert(#13#10 + 'uses ' + GHostUnitName + ';' + #13#10, Content, interfaceIdx + Length('interface'));
  1440. end;
  1441. end
  1442. // This part is for units that might not have an interface section, less common
  1443. else
  1444. begin
  1445. usesPos := Pos('uses', LowerCase(Content));
  1446. if usesPos > 0 then
  1447. begin
  1448. Insert(' ' + GHostUnitName + ', ', Content, usesPos + Length('uses'));
  1449. end
  1450. else
  1451. begin
  1452. programLineEnd := Pos(';', LowerCase(Content));
  1453. if programLineEnd > 0 then
  1454. Insert(#13#10 + 'uses ' + GHostUnitName + ';' + #13#10, Content, programLineEnd + 1);
  1455. end;
  1456. end;
  1457. end;
  1458. end;
  1459.  
  1460. LexFile(Content, Tokens, Count);
  1461.  
  1462. outBuf := '';
  1463. for i := 0 to Count - 1 do
  1464. begin
  1465. if IsProgramFile and (Tokens[i].Kind = tkIdent) and (LowerCase(Tokens[i].Text) = 'interface') then begin Continue; end;
  1466. case Tokens[i].Kind of
  1467. tkIdent:
  1468. begin
  1469. repl := Tokens[i].Text;
  1470. if LookupNewName(LowerCase(Tokens[i].Text), MapOrig, MapNew, repl) then
  1471. outBuf := outBuf + repl
  1472. else
  1473. outBuf := outBuf + Tokens[i].Text;
  1474. end;
  1475. tkString:
  1476. begin
  1477. rawString := Tokens[i].Text;
  1478. if rawString = '''%%XOR_KEY%%''' then
  1479. begin
  1480. outBuf := outBuf + '''' + StringReplace(GXorKey, '''', '''''', [rfReplaceAll]) + '''';
  1481. end
  1482. else if (Length(rawString) > 2) and (rawString[1] = '''') and (not Tokens[i].InConstBlock) then
  1483. begin
  1484. actualString := StringReplace(Copy(rawString, 2, Length(rawString) - 2), '''''', '''', [rfReplaceAll]);
  1485.  
  1486. isExternalDecl := False;
  1487. j := i - 1;
  1488. while j >= 0 do
  1489. begin
  1490. if Tokens[j].Kind in [tkWhitespace, tkComment] then
  1491. begin
  1492. Dec(j);
  1493. continue;
  1494. end;
  1495. if Tokens[j].Kind = tkIdent then
  1496. begin
  1497. lcText := LowerCase(Tokens[j].Text);
  1498. if (lcText = 'external') or (lcText = 'name') then
  1499. isExternalDecl := True;
  1500. end;
  1501. break;
  1502. end;
  1503.  
  1504. if (Length(actualString) > 1) and not isExternalDecl and not IsCaseLabel(Tokens, i)
  1505. and not ((Length(actualString) > 2) and (actualString[1] = '{') and (actualString[Length(actualString)] = '}')) then
  1506. begin
  1507. replacement := GXorStrFuncName + '(' + EncryptStringForPascal(actualString, GXorKey) + ')';
  1508.  
  1509. isConcatenated := False;
  1510. k := i - 1;
  1511. while (k >= 0) and (Tokens[k].Kind in [tkWhitespace, tkComment]) do Dec(k);
  1512. if (k >= 0) and (Tokens[k].Kind = tkSymbol) and (Tokens[k].Text = '+') then
  1513. isConcatenated := True;
  1514.  
  1515. if not isConcatenated then
  1516. begin
  1517. k := i + 1;
  1518. while (k < Count) and (Tokens[k].Kind in [tkWhitespace, tkComment]) do Inc(k);
  1519. if (k < Count) and (Tokens[k].Kind = tkSymbol) and (Tokens[k].Text = '+') then
  1520. isConcatenated := True;
  1521. end;
  1522.  
  1523. if not isConcatenated then
  1524. begin
  1525. parenIdx := -1;
  1526. prevIdent := '';
  1527. for j := i - 1 downto 0 do
  1528. begin
  1529. if Tokens[j].Kind = tkSymbol then
  1530. begin
  1531. if Tokens[j].Text = '(' then
  1532. begin
  1533. parenIdx := j;
  1534. break;
  1535. end;
  1536. if (Tokens[j].Text = ';') or (Tokens[j].Text = ':=') or (Tokens[j].Text = '=') then break;
  1537. end;
  1538. end;
  1539.  
  1540. if parenIdx > 0 then
  1541. begin
  1542. for j := parenIdx - 1 downto 0 do
  1543. begin
  1544. if Tokens[j].Kind in [tkWhitespace, tkComment] then continue;
  1545. if Tokens[j].Kind = tkIdent then
  1546. prevIdent := LowerCase(Tokens[j].Text);
  1547. break;
  1548. end;
  1549.  
  1550. needsPWideChar := (Length(prevIdent) > 0) and (LowerCase(prevIdent[Length(prevIdent)]) = 'w');
  1551. if needsPWideChar then
  1552. replacement := 'PWideChar(' + replacement + ')'
  1553. else
  1554. replacement := 'PAnsiChar(' + replacement + ')';
  1555. end;
  1556. end;
  1557.  
  1558. outBuf := outBuf + replacement;
  1559. end
  1560. else
  1561. outBuf := outBuf + Tokens[i].Text;
  1562. end else
  1563. outBuf := outBuf + Tokens[i].Text;
  1564. end;
  1565. else
  1566. outBuf := outBuf + Tokens[i].Text;
  1567. end;
  1568. end;
  1569.  
  1570. SS.Text := outBuf;
  1571. SS.SaveToFile(OutPath);
  1572.  
  1573. finally
  1574. SS.Free;
  1575. end;
  1576.  
  1577. if ENABLE_POSTPROCESS then
  1578. PostProcessWrittenPas(OutPath);
  1579. end;
  1580.  
  1581. function GenName(Len: Integer): string;
  1582. var
  1583. i: Integer;
  1584. begin
  1585. if Len < 1 then Len := 8;
  1586. Result := Chr(Ord('a') + Random(26));
  1587. for i := 2 to Len do
  1588. Result := Result + Chr(Ord('a') + Random(26));
  1589. end;
  1590.  
  1591. function GenRandomColor: string;
  1592. begin
  1593. Result := '$' + IntToHex(Random($FFFFFF), 8);
  1594. end;
  1595.  
  1596. procedure RewriteLfmFile(const InLfmPath, OutLfmPath: string; MapOrig, MapNew: TStrings);
  1597. var
  1598. Lfm, OutLfm: TStringList;
  1599. i: Integer;
  1600. line, trimmedLine, key, value, newName, origName, newCaption: string;
  1601. inIconData: Boolean;
  1602. begin
  1603. if not FileExists(InLfmPath) then Exit;
  1604.  
  1605. Lfm := TStringList.Create;
  1606. OutLfm := TStringList.Create;
  1607. try
  1608. Lfm.LoadFromFile(InLfmPath);
  1609.  
  1610. inIconData := False;
  1611. for i := 0 to Lfm.Count - 1 do
  1612. begin
  1613. line := Lfm[i];
  1614. trimmedLine := Trim(line);
  1615.  
  1616. if StartsText('Icon.Data', trimmedLine) then
  1617. begin
  1618. inIconData := True;
  1619. Continue;
  1620. end;
  1621. if inIconData then
  1622. begin
  1623. if StartsText('}', trimmedLine) then
  1624. inIconData := False;
  1625. Continue;
  1626. end;
  1627.  
  1628. if StartsText('object ', LowerCase(trimmedLine)) then
  1629. begin
  1630. key := copy(trimmedLine, 8, Length(trimmedLine));
  1631. if Pos(':', key) > 0 then
  1632. begin
  1633. origName := Copy(key, 1, Pos(':', key) - 1);
  1634. if LookupNewName(LowerCase(origName), MapOrig, MapNew, newName) then
  1635. line := StringReplace(line, origName + ':', newName + ':', [rfIgnoreCase]);
  1636. end;
  1637. end
  1638. else
  1639. begin
  1640. if StartsText('Caption =', trimmedLine) then
  1641. begin
  1642. value := Trim(Copy(trimmedLine, Pos('=', trimmedLine) + 1, MaxInt));
  1643. if (Length(value) > 2) and (value[1] = '''') and (value[Length(value)] = '''') then
  1644. begin
  1645. newCaption := GenName(Random(6) + 3);
  1646. line := Copy(line, 1, Pos('=', line)) + ' ''' + newCaption + '''';
  1647. end;
  1648. end
  1649. else if StartsText('Color =', trimmedLine) then
  1650. begin
  1651. line := Copy(line, 1, Pos('=', line)) + ' ' + GenRandomColor;
  1652. end
  1653. else if StartsText('Font.Color =', trimmedLine) then
  1654. begin
  1655. line := Copy(line, 1, Pos('=', line)) + ' ' + GenRandomColor;
  1656. end
  1657. else if StartsText('Font.Size =', trimmedLine) then
  1658. begin
  1659. line := Copy(line, 1, Pos('=', line)) + ' ' + IntToStr(Random(6) + 8);
  1660. end;
  1661. end;
  1662. OutLfm.Add(line);
  1663. end;
  1664.  
  1665. OutLfm.SaveToFile(OutLfmPath);
  1666. finally
  1667. Lfm.Free;
  1668. OutLfm.Free;
  1669. end;
  1670. end;
  1671.  
  1672. procedure CreateHostUnit(const AFileName: string);
  1673. var
  1674. sl: TStringList;
  1675. begin
  1676. sl := TStringList.Create;
  1677. try
  1678. sl.Add('unit ' + GHostUnitName + ';');
  1679. sl.Add('');
  1680. sl.Add('{$mode objfpc}{$H+}');
  1681. sl.Add('');
  1682. sl.Add('interface');
  1683. sl.Add('');
  1684. sl.Add('function ' + GXorStrFuncName + '(const s: array of Byte): string;');
  1685. sl.Add('');
  1686. sl.Add('implementation');
  1687. sl.Add('');
  1688. sl.Add(GPolymorphicFuncCode);
  1689. sl.Add('');
  1690. sl.Add('end.');
  1691. sl.SaveToFile(AFileName);
  1692. finally
  1693. sl.Free;
  1694. end;
  1695. end;
  1696.  
  1697. procedure AddUnitToProject(const AProjectFile, AUnitPath: string);
  1698. var
  1699. LPI, NewLPI: TStringList;
  1700. i: integer;
  1701. UnitFileName, RelativeUnitPath: string;
  1702. ClosingTagLine: integer;
  1703. begin
  1704. if not FileExists(AProjectFile) then
  1705. begin
  1706. WriteLn('WARNING: Could not find project file to update: ', AProjectFile);
  1707. Exit;
  1708. end;
  1709.  
  1710. LPI := TStringList.Create;
  1711. NewLPI := TStringList.Create;
  1712. try
  1713. LPI.LoadFromFile(AProjectFile);
  1714. UnitFileName := ExtractFileName(AUnitPath);
  1715.  
  1716. for i := 0 to LPI.Count - 1 do
  1717. begin
  1718. if (Pos(UnitFileName, LPI[i]) > 0) and (Pos('<Filename Value="', LPI[i]) > 0) then
  1719. begin
  1720. WriteLn('INFO: Host unit is already part of the project file.');
  1721. Exit;
  1722. end;
  1723. end;
  1724.  
  1725. RelativeUnitPath := ExtractRelativePath(ExtractFilePath(AProjectFile), AUnitPath);
  1726.  
  1727. ClosingTagLine := -1;
  1728. for i := LPI.Count - 1 downto 0 do
  1729. begin
  1730. if Pos('</ProjectUnits>', Trim(LPI[i])) > 0 then
  1731. begin
  1732. ClosingTagLine := i;
  1733. break;
  1734. end;
  1735. end;
  1736.  
  1737. if ClosingTagLine = -1 then
  1738. begin
  1739. WriteLn('WARNING: Could not find </ProjectUnits> tag in ', AProjectFile);
  1740. Exit;
  1741. end;
  1742.  
  1743. for i := 0 to ClosingTagLine - 1 do
  1744. NewLPI.Add(LPI[i]);
  1745.  
  1746. NewLPI.Add(' <Unit>' +
  1747. '<Filename Value="' + RelativeUnitPath + '"/>' +
  1748. '<IsPartOfProject Value="True"/>' +
  1749. '</Unit>');
  1750.  
  1751. for i := ClosingTagLine to LPI.Count - 1 do
  1752. NewLPI.Add(LPI[i]);
  1753.  
  1754. NewLPI.SaveToFile(AProjectFile);
  1755. WriteLn('SUCCESS: Added ', UnitFileName, ' to ', ExtractFileName(AProjectFile));
  1756.  
  1757. finally
  1758. LPI.Free;
  1759. NewLPI.Free;
  1760. end;
  1761. end;
  1762.  
  1763. procedure RewriteAll(const Files: TStrings; Inplace: Boolean; MapOrig, MapNew: TStrings);
  1764. var
  1765. f: Integer;
  1766. pasPath, pasOutPath, lfmPath, lfmOutPath, pasBakPath, lfmBakPath, LpiPath: string;
  1767. begin
  1768. if Files.Count = 0 then Exit;
  1769.  
  1770. if not GOnlyGUI then
  1771. begin
  1772. if not GOnlyGUI then
  1773. begin
  1774. GHostUnitName := 'obf_runtime';
  1775. GHostUnitFileName := IncludeTrailingPathDelimiter(GRoot) + GHostUnitName + '.pas';
  1776. WriteLn('Creating dedicated host unit: ', GHostUnitFileName);
  1777. CreateHostUnit(GHostUnitFileName);
  1778.  
  1779. LpiPath := ChangeFileExt(GRoot, '.lpi');
  1780. if not FileExists(LpiPath) then
  1781. LpiPath := IncludeTrailingPathDelimiter(GRoot) + 'cheatengine.lpi';
  1782.  
  1783. AddUnitToProject(LpiPath, GHostUnitFileName);
  1784. end;
  1785. end;
  1786. for f := 0 to Files.Count - 1 do
  1787. begin
  1788. pasPath := Files[f];
  1789. if AnsiSameText(ExtractFileName(pasPath), ExtractFileName(GHostUnitFileName)) then
  1790. Continue;
  1791.  
  1792. lfmPath := ChangeFileExt(pasPath, '.lfm');
  1793. if Inplace then
  1794. begin
  1795. pasOutPath := pasPath;
  1796. lfmOutPath := lfmPath;
  1797. pasBakPath := pasPath + '.bak';
  1798. lfmBakPath := lfmPath + '.bak';
  1799. if not GOnlyGUI then begin if FileExists(pasBakPath) then SysUtils.DeleteFile(pasBakPath); end;
  1800. if not GOnlyGUI then begin if not SysUtils.RenameFile(pasPath, pasBakPath) then raise Exception.Create('Failed to backup ' + pasPath); end;
  1801. if FileExists(lfmPath) then
  1802. begin
  1803. if FileExists(lfmBakPath) then SysUtils.DeleteFile(lfmBakPath);
  1804. if not SysUtils.RenameFile(lfmPath, lfmBakPath) then
  1805. begin
  1806. SysUtils.RenameFile(pasBakPath, pasPath);
  1807. raise Exception.Create('Failed to backup ' + lfmPath);
  1808. end;
  1809. end;
  1810. try
  1811. if FileExists(lfmBakPath) then
  1812. begin
  1813. WriteLn('Rewriting LFM: ', ExtractFileName(lfmPath));
  1814. RewriteLfmFile(lfmBakPath, lfmOutPath, MapOrig, MapNew);
  1815. end;
  1816. if not GOnlyGUI then WriteLn('Rewriting PAS: ', ExtractFileName(pasPath));
  1817. if not GOnlyGUI then RewritePasFile(pasBakPath, pasOutPath, MapOrig, MapNew);
  1818. SysUtils.DeleteFile(pasBakPath);
  1819. if FileExists(lfmBakPath) then SysUtils.DeleteFile(lfmBakPath);
  1820. except
  1821. on E: Exception do
  1822. begin
  1823. WriteLn('ERROR rewriting ', ExtractFileName(pasPath), ': ', E.Message);
  1824. if (not GOnlyGUI) and FileExists(pasBakPath) then
  1825. begin
  1826. if FileExists(pasPath) then SysUtils.DeleteFile(pasPath);
  1827. SysUtils.RenameFile(pasBakPath, pasPath);
  1828. end;
  1829. if FileExists(lfmBakPath) then
  1830. begin
  1831. if FileExists(lfmPath) then SysUtils.DeleteFile(lfmPath);
  1832. SysUtils.RenameFile(lfmBakPath, lfmPath);
  1833. end;
  1834. raise;
  1835. end;
  1836. end;
  1837. end
  1838. else
  1839. begin
  1840. pasOutPath := ChangeFileExt(pasPath, '.obf' + ExtractFileExt(pasPath));
  1841. lfmOutPath := ChangeFileExt(pasOutPath, '.lfm');
  1842. if FileExists(lfmPath) then
  1843. begin
  1844. WriteLn('Rewriting LFM: ', ExtractFileName(lfmPath));
  1845. RewriteLfmFile(lfmPath, lfmOutPath, MapOrig, MapNew);
  1846. end;
  1847. if not GOnlyGUI then WriteLn('Rewriting ', ExtractFileName(pasPath), ' to ', ExtractFileName(pasOutPath));
  1848. if not GOnlyGUI then RewritePasFile(pasPath, pasOutPath, MapOrig, MapNew);
  1849. end;
  1850. end;
  1851. end;
  1852.  
  1853. procedure ParseArgs;
  1854. var
  1855. i: Integer;
  1856. a, v: string;
  1857. begin
  1858. for i := 1 to ParamCount do
  1859. begin
  1860. a := ParamStr(i);
  1861. if AnsiStartsText('--root=', a) then
  1862. GRoot := Copy(a, 8, MaxInt)
  1863. else if AnsiSameText(a, '--inplace') then
  1864. GInplace := True
  1865. else if AnsiSameText(a, '--only-gui') then
  1866. GOnlyGUI := True
  1867. else if AnsiStartsText('--seed=', a) then
  1868. begin
  1869. v := Copy(a, 8, MaxInt);
  1870. try
  1871. GSeed := StrToInt(v);
  1872. GSeedGiven := True;
  1873. except
  1874. on E: Exception do
  1875. begin
  1876. WriteLn('Invalid --seed value: ', v);
  1877. Halt(1);
  1878. end;
  1879. end;
  1880. end
  1881. else if (a='-h') or (a='--help') then
  1882. begin
  1883. WriteLn('PasObf - Pascal Obfuscator');
  1884. WriteLn(' --root=PATH');
  1885. WriteLn(' --inplace');
  1886. WriteLn(' --seed=N');
  1887. Halt(0);
  1888. end;
  1889. end;
  1890. if GRoot = '' then
  1891. begin
  1892. WriteLn('Usage: PasObf --root=PATH [options]');
  1893. Halt(1);
  1894. end;
  1895. end;
  1896.  
  1897. function GeneratePolymorphicXorStr: string;
  1898. var
  1899. funcName, keyVar, loopVar, junkVar: string;
  1900. junkCode: string;
  1901. sl: TStringList;
  1902. begin
  1903. funcName := 'fn_obfstr';
  1904. GXorStrFuncName := funcName;
  1905. keyVar := 'k_' + GenName(6);
  1906. loopVar := 'i_' + GenName(6);
  1907. junkVar := 'j_' + GenName(6);
  1908. case Random(3) of
  1909. 0: junkCode := ' ' + junkVar + ' := ' + junkVar + ' + ' + loopVar + ' and $FF;';
  1910. 1: junkCode := ' ' + junkVar + ' := (' + junkVar + ' * 3) xor ' + loopVar + ';';
  1911. 2: junkCode := ' ' + junkVar + ' := ' + junkVar + ' - ' + loopVar + ';';
  1912. end;
  1913. sl := TStringList.Create;
  1914. try
  1915. sl.Add('function ' + funcName + '(const s: array of Byte): string;');
  1916. sl.Add('const');
  1917. sl.Add(' ' + keyVar + ' = ''%%XOR_KEY%%'';');
  1918. sl.Add('var');
  1919. sl.Add(' ' + loopVar + ', ' + junkVar + ': Integer;');
  1920. sl.Add('begin');
  1921. sl.Add(' ' + junkVar + ' := 0;');
  1922. sl.Add(' SetLength(Result, Length(s));');
  1923. sl.Add(' if Length(' + keyVar + ') = 0 then');
  1924. sl.Add(' begin');
  1925. sl.Add(' for ' + loopVar + ' := 0 to High(s) do Result[' + loopVar + '+1] := Chr(s['+ loopVar +']);');
  1926. sl.Add(' end');
  1927. sl.Add(' else');
  1928. sl.Add(' begin');
  1929. sl.Add(' for ' + loopVar + ' := 0 to High(s) do');
  1930. sl.Add(' begin');
  1931. sl.Add(' Result[' + loopVar + '+1] := Chr(s[' + loopVar + '] xor Ord(' + keyVar + '[(' + loopVar + ' mod Length(' + keyVar + ')) + 1]));');
  1932. sl.Add(junkCode);
  1933. sl.Add(' end;');
  1934. sl.Add(' end;');
  1935. sl.Add('end;');
  1936. Result := sl.Text;
  1937. finally
  1938. sl.Free;
  1939. end;
  1940. end;
  1941.  
  1942. procedure Main;
  1943. var
  1944. Files: TStringList;
  1945. MapOrig, MapNew: TStringList;
  1946. Response: string;
  1947. i: Integer;
  1948. begin
  1949. InitKeywords;
  1950. ParseArgs;
  1951. if GSeedGiven then RandSeed := GSeed else Randomize;
  1952. SetLength(GXorKey, 16);
  1953. for i := 1 to Length(GXorKey) do
  1954. GXorKey[i] := Chr(Random(94) + 33);
  1955. GPolymorphicFuncCode := GeneratePolymorphicXorStr;
  1956. if GInplace then
  1957. begin
  1958. WriteLn('WARNING: The --inplace flag will overwrite your files in place.');
  1959. Write('Proceed? [y/N]: ');
  1960. ReadLn(Response);
  1961. if not (AnsiSameText(Response, 'y') or AnsiSameText(Response, 'yes')) then Halt(0);
  1962. end;
  1963. PublicNames := TStrSet.Create;
  1964. DeclaredHere := TStrSet.Create;
  1965. SkipNames := TStrSet.Create;
  1966. Files := TStringList.Create;
  1967. MapOrig := TStringList.Create;
  1968. MapNew := TStringList.Create;
  1969. try
  1970. SplitExtPasFiles(GRoot, Files, True);
  1971. if Files.Count = 0 then
  1972. begin
  1973. WriteLn('No .pas/.pp files found under ', GRoot);
  1974. Halt(2);
  1975. end;
  1976. RewriteAll(Files, GInplace, MapOrig, MapNew);
  1977. WriteLn('Done.');
  1978. WriteLn('String literals were encrypted with key: ', GXorKey);
  1979. finally
  1980. PublicNames.Free;
  1981. DeclaredHere.Free;
  1982. SkipNames.Free;
  1983. Files.Free;
  1984. MapOrig.Free;
  1985. MapNew.Free;
  1986. end;
  1987. end;
  1988.  
  1989. begin
  1990. Main;
  1991. end.
  1992.  
  1993.  
Advertisement
Add Comment
Please, Sign In to add comment