Wine - Starting Simple
“If you wish to make apple pie from scratch, you must first invent the universe.” ~ Carl Sagan
This also applies to Wine.
Contents
Preamble
Wine (originally an acronym for “Wine is Not an Emulator” as its own site states) is a suite of software for running Windows applications on Linux. It effectively translates, to a format that Linux understands, Windows API calls and supporting elements which make up a pseudo operating system, that is “layered” on top of Linux.
Note: Wine isn’t exclusively Linux, it translates to POSIX calls so works with macOS, FreeBSD, and other systems which are compliant.
Wine aims to provide enough of a Windows-adjacent installation to a wide range of Windows software, including complex games.
A very crude example is the system call for opening a file, if you use Wine to run a program that opens a file, because it’s a format that’s unique to Windows (for whatever reason) then the running application calls what it thinks is the Windows call to open a file, but it is then intercepted by Wine, which forwards it to Linux in a way that Linux understands.
Sometimes these calls are simple, and sometimes they’re very complex; sometimes it’s enough just to fake a response to the application, or even define a function as a stub for the purpose of dynamic linking (but have it error if that stub is actually called).
Installation
A lot of the modern installations of Wine on Linux are handled and managed by some sort of additional application, say Lutris, Steam, Bottles, Heroic, and the like. However, to understand the underpinnings of Wine, it’s important to start at first principles, before we layer more complexity onto an already complex system.
Fedora Focus
My distribution of choice.
In Fedora, there are quite a few Wine packages, and it is possible to install specific components, or just subsets, instead of the entire suite. If you want to avoid what some people might uncharitably call “bloat.”
You might find that only
wine-core is
installed on your system, as this is a subset of the larger “wine” package and
is effectively just the “basic” stuff required to get Wine working. It’s the
singular largest package in the list of Wine components.
In Fedora, wine is a “meta-package” which includes everything and the kitchen
sink, so the full wine suite. On top of the Wine binaries and libraries, it
includes packages for fonts, network shares, audio codecs, DXVK (see below,)
LDAP, and much more besides. If your application is complaining about a missing
component that it requires to get working, say LDAP to enable Authentication,
then you may need to either install one of the other subset packages, or just
install the meta-package itself.
For now, we’ll stick with simple:
$ sudo dnf install wine-core
Installed Elements
Focusing on wine-core for a second, you can get a detailed list of what a
package installs with dnf repoquery --list wine-core. It’s effectively the
wine binaries and libraries required for running Windows software, with no
bells and whistles.
$ dnf repoquery --list wine-core | sort -u | wc -l
Last metadata expiration check: 0:09:48 ago on Sun 23 Apr 2023 13:49:50 IST.
1797
That’s a lot of files!
It includes:
- Wine’s version of Windows DLLs
- The
winebinaries and supporting applications (likewineserver) - Unix libraries to interact with the “host system” (e.g.
winevulkan.so)
By way of a second example, if we look at wine-dxvk we can see this is a
sub-package that adds the upstream DXVK project libraries, which aren’t included
in Wine by default because they have different goals and good reasons for not
being (note that this isn’t a bad thing, it just means that other houses, like
Valve, have to couple the two into consistent bundles).
$ dnf rq --list wine-dxvk
Last metadata expiration check: 0:38:57 ago on Sun 23 Apr 2023 13:18:10 IST.
/usr/lib/wine/i386-windows/dxvk-d3d10.dll
/usr/lib/wine/i386-windows/dxvk-d3d10_1.dll
/usr/lib/wine/i386-windows/dxvk-d3d10core.dll
/usr/lib/wine/i386-windows/dxvk-d3d11.dll
/usr/share/doc/wine-dxvk
/usr/share/doc/wine-dxvk/README.md
/usr/share/licenses/wine-dxvk
/usr/share/licenses/wine-dxvk/LICENSE
/usr/lib64/wine/x86_64-windows/dxvk-d3d10.dll
/usr/lib64/wine/x86_64-windows/dxvk-d3d10_1.dll
/usr/lib64/wine/x86_64-windows/dxvk-d3d10core.dll
/usr/lib64/wine/x86_64-windows/dxvk-d3d11.dll
/usr/share/doc/wine-dxvk
/usr/share/doc/wine-dxvk/README.md
/usr/share/licenses/wine-dxvk
/usr/share/licenses/wine-dxvk/LICENSE
Note: DXVK is a “translation layer” which takes DirectX 9/10/11 calls and translates them to Vulkan equivalents, which is supported on Linux… and Windows actually, you can use DXVK on Windows to get better performance out of games and solve problems. Intel use it for their Arc cards when used to run older games ‘well’ on Windows.
Fedora uses the alternatives system to create a specific symlink based on
existing criteria, the alternatives entry (created by a script when installing
wine-dxvk and only applied if you have a capable GPU) looks like the
following:
$ cat /var/lib/alternatives/wine-d3d11\(x86-64\)
auto
/usr/lib64/wine/x86_64-windows/d3d11.dll
/usr/lib64/wine/x86_64-windows/wine-d3d11.dll
10
/usr/lib64/wine/x86_64-windows/dxvk-d3d11.dll
20
The higher the priority (20 here) the higher the precedence.
In practice, what this means is that /usr/lib64/wine/x86_64-windows/d3d11.dll
is a symlink to /usr/lib64/wine/x86_64-windows/dxvk-d3d11.dll and it’s this
file which gets placed in the vaunted d3d11.dll spot in the WINEPREFIX when
created:
$ md5sum ~/.test/drive_c/windows/syswow64/d3d11.dll
fe95e027a4ba886294280b63efc5836b
$ md5sum ~/.test/drive_c/windows/syswow64/dxvk-d3d11.dll
fe95e027a4ba886294280b63efc5836b
$ md5sum /usr/lib/wine/i386-windows/dxvk-d3d11.dll
fe95e027a4ba886294280b63efc5836b
Note the use of a 32-bit library here, and the -still confusing- fact that
syswow64 is actually the ‘Windows 32-bit on Windows 64-bit’ folder, whereas
system32 has the 64-bit DLLs… this is why software engineers shouldn’t be
allowed to name things.
On Fedora, with wine-dxvk installed, you don’t need to set any “Native” or
“Builtin” options to use DXVK (at least for 10 & 11, 9 seems to still use
wine-d3d9.dll at the time of writing.) This is because any application looking
to load d3d10.dll, for example, will get the DXVK version.
Basic Example
Now that we have Wine installed (with wine-core) we can have a go at
installing some Windows software. For the sake of sanity, I’m going to
deliberately use the Windows version of Sauerbraten, but the concepts hold true
for other software which doesn’t also have a Linux build.
Once we’ve downloaded an executable from http://sauerbraten.org/ we can run it from the command line (GUIs are available, but again, simple).
wine64 ~/Downloads/sauerbraten_2020_12_21_windows.exe
Note: We’re using wine64 to create a 64-bit Wine Prefix, which can only be
set at creation time (of the prefix) and can’t be changed. We want to avoid
creating a 32-bit prefix by accident, and then discovering we want a 64-bit one
later.
If this is your first time installing anything, specifically in this prefix, you
will likely be prompted with a Mono installer. I typically don’t install it
unless I discover I need it. Mono is a .NET implementation, which many
applications depend upon. Fedora also has a wine-mono sub-package which can be
installed to provide this, and it is recommended that if you do need Mono,
you install your distribution’s version.
Click ‘Cancel’ on the Mono installer and click through the Sauerbraten install. I leave everything enabled for this example.
It will install to C:\Program Files (x86)\Sauerbraten which is actually our
prefix, the default of which is in ~/.wine on Fedora (though this can be
changed.)
We can run our Sauerbraten install once it’s completed, by expressly using
wine64 again:
$ wine64 ~/.wine/drive_c/Program\ Files\ \(x86\)/Sauerbraten/Sauerbraten.lnk
It should open!
When we want a bit more information about the graphics stack of the game, we can
also still use applications like mangohud by prefixing our command:
$ MANGOHUD_CONFIG=full mangohud --dlsym \
wine64 ~/.wine/drive_c/Program\ Files\ \(x86\)/Sauerbraten/Sauerbraten.lnk
Or if we wanted to use Zink (to use Mesa’s OpenGL -> Vulkan layer) we can even add to our command, and use:
$ MESA_LOADER_DRIVER_OVERRIDE=zink \
MANGOHUD_CONFIG=full mangohud --dlsym \
wine64 ~/.wine/drive_c/Program\ Files\ \(x86\)/Sauerbraten/Sauerbraten.lnk
Useful Titbits
Here’s some elements of Wine which it’s handy to be aware of, and which aren’t made overly clear in the documentation, or at first-use.
wineserver
It would be remiss to continue without mentioning the wineserver process. If
the Wine DLLs provide reimplementations of Windows libraries, then wineserver
provides a reimplementation of system services (and the NT Kernel elements
required to run software). It isn’t a complete rewrite of the Windows NT kernel,
but it’s a lot.
You can add debug messages (at a normal level if invoked this way) with
WINEDEBUG="+server" to get wineserver logs to stderr.
WINEARCH
We’ve already touched briefly on wine64 but it’s worth noting that you can
also specify the architecture with WINEARCH=win64 (or 32) and the regular
wine command if required, i.e.:
WINEARCH=win64 \
wine ~/.wine/drive_c/Program\ Files\ \(x86\)/Sauerbraten/Sauerbraten.lnk
WINEPREFIX
The prefix, or pseudo C drive, is the “virtual” (pseudo) Windows installation into which our applications are installed.
When a new prefix is created, the Wine DLLs are copied into it, meaning that yes, you can end up with multiple versions of the same DLLs on your hard drive.
By default, as suggested above, a single prefix is created in your /home user
directory, typically. This might be desirable, and might not be, as sometimes
you want to use “clean” prefixes for certain software. If you’ve had to install
a lot of additional libraries and applications as dependencies for a single
application, you might want to create a completely separate prefix for another
application, to ensure no strange behaviour thanks to those previous changes.
Note: Purists will point out that you can set individual configuration for different applications inside the same prefix (Valve do this for specific games, if you’ve ever looked at Proton configuration) but I find it adds unnecessary complexity when you’re not shipping a solution.
One example might be that you’d like a clean prefix if you’ve installed the
Microsoft version of d3dcompiler_47, and want to use the “builtin” Wine version
for another application.
Different prefixes are accomplished with the WINEPREFIX environment variable,
if we’d wanted to install Sauerbraten in its own prefix, we could have done so
with:
$ WINEPREFIX=~/.sauer wine64 ~/Downloads/sauerbraten_2020_12_21_windows.exe
To delete this prefix, you would simply rm -r ~/.sauer and the application is
gone.
WINEDLLOVERRIDES
I hate this subject, because it’s very unfriendly and tricky to explain to newcomers.
Native Dynamic Link Libraries (DLLs) are those which typically come bundled with your application, are sourced from third parties, or which might have been taken from Windows. In a word they’re the “real” version of a DLL, rather than the rewritten one from the Wine developers. These are not included with Wine.
Builtin DLLs are those which the Wine developers have written to be compatible (or as close as they can get) with the native DLLs. These are the ones which are always used by default. They might also offer different functionality where appropriate, such as visual elements which “fit better” on Linux than Windows, but which the application doesn’t care about.
You override which DLL is loaded by an application either with winecfg or at
the CLI with WINEDLLOVERRIDES. From the man page:
WINEDLLOVERRIDES="comdlg32,shell32=n,b"
Try to load comdlg32 and shell32 as native windows dll first and try the
builtin version if the native load fails.
It’s worth noting that if you use an application like winetricks to install
native DLLs, you should then instruct the load order of the DLLs. winetricks
will install to the prefix, though Wine can still load its builtin DLLs from
elsewhere on your filesystem.
Note: Native DLLs are usually loaded from the prefix’s local
C:\windows\system32 directory, winetricks for example, will overwrite the
copied “builtin” DLLs here, originally from your /usr/lib/wine directory.
I personally think these labels can be misleading.
The Wine User guide has a useful section on DLL overrides.
It also includes this usual DLL load order:
- The directory the program was started from.
- The current directory.
- The Windows system directory.
- The Windows directory.
- The PATH variable directories.
To see which DLLs are being loaded, you can use WINEDEBUG=+loaddll:
$ WINEDEBUG=+loaddll \
wine64 ~/.wine/drive_c/Program\ Files\ \(x86\)/Sauerbraten/Sauerbraten.lnk \
2>&1 | tee sauer.debug
MinGW and System DLLs Aside
Parsing the resulting file, we can check for ’native’ loaded DLLs with something
like grep "native" sauer.debug.
In our example, we can see that wine is loading the following from elsewhere
on our hard drive (Z: in a prefix is a map to your “host” filesystem):
zlib1.dlllibssp-0.dlllibpng16-16.dlllibjpeg-62.dlllibtiff-5.dll
Sauerbraten actually ships with these DLLs anyway, so why is Wine loading them from outside the prefix? Specifically from:
/usr/x86_64-w64-mingw32/sys-root/mingw/bin/lib*.dll
For context, these DLLs are provided as part of the mingw64-filesystem package
in Fedora, usually used by developers to build Windows packages, on their Linux
installation.
MinGW, as a project, encompasses various elements to allow applications to compile and run on Windows systems. Here, it’s providing DLLs for basic functionality that allow Sauerbraten (and Wine) to run whilst using these “Windows” DLLs.
Note: For developers, MinGW can be used to provide a minimal implementation of Windows APIs to those developers using GCC, so they don’t have to make many changes when building their application, or game, for Windows and Linux.
So we understand that these DLLs are here so that Windows applications can be
built on Linux, why is Wine loading them, especially when it’s got copies in the
Sauerbraten prefix at ~/.wine/drive_c/Program Files/(x86)/Sauerbraten/bin &
bin64?
I had to jump onto the #winehq IRC to solve this one, but effectively some DLLs are fixed by Wine at compile-time, and can’t really be overwritten. This patch goes a long way to explaining things. Thanks to Zebediah at CodeWeavers for the pointers.
Fedora sets this value in their wine builds to:
--with-system-dllpath=/usr/x86_64-w64-mingw32/sys-root/mingw/bin
See: https://src.fedoraproject.org/rpms/wine/blob/rawhide/f/wine.spec#_746
How it Works
Now that we’ve proven Wine works by getting our game installed (and having it actually playable) we should take a look into how it works.
a.exe Example
We’re not immediately going to use Sauerbraten for this, because it’s large and
complex, instead we’re going to use a small program (compiled locally with the
i686-w64-mingw32-gcc compiler) that outputs “Hello, Wine!”:
#include <stdio.h>
int main() {
printf("Hello, Wine!\n");
return 0;
}
Like so:
$ WINEDEBUG="fixme-all" wine a.exe
Hello, Wine!
Note: Removal of the fixme log lines is purely to take some noise out.
Remember that this will use our default prefix, which unless you’ve changed
anything, will still be ~/.wine.
Recall earlier I spoke about Windows applications making calls that it expects the underlying “Windows” system to respond to, with answers. In the case of Wine, the application makes calls which are translated into Linux (POSIX) calls, and then responded to, this “interception” is the translation which Wine performs.
We can trace the messages between Wine modules, including DLLs that Wine does
with WINEDEBUG=+relay.
Our full command looks like this:
WINEDEBUG="fixme-all,+timestamp,+server,+loaddll,+relay" \
wine a.exe > a.wine.log 2>&1`
Breaking this down a bit, the WINEDEBUG entries disable the noisy fixme
messages; add timestamps to our log line; gather wineserver logs; log the DLLs
that are loaded; and enable those relay logs. We’re then saving all stderr
and stdout output to a file called a.wine.log.
Note: Relay options can be narrowed down further, by adding Registry entries inside your prefix that deliberately exclude the logs for calls that you name. This is very useful for reducing the size of the log you’re debugging.
Remember that WINEDEBUG and especially +relay will slow things down
considerably.
We’re not going to take everything from this output, but we will look at the
pertinent messages that surround printing Hello, Wine! to our screen:
22853.760:010c:Call KERNEL32.WriteFile(...) ret=656a16a1
22853.760:010c:Call ntdll.NtWriteFile(...) ret=7b0204b4
Hello, Wine!
22853.760:010c:Ret ntdll.NtWriteFile() retval=00000000 ret=7b0204b4
22853.760:010c:Ret KERNEL32.WriteFile() retval=00000001 ret=656a16a1
Note that the functions ntdll.NtWriteFile() and KERNEL32.WriteFile() were
invoked.
WriteFile is defined
here.
NtWriteFile is defined
here.
Our program is built using i686-w64-mingw32-gcc as stated above, so will be
built against the MinGW versions of our includes, to work with Windows systems.
This means that the WriteFile function defined in this standard set (though it
obviously doesn’t have to be the GNU flavour) will be called by our program.
This call is acted upon by the WriteFile logic in the KERNEL32 DLL, then the
NtWriteFile ntdll entry in turn, which forwards the request to wineserver
here.
wineserver is then responsible for writing our “Hello, Wine!” to the screen.
All of this wouldn’t be possible without the wine binary itself, whose job it
is to arrange the program’s instructions and necessary libraries, in memory,
prior to execution. wine is compiled to work on your system, so it knows what
to act upon and how, when the programs it executes try to perform some function.
It’s like a little, temporary, Windows-ish enclave on your running system.
Further Reading
I intend to write more about the various elements of Wine and its related projects, but this is entirely to get my own head around the system. If I’ve made incorrect assumptions or misunderstood any elements, I’m happy to update this document (and my understanding by extension!)
The example given of writing output to a screen above is just one example of a rewritten function that Wine performs for an application. There are several more besides and they can work very differently (like OpenGL) for example.
Wine is a large project with very talented people working on it, and I would support anyone who wants to contribute, financially or with their own time.
CodeWeavers provide an officially supported version of Wine.
References
PE Migration - https://www.winehq.org/pipermail/wine-devel/2022-April/213677.html
IRC - #winehq on Libera.Chat
Debugging Wine - https://wiki.winehq.org/Wine_Developer%27s_Guide/Debugging_Wine
Debug Channels - https://wiki.winehq.org/Debug_Channels#Useful_Channels