Setup.PathRedir.pas 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. unit Setup.PathRedir;
  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. The ApplyPathRedirRules function rewrites paths containing System32,
  8. SysWOW64, and Sysnative to achieve the same effect as disabling/enabling
  9. WOW64 file system redirection.
  10. Before rewriting, the path is expanded and converted to a super
  11. (extended-length) path. If the rfNormalPath flag is specified, the path is
  12. converted back to a normal path if possible (even if a super path was
  13. passed to the function). Use rfNormalPath only in cases where a super
  14. path is known to not work properly.
  15. Used only by the Setup project.
  16. }
  17. interface
  18. uses
  19. Windows, SysUtils;
  20. type
  21. TPathRedirFlags = set of (rfNormalPath);
  22. TPathRedirTargetProcess = (tpCurrent, tpNativeBit, tp32Bit,
  23. tp32BitPreferSystem32);
  24. function ApplyPathRedirRules(const A64Bit: Boolean; const APath: String;
  25. const ATargetProcess: TPathRedirTargetProcess;
  26. const AFlags: TPathRedirFlags = []): String;
  27. procedure InitializePathRedir(const AWindows64Bit: Boolean;
  28. const ASystem32Path, ASysWow64Path, ASysNativePath: String);
  29. implementation
  30. uses
  31. PathFunc,
  32. Setup.InstFunc;
  33. type
  34. TPathRedir = class
  35. strict private
  36. FWindows64Bit: Boolean;
  37. FSystem32Path, FSysWow64Path, FSysNativePath: String;
  38. public
  39. constructor Create(const AWindows64Bit: Boolean;
  40. const ASystem32Path, ASysWow64Path, ASysNativePath: String);
  41. function ApplyRules(const A64Bit: Boolean; const APath: String;
  42. const AFlags: TPathRedirFlags;
  43. const ATargetProcess: TPathRedirTargetProcess): String;
  44. end;
  45. var
  46. [volatile] PathRedirInstance: TPathRedir;
  47. [volatile] PathRedirActiveUseCount: Integer;
  48. procedure InitializePathRedir(const AWindows64Bit: Boolean;
  49. const ASystem32Path, ASysWow64Path, ASysNativePath: String);
  50. begin
  51. const LInstance = TPathRedir.Create(AWindows64Bit, ASystem32Path,
  52. ASysWow64Path, ASysNativePath);
  53. MemoryBarrier;
  54. if AtomicCmpExchange(Pointer(PathRedirInstance), Pointer(LInstance), nil) <> nil then begin
  55. LInstance.Free;
  56. InternalError('PathRedir: Already initialized');
  57. end;
  58. end;
  59. function ApplyPathRedirRules(const A64Bit: Boolean; const APath: String;
  60. const ATargetProcess: TPathRedirTargetProcess;
  61. const AFlags: TPathRedirFlags = []): String;
  62. begin
  63. while True do begin
  64. const CurCount = PathRedirActiveUseCount;
  65. if CurCount < 0 then
  66. InternalError('PathRedir: Unit was finalized');
  67. if AtomicCmpExchange(PathRedirActiveUseCount, CurCount + 1, CurCount) = CurCount then
  68. Break;
  69. end;
  70. MemoryBarrier;
  71. try
  72. if PathRedirInstance = nil then
  73. InternalError('PathRedir: Not initialized');
  74. Result := PathRedirInstance.ApplyRules(A64Bit, APath, AFlags, ATargetProcess);
  75. finally
  76. MemoryBarrier;
  77. AtomicDecrement(PathRedirActiveUseCount);
  78. end;
  79. end;
  80. { TPathRedir }
  81. constructor TPathRedir.Create(const AWindows64Bit: Boolean;
  82. const ASystem32Path, ASysWow64Path, ASysNativePath: String);
  83. procedure CheckAndAssignPath(var OutPath: String; const Dir, Title: String);
  84. begin
  85. var TestExpandedPath: String;
  86. if (Length(Dir) >= 4) and PathCharIsDriveLetter(Dir[1]) and
  87. (Dir[2] = ':') and (Dir[3] = '\') and
  88. PathExpand(Dir, TestExpandedPath) and
  89. PathSame(Dir, TestExpandedPath) and
  90. not PathCharIsSlash(Dir[High(Dir)]) then begin
  91. OutPath := '\\?\' + Dir;
  92. Exit;
  93. end;
  94. InternalErrorFmt('Path for %s directory is invalid: "%s"', [Title, Dir]);
  95. end;
  96. begin
  97. inherited Create;
  98. if AWindows64Bit then begin
  99. CheckAndAssignPath(FSystem32Path, ASystem32Path, 'System32');
  100. CheckAndAssignPath(FSysWow64Path, ASysWow64Path, 'SysWOW64');
  101. CheckAndAssignPath(FSysNativePath, ASysNativePath, 'Sysnative');
  102. end;
  103. FWindows64Bit := AWindows64Bit;
  104. end;
  105. function TPathRedir.ApplyRules(const A64Bit: Boolean; const APath: String;
  106. const AFlags: TPathRedirFlags;
  107. const ATargetProcess: TPathRedirTargetProcess): String;
  108. procedure SubstitutePath(var Path: String; const FromDir, ToDir: String);
  109. begin
  110. { Just an extra layer of safety }
  111. if (FromDir = '') or (ToDir = '') then
  112. InternalError('PathRedir: SubstitutePath received invalid parameter');
  113. const PathLen = Length(Path);
  114. const FromDirLen = Length(FromDir);
  115. if (PathLen = FromDirLen) or
  116. ((PathLen > FromDirLen) and (Path[Low(Path) + FromDirLen] = '\')) then
  117. if PathStartsWith(Path, FromDir) then
  118. Path := ToDir + Copy(Path, FromDirLen+1, Maxint);
  119. end;
  120. begin
  121. if APath = '' then
  122. InternalError('PathRedir: Called with empty path string');
  123. { Windows supports an undocumented "\??\" prefix that works like "\\?\".
  124. However, PathExpand (GetFullPathName) doesn't understand it and will
  125. prepend the current drive (e.g., "C:\??\"). So don't allow it. }
  126. if PathStartsWith(APath, '\??\') then
  127. InternalError('PathRedir: "\??\" prefix not allowed');
  128. var NewPath: String;
  129. if not PathConvertNormalToSuper(APath, NewPath, True) then
  130. InternalError('PathRedir: PathConvertNormalToSuper failed');
  131. if FWindows64Bit then begin
  132. { Running on 64-bit Windows }
  133. const TargetProcess64Bit =
  134. {$IFDEF WIN64} (ATargetProcess = tpCurrent) or {$ENDIF}
  135. (ATargetProcess = tpNativeBit);
  136. if A64Bit then begin
  137. { It's a 64-bit path (i.e., System32 means 64-bit system directory).
  138. System32 -> Sysnative: When target process is 32-bit. }
  139. if not TargetProcess64Bit then
  140. SubstitutePath(NewPath, FSystem32Path, FSysNativePath);
  141. end else begin
  142. { It's a 32-bit path (i.e., System32 means 32-bit system directory).
  143. SysWOW64 -> System32: In special tp32BitPreferSystem32 case only.
  144. System32 -> SysWOW64: Otherwise.
  145. If you're wondering why it does the latter not only for a 64-bit
  146. target process but also for a 32-bit target process:
  147. - GenerateUninstallInfoFilename makes use of this rewrite, see its
  148. comments.
  149. - It also helps 32-bit target processes avoid some exceptions that
  150. apply to System32 but not to SysWOW64. For example: certain
  151. System32 subdirectories are exempt from redirection. This is not
  152. the case for SysWOW64. }
  153. if ATargetProcess = tp32BitPreferSystem32 then
  154. SubstitutePath(NewPath, FSysWow64Path, FSystem32Path)
  155. else
  156. SubstitutePath(NewPath, FSystem32Path, FSysWow64Path);
  157. end;
  158. { Sysnative -> System32: When process is 64-bit, regardless of path
  159. bitness (because the Sysnative alias never works in 64-bit processes). }
  160. if TargetProcess64Bit then
  161. SubstitutePath(NewPath, FSysNativePath, FSystem32Path);
  162. end else begin
  163. { Running on 32-bit Windows; no substitutions are made }
  164. if A64Bit then
  165. InternalError('PathRedir: A64Bit=True but not running 64-bit Windows');
  166. end;
  167. if rfNormalPath in AFlags then
  168. NewPath := PathConvertSuperToNormal(NewPath);
  169. { Save memory: Return reference to passed-in string if no changes were made }
  170. if NewPath = APath then
  171. Result := APath
  172. else
  173. Result := NewPath;
  174. end;
  175. initialization
  176. finalization
  177. if AtomicExchange(PathRedirActiveUseCount, -1) = 0 then begin
  178. MemoryBarrier;
  179. FreeAndNil(PathRedirInstance);
  180. end;
  181. end.