Initial commit.
This commit is contained in:
commit
c1a48a018f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/__pycache__
|
||||||
|
usvfs.log
|
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
99
list-nt-query.py
Normal file
99
list-nt-query.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import argparse
|
||||||
|
import ctypes
|
||||||
|
import os.path
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import win32file
|
||||||
|
|
||||||
|
|
||||||
|
class FILE_DIRECTORY_INFORMATION(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("NextEntryOffset", ctypes.c_uint32),
|
||||||
|
("FileIndex", ctypes.c_uint32),
|
||||||
|
("CreationTime", ctypes.c_int64),
|
||||||
|
("LastAccessTime", ctypes.c_int64),
|
||||||
|
("LastWriteTime", ctypes.c_int64),
|
||||||
|
("ChangeTime", ctypes.c_int64),
|
||||||
|
("EndOfFile", ctypes.c_int64),
|
||||||
|
("AllocationSize", ctypes.c_int64),
|
||||||
|
("FileAttributes", ctypes.c_uint32),
|
||||||
|
("FileNameLength", ctypes.c_uint32),
|
||||||
|
("FileName", ctypes.c_wchar),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser("list directory using NtQueryOpenFile")
|
||||||
|
parser.add_argument("-r", "--recurse", action="store_true", help="list recursively")
|
||||||
|
parser.add_argument("directory", type=Path, help="directory to list")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
recursive: bool = args.recurse
|
||||||
|
directory: Path = args.directory
|
||||||
|
|
||||||
|
kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
|
||||||
|
ntdll = ctypes.windll.LoadLibrary("ntdll.dll")
|
||||||
|
|
||||||
|
h = kernel32.CreateFileW(
|
||||||
|
os.path.expanduser(str(directory)),
|
||||||
|
win32file.GENERIC_READ,
|
||||||
|
win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
win32file.FILE_FLAG_BACKUP_SEMANTICS,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if h == -1:
|
||||||
|
print("failed to open directory")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print(f"handle={h}")
|
||||||
|
|
||||||
|
io_status_block = ctypes.create_string_buffer(64)
|
||||||
|
length = 1024
|
||||||
|
file_information_buffer = ctypes.create_string_buffer(length)
|
||||||
|
|
||||||
|
while (
|
||||||
|
ntdll.NtQueryDirectoryFile(
|
||||||
|
h,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
io_status_block,
|
||||||
|
file_information_buffer,
|
||||||
|
length,
|
||||||
|
1,
|
||||||
|
False,
|
||||||
|
0,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
):
|
||||||
|
index = 0
|
||||||
|
while index < length:
|
||||||
|
file_information = FILE_DIRECTORY_INFORMATION.from_buffer(
|
||||||
|
file_information_buffer, index
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"{name} ({length}) -> {offset}".format(
|
||||||
|
offset=file_information.NextEntryOffset,
|
||||||
|
length=file_information.FileNameLength,
|
||||||
|
name=ctypes.wstring_at(
|
||||||
|
ctypes.addressof(file_information)
|
||||||
|
+ FILE_DIRECTORY_INFORMATION.FileName.offset,
|
||||||
|
file_information.FileNameLength // 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_information.NextEntryOffset == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
index += file_information.NextEntryOffset
|
||||||
|
|
||||||
|
kernel32.CloseHandle(h)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
35
main.py
Normal file
35
main.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import usvfs as usvfs_p
|
||||||
|
|
||||||
|
usvfs = usvfs_p.USVFS("./usvfs_x64.dll")
|
||||||
|
print(f"USVFS version: {usvfs.version()}")
|
||||||
|
|
||||||
|
parameters = usvfs.make_parameters()
|
||||||
|
parameters.instance_name = "instance"
|
||||||
|
parameters.debug_mode = False
|
||||||
|
parameters.log_level = usvfs_p.LogLevel.Debug
|
||||||
|
parameters.crash_dumps_type = usvfs_p.CrashDumpType.Off
|
||||||
|
parameters.crash_dumps_path = ""
|
||||||
|
|
||||||
|
filesystem = usvfs.make_virtual_filesystem()
|
||||||
|
|
||||||
|
filesystem.link_directory("./virtual", "./data", recursive=True)
|
||||||
|
|
||||||
|
usvfs.connect_virtual_filesystem(parameters, filesystem)
|
||||||
|
|
||||||
|
usvfs.run_hooked_process(R"cmd.exe /C dir .\data\subvirtual", cwd=Path())
|
||||||
|
|
||||||
|
usvfs.run_hooked_process(
|
||||||
|
R"C:\Users\MikaelCAPELLE\.envs\MO2\Scripts\python.exe list-nt-query.py .\data\subvirtual",
|
||||||
|
cwd=Path(),
|
||||||
|
)
|
||||||
|
|
||||||
|
usvfs.run_hooked_process(
|
||||||
|
R'''python -c "from pathlib import Path; print(list(Path('data').glob('**/*')))"'''
|
||||||
|
)
|
||||||
|
|
||||||
|
with open("usvfs.log", "w") as fp:
|
||||||
|
fp.write("\n".join(usvfs.get_log_messages()) + "\n")
|
||||||
|
|
||||||
|
usvfs.disconnect_virtual_filesystem()
|
410
usvfs.py
Normal file
410
usvfs.py
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
import dataclasses
|
||||||
|
from datetime import timedelta
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Final, Literal, overload
|
||||||
|
|
||||||
|
|
||||||
|
class _C_USVFSParameters(ctypes.Structure):
|
||||||
|
_fields_ = (
|
||||||
|
("instanceName", ctypes.c_char * 65),
|
||||||
|
("currentSHMName", ctypes.c_char * 65),
|
||||||
|
("currentInverseSHMName", ctypes.c_char * 65),
|
||||||
|
("debugMode", ctypes.c_bool),
|
||||||
|
("logLevel", ctypes.c_uint8),
|
||||||
|
("crashDumpsType", ctypes.c_uint8),
|
||||||
|
("crashDumpsPath", ctypes.c_char),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _C_STARTUPINFOW(ctypes.Structure):
|
||||||
|
_fields_ = (
|
||||||
|
("cb", ctypes.c_int32),
|
||||||
|
("lpReserved", ctypes.c_wchar_p),
|
||||||
|
("lpDesktop", ctypes.c_wchar_p),
|
||||||
|
("lpTitle", ctypes.c_wchar_p),
|
||||||
|
("dwX", ctypes.c_int32),
|
||||||
|
("dwY", ctypes.c_int32),
|
||||||
|
("dwXSize", ctypes.c_int32),
|
||||||
|
("dwYSize", ctypes.c_int32),
|
||||||
|
("dwXCountChars", ctypes.c_int32),
|
||||||
|
("dwYCountChars", ctypes.c_int32),
|
||||||
|
("dwFillAttribute", ctypes.c_int32),
|
||||||
|
("dwFlags", ctypes.c_int32),
|
||||||
|
("wShowWindow", ctypes.c_int16),
|
||||||
|
("cbReserved2", ctypes.c_int16),
|
||||||
|
("lpReserved2", ctypes.POINTER(ctypes.c_int8)),
|
||||||
|
("hStdInput", ctypes.c_void_p),
|
||||||
|
("hStdOutput", ctypes.c_void_p),
|
||||||
|
("hStdError", ctypes.c_void_p),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _C_PROCESS_INFORMATION(ctypes.Structure):
|
||||||
|
_fields_ = (
|
||||||
|
("hProcess", ctypes.c_void_p),
|
||||||
|
("hThread", ctypes.c_void_p),
|
||||||
|
("dwProcessId", ctypes.c_int32),
|
||||||
|
("dwThreadId", ctypes.c_int32),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LogLevel(Enum):
|
||||||
|
Debug = 0
|
||||||
|
Info = 1
|
||||||
|
Warning = 2
|
||||||
|
Error = 3
|
||||||
|
|
||||||
|
|
||||||
|
class CrashDumpType(Enum):
|
||||||
|
Off = 0
|
||||||
|
Mini = 1
|
||||||
|
Data = 2
|
||||||
|
Full = 3
|
||||||
|
|
||||||
|
|
||||||
|
class USVFSParameters:
|
||||||
|
def __init__(self, usvfs: ctypes.WinDLL):
|
||||||
|
self._usvfs = usvfs
|
||||||
|
self._c_params = usvfs.usvfsCreateParameters()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self._usvfs.usvfsFreeParameters(self._c_params)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def instance_name(self) -> str:
|
||||||
|
return self._c_params.contents.instanceName.decode()
|
||||||
|
|
||||||
|
@instance_name.setter
|
||||||
|
def instance_name(self, value: str) -> None:
|
||||||
|
self._usvfs.usvfsSetInstanceName(self._c_params, value.encode())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_shm_name(self) -> str:
|
||||||
|
return self._c_params.contents.currentSHMName.decode()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_inverse_shm_name(self) -> str:
|
||||||
|
return self._c_params.contents.currentInverseSHMName.decode()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debug_mode(self) -> bool:
|
||||||
|
return self._c_params.contents.debugMode
|
||||||
|
|
||||||
|
@debug_mode.setter
|
||||||
|
def debug_mode(self, debug: bool) -> None:
|
||||||
|
self._usvfs.usvfsSetDebugMode(self._c_params, debug)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_level(self) -> LogLevel:
|
||||||
|
return LogLevel(self._c_params.contents.logLevel)
|
||||||
|
|
||||||
|
@log_level.setter
|
||||||
|
def log_level(self, level: LogLevel) -> None:
|
||||||
|
self._usvfs.usvfsSetLogLevel(self._c_params, level.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def crash_dumps_type(self) -> CrashDumpType:
|
||||||
|
return CrashDumpType(self._c_params.contents.crashDumpsType)
|
||||||
|
|
||||||
|
@crash_dumps_type.setter
|
||||||
|
def crash_dumps_type(self, dump_type: CrashDumpType) -> None:
|
||||||
|
self._usvfs.usvfsSetCrashDumpType(self._c_params, dump_type.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def crash_dumps_path(self) -> Path:
|
||||||
|
return Path(self._c_params.contents.crashDumpsPath.decode())
|
||||||
|
|
||||||
|
@crash_dumps_path.setter
|
||||||
|
def crash_dumps_path(self, path: Path | str) -> None:
|
||||||
|
self._usvfs.usvfsSetCrashDumpPath(self._c_params, str(path).encode())
|
||||||
|
|
||||||
|
|
||||||
|
_LINKFLAG_FAILIFEXISTS = 0x00000001
|
||||||
|
_LINKFLAG_MONITORCHANGES = 0x00000002
|
||||||
|
_LINKFLAG_CREATETARGET = 0x00000004
|
||||||
|
_LINKFLAG_RECURSIVE = 0x00000008
|
||||||
|
_LINKFLAG_FAILIFSKIPPED = 0x00000010
|
||||||
|
|
||||||
|
|
||||||
|
class VFS:
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class _Entry:
|
||||||
|
mode: Literal["file", "directory"]
|
||||||
|
source: str
|
||||||
|
target: str
|
||||||
|
flags: int
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._entries: list[VFS._Entry] = []
|
||||||
|
|
||||||
|
def link_file(
|
||||||
|
self,
|
||||||
|
source: Path | str,
|
||||||
|
destination: Path | str,
|
||||||
|
*,
|
||||||
|
fail_if_exists: bool = False,
|
||||||
|
fail_if_skipped: bool = False,
|
||||||
|
) -> None:
|
||||||
|
self._entries.append(
|
||||||
|
VFS._Entry(
|
||||||
|
mode="file",
|
||||||
|
source=str(Path(source).absolute()),
|
||||||
|
target=str(Path(destination).absolute()),
|
||||||
|
flags=(fail_if_exists and _LINKFLAG_FAILIFEXISTS)
|
||||||
|
| (fail_if_skipped and _LINKFLAG_FAILIFSKIPPED),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def link_directory(
|
||||||
|
self,
|
||||||
|
source: Path | str,
|
||||||
|
destination: Path | str,
|
||||||
|
*,
|
||||||
|
fail_if_exists: bool = False,
|
||||||
|
monitor_changes: bool = False,
|
||||||
|
recursive: bool = False,
|
||||||
|
create_target: bool = False,
|
||||||
|
fail_if_skipped: bool = False,
|
||||||
|
) -> None:
|
||||||
|
self._entries.append(
|
||||||
|
VFS._Entry(
|
||||||
|
mode="directory",
|
||||||
|
source=str(Path(source).absolute()),
|
||||||
|
target=str(Path(destination).absolute()),
|
||||||
|
flags=(fail_if_exists and _LINKFLAG_FAILIFEXISTS)
|
||||||
|
| (monitor_changes and _LINKFLAG_MONITORCHANGES)
|
||||||
|
| (recursive and _LINKFLAG_RECURSIVE)
|
||||||
|
| (create_target and _LINKFLAG_CREATETARGET)
|
||||||
|
| (fail_if_skipped and _LINKFLAG_FAILIFSKIPPED),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Process:
|
||||||
|
def __init__(self, command: str, cwd: Path | None):
|
||||||
|
self.command: Final = command
|
||||||
|
self.cwd: Final = cwd
|
||||||
|
|
||||||
|
self.si = _C_STARTUPINFOW()
|
||||||
|
self.si.cb = ctypes.sizeof(_C_STARTUPINFOW)
|
||||||
|
self.pi = _C_PROCESS_INFORMATION()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def process_handle(self) -> int:
|
||||||
|
return self.pi.hProcess
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thread_handle(self) -> int:
|
||||||
|
return self.pi.hThread
|
||||||
|
|
||||||
|
@property
|
||||||
|
def process_id(self) -> int:
|
||||||
|
return self.pi.dwProcessId
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thread_id(self) -> int:
|
||||||
|
return self.pi.dwThreadId
|
||||||
|
|
||||||
|
|
||||||
|
class USVFS:
|
||||||
|
instance: USVFS | None = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, path: Path | str, logging: bool | Literal["console"] = True
|
||||||
|
) -> None:
|
||||||
|
assert USVFS.instance is None
|
||||||
|
USVFS.instance = self
|
||||||
|
|
||||||
|
self._usvfs = ctypes.windll.LoadLibrary(str(path))
|
||||||
|
self._usvfs.usvfsCreateParameters.restype = ctypes.POINTER(_C_USVFSParameters)
|
||||||
|
self._usvfs.usvfsVersionString.restype = ctypes.c_char_p
|
||||||
|
self._usvfs.usvfsGetVFSProcessList2.argtypes = (
|
||||||
|
ctypes.POINTER(ctypes.c_size_t),
|
||||||
|
ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._vfs: VFS | None = None
|
||||||
|
|
||||||
|
self._process_by_id: dict[int, Process] = {}
|
||||||
|
|
||||||
|
if logging:
|
||||||
|
self._usvfs.usvfsInitLogging(logging == "console")
|
||||||
|
|
||||||
|
def version(self) -> str:
|
||||||
|
return self._usvfs.usvfsVersionString().decode()
|
||||||
|
|
||||||
|
def print_debug_info(self):
|
||||||
|
self._usvfs.usvfsPrintDebugInfo()
|
||||||
|
|
||||||
|
def make_parameters(self) -> USVFSParameters:
|
||||||
|
return USVFSParameters(self._usvfs)
|
||||||
|
|
||||||
|
def make_virtual_filesystem(self) -> VFS:
|
||||||
|
return VFS()
|
||||||
|
|
||||||
|
def connect_virtual_filesystem(
|
||||||
|
self, parameters: USVFSParameters, filesystem: VFS, disconnect: bool = False
|
||||||
|
) -> None:
|
||||||
|
if self._vfs is not None and not disconnect:
|
||||||
|
raise ValueError("cannot connect to two virtual filesystem at once")
|
||||||
|
|
||||||
|
if disconnect:
|
||||||
|
self.disconnect_virtual_filesystem()
|
||||||
|
|
||||||
|
self._usvfs.usvfsConnectVFS(parameters._c_params) # pyright: ignore[reportPrivateUsage]
|
||||||
|
|
||||||
|
self._vfs = filesystem
|
||||||
|
|
||||||
|
for entry in self._vfs._entries: # pyright: ignore[reportPrivateUsage]
|
||||||
|
func = (
|
||||||
|
self._usvfs.usvfsVirtualLinkFile
|
||||||
|
if entry.mode == "file"
|
||||||
|
else self._usvfs.usvfsVirtualLinkDirectoryStatic
|
||||||
|
)
|
||||||
|
func(entry.source, entry.target, entry.flags)
|
||||||
|
|
||||||
|
def disconnect_virtual_filesystem(self) -> None:
|
||||||
|
if self._vfs is not None:
|
||||||
|
self._usvfs.usvfsDisconnectVFS()
|
||||||
|
self._vfs = None
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def run_hooked_process(
|
||||||
|
self,
|
||||||
|
command: str,
|
||||||
|
*,
|
||||||
|
cwd: Path | None = None,
|
||||||
|
wait: Literal[False],
|
||||||
|
) -> Process: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def run_hooked_process(
|
||||||
|
self,
|
||||||
|
command: str,
|
||||||
|
cwd: Path | None = None,
|
||||||
|
wait: timedelta | Literal["infinite"] = "infinite",
|
||||||
|
) -> Process: ...
|
||||||
|
|
||||||
|
def run_hooked_process(
|
||||||
|
self,
|
||||||
|
command: str,
|
||||||
|
cwd: Path | None = None,
|
||||||
|
wait: timedelta | Literal["infinite"] | Literal[False] = "infinite",
|
||||||
|
) -> Process | int:
|
||||||
|
p = Process(command, cwd)
|
||||||
|
create_ret = self._usvfs.usvfsCreateProcessHooked(
|
||||||
|
0,
|
||||||
|
command,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
False,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0 if cwd is None else str(cwd),
|
||||||
|
ctypes.pointer(p.si),
|
||||||
|
ctypes.pointer(p.pi),
|
||||||
|
)
|
||||||
|
|
||||||
|
if create_ret == 0:
|
||||||
|
raise Exception("failed to create process")
|
||||||
|
|
||||||
|
self._process_by_id[p.process_id] = p
|
||||||
|
|
||||||
|
if wait:
|
||||||
|
return self.wait_for_process(p, wait)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
def wait_for_process(
|
||||||
|
self, process: Process, wait: timedelta | Literal["infinite"] = "infinite"
|
||||||
|
) -> int:
|
||||||
|
wait_i: int = 2**32 - 1 # INFINITE
|
||||||
|
if wait != "infinite":
|
||||||
|
wait_i = int(wait.total_seconds() * 1000)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ctypes.windll.kernel32.WaitForSingleObject(process.process_handle, wait_i)
|
||||||
|
!= 0
|
||||||
|
):
|
||||||
|
raise Exception("failed to wait for process")
|
||||||
|
|
||||||
|
exit_code = ctypes.c_int32(99)
|
||||||
|
if not ctypes.windll.kernel32.GetExitCodeProcess(
|
||||||
|
process.process_handle, ctypes.pointer(exit_code)
|
||||||
|
):
|
||||||
|
raise Exception("failed to get process exist code")
|
||||||
|
|
||||||
|
ctypes.windll.kernel32.CloseHandle(process.process_handle)
|
||||||
|
ctypes.windll.kernel32.CloseHandle(process.pi.hThread)
|
||||||
|
|
||||||
|
del self._process_by_id[process.process_id]
|
||||||
|
|
||||||
|
return exit_code.value
|
||||||
|
|
||||||
|
def get_hooked_processes(self) -> list[Process]:
|
||||||
|
size = ctypes.c_size_t(0)
|
||||||
|
buffer = ctypes.POINTER(ctypes.c_int32)()
|
||||||
|
if (
|
||||||
|
self._usvfs.usvfsGetVFSProcessList2(
|
||||||
|
ctypes.byref(size), ctypes.byref(buffer)
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
):
|
||||||
|
raise Exception("failed to get VFS process list")
|
||||||
|
|
||||||
|
if size.value == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
processes = [
|
||||||
|
process
|
||||||
|
for i in range(0, size.value)
|
||||||
|
if (process := self._process_by_id.get(buffer[i], None)) is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
# this crashes, I don't know why
|
||||||
|
# ctypes.cdll.msvcrt.free(ctypes.cast(buffer, ctypes.c_void_p))
|
||||||
|
|
||||||
|
return processes
|
||||||
|
|
||||||
|
def get_log_messages(self) -> list[str]:
|
||||||
|
size = 2048
|
||||||
|
buffer = ctypes.create_string_buffer(size)
|
||||||
|
|
||||||
|
messages: list[str] = []
|
||||||
|
while self._usvfs.usvfsGetLogMessages(buffer, size, False):
|
||||||
|
messages.append(buffer.value.decode())
|
||||||
|
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def add_blacklisted_executable(self, executable: str) -> None:
|
||||||
|
self._usvfs.usvfsBlacklistExecutable(executable)
|
||||||
|
|
||||||
|
def clear_blacklist_executables(self):
|
||||||
|
self._usvfs.usvfsClearExecutableBlacklist()
|
||||||
|
|
||||||
|
def add_force_loaded_library(self, process: str, library: str | Path) -> None:
|
||||||
|
self._usvfs.usvfsForceLoadLibrary(process, str(library))
|
||||||
|
|
||||||
|
def clear_force_loaded_libraries(self):
|
||||||
|
self._usvfs.usvfsClearLibraryForceLoads()
|
||||||
|
|
||||||
|
def add_skip_file_suffix(self, suffix: str) -> None:
|
||||||
|
self._usvfs.usvfsAddSkipFileSuffix(suffix)
|
||||||
|
|
||||||
|
def clear_skip_file_suffixes(self):
|
||||||
|
self._usvfs.usvfsClearSkipFileSuffixes()
|
||||||
|
|
||||||
|
def add_skip_directory(self, name: str) -> None:
|
||||||
|
self._usvfs.usvfsAddSkipDirectory(name)
|
||||||
|
|
||||||
|
def clear_skip_directory(self):
|
||||||
|
self._usvfs.usvfsClearSkipDirectories()
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
del self._usvfs
|
||||||
|
USVFS.instance = None
|
BIN
usvfs_x64.dll
Normal file
BIN
usvfs_x64.dll
Normal file
Binary file not shown.
0
virtual/base.txt
Normal file
0
virtual/base.txt
Normal file
0
virtual/subvirtual/sub.txt
Normal file
0
virtual/subvirtual/sub.txt
Normal file
Loading…
Reference in New Issue
Block a user