About a year ago I was setting up a Ubuntu system for a family of non-technical users. They like to play games and one of their favorites is Diablo II. It works well on Wine if you set it to use Glide and install a Glide wrapper. The game's copy protection is properly supported by Wine but it's not necessary as Blizzard removed the CD check in the recent patches.
There are two problems with installing Windows applications like Diablo II for multiple users. First, because of the isolation of user accounts on *nix systems, you have to login and repeat the install process for each account. Second, each installation after the first wastes disk space and with Diablo II it's several gigabytes, especially if you have a lot of mods.
It's possible to manually copy the first installation and edit the Wine registry, menu entries, and fix symlinks, but it's tedious. So I began messing around with some shell scripts to automate the process. I'm not an expert with shell scripts but I improved with time and some help from my LUG-mates. After several false starts I got a basic script functioning. That solved the first problem but not the space issue.
After messing around with some LiveCDs, I got the idea to try to share the wine directory with a union mount. First I tried FunionFS but it had several bugs that prevented it from working (like not being able to change an existing file). So I switched to Aufs. It worked but it can't be run by a user as it requires root permission to mount. To keep it easy for users I had to use pam_mount and mount it at login. I added the ability for the script to export a sample mount entry for pam_mount.conf.xml to save time.
I then got the idea to add handling for separate Wine directories for each application (like CrossOver Bottles). That brought up another issue, the desktop menu entries for Wine's utilities like winefile and winecfg. I needed to add duplicate entries with different WINEPREFIX settings and associate them with each application. I came up with a primitive solution for locating the entries and duplicating them in an alternate location as a submenu below the primary application's menu (Wine > Programs > (application) > Wine Utils).
I then got another idea - application merging. One problem with games is that there are a lot of third-party mods and other customizations for them. There are also a lot of updates. This requires editing of configuration files, extracting files from archives, and file management. These can't always be done easily with Linux tools. One problem is patches for older games are often in zip files. The contents are intended to overwrite existing files but sometimes the filenames have different case. If you extract them with a native Linux application you end up with duplicates instead of overwrites. The other problem with Linux tools is that they always give a "/" or "/home/user" oriented view when the user expecting C:, especially when following an online instructions for installing a patch or mod. The concept of application merging is simple - select some utilities that have minimal dependences, install to a separate Wine directory, then add it as an Aufs branch to the mount for each main application. You install each one once and share it but then it can be used within every other Wine application directory (and behaves as if it was installed in each) without wasting much storage space. Wine's registry files are text so entries for a merging application can be duplicated with just diff and patch.
Then I realized this had another major benefit for game mods - it would be possible to install multiple, even conflicting mods, and mount them as separate Aufs mount points at the same time. This is especially useful for games that don't have integrated mod management. Using Diablo II as an example, you would install and update it, share it with wineappshare.sh, then mount it via Aufs with a new read/write directory branch. Then install a mod like MedianXL (which ends up in the read/write branch). Then unmount the directory, move and share the read/write directory as a new read-only branch for MedianXL and mount it on top of the Diablo II directory with a new read/write branch. If you mount the Diablo II directory again using a different read/write branch, you can run regular Diablo II and the MedianXL version at the same time. Effectively they are in separate "bottles" but share the bulk of the install in read-only branches so there is little additional overhead.
Of course the tricks don't stop there. You can imagine putting the shared parent branches on a compressed volume to save space, mount it on a server via NFS, and use pam_mount to mount the read/write branches on a USB drive on the client. Imagine the possibilities for a gaming cafe system.
At this point I knew Winesharer would revolutionize Windows gaming on Linux. Just as soon as I finish writing it. Then perfecting it. Then documenting some really complicated examples. And make some awesome video demonstrations. And write the book. Then push it out of the code cave and bask in the glory. At least that was the plan about a year ago before life and reality got in the way. So now I'm down to the old "release early and often" process which completely eliminates the "shock and awe" value. At least this way it may have an impact before Wine is forgotten due to lack of interest in running legacy Windows applications and everyone switching to GNU/Hurd.
Winesharer consists of three scripts:
wineappshare.sh - strips out user-specific directory links, tracks down related icons and XDG menu entires, and copies the Wine directory (bottle) to a shared location - /srv/wine by default. The hardest part was finding the menu entries (*.desktop) as Wine doesn't keep track of them. I had to do a linear search by grepping for matching WINEPREFIX values then calculating what the matching menu directory (*.menu) path would be. It also doesn't help that the "Icon=" references can specify icon file extensions with ".png", ".xpm", or not at all.
winemergeprep.sh - makes registry diffs and a list of modified files for "mergeable" utilities. The file listing excludes any unchanged/unused files like the fake DLLs that Wine adds in the System directory. It is run twice - after initial creation and configuration of the Wine directory and before the target application is installed, and again after installation and configuration of the application. The applications I was using are 7-Zip, xplorer², FontPage, IPaddress, SciTE (or EditPad Lite), @icon sushi, and Dependency Walker. Some of these are only for debugging. After prep the mergeable application directory is shared with wineappshare.sh like other applications and wineappinstall.sh performs the special handling of their branches and patches when other applications are installed for users.
wineappinstall.sh - where things got ugly. Setting up the read/write directory and creating the template pam_mount entry for Aufs mounting was easy. So was copying over the icons. Recreating symlinks between the user's profile directories (My Doucments > ~/Documents, etc.) was a lot more complicated. I wanted to do it correctly by following the XDG Base Directory Specification, first by checking for a local (user) configuration, then the system-wide defaults, then look for commonly-used defaults, and finally just defaulting to ~. I got that part sort of working. The final problem was trying to merge the shared application menu entries, the "mergeable" applications entires, and any existing entries without damage. Doing this in an orderly (and deterministic) fashion is difficult and shell scripts aren't great for text processing. That's where I left off.
The scripts all require the Wine directory name and it's assumed to be in ~. For example, specifying Diablo II's directory would just be ".wine-Diablo_II". Note that spaces should not exist in the directory name. The Winesharer scripts handle them but others, like Dan Kegel's winetricks, had trouble with them. Second, the scripts search through the registry for the username of the installer in order to change it to the target user's name later. Because of this it needs to be globally unique (in ALL Windows applications) so the scripts don't change something that is not related to the user. I had a Wine administration account named "wineadmin" which should be safe as long as there isn't any client/server wine (the drinking type) management applications that use the same keyword or value in the registry. The sharing directory is in /srv to comply with v2.3 of the Filesystem Hierarchy Standard. I was using a "_rw" suffix for the read/write branch directories.
What's next? Nothing. This was intended to be a one week feasibility study but suffered from a ridiculous amount of feature creep. Between the earlier draft scripts and command-line tests I know it's possible to do but text processing in shell scripts is tedious and I don't have the time to finish it. The scripts are ugly, broken, and can't handle all possible problems. I do like cats so I made it a point to eliminate cat abuse but I left a lot of dysfunctional grep|sed marriages since reducing them is time consuming and the extra processes give my idle Phenom cores something to do. There's a lot of arrays and case statements. I didn't follow any column limits either. Because this was a work-in-progress I also discarded the Unixy notion of minimal feedback - my scripts write entire novels to the terminal. There are a lot of comments, especially in the unfinished portions of wineappinstall.sh (which is guaranteed to not work). I didn't even begin implementing the integrated multimedia help styled after 0verkill. Instead, this pre-alpha work includes a bonus pack of bugs. I don't think the scripts can fail in such a way as to wipe out your filesystems and install Vista but I'm not guaranteeing they won't either. This mess was developed on Ubuntu 8.04 (Hardy Heron).
My goal with this project is to inspire others to implement a more robust (not to mention functional) solution incorporating these ideas. A user space union filesystem like FunionFS would be more convenient than Aufs but I don't know of any alternatives. I think that having submenus for mergeables in each application menu is ugly. A front-end utility for dynamically setting WINEPREFIX and launching them would be better. One problem I thought of but don't know how to handle are applications that require registration keys at installation instead of first-run. For some, their registration keys can be purged from the registry and they will prompt for a new key when executed again. Others won't and may lock-out and refuse to run even with a valid key, requiring a full reinstall.
4 comments:
Nice! im useless with bash otherwise id take a stab at it..
I have some ideas tho: how about rsyncin the .wine dir against a prestine .wine dir after install, then rm .wine, cp .wine_orig .wine. and at runtime resync .wine with the game dir? no ugly fs hacks needed. the rsyncing could possibly even be automatic with inotify or something..
I sure hope this script gets some loving..
If I understand correctly you propose using rsync to automatically create and synch a copy of a shared .wine-app directory into the user's home directory at login or from a menu item when the applications is launched. Afterwards the unchanged files are deleted at logout or when the application closes. The files left in the user's home would only be the ones that changed. This would reduce the space wasted by duplicating files on demand when an application is used but with large ones (like most major games) the duplication could take a long time and result in a slow application start. With a union filesystem the mounting is practically instantaneous. Configuration is complicated because FunionFS is broke and Aufs needs root permissions to mount anything. If FunionFS was fixed or replaced with a different user-space union filesystem then configuration would be much simpler.
You wrote "Note that spaces should not exist in the directory name. The Winesharer scripts handle them but others, like Dan Kegel's winetricks, had trouble with them."
We fixed all the known problems in winetricks related to spaces in paths. What problems did you see?
Please either email them to me at dank@kegel.com, or file an issue at
http://code.google.com/p/winezeug/issues/list if you're feeling energetic. Thanks!
@Dan Kegel
I don't remember which app(s) caused it but it has been a while so maybe it's already fixed. If I encounter a similar problem again I'll let you know.
Post a Comment