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\B', DONT_OVERWRITE_EXISTING);
|
||||||
VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING);
|
VfsBase.MapDir(RootDir, RootDir + '\Mods\A', DONT_OVERWRITE_EXISTING);
|
||||||
VfsBase.RunVfs(SORT_FIFO);
|
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;
|
DirListing.Rewind;
|
||||||
Check(DirListing.GetDebugDump() = 'vcredist.bmp'#13#10'eula.1028.txt', 'Invalid virtual directoring listing. Got: ' + DirListing.GetDebugDump());
|
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.
|
||||||
71
VfsBase.pas
71
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_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.
|
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 *)
|
(* Single redirected VFS entry: file or directory *)
|
||||||
TVfsItem = class
|
TVfsItem = class
|
||||||
@ -79,6 +79,8 @@ type
|
|||||||
procedure RestoreVfsForThread;
|
procedure RestoreVfsForThread;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TSingleArgExternalFunc = function (Arg: pointer = nil): integer; stdcall;
|
||||||
|
|
||||||
var
|
var
|
||||||
(* Global VFS access synchronizer *)
|
(* Global VFS access synchronizer *)
|
||||||
VfsCritSection: Concur.TCritSection;
|
VfsCritSection: Concur.TCritSection;
|
||||||
@ -86,9 +88,13 @@ var
|
|||||||
|
|
||||||
function GetThreadVfsDisabler: TThreadVfsDisabler;
|
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;
|
function RunVfs (DirListingOrder: TDirListingSortType): boolean;
|
||||||
|
|
||||||
|
(* Temporarily pauses VFS, but does not reset existing mappings *)
|
||||||
|
function PauseVfs: boolean;
|
||||||
|
|
||||||
(* Stops VFS and clears all mappings *)
|
(* Stops VFS and clears all mappings *)
|
||||||
function ResetVfs: boolean;
|
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 *)
|
(* Maps real directory contents to virtual path. Target must exist for success *)
|
||||||
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
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 (***)
|
(***) implementation (***)
|
||||||
|
|
||||||
@ -115,6 +124,9 @@ var
|
|||||||
|
|
||||||
(* Global VFS state indicator. If false, all VFS search operations must fail *)
|
(* Global VFS state indicator. If false, all VFS search operations must fail *)
|
||||||
VfsIsRunning: boolean = false;
|
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 *)
|
(* Automatical VFS items priority management *)
|
||||||
OverwritingPriority: integer = INITIAL_OVERWRITING_PRIORITY;
|
OverwritingPriority: integer = INITIAL_OVERWRITING_PRIORITY;
|
||||||
@ -271,15 +283,32 @@ begin
|
|||||||
Enter;
|
Enter;
|
||||||
|
|
||||||
if not VfsIsRunning then begin
|
if not VfsIsRunning then begin
|
||||||
BuildVfsItemsTree();
|
if not VfsTreeIsBuilt then begin
|
||||||
SortVfsDirListings(DirListingOrder);
|
BuildVfsItemsTree();
|
||||||
|
SortVfsDirListings(DirListingOrder);
|
||||||
|
VfsTreeIsBuilt := true;
|
||||||
|
end;
|
||||||
|
|
||||||
VfsIsRunning := true;
|
VfsIsRunning := true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Leave;
|
||||||
|
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;
|
Leave;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end; // .function RunVfs
|
end;
|
||||||
|
|
||||||
function ResetVfs: boolean;
|
function ResetVfs: boolean;
|
||||||
begin
|
begin
|
||||||
@ -288,8 +317,9 @@ begin
|
|||||||
if result then begin
|
if result then begin
|
||||||
with VfsCritSection do begin
|
with VfsCritSection do begin
|
||||||
Enter;
|
Enter;
|
||||||
VfsIsRunning := false;
|
|
||||||
VfsItems.Clear();
|
VfsItems.Clear();
|
||||||
|
VfsIsRunning := false;
|
||||||
|
VfsTreeIsBuilt := false;
|
||||||
Leave;
|
Leave;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -481,9 +511,36 @@ end; // .function _MapDir
|
|||||||
|
|
||||||
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
||||||
begin
|
begin
|
||||||
result := _MapDir(NormalizePath(VirtPath), NormalizePath(RealPath), nil, OverwriteExisting, AUTO_PRIORITY) <> nil;
|
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;
|
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
|
begin
|
||||||
VfsCritSection.Init;
|
VfsCritSection.Init;
|
||||||
VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);
|
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,
|
TestFramework, GuiTestRunner,
|
||||||
VfsUtils, VfsBase, VfsDebug,
|
VfsUtils, VfsBase, VfsDebug,
|
||||||
VfsApiDigger, VfsExport, VfsOpenFiles,
|
VfsApiDigger, VfsExport, VfsOpenFiles,
|
||||||
|
VfsHooks, VfsControl,
|
||||||
|
VfsTestHelper,
|
||||||
VfsDebugTest, VfsUtilsTest, VfsBaseTest,
|
VfsDebugTest, VfsUtilsTest, VfsBaseTest,
|
||||||
VfsApiDiggerTest, VfsOpenFilesTest;
|
VfsApiDiggerTest, VfsOpenFilesTest, VfsIntegratedTest;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
VfsTestHelper.InitConsole;
|
||||||
TGUITestRunner.RunRegisteredTests;
|
TGUITestRunner.RunRegisteredTests;
|
||||||
end.
|
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