First of all, apologies if I'm posting this in the wrong forum, I did read most of the things that were written about this subject in the IBM PC threads, but from skimming at it, I found nothing similar to what I'm suggesting, so I do think this idea is new. If not, we can use this topic to summarize what has been done on this.
Anyway, as everyone knows, TASing PC games is a nightmare. The reasons for this are that, unlike Console games, PC games take input from lots of sources, and might interact with the OS in completely different ways, which are usually affected by several hidden configurations, which makes it very hard to run processes in a deterministic way. The most immediate solution, virtualization, doesn't work too well, either, because virtualization usually makes things too slow, and it works by virtualizing the entire operating system, which means that it's pretty hard to separate what syscalls and library calls are regular OS processes, and what is actually the application you are trying to virtualize.
Given this, an idea I had was to use Docker and Wine to run the application in a containerized environment, and after some trouble I managed to get the latest version of Starcraft running on it.
For those who don't know, containers are a more lightweight alternative to running a Virtual Machine. Containers, at least in Linux, make use of a feature called
kernel namespaces. Essentially, you can think of the OS as an entity that provides lots of "services" to an application, like creating processes, using networking, file systems, etc. Now, when you spawn something in a kernel namespace, whenever the application requests something from the Linux kernel, Linux can see the namespace it requested from, and based on that, it can "hide" lots of things, to give the application the illusion that it's running in an isolated environment. Of course, this isolation is never complete, because you need to communicate somehow with the application, if not why start it?
In any case, containers give you the ability to declare precisely the dependencies that the application has and only interact with the main OS resources in the way that you specified it. When you do this, you get the predictability of a virtual machine without the performance hit, because the app is still running in an ordinary process. To be fair, there's still some performance hit, because in order to run any application you need to include a lot of OS things in a container image, and this does take some time to startup. My containerized starcraft usually takes 2 minutes to start, but after startup the performance is just the same.
Let me explain what I did and why I think it has potential to improve TASing for PC games. In order to run things in docker, you need to create an image, which you specify with a Dockerfile. I installed wine in it with the following commands:
Language: Dockerfile
FROM ubuntu:focal
## for apt to be noninteractive
ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true
## preseed tzdata, update package index, upgrade packages and install needed software
RUN truncate -s0 /tmp/preseed.cfg; \
echo "tzdata tzdata/Areas select Europe" >> /tmp/preseed.cfg; \
echo "tzdata tzdata/Zones/Europe select Berlin" >> /tmp/preseed.cfg; \
debconf-set-selections /tmp/preseed.cfg && \
rm -f /etc/timezone /etc/localtime && \
apt-get update && \
apt-get install -y tzdata
## cleanup of files from setup
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN apt update
RUN apt install -y wget
RUN dpkg --add-architecture i386
RUN mkdir -pm755 /etc/apt/keyrings
RUN wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
RUN wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/focal/winehq-focal.sources
RUN apt update
RUN apt install -y winehq-staging=8.4~focal-1 wine-staging=8.4~focal-1 wine-staging-amd64=8.4~focal-1 wine-staging-i386=8.4~focal-1
RUN wget -O /home/winetricks https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
RUN chmod +x /home/winetricks
RUN /home/winetricks sound=pulse
COPY StarCraft-Setup.exe /home/
Several comments here. I used an old version of ubuntu, 20.04 or "focal" because it seemed that Blizzard's battle.net client was refusing to start in the newest one because of "digital certificate verification". I suppose Blizzard is actively trying to stop people running Starcraft on wine, because of this I used this old Ubuntu version with the exact same wine version reported with platinum status, 8.4. In any case, the magic of Docker containers is that we only need to get a working image once, and then after that we can simply boot it up in any linux distro that it will work. I'm not distributing the image I made here, because even though StarCraft is a free game, that would probably violate Blizzard's EULA.
The setup works like this: first is some automation to setup a locale in wine without the terminal (everything needs to be noninteractive for the image to build). Then I install the specific version of wine I want, and then setup winetricks and configure it to use pulseaudio. I will go into more detail later. It's the easiest way to get the audio working. Finally, I copy the battle-net client I downloaded from the Blizzard website into the image and do the rest of the setup manually.
The way I got starcraft to work was: I logged into the container forwarding graphical output to X (more on that later), and then I ran the setup client as root, logged into my blizzard account, accepted everything and it installed starcraft. It installed all files on /root/.wine/drive_c/Program\ Files\ \(x86\)/
Then I created a user with uid 1000 and gid 1000, which matches the one from my user in the main host machine (1000 is the default id for the initial ubuntu user, unless you did something crazy when you installed it). Then I changed the ownership of everything in the container /root folder to this new user. The purpose of this is to setup pulseaudio as mentioned
here. Essentially we setup a shared socket for the containerized process to communicate with pulseaudio, so that we can hear its sound, but for some hacky reason you need to run it with a user that has the same ID as the user in the host machine where the pulseaudio server is actually running.
With this done, I setup a script that runs starcraft without Blizzard's launcher as this new user:
Language: bash
#!/bin/bash
wine /root/.wine/drive_c/Program\ Files\ \(x86\)/StarCraft/x86_64/StarCraft.exe -launch
And then I saved the image with docker commit to persist my changes and it works, as long as you remember to allow the container to access the X server so that it can create a window and receive input, and the pulseaudio socket and configuration to receive the sound. My full command was:
docker run -it --rm --env PULSE_SERVER=unix:/tmp/pulseaudio.socket --env PULSE_COOKIE=/tmp/pulseaudio.cookie -v /tmp/pulseaudio.socket:/tmp/pulseaudio.socket \
-v /tmp/pulseaudio.client.conf:/etc/pulse/client.conf --user $(id -u):$(id -g) -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
--network none --entrypoint /home/wineuser/sc starcraft
So, why do I think this idea has potential? Well, with this I managed to isolate everything and the only performance hit is on startup. We know exactly the image used to run the game, we can completely disable the network and possibly other os stuff that might cause desyncs, and it's even more fantastic once you understand how the communication with X and pulseaudio actually happens.
For those who don't know, communication with them happens through
Unix domain sockets, which are just like network sockets, but Unix specific because they handle stuff which makes no sense to pass through the network. In my setup, I simply opened all the sockets in /tmp/.X11-unix to make it simple, but in theory it should also be possible to mess around with X to create a specific socket to trade UI and input data with the game, and use Docker to pretend it's the main one in the containerized OS, so that the game accesses it.
The end result is: with some combination of Docker and wine magic, we can redirect all the stuff that matters to TASing, which is audio, video and input, to some unix domain sockets, and we could potentially code a library around them to intercept these calls. This not only is much simpler than what libTAS does, which is wrap around several different types of library calls, but also works much better! That's because not only the game is running in an isolated environment, but also because wine redirects EVERYTHING a multimedia app needs to the X server and pulseaudio, unlike standard Linux apps which can depend on lots of different stuff. So this setup should in theory work no matter what library the app is using to run the game, as long as wine is able to run it.
Let me know what you guys think of this idea, and if you have more suggestions!