#!/usr/bin/env bash # Prepare a Bottles/Wine prefix so Fraud Launcher 2 and Cosmonarchy BW will run. # # Fixes two things that a stock Wine prefix gets wrong: # # 1. Fraud Launcher 2 (.NET 8 WPF) aborts at startup because Wine's icu.dll # forwards to icuuc68.dll, which does not exist: # err:module:find_forwarded_export module not found for forward # 'icuuc68.u_charsToUChars_68' used by L"C:\\windows\\system32\\icu.dll" # Setting DOTNET_SYSTEM_GLOBALIZATION_USENLS=1 makes .NET use Windows NLS # instead of ICU. (DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 also works but # throws away culture support, so prefer NLS.) # # 2. aize_debug.qdp initialises its console with FreeType against two Windows # system fonts that Wine does not ship, and dies with "Fonts could not be # loaded": # %systemroot%\fonts\lucon.ttf (Lucida Console) # %systemroot%\fonts\arialuni.ttf (Arial Unicode MS) # FreeType opens these by path, not by name, so any monospace / wide-coverage # TTF dropped in under those filenames satisfies it. # # 3. Cosmonarchy ships cnc-ddraw (ddraw.dll beside StarCraft.exe), but Wine # loads its own builtin ddraw.dll unless told otherwise, so cnc-ddraw is # silently ignored. StarCraft then performs a real exclusive-fullscreen # 640x480 DirectDraw mode switch, which on a multi-monitor X11 desktop # shows up as an entirely black screen. Overriding ddraw to native,builtin # makes Wine load cnc-ddraw so the game renders in a normal window. # # 4. With cnc-ddraw actually loaded, its default renderer=auto still draws a # black window here; renderer=gdi renders correctly. The bundled GLSL # shader only applies to the OpenGL renderer, so it is cleared alongside. # (Both were changed together, so which one mattered is not isolated - if # you want the shader back, try renderer=opengl first.) # # Usage: # tools/linux/setup-bottle.sh [--check] [--bottle NAME | --prefix PATH] # # --check report what is missing, change nothing # --bottle NAME Bottles bottle name (default: Blizzard) # --prefix PATH a plain WINEPREFIX, instead of a Bottles bottle # --game-dir PATH the StarCraft directory holding ddraw.ini # (default: read from the prefix's registry InstallPath) # set -euo pipefail BOTTLE=Blizzard PREFIX= GAMEDIR= CHECK=0 while [ $# -gt 0 ]; do case "$1" in --check) CHECK=1; shift ;; --bottle) BOTTLE="${2:?--bottle needs a name}"; shift 2 ;; --prefix) PREFIX="${2:?--prefix needs a path}"; shift 2 ;; --game-dir) GAMEDIR="${2:?--game-dir needs a path}"; shift 2 ;; -h|--help) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "unknown argument: $1" >&2; exit 2 ;; esac done if [ -z "$PREFIX" ]; then for base in \ "$HOME/.var/app/com.usebottles.bottles/data/bottles/bottles" \ "$HOME/.local/share/bottles/bottles" do if [ -d "$base/$BOTTLE" ]; then PREFIX="$base/$BOTTLE"; break; fi done fi if [ -z "$PREFIX" ] || [ ! -d "$PREFIX/drive_c" ]; then echo "error: no Wine prefix found (bottle '$BOTTLE')." >&2 echo " pass --bottle NAME or --prefix /path/to/prefix" >&2 exit 1 fi echo "prefix: $PREFIX" rc=0 note() { if [ "$CHECK" = 1 ]; then echo " MISSING $1"; rc=1; else echo " fixed $1"; fi; } ok() { echo " ok $1"; } # --- 1. fonts ----------------------------------------------------------------- # Each entry is "target-filenamecandidate source paths, first match wins". install_font() { local target="$1"; shift local fontdir dest src fontdir="$PREFIX/drive_c/windows/Fonts" [ -d "$fontdir" ] || fontdir="$PREFIX/drive_c/windows/fonts" mkdir -p "$fontdir" dest="$fontdir/$target" if [ -s "$dest" ]; then ok "$target"; return 0; fi if [ "$CHECK" = 1 ]; then note "$target"; return 0; fi for src in "$@"; do if [ -s "$src" ]; then cp -- "$src" "$dest" note "$target <- $(basename "$src")" return 0 fi done # last resort: ask fontconfig for anything matching if command -v fc-match >/dev/null 2>&1; then src=$(fc-match -f '%{file}' "${FC_QUERY:-sans}" 2>/dev/null || true) if [ -n "$src" ] && [ -s "$src" ]; then cp -- "$src" "$dest" note "$target <- $(basename "$src") (fontconfig)" return 0 fi fi echo " FAILED $target - no source font found; install fonts-dejavu" >&2 rc=1 } echo "fonts (for aize_debug.qdp's FreeType console):" FC_QUERY=monospace install_font lucon.ttf \ /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf \ /usr/share/fonts/dejavu/DejaVuSansMono.ttf \ /usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf \ /usr/share/fonts/liberation/LiberationMono-Regular.ttf FC_QUERY=sans install_font arialuni.ttf \ /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf \ /usr/share/fonts/dejavu/DejaVuSans.ttf \ /usr/share/fonts/truetype/noto/NotoSans-Regular.ttf \ /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf # --- 2. .NET globalization (Fraud Launcher 2) --------------------------------- echo ".NET globalization (for Fraud Launcher 2):" VAR=DOTNET_SYSTEM_GLOBALIZATION_USENLS YML="$PREFIX/bottle.yml" if [ -f "$YML" ]; then if grep -q "^ *$VAR:" "$YML"; then ok "$VAR set in bottle.yml" elif [ "$CHECK" = 1 ]; then note "$VAR in bottle.yml" else cp -- "$YML" "$YML.bak-$(date +%Y%m%d%H%M%S)" python3 - "$YML" "$VAR" <<'PY' import re, sys path, var = sys.argv[1], sys.argv[2] s = open(path, encoding='utf-8').read() line = " %s: '1'\n" % var if re.search(r'^Environment_Variables:\s*\{\}\s*$', s, re.M): s = re.sub(r'^Environment_Variables:\s*\{\}\s*$', "Environment_Variables:\n" + line.rstrip('\n'), s, count=1, flags=re.M) elif re.search(r'^Environment_Variables:\s*$', s, re.M): s = re.sub(r'^(Environment_Variables:\s*\n)', r'\1' + line, s, count=1, flags=re.M) else: raise SystemExit("could not find Environment_Variables: in %s" % path) open(path, 'w', encoding='utf-8').write(s) PY note "$VAR in bottle.yml (backup written alongside)" fi else # plain WINEPREFIX: no bottle.yml to edit, so tell the caller how to export it if [ "$CHECK" = 1 ]; then note "$VAR (no bottle.yml; export it yourself)" else echo " note no bottle.yml here - run the launcher with:" echo " $VAR=1 wine 'Fraud Launcher 2.exe'" fi fi # --- 3. ddraw override, so Wine loads cnc-ddraw rather than its own builtin ---- echo "ddraw override (cnc-ddraw):" REG="$PREFIX/user.reg" HELPER="$(dirname "$0")/add-dll-override.py" if [ ! -f "$REG" ]; then echo " FAILED no user.reg in $PREFIX" >&2 rc=1 elif grep -q '^"ddraw"=' "$REG"; then ok "ddraw = $(grep -m1 '^\"ddraw\"=' "$REG" | cut -d= -f2- | tr -d '\"')" elif [ "$CHECK" = 1 ]; then note "ddraw=native,builtin in user.reg" elif pgrep -f "wineserver" >/dev/null 2>&1 && pgrep -af wineserver | grep -qF "$PREFIX"; then echo " SKIPPED a wineserver is live for this prefix - close the game and re-run" >&2 rc=1 elif [ ! -f "$HELPER" ]; then echo " FAILED helper not found: $HELPER" >&2 rc=1 else cp -- "$REG" "$REG.bak-$(date +%Y%m%d%H%M%S)" python3 "$HELPER" "$REG" ddraw native,builtin >/dev/null note "ddraw=native,builtin in user.reg (backup written alongside)" fi # --- 4. cnc-ddraw renderer ---------------------------------------------------- echo "cnc-ddraw renderer:" if [ -z "$GAMEDIR" ]; then # HKLM\Software\Wow6432Node\Blizzard Entertainment\Starcraft -> InstallPath winpath=$(grep -a -m1 '^"InstallPath"=' "$PREFIX/system.reg" 2>/dev/null \ | sed 's/^"InstallPath"="//; s/"$//') if [ -n "$winpath" ]; then rel=$(printf '%s' "$winpath" | sed 's/\\\\/\//g; s|^[A-Za-z]:||; s|/*$||') GAMEDIR="$PREFIX/drive_c$rel" fi fi INI="$GAMEDIR/ddraw.ini" PATCHER="$(dirname "$0")/patch-ddraw-ini.py" if [ -z "$GAMEDIR" ] || [ ! -f "$INI" ]; then echo " SKIPPED no ddraw.ini found${GAMEDIR:+ at $GAMEDIR}; pass --game-dir" >&2 elif [ ! -f "$PATCHER" ]; then echo " FAILED helper not found: $PATCHER" >&2 rc=1 elif python3 "$PATCHER" --check "$INI" renderer=gdi shader= >/dev/null 2>&1; then ok "renderer=gdi in $(basename "$GAMEDIR")/ddraw.ini" elif [ "$CHECK" = 1 ]; then note "renderer=gdi in $(basename "$GAMEDIR")/ddraw.ini" else cp -- "$INI" "$INI.bak-$(date +%Y%m%d%H%M%S)" python3 "$PATCHER" "$INI" renderer=gdi shader= >/dev/null note "renderer=gdi, shader= in $(basename "$GAMEDIR")/ddraw.ini (backup written alongside)" fi echo if [ "$CHECK" = 1 ] && [ "$rc" != 0 ]; then echo "check failed: run without --check to apply." elif [ "$rc" = 0 ]; then echo "bottle ready." fi exit $rc