Snowflake's Filesystem API allows for complicated game installation for modern, NAND based consoles. However, at runtime, this becomes complicated with the need to copy over installed DLC data, and merge it with save files, and other system files that must be present at runtime into the runtime root.
We need to have some way of linking directories together on the underlying filesystem without needing to copy entire directory trees. #815 proposed a virtualized filesystem, but this requires dual implementations on both Linux and Windows, as well as a third-party external dependency (and unmanaged code on Linux to implement a FUSE driver).
Instead, a restricted API to create symbolic links onto the filesystem is proposed here. This means that the install script for Snowflake will have to enable Developer Mode on the user's system. I see this as equivalently invasive as installing a signed kernel-mode driver as WinFsp does, and this approach allows us to write relatively less platform-specific code.
API
Terminology: A projecting directory allows symlinks to be created inside, while a projectable directory is a directory that can have symlinks pointing to it.
When a runtime directory is requested for a game, it shall be a IDisposableDirectory , which aRE are lifetime limited, and will be deleted on Dispose.
IDisposableDirectory can open directories as "projecting", i.e. symbolic links can be created if no such existing file exists. Note that the root IDisposableDirectory is not projecting.
There are a few restrictions on IDisposableDirectory and IProjectingDirectory. IDisposableDirectory does not create manifests (there would be no point, since it is a temporary directory) and neither does IProjectingDirectory. IFile and IDirectory instances can be opened as normal, but they too will leave no manifest files behind.
Once a directory has been opened as a projection, it is *inaccessible through `Open**. Doing so will throwUnauthorizedAccessException. Another handle to theIReadOnly*object will not be retrievable.RootedPath,UnsafeGetFilePathwill respect the symbolic link, andIFiles that are links will be dereferenced (symbolic links to their real paths will be reified), and theIReadOnlyFile` shall have the GUID of the link.
IDirectory instances with links shall not be projectable.
Notably, the resultant object is non-projecting. This ensures that only one level of symbolic link can be created.
On dispose, IDisposableDirectory will be deleted, along with all the symbolic links created under that file tree.
Drawbacks
All symbolic link libraries available for C# today are terrible and untested. Filesystem code has to be thoroughly tested to ensure we have a good base to build upon: consider vcdiff for our save-diffing implementation. We will have to write our own symlink library.
Cleanup can be managed as long as IDisposableDirectory is disposed. If it becomes dangling due to a crash, we are left with dangling symlinks. This is not too bad of an issue, but is a drawback as opposed to a virtual filesystem which would automatically unmount and become invalid on a crash.
Rationale
Snowflake's Filesystem API allows for complicated game installation for modern, NAND based consoles. However, at runtime, this becomes complicated with the need to copy over installed DLC data, and merge it with save files, and other system files that must be present at runtime into the runtime root.
We need to have some way of linking directories together on the underlying filesystem without needing to copy entire directory trees. #815 proposed a virtualized filesystem, but this requires dual implementations on both Linux and Windows, as well as a third-party external dependency (and unmanaged code on Linux to implement a FUSE driver).
Instead, a restricted API to create symbolic links onto the filesystem is proposed here. This means that the install script for Snowflake will have to enable Developer Mode on the user's system. I see this as equivalently invasive as installing a signed kernel-mode driver as WinFsp does, and this approach allows us to write relatively less platform-specific code.
API
Terminology: A projecting directory allows symlinks to be created inside, while a projectable directory is a directory that can have symlinks pointing to it.
When a runtime directory is requested for a game, it shall be a
IDisposableDirectory
, which aRE are lifetime limited, and will be deleted onDispose
.IDisposableDirectory
can open directories as "projecting", i.e. symbolic links can be created if no such existing file exists. Note that the rootIDisposableDirectory
is not projecting.There are a few restrictions on
IDisposableDirectory
andIProjectingDirectory
.IDisposableDirectory
does not create manifests (there would be no point, since it is a temporary directory) and neither doesIProjectingDirectory
.IFile
andIDirectory
instances can be opened as normal, but they too will leave no manifest files behind.Once a directory has been opened as a projection, it is *inaccessible through `Open
**. Doing so will throw
UnauthorizedAccessException. Another handle to the
IReadOnly*object will not be retrievable.
RootedPath,
UnsafeGetFilePathwill respect the symbolic link, and
IFiles that are links will be dereferenced (symbolic links to their real paths will be reified), and the
IReadOnlyFile` shall have the GUID of the link.IDirectory
instances with links shall not be projectable.Notably, the resultant object is non-projecting. This ensures that only one level of symbolic link can be created.
On dispose,
IDisposableDirectory
will be deleted, along with all the symbolic links created under that file tree.Drawbacks
All symbolic link libraries available for C# today are terrible and untested. Filesystem code has to be thoroughly tested to ensure we have a good base to build upon: consider vcdiff for our save-diffing implementation. We will have to write our own symlink library.
Cleanup can be managed as long as
IDisposableDirectory
is disposed. If it becomes dangling due to a crash, we are left with dangling symlinks. This is not too bad of an issue, but is a drawback as opposed to a virtual filesystem which would automatically unmount and become invalid on a crash.