mirror of
https://github.com/CloudDelphi/Virtual-File-System
synced 2025-12-19 09:53:54 +01:00
Written first integration test. Added debug console support
This commit is contained in:
parent
65e182822a
commit
6b5de215d2
1
Tests/Fs/Mods/Apache/503.html
Normal file
1
Tests/Fs/Mods/Apache/503.html
Normal file
@ -0,0 +1 @@
|
||||
Please, call us later.
|
||||
@ -46,8 +46,14 @@ begin
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\B', DONT_OVERWRITE_EXISTING);
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING);
|
||||
VfsBase.RunVfs(SORT_FIFO);
|
||||
VfsBase.GetVfsDirInfo(RootDir, '*', DirInfo, DirListing);
|
||||
|
||||
VfsBase.PauseVfs;
|
||||
VfsBase.GetVfsDirInfo(RootDir, '*', DirInfo, DirListing);
|
||||
DirListing.Rewind;
|
||||
Check(DirListing.GetDebugDump() = '', 'Virtual directory listing must be empty when VFS is paused. Got: ' + DirListing.GetDebugDump());
|
||||
|
||||
VfsBase.RunVfs(SORT_FIFO);
|
||||
VfsBase.GetVfsDirInfo(RootDir, '*', DirInfo, DirListing);
|
||||
DirListing.Rewind;
|
||||
Check(DirListing.GetDebugDump() = 'vcredist.bmp'#13#10'eula.1028.txt', 'Invalid virtual directoring listing. Got: ' + DirListing.GetDebugDump());
|
||||
|
||||
|
||||
71
Tests/VfsControl.pas
Normal file
71
Tests/VfsControl.pas
Normal file
@ -0,0 +1,71 @@
|
||||
unit VfsControl;
|
||||
(*
|
||||
Facade unit for high-level VFS API.
|
||||
*)
|
||||
|
||||
|
||||
(***) interface (***)
|
||||
|
||||
uses
|
||||
Windows, SysUtils,
|
||||
Utils,
|
||||
VfsBase, VfsUtils, VfsHooks;
|
||||
|
||||
|
||||
(* Runs all VFS subsystems, unless VFS is already running *)
|
||||
function RunVfs (DirListingOrder: VfsBase.TDirListingSortType): boolean;
|
||||
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
|
||||
function GetCurrentDirW: WideString;
|
||||
var
|
||||
Buf: array [0..32767 - 1] of WideChar;
|
||||
ResLen: integer;
|
||||
|
||||
begin
|
||||
result := '';
|
||||
ResLen := Windows.GetCurrentDirectoryW(sizeof(Buf), @Buf);
|
||||
|
||||
if ResLen > 0 then begin
|
||||
SetLength(result, ResLen);
|
||||
Utils.CopyMem(ResLen * sizeof(WideChar), @Buf, PWideChar(result));
|
||||
end;
|
||||
end;
|
||||
|
||||
function SetCurrentDirW (const DirPath: WideString): boolean;
|
||||
var
|
||||
AbsPath: WideString;
|
||||
|
||||
begin
|
||||
AbsPath := VfsUtils.NormalizePath(DirPath);
|
||||
result := Windows.SetCurrentDirectoryW(PWideChar(AbsPath));
|
||||
end;
|
||||
|
||||
function RunVfs (DirListingOrder: VfsBase.TDirListingSortType): boolean;
|
||||
var
|
||||
CurrDir: WideString;
|
||||
|
||||
begin
|
||||
with VfsBase.VfsCritSection do begin
|
||||
Enter;
|
||||
|
||||
result := VfsBase.RunVfs(DirListingOrder);
|
||||
|
||||
if result then begin
|
||||
VfsHooks.InstallHooks;
|
||||
|
||||
// Try to ensure, that current directory handle is tracked by VfsOpenFiles
|
||||
CurrDir := GetCurrentDirW;
|
||||
|
||||
if CurrDir <> '' then begin
|
||||
SetCurrentDirW(CurrDir);
|
||||
end;
|
||||
end;
|
||||
|
||||
Leave;
|
||||
end; // .with
|
||||
end; // function RunVfs
|
||||
|
||||
end.
|
||||
97
Tests/VfsIntegratedTest.pas
Normal file
97
Tests/VfsIntegratedTest.pas
Normal file
@ -0,0 +1,97 @@
|
||||
unit VfsIntegratedTest;
|
||||
|
||||
(***) interface (***)
|
||||
|
||||
uses
|
||||
SysUtils, TestFramework, Windows,
|
||||
Utils, WinUtils, ConsoleApi,
|
||||
VfsUtils, VfsBase, VfsDebug,
|
||||
VfsControl;
|
||||
|
||||
type
|
||||
TestIntegrated = class (TTestCase)
|
||||
private
|
||||
Inited: boolean;
|
||||
|
||||
function GetRootDir: string;
|
||||
|
||||
protected
|
||||
procedure SetUp; override;
|
||||
procedure TearDown; override;
|
||||
|
||||
published
|
||||
procedure TestGetFileAttributes;
|
||||
end;
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
|
||||
procedure LogSomething (Operation, Message: pchar); stdcall;
|
||||
begin
|
||||
WriteLn('>> ', string(Operation), ': ', string(Message), #13#10);
|
||||
end;
|
||||
|
||||
function TestIntegrated.GetRootDir: string;
|
||||
begin
|
||||
result := SysUtils.ExtractFileDir(WinUtils.GetExePath) + '\Tests\Fs';
|
||||
end;
|
||||
|
||||
procedure TestIntegrated.SetUp;
|
||||
var
|
||||
RootDir: string;
|
||||
|
||||
begin
|
||||
if not Inited then begin
|
||||
Inited := true;
|
||||
RootDir := Self.GetRootDir;
|
||||
VfsBase.ResetVfs();
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\FullyVirtual', DONT_OVERWRITE_EXISTING);
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\B', DONT_OVERWRITE_EXISTING);
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING);
|
||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\Apache', DONT_OVERWRITE_EXISTING);
|
||||
VfsDebug.SetLoggingProc(LogSomething);
|
||||
VfsControl.RunVfs(VfsBase.SORT_FIFO);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TestIntegrated.TearDown;
|
||||
begin
|
||||
VfsBase.PauseVfs();
|
||||
VfsDebug.SetLoggingProc(nil);
|
||||
end;
|
||||
|
||||
procedure TestIntegrated.TestGetFileAttributes;
|
||||
var
|
||||
RootDir: string;
|
||||
|
||||
function HasValidAttrs (const Path: string; const RequiredAttrs: integer = 0; const ForbiddenAttrs: integer = 0): boolean;
|
||||
var
|
||||
Attrs: integer;
|
||||
|
||||
begin
|
||||
Attrs := Int(Windows.GetFileAttributes(pchar(Path)));
|
||||
result := Attrs <> -1;
|
||||
|
||||
if result then begin
|
||||
if RequiredAttrs <> 0 then begin
|
||||
result := (Attrs and RequiredAttrs) = RequiredAttrs;
|
||||
end;
|
||||
|
||||
if result and (ForbiddenAttrs <> 0) then begin
|
||||
result := (Attrs and ForbiddenAttrs) = 0;
|
||||
end;
|
||||
end;
|
||||
end; // .function HasValidAttrs
|
||||
|
||||
begin
|
||||
RootDir := Self.GetRootDir;
|
||||
Check(not HasValidAttrs(RootDir + '\non-existing.non'), '{1}');
|
||||
Check(HasValidAttrs(RootDir + '\Hobbots\mms.cfg', 0, Windows.FILE_ATTRIBUTE_DIRECTORY), '{2}');
|
||||
Check(HasValidAttrs(RootDir + '\503.html', 0, Windows.FILE_ATTRIBUTE_DIRECTORY), '{3}');
|
||||
Check(HasValidAttrs(RootDir + '\Hobbots\', Windows.FILE_ATTRIBUTE_DIRECTORY), '{4}');
|
||||
Check(HasValidAttrs(RootDir + '\Mods', Windows.FILE_ATTRIBUTE_DIRECTORY), '{5}');
|
||||
end;
|
||||
|
||||
begin
|
||||
RegisterTest(TestIntegrated.Suite);
|
||||
end.
|
||||
46
Tests/VfsTestHelper.pas
Normal file
46
Tests/VfsTestHelper.pas
Normal file
@ -0,0 +1,46 @@
|
||||
unit VfsTestHelper;
|
||||
(*
|
||||
|
||||
*)
|
||||
|
||||
|
||||
(***) interface (***)
|
||||
|
||||
uses
|
||||
SysUtils, Windows,
|
||||
Utils;
|
||||
|
||||
(* Initializes debug console *)
|
||||
procedure InitConsole;
|
||||
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
|
||||
procedure InitConsole;
|
||||
var
|
||||
Rect: TSmallRect;
|
||||
BufSize: TCoord;
|
||||
hIn: THandle;
|
||||
hOut: THandle;
|
||||
|
||||
begin
|
||||
AllocConsole;
|
||||
SetConsoleCP(GetACP);
|
||||
SetConsoleOutputCP(GetACP);
|
||||
hIn := GetStdHandle(STD_INPUT_HANDLE);
|
||||
hOut := GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
pinteger(@System.Input)^ := hIn;
|
||||
pinteger(@System.Output)^ := hOut;
|
||||
BufSize.x := 120;
|
||||
BufSize.y := 1000;
|
||||
SetConsoleScreenBufferSize(hOut, BufSize);
|
||||
Rect.Left := 0;
|
||||
Rect.Top := 0;
|
||||
Rect.Right := 120 - 1;
|
||||
Rect.Bottom := 50 - 1;
|
||||
SetConsoleWindowInfo(hOut, true, Rect);
|
||||
SetConsoleTextAttribute(hOut, (0 shl 4) or $0F);
|
||||
end; // .procedure InitConsole;
|
||||
|
||||
end.
|
||||
67
VfsBase.pas
67
VfsBase.pas
@ -36,7 +36,7 @@ type
|
||||
SORT_FIFO - Items of the first mapped directory will be listed before the second mapped directory items.
|
||||
SORT_LIFO - Items of The last mapped directory will be listed before all other mapped directory items.
|
||||
*)
|
||||
TDirListingSortType = (SORT_FIFO, SORT_LIFO);
|
||||
TDirListingSortType = (SORT_FIFO = 0, SORT_LIFO = 1);
|
||||
|
||||
(* Single redirected VFS entry: file or directory *)
|
||||
TVfsItem = class
|
||||
@ -79,6 +79,8 @@ type
|
||||
procedure RestoreVfsForThread;
|
||||
end;
|
||||
|
||||
TSingleArgExternalFunc = function (Arg: pointer = nil): integer; stdcall;
|
||||
|
||||
var
|
||||
(* Global VFS access synchronizer *)
|
||||
VfsCritSection: Concur.TCritSection;
|
||||
@ -86,9 +88,13 @@ var
|
||||
|
||||
function GetThreadVfsDisabler: TThreadVfsDisabler;
|
||||
|
||||
(* Runs VFS. Higher level API must install hooks in VfsCritSection protected area *)
|
||||
(* Runs VFS. Higher level API must install hooks in VfsCritSection protected area.
|
||||
Listing order is ignored if VFS is resumed from pause *)
|
||||
function RunVfs (DirListingOrder: TDirListingSortType): boolean;
|
||||
|
||||
(* Temporarily pauses VFS, but does not reset existing mappings *)
|
||||
function PauseVfs: boolean;
|
||||
|
||||
(* Stops VFS and clears all mappings *)
|
||||
function ResetVfs: boolean;
|
||||
|
||||
@ -101,6 +107,9 @@ function GetVfsDirInfo (const AbsVirtPath, Mask: WideString; {OUT} var DirInfo:
|
||||
(* Maps real directory contents to virtual path. Target must exist for success *)
|
||||
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
||||
|
||||
(* Calls specified function with a single argument and returns its result. VFS is disabled for current thread during function exection *)
|
||||
function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall;
|
||||
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
@ -116,6 +125,9 @@ var
|
||||
(* Global VFS state indicator. If false, all VFS search operations must fail *)
|
||||
VfsIsRunning: boolean = false;
|
||||
|
||||
(* If true, VFS file/directory hierarchy is built and no mapping is allowed untill full reset *)
|
||||
VfsTreeIsBuilt: boolean = false;
|
||||
|
||||
(* Automatical VFS items priority management *)
|
||||
OverwritingPriority: integer = INITIAL_OVERWRITING_PRIORITY;
|
||||
AddingPriority: integer = INITIAL_ADDING_PRIORITY;
|
||||
@ -271,16 +283,33 @@ begin
|
||||
Enter;
|
||||
|
||||
if not VfsIsRunning then begin
|
||||
if not VfsTreeIsBuilt then begin
|
||||
BuildVfsItemsTree();
|
||||
SortVfsDirListings(DirListingOrder);
|
||||
VfsTreeIsBuilt := true;
|
||||
end;
|
||||
|
||||
VfsIsRunning := true;
|
||||
end;
|
||||
|
||||
Leave;
|
||||
end;
|
||||
end;
|
||||
end; // .with
|
||||
end; // .if
|
||||
end; // .function RunVfs
|
||||
|
||||
function PauseVfs: boolean;
|
||||
begin
|
||||
result := not DisableVfsForThisThread;
|
||||
|
||||
if result then begin
|
||||
with VfsCritSection do begin
|
||||
Enter;
|
||||
VfsIsRunning := false;
|
||||
Leave;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ResetVfs: boolean;
|
||||
begin
|
||||
result := not DisableVfsForThisThread;
|
||||
@ -288,8 +317,9 @@ begin
|
||||
if result then begin
|
||||
with VfsCritSection do begin
|
||||
Enter;
|
||||
VfsIsRunning := false;
|
||||
VfsItems.Clear();
|
||||
VfsIsRunning := false;
|
||||
VfsTreeIsBuilt := false;
|
||||
Leave;
|
||||
end;
|
||||
end;
|
||||
@ -481,9 +511,36 @@ end; // .function _MapDir
|
||||
|
||||
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
||||
begin
|
||||
with VfsCritSection do begin
|
||||
Enter;
|
||||
|
||||
result := not VfsIsRunning and not VfsTreeIsBuilt;
|
||||
|
||||
if result then begin
|
||||
result := _MapDir(NormalizePath(VirtPath), NormalizePath(RealPath), nil, OverwriteExisting, AUTO_PRIORITY) <> nil;
|
||||
end;
|
||||
|
||||
Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall;
|
||||
begin
|
||||
with GetThreadVfsDisabler do begin
|
||||
try
|
||||
DisableVfsForThread;
|
||||
result := Func(Arg);
|
||||
except
|
||||
on E: Exception do begin
|
||||
RestoreVfsForThread;
|
||||
raise E;
|
||||
end;
|
||||
end; // .try
|
||||
|
||||
RestoreVfsForThread;
|
||||
end; // .with
|
||||
end; // .function CallWithoutVfs
|
||||
|
||||
begin
|
||||
VfsCritSection.Init;
|
||||
VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);
|
||||
|
||||
556
VfsHooks.pas
Normal file
556
VfsHooks.pas
Normal file
@ -0,0 +1,556 @@
|
||||
unit VfsHooks;
|
||||
(*
|
||||
Description: WinNT code hooks package.
|
||||
*)
|
||||
|
||||
|
||||
(***) interface (***)
|
||||
|
||||
uses
|
||||
Windows, SysUtils, Math,
|
||||
Utils, WinNative, Concur,
|
||||
StrLib, Alg,
|
||||
VfsBase, VfsUtils, VfsPatching,
|
||||
VfsDebug, VfsApiDigger, VfsOpenFiles;
|
||||
|
||||
|
||||
(* Installs VFS hooks, if not already installed, in a thread-safe manner *)
|
||||
procedure InstallHooks;
|
||||
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
|
||||
var
|
||||
HooksCritSection: Concur.TCritSection;
|
||||
HooksInstalled: boolean = false;
|
||||
|
||||
NativeNtQueryAttributesFile: WinNative.TNtQueryAttributesFile;
|
||||
NativeNtQueryFullAttributesFile: WinNative.TNtQueryFullAttributesFile;
|
||||
NativeNtOpenFile: WinNative.TNtOpenFile;
|
||||
NativeNtCreateFile: WinNative.TNtCreateFile;
|
||||
NativeNtClose: WinNative.TNtClose;
|
||||
NativeNtQueryDirectoryFile: WinNative.TNtQueryDirectoryFile;
|
||||
|
||||
|
||||
(* There is no 100% portable and reliable way to get file path by handle, unless file creation/opening
|
||||
was tracked. Thus we rely heavily on VfsOpenFiles.
|
||||
In Windows access to files in curren directory under relative paths is performed via [hDir, RelPath] pair,
|
||||
thus it's strongly recommended to ensure, that current directory handle is tracked by VfsOpenedFiles.
|
||||
It can be perfomed via SetCurrentDir(GetCurrentDir) after VFS was run *)
|
||||
function GetFilePathByHandle (hFile: THandle): WideString;
|
||||
begin
|
||||
result := VfsOpenFiles.GetOpenedFilePath(hFile);
|
||||
end;
|
||||
|
||||
(* Returns single absolute path, not dependant on RootDirectory member. '\??\' prefix is always removed, \\.\ and \\?\ paths remain not touched. *)
|
||||
function GetFileObjectPath (ObjectAttributes: POBJECT_ATTRIBUTES): WideString;
|
||||
var
|
||||
FilePath: WideString;
|
||||
DirPath: WideString;
|
||||
|
||||
begin
|
||||
FilePath := ObjectAttributes.ObjectName.ToWideStr();
|
||||
result := '';
|
||||
|
||||
if FilePath <> '' then begin
|
||||
if FilePath[1] = '\' then begin
|
||||
FilePath := VfsUtils.StripNtAbsPathPrefix(FilePath);
|
||||
end;
|
||||
|
||||
if ObjectAttributes.RootDirectory <> 0 then begin
|
||||
DirPath := GetFilePathByHandle(ObjectAttributes.RootDirectory);
|
||||
|
||||
if DirPath <> '' then begin
|
||||
if DirPath[Length(DirPath)] <> '\' then begin
|
||||
result := DirPath + '\' + FilePath;
|
||||
end else begin
|
||||
result := DirPath + FilePath;
|
||||
end;
|
||||
end;
|
||||
end else begin
|
||||
result := FilePath;
|
||||
end;
|
||||
end; // .if
|
||||
end; // .function GetFileObjectPath
|
||||
|
||||
function Hook_NtQueryAttributesFile (OrigFunc: WinNative.TNtQueryAttributesFile; ObjectAttributes: POBJECT_ATTRIBUTES; FileInformation: PFILE_BASIC_INFORMATION): NTSTATUS; stdcall;
|
||||
var
|
||||
ExpandedPath: WideString;
|
||||
RedirectedPath: WideString;
|
||||
ReplacedObjAttrs: WinNative.TObjectAttributes;
|
||||
FileInfo: TNativeFileInfo;
|
||||
HadTrailingDelim: boolean;
|
||||
|
||||
begin
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryAttributesFile', Format('Dir: %d. Path: "%s"', [ObjectAttributes.RootDirectory, ObjectAttributes.ObjectName.ToWideStr()]));
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs := ObjectAttributes^;
|
||||
ReplacedObjAttrs.Length := sizeof(ReplacedObjAttrs);
|
||||
ExpandedPath := GetFileObjectPath(ObjectAttributes);
|
||||
RedirectedPath := '';
|
||||
|
||||
if ExpandedPath <> '' then begin
|
||||
RedirectedPath := VfsBase.GetVfsItemRealPath(StrLib.ExcludeTrailingDelimW(ExpandedPath, @HadTrailingDelim), @FileInfo);
|
||||
end;
|
||||
|
||||
// Return cached VFS file info
|
||||
if RedirectedPath <> '' then begin
|
||||
if not HadTrailingDelim or Utils.HasFlag(FILE_ATTRIBUTE_DIRECTORY, FileInfo.Base.FileAttributes) then begin
|
||||
FileInformation.CreationTime := FileInfo.Base.CreationTime;
|
||||
FileInformation.LastAccessTime := FileInfo.Base.LastAccessTime;
|
||||
FileInformation.LastWriteTime := FileInfo.Base.LastWriteTime;
|
||||
FileInformation.ChangeTime := FileInfo.Base.ChangeTime;
|
||||
FileInformation.FileAttributes := FileInfo.Base.FileAttributes;
|
||||
result := WinNative.STATUS_SUCCESS;
|
||||
end else begin
|
||||
result := WinNative.STATUS_NO_SUCH_FILE;
|
||||
end;
|
||||
end
|
||||
// Query file with real path
|
||||
else begin
|
||||
RedirectedPath := ExpandedPath;
|
||||
|
||||
if RedirectedPath <> '' then begin
|
||||
if RedirectedPath[1] <> '\' then begin
|
||||
RedirectedPath := '\??\' + RedirectedPath;
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs.RootDirectory := 0;
|
||||
ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE;
|
||||
ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath);
|
||||
end;
|
||||
|
||||
result := OrigFunc(@ReplacedObjAttrs, FileInformation);
|
||||
end; // .else
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryAttributesFile', Format('Result: %x. Attrs: 0x%x. Path: "%s" => "%s"', [result, FileInformation.FileAttributes, string(ExpandedPath), string(RedirectedPath)]));
|
||||
end;
|
||||
end; // .function Hook_NtQueryAttributesFile
|
||||
|
||||
function Hook_NtQueryFullAttributesFile (OrigFunc: WinNative.TNtQueryFullAttributesFile; ObjectAttributes: POBJECT_ATTRIBUTES; FileInformation: PFILE_NETWORK_OPEN_INFORMATION): NTSTATUS; stdcall;
|
||||
var
|
||||
ExpandedPath: WideString;
|
||||
RedirectedPath: WideString;
|
||||
ReplacedObjAttrs: WinNative.TObjectAttributes;
|
||||
HadTrailingDelim: boolean;
|
||||
|
||||
begin
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryFullAttributesFile', Format('Dir: %d. Path: "%s"', [ObjectAttributes.RootDirectory, ObjectAttributes.ObjectName.ToWideStr()]));
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs := ObjectAttributes^;
|
||||
ReplacedObjAttrs.Length := sizeof(ReplacedObjAttrs);
|
||||
ExpandedPath := GetFileObjectPath(ObjectAttributes);
|
||||
RedirectedPath := '';
|
||||
|
||||
if ExpandedPath <> '' then begin
|
||||
RedirectedPath := VfsBase.GetVfsItemRealPath(StrLib.ExcludeTrailingDelimW(ExpandedPath, @HadTrailingDelim));
|
||||
end;
|
||||
|
||||
if RedirectedPath = '' then begin
|
||||
RedirectedPath := ExpandedPath;
|
||||
end else if HadTrailingDelim then begin
|
||||
RedirectedPath := RedirectedPath + '\';
|
||||
end;
|
||||
|
||||
if (RedirectedPath <> '') and (RedirectedPath[1] <> '\') then begin
|
||||
RedirectedPath := '\??\' + RedirectedPath;
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs.RootDirectory := 0;
|
||||
ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE;
|
||||
ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath);
|
||||
|
||||
result := OrigFunc(@ReplacedObjAttrs, FileInformation);
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryFullAttributesFile', Format('Result: %x. Attrs: 0x%x. Path: "%s" => "%s"', [result, FileInformation.FileAttributes, string(ExpandedPath), string(RedirectedPath)]));
|
||||
end;
|
||||
end; // .Hook_NtQueryFullAttributesFile
|
||||
|
||||
function Hook_NtOpenFile (OrigFunc: WinNative.TNtOpenFile; FileHandle: PHANDLE; DesiredAccess: ACCESS_MASK; ObjectAttributes: POBJECT_ATTRIBUTES;
|
||||
IoStatusBlock: PIO_STATUS_BLOCK; ShareAccess: ULONG; OpenOptions: ULONG): NTSTATUS; stdcall;
|
||||
begin
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtOpenFile', ObjectAttributes.ObjectName.ToWideStr());
|
||||
end;
|
||||
|
||||
result := WinNative.NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, nil, 0, ShareAccess, WinNative.FILE_OPEN, OpenOptions, nil, 0);
|
||||
end;
|
||||
|
||||
function Hook_NtCreateFile (OrigFunc: WinNative.TNtCreateFile; FileHandle: PHANDLE; DesiredAccess: ACCESS_MASK; ObjectAttributes: POBJECT_ATTRIBUTES; IoStatusBlock: PIO_STATUS_BLOCK;
|
||||
AllocationSize: PLARGE_INTEGER; FileAttributes: ULONG; ShareAccess: ULONG; CreateDisposition: ULONG; CreateOptions: ULONG; EaBuffer: PVOID; EaLength: ULONG): NTSTATUS; stdcall;
|
||||
var
|
||||
ExpandedPath: WideString;
|
||||
RedirectedPath: WideString;
|
||||
ReplacedObjAttrs: WinNative.TObjectAttributes;
|
||||
HadTrailingDelim: boolean;
|
||||
|
||||
FileInfo: Windows.TWin32FindDataW;
|
||||
|
||||
begin
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtCreateFile', ObjectAttributes.ObjectName.ToWideStr());
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs := ObjectAttributes^;
|
||||
ReplacedObjAttrs.Length := sizeof(ReplacedObjAttrs);
|
||||
ExpandedPath := GetFileObjectPath(ObjectAttributes);
|
||||
RedirectedPath := '';
|
||||
|
||||
if (ExpandedPath <> '') and ((DesiredAccess and WinNative.DELETE) = 0) and (CreateDisposition = WinNative.FILE_OPEN) then begin
|
||||
RedirectedPath := VfsBase.GetVfsItemRealPath(StrLib.ExcludeTrailingDelimW(ExpandedPath, @HadTrailingDelim), @FileInfo);
|
||||
end;
|
||||
|
||||
if RedirectedPath = '' then begin
|
||||
RedirectedPath := ExpandedPath;
|
||||
end else if HadTrailingDelim then begin
|
||||
RedirectedPath := RedirectedPath + '\';
|
||||
end;
|
||||
|
||||
if (RedirectedPath <> '') and (RedirectedPath[1] <> '\') then begin
|
||||
RedirectedPath := '\??\' + RedirectedPath;
|
||||
end;
|
||||
|
||||
ReplacedObjAttrs.RootDirectory := 0;
|
||||
ReplacedObjAttrs.Attributes := ReplacedObjAttrs.Attributes or WinNative.OBJ_CASE_INSENSITIVE;
|
||||
ReplacedObjAttrs.ObjectName.AssignExistingStr(RedirectedPath);
|
||||
|
||||
result := OrigFunc(FileHandle, DesiredAccess, @ReplacedObjAttrs, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
|
||||
|
||||
if (result = WinNative.STATUS_SUCCESS) and Utils.HasFlag(WinNative.FILE_SYNCHRONOUS_IO_NONALERT, CreateOptions) and Utils.HasFlag(WinNative.SYNCHRONIZE, DesiredAccess) then begin
|
||||
VfsOpenFiles.SetOpenedFileInfo(FileHandle^, TOpenedFile.Create(FileHandle^, ExpandedPath));
|
||||
end;
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
if ExpandedPath <> StripNtAbsPathPrefix(RedirectedPath) then begin
|
||||
WriteLog('NtCreateFile', Format('Access: 0x%x. Handle: %x. Status: %x. Redirected "%s" => "%s"', [DesiredAccess, FileHandle^, result, StrLib.WideToAnsiSubstitute(ExpandedPath), StrLib.WideToAnsiSubstitute(StripNtAbsPathPrefix(RedirectedPath))]));
|
||||
end else begin
|
||||
WriteLog('NtCreateFile', Format('Access: 0x%x. Handle: %x. Status: %x. Path: "%s"', [DesiredAccess, FileHandle^, result, StrLib.WideToAnsiSubstitute(ExpandedPath)]));
|
||||
end;
|
||||
end;
|
||||
end; // .function Hook_NtCreateFile
|
||||
|
||||
function Hook_NtClose (OrigFunc: WinNative.TNtClose; hData: HANDLE): NTSTATUS; stdcall;
|
||||
begin
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtClose', Format('Handle: %x', [integer(hData)]));
|
||||
end;
|
||||
|
||||
with OpenFilesCritSection do begin
|
||||
Enter;
|
||||
result := OrigFunc(hData);
|
||||
|
||||
if WinNative.NT_SUCCESS(result) then begin
|
||||
VfsOpenFiles.DeleteOpenedFileInfo(hData);
|
||||
end;
|
||||
|
||||
Leave;
|
||||
end;
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtClose', Format('Status: %x', [integer(result)]));
|
||||
end;
|
||||
end; // .function Hook_NtClose
|
||||
|
||||
function IsSupportedFileInformationClass (FileInformationClass: integer): boolean;
|
||||
begin
|
||||
result := (FileInformationClass <= High(byte)) and (FILE_INFORMATION_CLASS(byte(FileInformationClass)) in [FileBothDirectoryInformation, FileDirectoryInformation, FileFullDirectoryInformation, FileIdBothDirectoryInformation, FileIdFullDirectoryInformation, FileNamesInformation]);
|
||||
end;
|
||||
|
||||
type
|
||||
TFileInfoConvertResult = (TOO_SMALL_BUF, COPIED_ALL, TRUNCATED_NAME);
|
||||
TTruncatedNamesStrategy = (DONT_TRUNCATE_NAMES, TRUNCATE_NAMES);
|
||||
|
||||
function ConvertFileInfoStruct (SrcInfo: PNativeFileInfo; TargetFormat: FILE_INFORMATION_CLASS; {n} Buf: pointer; BufSize: integer; TruncatedNamesStrategy: TTruncatedNamesStrategy;
|
||||
{OUT} var BytesWritten: integer): TFileInfoConvertResult;
|
||||
var
|
||||
{n} FileNameBuf: pointer;
|
||||
FileNameBufSize: integer;
|
||||
StructBaseSize: integer;
|
||||
StructFullSize: integer;
|
||||
|
||||
begin
|
||||
{!} Assert(SrcInfo <> nil);
|
||||
{!} Assert(IsSupportedFileInformationClass(ord(TargetFormat)), Format('Unsupported file information class: %d', [ord(TargetFormat)]));
|
||||
FileNameBuf := nil;
|
||||
// * * * * * //
|
||||
BytesWritten := 0;
|
||||
StructBaseSize := WinNative.GetFileInformationClassSize(TargetFormat);
|
||||
StructFullSize := StructBaseSize + Int(SrcInfo.Base.FileNameLength);
|
||||
|
||||
if (Buf = nil) or (BufSize < StructBaseSize) then begin
|
||||
result := TOO_SMALL_BUF;
|
||||
exit;
|
||||
end;
|
||||
|
||||
result := COPIED_ALL;
|
||||
|
||||
if BufSize < StructFullSize then begin
|
||||
result := TRUNCATED_NAME;
|
||||
|
||||
if TruncatedNamesStrategy = DONT_TRUNCATE_NAMES then begin
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
case TargetFormat of
|
||||
FileNamesInformation: PFILE_NAMES_INFORMATION(Buf).FileNameLength := SrcInfo.Base.FileNameLength;
|
||||
|
||||
FileBothDirectoryInformation, FileDirectoryInformation, FileFullDirectoryInformation, FileIdBothDirectoryInformation, FileIdFullDirectoryInformation: begin
|
||||
Utils.CopyMem(StructBaseSize, @SrcInfo.Base, Buf);
|
||||
end;
|
||||
else
|
||||
{!} Assert(IsSupportedFileInformationClass(ord(TargetFormat)), Format('Unexpected unsupported file information class: %d', [ord(TargetFormat)]));
|
||||
end;
|
||||
|
||||
FileNameBufSize := Min(BufSize - StructBaseSize, SrcInfo.Base.FileNameLength) and not $00000001;
|
||||
FileNameBuf := Utils.PtrOfs(Buf, StructBaseSize);
|
||||
|
||||
Utils.CopyMem(FileNameBufSize, PWideChar(SrcInfo.FileName), FileNameBuf);
|
||||
|
||||
BytesWritten := StructBaseSize + FileNameBufSize;
|
||||
end; // .function ConvertFileInfoStruct
|
||||
|
||||
function Hook_NtQueryDirectoryFile (OrigFunc: WinNative.TNtQueryDirectoryFile; FileHandle: HANDLE; Event: HANDLE; ApcRoutine: pointer; ApcContext: PVOID; Io: PIO_STATUS_BLOCK; Buffer: PVOID;
|
||||
BufLength: ULONG; InfoClass: integer (* FILE_INFORMATION_CLASS *); SingleEntry: BOOLEAN; {n} Mask: PUNICODE_STRING; RestartScan: BOOLEAN): NTSTATUS; stdcall;
|
||||
const
|
||||
ENTRIES_ALIGNMENT = 8;
|
||||
|
||||
type
|
||||
PPrevEntry = ^TPrevEntry;
|
||||
TPrevEntry = packed record
|
||||
NextEntryOffset: ULONG;
|
||||
FileIndex: ULONG;
|
||||
end;
|
||||
|
||||
var
|
||||
{Un} OpenedFile: TOpenedFile;
|
||||
{Un} FileInfo: TFileInfo;
|
||||
{n} BufCurret: pointer;
|
||||
{n} PrevEntry: PPrevEntry;
|
||||
BufSize: integer;
|
||||
BufSizeLeft: integer;
|
||||
BytesWritten: integer;
|
||||
IsFirstEntry: boolean;
|
||||
Proceed: boolean;
|
||||
TruncatedNamesStrategy: TTruncatedNamesStrategy;
|
||||
StructConvertResult: TFileInfoConvertResult;
|
||||
EmptyMask: UNICODE_STRING;
|
||||
EntryName: WideString;
|
||||
|
||||
begin
|
||||
OpenedFile := nil;
|
||||
FileInfo := nil;
|
||||
BufCurret := nil;
|
||||
PrevEntry := nil;
|
||||
BufSize := 0;
|
||||
// * * * * * //
|
||||
with OpenFilesCritSection do begin
|
||||
if Mask = nil then begin
|
||||
EmptyMask.Reset;
|
||||
Mask := @EmptyMask;
|
||||
end;
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryDirectoryFile', Format('Handle: %x. InfoClass: %s. Mask: %s', [integer(FileHandle), WinNative.FileInformationClassToStr(InfoClass), AnsiString(Mask.ToWideStr())]));
|
||||
end;
|
||||
|
||||
Enter;
|
||||
|
||||
// FIXME REWRITE ME
|
||||
//OpenedFile := OpenedFiles[pointer(FileHandle)];
|
||||
|
||||
if (OpenedFile = nil) or (Event <> 0) or (ApcRoutine <> nil) or (ApcContext <> nil) then begin
|
||||
WriteLog('NtQueryDirectoryFile', Format('Calling native NtQueryDirectoryFile. OpenedFile: %x. %d %d %d', [integer(OpenedFile), integer(Event), integer(ApcRoutine), integer(ApcContext)]));
|
||||
result := OrigFunc(FileHandle, Event, ApcRoutine, ApcContext, Io, Buffer, BufLength, InfoClass, SingleEntry, Mask, RestartScan);
|
||||
end else begin
|
||||
int(Io.Information) := 0;
|
||||
result := STATUS_SUCCESS;
|
||||
|
||||
if RestartScan then begin
|
||||
SysUtils.FreeAndNil(OpenedFile.DirListing);
|
||||
end;
|
||||
|
||||
OpenedFile.FillDirListing(Mask.ToWideStr());
|
||||
|
||||
Proceed := (Buffer <> nil) and (BufLength > 0);
|
||||
|
||||
// Validate buffer
|
||||
if not Proceed then begin
|
||||
result := STATUS_INVALID_BUFFER_SIZE;
|
||||
end else begin
|
||||
BufSize := Utils.IfThen(int(BufLength) > 0, int(BufLength), High(int));
|
||||
end;
|
||||
|
||||
// Validate information class
|
||||
if Proceed then begin
|
||||
Proceed := IsSupportedFileInformationClass(InfoClass);
|
||||
|
||||
if not Proceed then begin
|
||||
result := STATUS_INVALID_INFO_CLASS;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Signal of scanning end, if necessary
|
||||
if Proceed then begin
|
||||
Proceed := not OpenedFile.DirListing.IsEnd;
|
||||
|
||||
if not Proceed then begin
|
||||
result := STATUS_NO_MORE_FILES;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Scan directory
|
||||
if Proceed then begin
|
||||
BufCurret := Buffer;
|
||||
BytesWritten := 1;
|
||||
|
||||
while (BytesWritten > 0) and OpenedFile.DirListing.GetNextItem(FileInfo) do begin
|
||||
// Align next record to 8-bytes boundary from Buffer start
|
||||
BufCurret := pointer(int(Buffer) + Alg.IntRoundToBoundary(int(Io.Information), ENTRIES_ALIGNMENT));
|
||||
BufSizeLeft := BufSize - (int(BufCurret) - int(Buffer));
|
||||
|
||||
IsFirstEntry := OpenedFile.DirListing.FileInd = 1;
|
||||
|
||||
if IsFirstEntry then begin
|
||||
TruncatedNamesStrategy := TRUNCATE_NAMES;
|
||||
end else begin
|
||||
TruncatedNamesStrategy := DONT_TRUNCATE_NAMES;
|
||||
end;
|
||||
|
||||
StructConvertResult := ConvertFileInfoStruct(@FileInfo.Data, FILE_INFORMATION_CLASS(byte(InfoClass)), BufCurret, BufSizeLeft, TruncatedNamesStrategy, BytesWritten);
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
EntryName := Copy(FileInfo.Data.FileName, 1, Min(BytesWritten - WinNative.GetFileInformationClassSize(InfoClass), FileInfo.Data.Base.FileNameLength) div 2);
|
||||
WriteLog('NtQueryDirectoryFile', 'Written entry: ' + EntryName);
|
||||
end;
|
||||
|
||||
//VarDump(['Converted struct to buf offset:', int(BufCurret) - int(Buffer), 'Written:', BytesWritten, 'Result:', ord(StructConvertResult)]);
|
||||
|
||||
with PFILE_ID_BOTH_DIR_INFORMATION(BufCurret)^ do begin
|
||||
NextEntryOffset := 0;
|
||||
FileIndex := 0;
|
||||
end;
|
||||
|
||||
if StructConvertResult = TOO_SMALL_BUF then begin
|
||||
OpenedFile.DirListing.SeekRel(-1);
|
||||
|
||||
if IsFirstEntry then begin
|
||||
result := STATUS_BUFFER_TOO_SMALL;
|
||||
end;
|
||||
end else if StructConvertResult = TRUNCATED_NAME then begin
|
||||
if IsFirstEntry then begin
|
||||
result := STATUS_BUFFER_OVERFLOW;
|
||||
Inc(int(Io.Information), BytesWritten);
|
||||
end else begin
|
||||
OpenedFile.DirListing.SeekRel(-1);
|
||||
end;
|
||||
end else if StructConvertResult = COPIED_ALL then begin
|
||||
if PrevEntry <> nil then begin
|
||||
int(Io.Information) := int(BufCurret) - int(Buffer) + BytesWritten;
|
||||
end else begin
|
||||
int(Io.Information) := BytesWritten;
|
||||
end;
|
||||
end; // .else
|
||||
|
||||
if (BytesWritten > 0) and (PrevEntry <> nil) then begin
|
||||
PrevEntry.NextEntryOffset := cardinal(int(BufCurret) - int(PrevEntry));
|
||||
end;
|
||||
|
||||
PrevEntry := BufCurret;
|
||||
|
||||
//Msg(Format('Written: %d. Total: %d', [BytesWritten, int(Io.Information)]));
|
||||
|
||||
if SingleEntry then begin
|
||||
BytesWritten := 0;
|
||||
end;
|
||||
end; // .while
|
||||
end; // .if
|
||||
|
||||
Io.Status.Status := result;
|
||||
end; // .else
|
||||
|
||||
Leave;
|
||||
end; // .with
|
||||
|
||||
if VfsDebug.LoggingEnabled then begin
|
||||
WriteLog('NtQueryDirectoryFile', Format('Status: %x. Written: %d bytes', [integer(result), integer(Io.Information)]));
|
||||
end;
|
||||
end; // .function Hook_NtQueryDirectoryFile
|
||||
|
||||
procedure InstallHooks;
|
||||
var
|
||||
hDll: Windows.THandle;
|
||||
NtdllHandle: integer;
|
||||
|
||||
begin
|
||||
with HooksCritSection do begin
|
||||
Enter;
|
||||
|
||||
if not HooksInstalled then begin
|
||||
HooksInstalled := true;
|
||||
|
||||
// Ensure, that library with VFS hooks installed is never unloaded
|
||||
if System.IsLibrary then begin
|
||||
WinNative.GetModuleHandleExW(WinNative.GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS or WinNative.GET_MODULE_HANDLE_EX_FLAG_PIN, @InstallHooks, hDll);
|
||||
end;
|
||||
|
||||
NtdllHandle:= Windows.GetModuleHandle('ntdll.dll');
|
||||
{!} Assert(NtdllHandle <> 0, 'Failed to load ntdll.dll library');
|
||||
|
||||
WriteLog('InstallHook', 'Installing NtQueryAttributesFile hook');
|
||||
NativeNtQueryAttributesFile := VfsPatching.SpliceWinApi
|
||||
(
|
||||
VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryAttributesFile'),
|
||||
@Hook_NtQueryAttributesFile
|
||||
);
|
||||
|
||||
// WriteLog('InstallHook', 'Installing NtQueryFullAttributesFile hook');
|
||||
// NativeNtQueryFullAttributesFile := VfsPatching.SpliceWinApi
|
||||
// (
|
||||
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryFullAttributesFile'),
|
||||
// @Hook_NtQueryFullAttributesFile
|
||||
// );
|
||||
|
||||
// WriteLog('InstallHook', 'Installing NtOpenFile hook');
|
||||
// NativeNtOpenFile := VfsPatching.SpliceWinApi
|
||||
// (
|
||||
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtOpenFile'),
|
||||
// @Hook_NtOpenFile
|
||||
// );
|
||||
|
||||
// WriteLog('InstallHook', 'Installing NtCreateFile hook');
|
||||
// NativeNtCreateFile := VfsPatching.SpliceWinApi
|
||||
// (
|
||||
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtCreateFile'),
|
||||
// @Hook_NtCreateFile
|
||||
// );
|
||||
|
||||
// WriteLog('InstallHook', 'Installing NtClose hook');
|
||||
// NativeNtClose := VfsPatching.SpliceWinApi
|
||||
// (
|
||||
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtClose'),
|
||||
// @Hook_NtClose
|
||||
// );
|
||||
|
||||
// WriteLog('InstallHook', 'Installing NtQueryDirectoryFile hook');
|
||||
// NativeNtQueryDirectoryFile := VfsPatching.SpliceWinApi
|
||||
// (
|
||||
// VfsApiDigger.GetRealProcAddress(NtdllHandle, 'NtQueryDirectoryFile'),
|
||||
// @Hook_NtQueryDirectoryFile
|
||||
// );
|
||||
end; // .if
|
||||
|
||||
Leave;
|
||||
end; // .with
|
||||
end; // .procedure InstallHooks
|
||||
|
||||
begin
|
||||
HooksCritSection.Init;
|
||||
end.
|
||||
129
VfsPatching.pas
Normal file
129
VfsPatching.pas
Normal file
@ -0,0 +1,129 @@
|
||||
unit VfsPatching;
|
||||
(*
|
||||
Description: Code patching facilities, based on PatchForge library.
|
||||
All hooks are thread-safe.
|
||||
*)
|
||||
|
||||
|
||||
(***) interface (***)
|
||||
|
||||
uses
|
||||
Windows, SysUtils, Utils, PatchForge;
|
||||
|
||||
|
||||
(* Replaces original STDCALL function with the new one with the same prototype and one extra argument.
|
||||
The argument is callable pointer, used to execute original function. The pointer is passed as THE FIRST
|
||||
argument before other arguments. *)
|
||||
function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer;
|
||||
|
||||
|
||||
(***) implementation (***)
|
||||
|
||||
|
||||
type
|
||||
(* Import *)
|
||||
TPatchMaker = PatchForge.TPatchMaker;
|
||||
TPatchHelper = PatchForge.TPatchHelper;
|
||||
|
||||
|
||||
(* Writes arbitrary data to any write-protected section *)
|
||||
function WriteAtCode (NumBytes: integer; {n} Src, {n} Dst: pointer): boolean;
|
||||
var
|
||||
OldPageProtect: integer;
|
||||
|
||||
begin
|
||||
{!} Assert(Utils.IsValidBuf(Src, NumBytes));
|
||||
{!} Assert(Utils.IsValidBuf(Dst, NumBytes));
|
||||
result := NumBytes = 0;
|
||||
|
||||
if not result then begin
|
||||
try
|
||||
result := Windows.VirtualProtect(Dst, NumBytes, Windows.PAGE_EXECUTE_READWRITE, @OldPageProtect);
|
||||
|
||||
if result then begin
|
||||
Utils.CopyMem(NumBytes, Src, Dst);
|
||||
Windows.VirtualProtect(Dst, NumBytes, OldPageProtect, @OldPageProtect);
|
||||
end;
|
||||
except
|
||||
result := false;
|
||||
end;
|
||||
end; // .if
|
||||
end; // .function WriteAtCode
|
||||
|
||||
(* Writes patch to any write-protected section *)
|
||||
function WritePatchAtCode (PatchMaker: TPatchMaker; {n} Dst: pointer): boolean;
|
||||
var
|
||||
Buf: Utils.TArrayOfByte;
|
||||
|
||||
begin
|
||||
{!} Assert(PatchMaker <> nil);
|
||||
{!} Assert((Dst <> nil) or (PatchMaker.Size = 0));
|
||||
// * * * * * //
|
||||
result := true;
|
||||
|
||||
if PatchMaker.Size > 0 then begin
|
||||
SetLength(Buf, PatchMaker.Size);
|
||||
PatchMaker.ApplyPatch(pointer(Buf), Dst);
|
||||
result := WriteAtCode(Length(Buf), pointer(Buf), Dst);
|
||||
end;
|
||||
end; // .function WritePatchAtCode
|
||||
|
||||
function SpliceWinApi (OrigFunc, HandlerFunc: pointer): pointer;
|
||||
const
|
||||
CODE_ADDR_ALIGNMENT = 8;
|
||||
|
||||
var
|
||||
{O} p: PatchForge.TPatchHelper;
|
||||
{OI} SpliceBridge: pbyte; // Memory is never freed
|
||||
OrigFuncBridgeLabel: string;
|
||||
OrigCodeBridgeStartPos: integer;
|
||||
OverwrittenCodeSize: integer;
|
||||
|
||||
begin
|
||||
{!} Assert(OrigFunc <> nil);
|
||||
{!} Assert(HandlerFunc <> nil);
|
||||
p := TPatchHelper.Wrap(TPatchMaker.Create);
|
||||
SpliceBridge := nil;
|
||||
result := nil;
|
||||
// * * * * * //
|
||||
|
||||
// === BEGIN generating SpliceBridge ===
|
||||
// Add pointer to original function bridge as the first argument
|
||||
p.WriteTribyte(PatchForge.INSTR_PUSH_PTR_ESP);
|
||||
p.WriteInt(PatchForge.INSTR_MOV_ESP_PLUS_4_CONST32);
|
||||
p.ExecActionOnApply(PatchForge.TAddLabelRealAddrAction.Create(p.NewAutoLabel(OrigFuncBridgeLabel)));
|
||||
p.WriteInt(0);
|
||||
|
||||
// Jump to new handler
|
||||
p.Jump(PatchForge.JMP, HandlerFunc);
|
||||
|
||||
// Ensure original code bridge is aligned
|
||||
p.Nop(p.Pos mod CODE_ADDR_ALIGNMENT);
|
||||
|
||||
// Set result to offset from splice bridge start to original function bridge
|
||||
result := pointer(p.Pos);
|
||||
|
||||
// Write original function bridge
|
||||
p.PutLabel(OrigFuncBridgeLabel);
|
||||
OrigCodeBridgeStartPos := p.Pos;
|
||||
p.WriteCode(OrigFunc, PatchForge.TMinCodeSizeDetector.Create(sizeof(PatchForge.TJumpCall32Rec)));
|
||||
OverwrittenCodeSize := p.Pos - OrigCodeBridgeStartPos;
|
||||
p.Jump(PatchForge.JMP, Utils.PtrOfs(OrigFunc, OverwrittenCodeSize));
|
||||
// === END generating SpliceBridge ===
|
||||
|
||||
// Persist splice bridge
|
||||
GetMem(SpliceBridge, p.Size);
|
||||
WritePatchAtCode(p.PatchMaker, SpliceBridge);
|
||||
|
||||
// Turn result from offset to absolute address
|
||||
result := Ptr(integer(SpliceBridge) + integer(result));
|
||||
|
||||
// Create and apply hook at target function start
|
||||
p.Clear();
|
||||
p.Jump(PatchForge.JMP, SpliceBridge);
|
||||
WritePatchAtCode(p.PatchMaker, OrigFunc);
|
||||
// * * * * * //
|
||||
p.Release;
|
||||
end;
|
||||
|
||||
end.
|
||||
@ -4,10 +4,13 @@ uses
|
||||
TestFramework, GuiTestRunner,
|
||||
VfsUtils, VfsBase, VfsDebug,
|
||||
VfsApiDigger, VfsExport, VfsOpenFiles,
|
||||
VfsHooks, VfsControl,
|
||||
VfsTestHelper,
|
||||
VfsDebugTest, VfsUtilsTest, VfsBaseTest,
|
||||
VfsApiDiggerTest, VfsOpenFilesTest;
|
||||
VfsApiDiggerTest, VfsOpenFilesTest, VfsIntegratedTest;
|
||||
|
||||
begin
|
||||
VfsTestHelper.InitConsole;
|
||||
TGUITestRunner.RunRegisteredTests;
|
||||
end.
|
||||
|
||||
|
||||
15
_TODO_.txt
Normal file
15
_TODO_.txt
Normal file
@ -0,0 +1,15 @@
|
||||
UTF-8 Logging
|
||||
|
||||
SetCurrentDirectoryW(GetCurrentDirectoryW)
|
||||
System.IsMultiThread for DLL and exported API
|
||||
|
||||
(* Trying to turn off DEP *)
|
||||
SetProcessDEPPolicyAddr := Windows.GetProcAddress(Kernel32Handle, 'SetProcessDEPPolicy');
|
||||
|
||||
if SetProcessDEPPolicyAddr <> nil then begin
|
||||
if PatchApi.Call(PatchApi.STDCALL_, SetProcessDEPPolicyAddr, [0]) <> 0 then begin
|
||||
Log.Write('VFS', 'SetProcessDEPPolicy', 'DEP was turned off');
|
||||
end else begin
|
||||
Log.Write('VFS', 'SetProcessDEPPolicy', 'Failed to turn DEP off');
|
||||
end;
|
||||
end;
|
||||
Loading…
Reference in New Issue
Block a user