Skip to main content

Python bindings for the userspace virtual filesystem (usvfs) library

Project description

py-usvfs

Python bindings for the userspace virtual filesystem (usvfs) library

What is usvfs?

To quote the devs:

USVFS (short for User Space Virtual File System) aims to allow windows applications to create file or directory links that are visible to only a select set of processes. It does so by using api hooking to fool file access functions into discovering/opening files that are in fact somewhere else

usvfs was originally written by TanninOne as a part of ModOrganizer, a mod manager application for various pc games (notably Bethesda titles). Development of ModOrganizer and usvfs was continued by the ModOrganizer2 team. usvfs is a core component of ModOrganizer. It is what allows ModOrganizer to easily install and uninstall mods without having to constantly move gigabytes of data on disk, and what gives it control over what happens when multiple mods override the same file.

However, usvfs was designed to also be usable as a standalone library. That's great, because this kind of functionality has a variety of interesting applications.

What is py-usvfs?

usvfs is written in C++. It needs to be, because it needs to perform low level tasks like hooking Windows API I/O function calls and injecting processes. I saw use in usvfs for some personal projects, but I usually prefer to work in higher level languages -- Python first and foremost. py-usvfs aims to make that possible.

py-usvfs is two things: a. Python bindings for the usvfs dll functionality, in the form of a pybind11 CPython extension b. A Python wrapper module that provides convenient, hopefully more Pythonic abstractions for that low level functionality

Basically, it's intended to allow to use usvfs in python (a) and make using it less of a pain (b).

Can I use this in a production environment?

Can you? Well, I'm not your mother. Should you? Probably not. usvfs is finicky, not always reliable and poorly documented. py-usvfs is no different. Caveat emptor.

py-usvfs is NOT properly tested. No guarantuee given that anything will work.

Requirements

Because usvfs works by hooking Windows API I/O function calls, it only runs on Windows machines. Consequently, py-usvfs only runs on Windows as well. Becuase py-usvfs exposes the functionality in usvfs_[x86|x64].dll via a CPython extension, it will only run on the official CPython implementation of Python.

The requirements therefore are:

  • Windows OS (win32 or win-amd64)
  • CPython 3.7.x (32-bits or 64-bits)

Installation

Install pre-built packages using pip

Pre-built Python packages are available from PyPI. Use pip to install.

pip install py-usvfs

Once installed, a Python module named usvfs will be available.

Build it yourself

Alternatively, you can build the required components from source code. No detailed instructions given, but these are the basics steps:

  1. Get usvfs source code from https://github.com/ModOrganizer2/usvfs/
  2. Build usvfs_dll in Release/win32 and Release/x64 configurations using MSVC
  3. Place the .dll files (usvfs_x86.dll, usvfs_x64.dll) in python-extension/usvfs/bin, place the .lib files (usvfs_x86.lib, usvfs_x64.lib) in python-extension/usvfs/lib, and place the usvfs proxy exe's (usvfs_proxy_x64.exe, usvfs_proxy_x86.exe) in python-extension/usvfs/bin (Note that even if you are only building for a single platform, you will alwyas need both the 32-bits and 64-bits usvfs_proxy exe's)
  4. Build python-extension in Release/win32 and Release/x64 configurations using MSVC
  5. Run ./build-wheels.bat to build Python wheel packages for 64-bits and 32-bits CPython
  6. You can now install the .whl files in dist/ using pip install [wheelname].whl

Documentation

Currently, no extensive documentation is available. Please refer to the source code and Python docstrings. See below for a basic usage example.

Basic usage

Suppose we have the following folder structure in our current working directory, and we wish to trick a program into thinking the contents of src/ are located at dest/.

[current working directory]/
 |
 +-- src/
 |   |-- source1.txt
 |   |-- source2.txt
 |   |
 |   +-- subfolder/
 |       |-- source3.txt
 |
 +-- dest/
import usvfs

# Define a virtual directory link rule
vdir = usvfs.VirtualDirectory('src/', 'dest/')  # If a process runnning 
                                                # in the vfs tries to access
                                                # dest/*, it will be redirected to src/*

# By default, py-usvfs links directories recursively, i.e. all underlying files 
# and directories of the source directory are linked automatically
# If this behaviour is undesired, you can unset the recursive link flag:
# vdir.link_recursively = False
# All link flags defined by usvfs can be set/unset in this way, but only 
# if they are relevant to the context (e.g. link_recursively is 
# only available for VirtualDirectory links, not VirtualFile links)

# Define our vfs layout
vfs_map = usvfs.Mapping()    # Create a vfs mapping. This is a collection 
                             # of virtual link rules that can be applied to the vfs
vfs_map.link(vdir)           # Add any VirtualDirectory and/or VirtualFile rules like this

# Set up the vfs
vfs = usvfs.UserspaceVFS()   # Create a usvfs controller with default instance name and configuration
vfs.initialize()             # Initialize it
vfs.set_mapping(vfs_map)     # Apply mapping

# Now we can start a process in the vfs using vfs.run_process(...)
# This function takes three arguments:
# 1. string containing executable path + any command line arguments 
# 2. (optional) string specifying a working directory
#    the default is the current working directory of your Python program
# 3. (optional) bool specifying if this call should block execution
#     until the child process finished (default is True)

vfs.run_process('notepad.exe')

# Usvfs will launch a usvfs_proxy process with the appropriate bitness, then 
# start and inject the specified process
# If you browse to dest/ in notepad, you should see the contents of src/
# If you then browse to dest/ in explorer, you will see that there are actually no files there!

# When you're done, be sure to clean up
vfs.close()

The Python wrapper classes hide some of the uglier usvfs wrangling in a clean, relatively easy to use API. If you want more low level access, you can call the usvfs dll (semi-)directly by accessing the functions defined in the usvfs.dll module.

import usvfs  # Note that you cannot import usvfs.dll directly

# Access dll functions, properties, structs like this:
usvfs.dll.LINKFLAG_MONITORCHANGES
usvfs.dll.LogLevel
usvfs.dll.USVFSParameters()
usvfs.dll.CreateVFS(...)
# etc...

This exposes most functions, structs and properties exported by usvfs_[x86|x64].dll (but not the debugging stuff). The function signatures in Python are similar to those in the usvfs C++ headers, but you can pass basic Python types as arguments and you don't have to deal with buffer wchar[]'s. See the usvfs header file for a list of the available functions and a (not particularly useful) explanation of what they do.

The exception here is usvfs.dll.CreateProcessHooked(). In C/C++ calls directly to the usvfs dll, you would have to pass a bunch of Windows API nonsense to the function and deal with Windows handles. The py-usvfs CPython extension takes care of that for you, because (a) it's convenient and (b) exposing all the required Windows API stuff to Python would be a pain. The downside is that you don't have easy access to the newly spawned process status and input/output via Windows handles. CreateProcessHooked() in py-usvfs takes two arguments: a str containing executable path and any command line arguments to pass to it, and a str with a path to the intended working directory for the new process.

License

(c) 2019 pwssnk -- Code available under GPL v3 license

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

py_usvfs-0.2.0-cp37-none-win_amd64.whl (2.5 MB view details)

Uploaded CPython 3.7 Windows x86-64

py_usvfs-0.2.0-cp37-none-win32.whl (1.7 MB view details)

Uploaded CPython 3.7 Windows x86

File details

Details for the file py_usvfs-0.2.0-cp37-none-win_amd64.whl.

File metadata

  • Download URL: py_usvfs-0.2.0-cp37-none-win_amd64.whl
  • Upload date:
  • Size: 2.5 MB
  • Tags: CPython 3.7, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.3

File hashes

Hashes for py_usvfs-0.2.0-cp37-none-win_amd64.whl
Algorithm Hash digest
SHA256 e98b4d143894d505925c37c47635f68fc0430c4c0eb3849ca5c35e94ae0a9584
MD5 2a098d6db842dde353cc4b4b4392f2f7
BLAKE2b-256 4e1e984c8a23f8d572366c9d2994d95fef7e2693ec43b4afb003c3e3c63d9d47

See more details on using hashes here.

File details

Details for the file py_usvfs-0.2.0-cp37-none-win32.whl.

File metadata

  • Download URL: py_usvfs-0.2.0-cp37-none-win32.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: CPython 3.7, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.3

File hashes

Hashes for py_usvfs-0.2.0-cp37-none-win32.whl
Algorithm Hash digest
SHA256 e28a4cc98d991ab79bde05ae12ff8d4b4fd2368cd9ea8ba0dd1f2492c4b2b95d
MD5 01c6e9b67d20ade178a5805bd5da5f09
BLAKE2b-256 05bf78bb4ad7eb2d5d4955693f24bb44bd7d1370a04db143430dfdf16dcc204e

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page