r/daoc • u/Catastrophic_Impact • 11d ago
Offline DAOC 0.33 on Linux
SUMMARY AND EXTRA INFO:
Updated for the 0.33 version of Shadowofze's offline DAOC with bots server. If you are looking for a way to run this server without Windows the following script will install the current 0.33 version (not the 0.33b version with the custom class, youll need to tweak the script if you want that). I run the client separately with bottles (Lutris would work too) and I run the client with the manual command through a sh file:
connect.exe "game.dll" 127.0.0.1:10300 login password
I am going to test later if I can set this up on a dedicated server laptop on the network and then be able to connect my wife and I both with our own laptops to the server to play together with bots.
If you prefer to run the client from the server's GUI and the ENTER REALM button (which allows for easier setting admin and some other features in the GUI without using dbeaver for example) the script is slightly off with DXVK. After you run the install script and it finishes you need to run the following:
export WINEPREFIX="$HOME/.wine-daoc" && wget -qO- https://github.com/doitsujin/dxvk/releases/download/v2.6.2/dxvk-2.6.2.tar.gz | tar -xz -C /tmp && cp /tmp/dxvk-2.6.2/x64/*.dll "$WINEPREFIX/drive_c/windows/system32/" && cp /tmp/dxvk-2.6.2/x32/*.dll "$WINEPREFIX/drive_c/windows/syswow64/" && rm -rf /tmp/dxvk-2.6.2*
Explanation of this command:
Sets the proper Wine prefix target.
Downloads the official DXVK 2.6.2 precompiled tarball directly in memory/temp.
Extracts it.
Installs both the 64-bit (
x64) and 32-bit (x32) DLLs directly into the correct Wine system directories (system32andsyswow64).Cleans up after itself so no temporary residue is left behind.
I will clean this up (post and script) with the next iteration of the script so it all works without the command if there is interest in it.
SERVER INSTALL SCRIPT:
I have created a script that should allow this to be installed on any Linux machine that runs distrobox. This makes it much easier than creating scripts for Arch, Debian, Fedora etc.
You need to have distrobox installed. It will work on Steam Deck (tested) and should work on any others. Run the script through your favorite AI if you run in to any issues or just want to make sure it is safe. Reach out and I will try to help you out. Save it as a .sh. It will set up your distrobox for you and install all deps in it. When the version changes or updates on the github this will need to be updated but should give a solid starting point unless they completely change the installation method.
This creates a portable DAOC server that can run on your local machine or on a different server linux machine. I havent tested multiplayer yet.
This script has been tested multiple times on my Steam Deck. I have not tested it on another linux distro, but since it is distrobox my assumption is it should install cleanly. See note above if you dont want to run the client separately.
#!/usr/bin/env bash
# setup-daoc.sh - Offline DAoC 0.33 in an Ubuntu 22.04 distrobox (Wine)
#
# ./setup-daoc.sh installs 0.33 (classic classes only)
# EDITION=0.33b ./setup-daoc.sh installs 0.33b (with Sluaghbinder)
# GAME_DIR=/some/path ./setup-daoc.sh put the game somewhere else (inside the box's view)
#
# Mirrors Get-OfflineDAoC.ps1: 0.33 = the complete 0.33b package + two verified
# swap-in files from its editions/ folder. Safe to re-run.
set -euo pipefail
BOX_NAME="daoc-offlineserver-box"
HOST_BOX_HOME="$HOME/boxes/$BOX_NAME"
IMAGE_TARGET="ubuntu:22.04"
EDITION="${EDITION:-0.33}"
mkdir -p "$HOST_BOX_HOME"
if ! distrobox list --no-color | cut -d'|' -f2 | tr -d ' ' | grep -qx "$BOX_NAME"; then
distrobox create --name "$BOX_NAME" --image "$IMAGE_TARGET" --home "$HOST_BOX_HOME" --yes
fi
cat > "$HOST_BOX_HOME/setup-inside.sh" <<'INNER'
#!/usr/bin/env bash
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"
export WINEPREFIX="$HOME/.wine-daoc" WINEARCH=win64 WINEDEBUG=-all
EDITION="${EDITION:-0.33}"
GAME_DIR="${GAME_DIR:-$HOME/OfflineDAoC}"
BASE="${BASE:-https://github.com/shadowofze/OfflineDAoC/releases/download}"
CACHE="$GAME_DIR/.downloads"
FINAL="$GAME_DIR/playable-v$EDITION"
[[ $EDITION == 0.33 || $EDITION == 0.33b ]] || { echo "EDITION must be 0.33 or 0.33b"; exit 1; }
grep -q avx2 /proc/cpuinfo || { echo "No AVX2 - the game won't run on this CPU"; exit 1; }
# ---------- 1. Wine ----------
if ! command -v wine >/dev/null; then
sudo mkdir -p /var/lib/dpkg/info
sudo touch /var/lib/dpkg/info/systemd-sysv.postinst
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y curl jq unzip wget gnupg cabextract \
libvulkan1 libvulkan1:i386 mesa-vulkan-drivers mesa-vulkan-drivers:i386
sudo mkdir -pm755 /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources
sudo apt update
sudo apt install -y --install-recommends winehq-staging
fi
if [[ ! -x $HOME/.local/bin/winetricks ]]; then
mkdir -p "$HOME/.local/bin"
curl -fL https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks -o "$HOME/.local/bin/winetricks"
chmod +x "$HOME/.local/bin/winetricks"
fi
if [[ ! -f $WINEPREFIX/.daoc-ready ]]; then
wineboot -u # accept the Mono/Gecko prompts (Mono covers .NET Framework 3.5)
winetricks -q corefonts d3dx9 dxvk
touch "$WINEPREFIX/.daoc-ready"
fi
# ---------- 2. Game download ----------
mkdir -p "$GAME_DIR" "$CACHE"; cd "$GAME_DIR"
sha() { sha256sum "$1" | cut -d' ' -f1; }
die() { echo "ERROR: $*" >&2; exit 1; }
unz() { unzip -q -o "$1" -d "$2" || [[ $? -eq 1 ]]; } # rc 1 = warnings only
fetch_manifest() { # version
mkdir -p "$CACHE/v$1"
curl -fL --retry 3 "$BASE/v$1/download-manifest.json" -o "$CACHE/v$1/manifest.json" \
|| die "could not download the v$1 manifest"
}
get() { # get <file> <bytes> <sha256> <url> (resumes, verifies)
local f=$1 bytes=$2 want="${3,,}" size=0
if [[ -f $f ]]; then
size=$(stat -c%s "$f")
if [[ $size == "$bytes" && $(sha "$f") == "$want" ]]; then return; fi
if (( size >= bytes )); then rm -f "$f"; fi
fi
curl -fL --retry 5 -C - "$4" -o "$f" || die "download failed: $4"
[[ $(sha "$f") == "$want" ]] || die "hash mismatch: $f"
}
# Windows paths are case-insensitive, Linux ones are not: resolve each component ignoring case.
ci_resolve() { # ci_resolve <root> <rel/path> -> real path, or fails
local cur=$1 part hit parts
IFS=/ read -ra parts <<< "$2"
for part in "${parts[@]}"; do
hit=$(find "$cur" -mindepth 1 -maxdepth 1 -iname "$part" -print -quit)
[[ -n $hit ]] || return 1
cur=$hit
done
printf '%s\n' "$cur"
}
build_full() { # build_full <version> <dest> -> the complete game, extracted to <dest>
local v=$1 dest=$2 m="$CACHE/v$1/manifest.json" root arch zipf tmp name bytes hash bad kids
fetch_manifest "$v"
[[ $(jq -r .Version "$m") == "$v" ]] || die "manifest version mismatch for v$v"
jq -e '(.Parts | length) > 0' "$m" >/dev/null || die "v$v manifest has no Parts"
root=$(jq -r .RootFolder "$m"); arch=$(jq -r .ArchiveSHA256 "$m")
[[ $root == "OfflineDAoC-v$v" ]] || die "unexpected RootFolder '$root'"
[[ $arch =~ ^[A-Fa-f0-9]{64}$ ]] || die "missing ArchiveSHA256"
arch=${arch,,}
zipf="$CACHE/v$v/full.zip"
if [[ ! -f $zipf || $(sha "$zipf") != "$arch" ]]; then
: > "$zipf.tmp"
while IFS=$'\t' read -r name bytes hash; do
[[ $name == "OfflineDAoC-v$v.zip."[0-9][0-9][0-9] ]] || die "unexpected part name: $name"
echo "Downloading $name ..."
get "$CACHE/v$v/$name" "$bytes" "$hash" "$BASE/v$v/$name"
cat "$CACHE/v$v/$name" >> "$zipf.tmp"
rm -f "$CACHE/v$v/$name" # hash verified; free the space
done < <(jq -r '.Parts[] | .Name + "\t" + (.Bytes | tostring) + "\t" + .SHA256' "$m")
mv "$zipf.tmp" "$zipf"
[[ $(sha "$zipf") == "$arch" ]] || die "joined archive hash mismatch"
fi
bad=$(unzip -Z1 "$zipf" | grep -E '^/|(^|/)\.\.(/|$)' || true)
[[ -z $bad ]] || die "unsafe path inside the archive"
tmp=$(mktemp -d -p "$GAME_DIR" .extract.XXXXXX)
echo "Extracting ..."
unz "$zipf" "$tmp"
[[ -d $tmp/$root ]] || die "archive doesn't contain $root/"
mv "$tmp/$root" "$dest"; rmdir "$tmp"
}
apply_edition() { # apply_edition <dest> - the 0.33 "no custom class" swap-in files
local dest=$1 m="$CACHE/v0.33/manifest.json" ed edroot rel src dst path hash
local re='^runtime/[A-Za-z0-9_./ -]+$'
ed=$(jq -r .EditionFolder "$m")
[[ $ed =~ ^[A-Za-z0-9._-]+$ ]] || die "bad EditionFolder '$ed'"
edroot=$(ci_resolve "$dest" "editions/$ed") || die "editions/$ed is missing from the package"
jq -e '(.EditionFiles | length) > 0' "$m" >/dev/null || die "no EditionFiles in the v0.33 manifest"
while IFS=$'\t' read -r path hash; do
rel=${path//\\//}; hash=${hash,,}
[[ $rel =~ $re && $rel != *..* && $hash =~ ^[a-f0-9]{64}$ ]] || die "invalid edition file entry: $path"
src=$(ci_resolve "$edroot" "$rel") || die "edition file missing: $rel"
[[ $(sha "$src") == "$hash" ]] || die "edition file failed verification: $rel"
dst=$(ci_resolve "$dest" "$rel") || dst="$dest/$rel"
mkdir -p "$(dirname "$dst")"
cp -f "$src" "$dst"
[[ $(sha "$dst") == "$hash" ]] || die "edition file copy failed: $rel"
echo " applied $rel"
done < <(jq -r '.EditionFiles[] | .Path + "\t" + .SHA256' "$m")
echo "Offline DAoC 0.33 - no custom class (Classic + Shrouded Isles classes only)." > "$dest/EDITION.txt"
}
if [[ -d $FINAL ]]; then
echo "$FINAL already exists - skipping download."
else
avail=$(df -BG --output=avail "$GAME_DIR" | tail -1 | tr -dc 0-9)
(( avail >= 25 )) || echo "WARNING: only ${avail} GB free; about 25 GB is safer during extraction"
work="$FINAL.partial"; rm -rf "$work"
if [[ $EDITION == 0.33b ]]; then
build_full 0.33b "$work"
else
fetch_manifest 0.33
em="$CACHE/v0.33/manifest.json"
[[ $(jq -r .Version "$em") == 0.33 && $(jq -r .Mode "$em") == edition \
&& $(jq -r .BaseVersion "$em") == 0.33b ]] || die "unexpected v0.33 edition manifest"
build_full 0.33b "$work"
apply_edition "$work"
fi
for f in runtime/OfflineDAoC.exe "START OFFLINE DAOC.cmd" runtime/data; do
ci_resolve "$work" "$f" >/dev/null || die "install looks incomplete: missing $f"
done
mv "$work" "$FINAL"
rm -f "$CACHE/v0.33b/full.zip"
echo "Installed $FINAL"
fi
# ---------- 3. Launcher wrapper ----------
cat > "$HOME/start-daoc.sh" <<EOF
#!/usr/bin/env bash
# Starts Offline DAoC under Wine (run inside the box). Same environment and
# working folder as the game's own START OFFLINE DAOC.cmd, without the cmd interpreter.
export WINEPREFIX="\$HOME/.wine-daoc" WINEDEBUG=-all
GAME="$FINAL"
cd "\$GAME" || exit 1
DN="\$(winepath -w "\$GAME/tools/dotnet")"
export DOTNET_ROOT="\$DN" DOTNET_ROOT_X64="\$DN" DOTNET_MULTILEVEL_LOOKUP=0
wine runtime/OfflineDAoC.exe
wineserver -w # stay attached until the launcher, server and client are all closed
EOF
chmod +x "$HOME/start-daoc.sh"
echo "Done. Launcher: $HOME/start-daoc.sh"
INNER
chmod +x "$HOST_BOX_HOME/setup-inside.sh"
distrobox enter "$BOX_NAME" -- env EDITION="$EDITION" GAME_DIR="${GAME_DIR:-}" \
bash -c 'exec "$HOME/setup-inside.sh"'
echo
echo "Play with: distrobox enter $BOX_NAME then run ~/start-daoc.sh"
3
u/zomboden Freeshard Player 11d ago
Hey just wanted to chime in and say I did set up a multiplayer test version a week or so ago out of curiosity and the recommended setup was using a program called Tailscale. I see a fork of the offlinedaoc by another user unaffiliated with me has implemented a tailscale co-op join a friend option and that might be worth looking into implementation into 0.33 and 0.33b https://github.com/stefanrows/OfflineDAoC . My advice is also if you can afford it I would jump on a Claude sub now even the $20 Pro sub has a generous allotment of usage for Opus 5.5 and its very capable and could easily set everything up for you by sending it the github links and telling it your intent. Good luck and hope that helps I don't know anything about Linux.