A Zig implementation of SHGetKnownFolderPath
0BSD License
This is a repository focusing on implementing a function that is similar to SHGetKnownFolderPath
, in order to get the same functionality in Zig while avoiding the dependency on shell32.dll. Ultimately, the intention is to merge the code into the Zig standard library in order to close https://github.com/ziglang/zig/issues/18098.
Two current features of the implementation that ideally will be maintained in the finished version:
This is a fully clean-room reimplementation with no decompilation involved. The method of reimplementation was/is the following:
SHGetKnownFolderPath
for a given known folder and then ran it with NtTrace to see which registry keys, etc were accessed by SHGetKnownFolderPath
. This was the main tool used to determine what a reimplementation should be doing.SHGetKnownFolderPath
outputs for all known folders (and additionally that the outputs match when no environment variables are set)This is my current understanding about what SHGetKnownFolderPath
is doing (this glosses over a lot of details, see the source code for a better understanding):
virtual
, then EFAIL
is returned by SHGetKnownFolderPath
sample_playlists
, sidebar_parts
, sidebar_default_parts
, start_menu_all_programs
, current_app_mods
, or local_storage
then FILE_NOT_FOUND
is (always?) returned by SHGetKnownFolderPath
. It is unclear exactly why this is the case.HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions
, e.g. for local_app_data
that'd be the subkey {F1B32785-6FBA-4FCF-9D55-7B8E7F157091}
of FolderDescriptions
. It's unclear what information it actually uses from this registry key (see known differences below).peruser
or common
, then SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
is checked to see if the path has been redirected.
HKEY_CURRENT_USER
for peruser
paths, or HKEY_LOCAL_MACHINE
for common
paths.User Shell Folders
may be a name (e.g. Local AppData
) or it may be the GUID of the folder (e.g. {374DE290-123F-4565-9164-39C4925E467B}
). Each folder will use one or the other, and which folder uses which seems to be completely arbitrary.User Shell Folders
is not checked for them.User Shell Folders
, the parent path will be looked up in User Shell Folders
until it hits a fixed
pathUser Shell Folders
or the path is of type fixed
, then a path is constructed using various methods.%WINDIR%
, %SystemDrive%
, %USERPROFILE%
, %ProgramData%
, %PUBLIC%
). When exactly this special casing takes place is not fully figured out yet (see known differences around user_profiles
below).With a default Windows 10 installation, here's how the Zig version currently compares to SHGetKnownFolderPath
(with KF_FLAG_DONT_VERIFY
set):
user_profiles
, the path returned is exactly the sameCurrent known differences to SHGetKnownFolderPath:
KF_FLAG_CREATE
, etc) and instead always functions as if SHGetKnownFolderPath was called with the sole option KF_FLAG_DONT_VERIFY. That is, the Zig version does not verify that the path it returns exists on the filesystem (while SHGetKnownFolderPath does that verification by default).
CREATE
and INIT
flags, SHGetKnownFolderPath can be responsible for things like creating/initializing special Library folders, desktop.ini files, folder attributes, etc, etc.KnownFolders.h
.
user_profiles
path differently:
%SystemDrive%
) if the environment variable is not set and HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\ProfilesDirectory
contains that environment variable.%SystemDrive%
for user_profiles
without the need for the environment variable being set.HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions
at all. This is something that SHGetKnownFolderPath
does for each known folder, but it doesn't seem to care that much about the values, or it only cares about the values for certain folders, or something. I couldn't make sense of when it matters (e.g. if the GUID for local_app_data
is not in FolderDescriptions, SHGetKnownFolderPath still returns a path, but if the GUID for local_app_data_low
is not in FolderDescriptions, then SHGetKnownFolderPath returns FILE_NOT_FOUND
).
FILE_NOT_FOUND
if its GUID does not have an entry in FolderDescriptions
can be found here: https://gist.github.com/squeek502/b51adfa7490101baafecdecb4cf771b7
Potential next steps to improve conformance of the reimplementation:
RtlQueryEnvironmentVariable
/RtlQueryEnvironmentVariable_U
/RtlExpandEnvironmentStrings_U
to get a more complete picture of how SHGetKnownFolderPath
is functioningFolderDescriptions
being missing/modifiedzig build
will give you a zig-out/bin/knownfolder.exe
. When run without arguments, it will get the path of every known folder and print out any paths that returned an error or that have unexpanded environment variables. When run with an argument, it will look up the path of the specified known folder and print the result (the argument must be a field name of the KnownFolder
enum).
zig build test
will run a test that compares the return of the Zig implementation with the return of SHGetKnownFolderPath
(the target must be Windows for this test to run)
zig build tools
will build two programs:
zig-out/bin/shknownfolder.exe
which works the same as knownfolder.exe
above, but will call SHGetKnownFolderPath
to get the path of each known folderzig-out/bin/spawnempty.exe
which will spawn a child process with a completely empty environment (no environment variables set at all) using the arguments you pass to it, e.g. spawnempty.exe shknownfolder.exe program_data
which will print the unexpanded path %SystemDrive%\ProgramData
with a default Windows 10 installation.