461 lines
12 KiB
Bash
Executable File
461 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
CACHE_DIR="${CACHE_DIR:-$REPO_ROOT/.cache}"
|
|
DIST_DIR="${DIST_DIR:-$REPO_ROOT/dist}"
|
|
WORK_ROOT="${WORK_ROOT:-$CACHE_DIR/work}"
|
|
DXVK_CACHE_DIR="${DXVK_CACHE_DIR:-$CACHE_DIR/dxvk}"
|
|
METADATA_DIR="${METADATA_DIR:-$DIST_DIR/metadata}"
|
|
STEAM_COMPAT_DIR_DEFAULT="${STEAM_COMPAT_DIR_DEFAULT:-$HOME/.local/share/Steam/compatibilitytools.d}"
|
|
UPSTREAM_REPO="${UPSTREAM_REPO:-GloriousEggroll/proton-ge-custom}"
|
|
ELSWORD_GE_VERSION="${ELSWORD_GE_VERSION:-11-1}"
|
|
PROTON_TAG="${PROTON_TAG:-GE-Proton${ELSWORD_GE_VERSION}}"
|
|
PROTON_ASSET="${PROTON_ASSET:-x86_64}"
|
|
RUNTIME_APPID="${RUNTIME_APPID:-4183110}"
|
|
WINE_SERIES="${WINE_SERIES:-Wine 11}"
|
|
DXVK_STABLE_TAG_DEFAULT="${DXVK_STABLE_TAG_DEFAULT:-latest}"
|
|
DXVK_GIT_REF="${DXVK_GIT_REF:-origin/master}"
|
|
MINGW_PTHREAD_ROOT="${MINGW_PTHREAD_ROOT:-}"
|
|
|
|
SAFE_LAUNCH_OPTIONS='LD_PRELOAD="" PROTON_DISCORD_BRIDGE=0 PROTON_USE_OPTISCALER=0 PROTON_USE_D7VK=0 LOW_LATENCY_LAYER=0 MANGOHUD=0 ENABLE_VKBASALT=0 VK_INSTANCE_LAYERS="" DXVK_HUD=0 %command%'
|
|
PERF_LAUNCH_OPTIONS='LD_PRELOAD="" PROTON_DISCORD_BRIDGE=0 PROTON_USE_OPTISCALER=0 PROTON_USE_D7VK=0 LOW_LATENCY_LAYER=1 MANGOHUD=0 ENABLE_VKBASALT=0 VK_INSTANCE_LAYERS="" DXVK_STATE_CACHE=1 DXVK_LOG_LEVEL=none %command%'
|
|
|
|
DEFAULT_ENV_KEYS=(
|
|
PROTON_DISCORD_BRIDGE
|
|
PROTON_USE_OPTISCALER
|
|
PROTON_USE_D7VK
|
|
MANGOHUD
|
|
ENABLE_VKBASALT
|
|
)
|
|
|
|
runtime_dll_names=(
|
|
libstdc++-6.dll
|
|
libgcc_s_seh-1.dll
|
|
libgcc_s_dw2-1.dll
|
|
libwinpthread-1.dll
|
|
libmcfgthread-2.dll
|
|
)
|
|
|
|
say() {
|
|
printf "\n==> %s\n" "$1" >&2
|
|
}
|
|
|
|
die() {
|
|
printf "Error: %s\n" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
|
|
}
|
|
|
|
require_toolchain_commands() {
|
|
local cmd
|
|
for cmd in \
|
|
bash \
|
|
curl \
|
|
git \
|
|
jq \
|
|
meson \
|
|
ninja \
|
|
objdump \
|
|
python3 \
|
|
tar \
|
|
zstd \
|
|
glslangValidator \
|
|
x86_64-w64-mingw32-gcc \
|
|
x86_64-w64-mingw32-g++ \
|
|
i686-w64-mingw32-gcc \
|
|
i686-w64-mingw32-g++; do
|
|
require_cmd "$cmd"
|
|
done
|
|
}
|
|
|
|
ensure_repo_layout() {
|
|
mkdir -p "$CACHE_DIR" "$WORK_ROOT" "$DIST_DIR" "$METADATA_DIR"
|
|
}
|
|
|
|
tool_name_for_variant() {
|
|
case "$1" in
|
|
stable) printf "Elsword-GE-%s\n" "$ELSWORD_GE_VERSION" ;;
|
|
dxvk-git) printf "Elsword-GE-%s+dxvk-git\n" "$ELSWORD_GE_VERSION" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
variant_label() {
|
|
case "$1" in
|
|
stable) printf "stable\n" ;;
|
|
dxvk-git) printf "dxvk-git\n" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
dxvk_channel_for_variant() {
|
|
case "$1" in
|
|
stable) printf "stable\n" ;;
|
|
dxvk-git) printf "git\n" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
dxvk_tag_for_variant() {
|
|
case "$1" in
|
|
stable) printf "%s\n" "$DXVK_STABLE_TAG_DEFAULT" ;;
|
|
dxvk-git) printf "git\n" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
archive_name_for_variant() {
|
|
case "$1" in
|
|
stable) printf "Elsword-GE-%s.tar.zst\n" "$ELSWORD_GE_VERSION" ;;
|
|
dxvk-git) printf "Elsword-GE-%s+dxvk-git.tar.zst\n" "$ELSWORD_GE_VERSION" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
archive_alias_for_variant() {
|
|
case "$1" in
|
|
stable) printf "%s\n" "$(archive_name_for_variant "$1")" ;;
|
|
dxvk-git) printf "Elsword-GE-%s-dxvk-git.tar.zst\n" "$ELSWORD_GE_VERSION" ;;
|
|
*) die "unsupported variant: $1" ;;
|
|
esac
|
|
}
|
|
|
|
metadata_file_for_variant() {
|
|
printf "%s/%s.env\n" "$METADATA_DIR" "$1"
|
|
}
|
|
|
|
release_title() {
|
|
printf "Elsword Proton-GE %s\n" "$ELSWORD_GE_VERSION"
|
|
}
|
|
|
|
workdir_for_variant() {
|
|
local variant="$1"
|
|
mktemp -d "$WORK_ROOT/${variant}.XXXXXX"
|
|
}
|
|
|
|
cleanup_workdir() {
|
|
local dir="$1"
|
|
if [[ -n "$dir" && -d "$dir" ]]; then
|
|
rm -rf "$dir"
|
|
fi
|
|
}
|
|
|
|
get_latest_dxvk_tag() {
|
|
curl -fsSL https://api.github.com/repos/doitsujin/dxvk/releases/latest \
|
|
| jq -r '.tag_name'
|
|
}
|
|
|
|
prepare_dxvk_cache() {
|
|
if [[ ! -d "$DXVK_CACHE_DIR/.git" ]]; then
|
|
say "Clone DXVK cache"
|
|
git clone --recursive https://github.com/doitsujin/dxvk.git "$DXVK_CACHE_DIR" >&2
|
|
else
|
|
say "Update DXVK cache"
|
|
git -C "$DXVK_CACHE_DIR" fetch --tags origin >&2
|
|
git -C "$DXVK_CACHE_DIR" checkout master >&2
|
|
git -C "$DXVK_CACHE_DIR" reset --hard "$DXVK_GIT_REF" >&2
|
|
git -C "$DXVK_CACHE_DIR" submodule update --init --recursive >&2
|
|
git -C "$DXVK_CACHE_DIR" clean -fdx >&2
|
|
fi
|
|
}
|
|
|
|
prepare_dxvk_source() {
|
|
local variant="$1"
|
|
local workdir="$2"
|
|
local channel tag checkout_dir
|
|
channel="$(dxvk_channel_for_variant "$variant")"
|
|
tag="$(dxvk_tag_for_variant "$variant")"
|
|
checkout_dir="$workdir/dxvk-src"
|
|
|
|
prepare_dxvk_cache
|
|
say "Prepare DXVK source ($(variant_label "$variant"))"
|
|
git clone --recursive "$DXVK_CACHE_DIR" "$checkout_dir" >&2
|
|
|
|
if [[ "$channel" == "stable" ]]; then
|
|
if [[ "$tag" == "latest" ]]; then
|
|
tag="$(get_latest_dxvk_tag)"
|
|
fi
|
|
[[ -n "$tag" ]] || die "failed to resolve DXVK stable tag"
|
|
git -C "$checkout_dir" fetch --tags https://github.com/doitsujin/dxvk.git >&2
|
|
git -C "$checkout_dir" checkout "refs/tags/$tag" >&2
|
|
git -C "$checkout_dir" submodule update --init --recursive >&2
|
|
fi
|
|
|
|
printf "%s\n" "$checkout_dir"
|
|
}
|
|
|
|
download_proton_tarball() {
|
|
local asset proton_tarball proton_url
|
|
|
|
if [[ "$PROTON_ASSET" == "aarch64" ]]; then
|
|
asset="${PROTON_TAG}-aarch64.tar.gz"
|
|
else
|
|
asset="${PROTON_TAG}.tar.gz"
|
|
fi
|
|
|
|
proton_tarball="$CACHE_DIR/$asset"
|
|
proton_url="https://github.com/${UPSTREAM_REPO}/releases/download/${PROTON_TAG}/${asset}"
|
|
|
|
if [[ ! -f "$proton_tarball" ]]; then
|
|
say "Download Proton-GE ${PROTON_TAG}"
|
|
curl -fL "$proton_url" -o "$proton_tarball"
|
|
else
|
|
say "Reuse cached Proton tarball"
|
|
fi
|
|
|
|
printf "%s\n" "$proton_tarball"
|
|
}
|
|
|
|
extract_proton_tool() {
|
|
local workdir="$1"
|
|
local tarball="$2"
|
|
local proton_root tool_root
|
|
|
|
proton_root="$workdir/proton"
|
|
mkdir -p "$proton_root"
|
|
tar -xf "$tarball" -C "$proton_root"
|
|
tool_root="$(find "$proton_root" -mindepth 1 -maxdepth 1 -type d | head -n1)"
|
|
[[ -n "$tool_root" ]] || die "could not find extracted Proton root"
|
|
printf "%s\n" "$tool_root"
|
|
}
|
|
|
|
inject_wrapper_defaults() {
|
|
local proton_wrapper="$1"
|
|
|
|
[[ -f "$proton_wrapper" ]] || die "missing proton wrapper: $proton_wrapper"
|
|
|
|
python3 - "$proton_wrapper" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
path = Path(sys.argv[1])
|
|
text = path.read_text()
|
|
marker = 'os.environ.setdefault("PROTON_DISCORD_BRIDGE", "0")'
|
|
if marker in text:
|
|
sys.exit(0)
|
|
|
|
block = (
|
|
'os.environ.setdefault("PROTON_DISCORD_BRIDGE", "0")\n'
|
|
'os.environ.setdefault("PROTON_USE_OPTISCALER", "0")\n'
|
|
'os.environ.setdefault("PROTON_USE_D7VK", "0")\n'
|
|
'os.environ.setdefault("MANGOHUD", "0")\n'
|
|
'os.environ.setdefault("ENABLE_VKBASALT", "0")\n'
|
|
)
|
|
|
|
needle = "import os\n"
|
|
if needle not in text:
|
|
raise SystemExit("Could not patch proton wrapper: missing import os")
|
|
|
|
text = text.replace(needle, needle + "\n" + block + "\n", 1)
|
|
path.write_text(text)
|
|
PY
|
|
}
|
|
|
|
resolve_runtime_dll_path() {
|
|
local arch="$1"
|
|
local dll="$2"
|
|
local compiler result nix_attr store_path
|
|
|
|
case "$arch" in
|
|
x86_64) compiler="x86_64-w64-mingw32-g++" ;;
|
|
x86) compiler="i686-w64-mingw32-g++" ;;
|
|
*) die "unsupported arch for runtime lookup: $arch" ;;
|
|
esac
|
|
|
|
result="$("$compiler" -print-file-name="$dll")"
|
|
if [[ -n "$result" && "$result" != "$dll" && -f "$result" ]]; then
|
|
printf "%s\n" "$result"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$dll" == "libmcfgthread-2.dll" ]]; then
|
|
case "$arch" in
|
|
x86_64) nix_attr="pkgsCross.mingwW64.windows.mcfgthreads" ;;
|
|
x86) nix_attr="pkgsCross.mingw32.windows.mcfgthreads" ;;
|
|
esac
|
|
store_path="$(NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix path-info --impure "nixpkgs#${nix_attr}")"
|
|
result="$(find "$store_path" -name "$dll" -type f | head -n1 || true)"
|
|
if [[ -n "$result" && -f "$result" ]]; then
|
|
printf "%s\n" "$result"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
copy_dll_to_targets() {
|
|
local dll_path="$1"
|
|
shift
|
|
local target_dir
|
|
|
|
for target_dir in "$@"; do
|
|
mkdir -p "$target_dir"
|
|
cp -f "$dll_path" "$target_dir/$(basename "$dll_path")"
|
|
done
|
|
}
|
|
|
|
replace_dxvk_payload() {
|
|
local dxvk_build_dir="$1"
|
|
local tool_root="$2"
|
|
local source_x64="$dxvk_build_dir/x64"
|
|
local source_x32="$dxvk_build_dir/x32"
|
|
local dll
|
|
|
|
mkdir -p \
|
|
"$tool_root/files/lib/wine/dxvk/x86_64-windows" \
|
|
"$tool_root/files/lib/wine/dxvk/i386-windows" \
|
|
"$tool_root/files/lib/wine/x86_64-windows" \
|
|
"$tool_root/files/lib/wine/i386-windows" \
|
|
"$tool_root/files/share/default_pfx/drive_c/windows/system32" \
|
|
"$tool_root/files/share/default_pfx/drive_c/windows/syswow64"
|
|
|
|
for dll in d3d8.dll d3d9.dll d3d10core.dll d3d11.dll dxgi.dll; do
|
|
cp -f "$source_x64/$dll" "$tool_root/files/lib/wine/dxvk/x86_64-windows/$dll"
|
|
cp -f "$source_x32/$dll" "$tool_root/files/lib/wine/dxvk/i386-windows/$dll"
|
|
cp -f "$source_x64/$dll" "$tool_root/files/lib/wine/x86_64-windows/$dll"
|
|
cp -f "$source_x32/$dll" "$tool_root/files/lib/wine/i386-windows/$dll"
|
|
cp -f "$source_x64/$dll" "$tool_root/files/share/default_pfx/drive_c/windows/system32/$dll"
|
|
cp -f "$source_x32/$dll" "$tool_root/files/share/default_pfx/drive_c/windows/syswow64/$dll"
|
|
done
|
|
}
|
|
|
|
patch_tool_manifests() {
|
|
local tool_root="$1"
|
|
local tool_name="$2"
|
|
|
|
cat > "$tool_root/compatibilitytool.vdf" <<EOF
|
|
"compatibilitytools"
|
|
{
|
|
"compat_tools"
|
|
{
|
|
"${tool_name}"
|
|
{
|
|
"install_path" "."
|
|
"display_name" "${tool_name}"
|
|
"from_oslist" "windows"
|
|
"to_oslist" "linux"
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
cat > "$tool_root/toolmanifest.vdf" <<EOF
|
|
"manifest"
|
|
{
|
|
"version" "2"
|
|
"commandline" "/proton %verb%"
|
|
"require_tool_appid" "${RUNTIME_APPID}"
|
|
"use_sessions" "1"
|
|
"compatmanager_layer_name" "proton"
|
|
}
|
|
EOF
|
|
|
|
printf "%s %s\n" "$(date +%s)" "$tool_name" > "$tool_root/version"
|
|
}
|
|
|
|
ensure_tool_tree_writable() {
|
|
local tool_root="$1"
|
|
local path
|
|
local paths=(
|
|
"$tool_root/proton"
|
|
"$tool_root/compatibilitytool.vdf"
|
|
"$tool_root/toolmanifest.vdf"
|
|
"$tool_root/version"
|
|
"$tool_root/files/lib/wine/dxvk/version"
|
|
"$tool_root/BUILD-INFO.txt"
|
|
)
|
|
|
|
for path in "${paths[@]}"; do
|
|
if [[ -e "$path" ]]; then
|
|
chmod u+w "$path"
|
|
fi
|
|
done
|
|
}
|
|
|
|
copy_required_runtime_dlls() {
|
|
local tool_root="$1"
|
|
local dxvk_build_dir="$2"
|
|
local metadata_log="$3"
|
|
local arch dll source_path required
|
|
local deps targets source_dir dxvk_dll
|
|
|
|
: > "$metadata_log"
|
|
|
|
for arch in x86_64 x86; do
|
|
if [[ "$arch" == "x86_64" ]]; then
|
|
source_dir="$dxvk_build_dir/x64"
|
|
targets=(
|
|
"$tool_root/files/lib/wine/dxvk/x86_64-windows"
|
|
"$tool_root/files/lib/wine/x86_64-windows"
|
|
"$tool_root/files/share/default_pfx/drive_c/windows/system32"
|
|
)
|
|
printf "[x86_64]\n" >> "$metadata_log"
|
|
else
|
|
source_dir="$dxvk_build_dir/x32"
|
|
targets=(
|
|
"$tool_root/files/lib/wine/dxvk/i386-windows"
|
|
"$tool_root/files/lib/wine/i386-windows"
|
|
"$tool_root/files/share/default_pfx/drive_c/windows/syswow64"
|
|
)
|
|
printf "[x86]\n" >> "$metadata_log"
|
|
fi
|
|
|
|
deps=""
|
|
for dxvk_dll in d3d9.dll dxgi.dll d3d10core.dll d3d11.dll; do
|
|
deps+=$'\n'"$(objdump -p "$source_dir/$dxvk_dll" | grep 'DLL Name:' | awk '{print $3}')"
|
|
done
|
|
deps="$(printf "%s\n" "$deps" | sed '/^$/d' | sort -u)"
|
|
printf "%s\n" "$deps" >> "$metadata_log"
|
|
|
|
for dll in "${runtime_dll_names[@]}"; do
|
|
required=0
|
|
if grep -Fxq "$dll" <<<"$deps"; then
|
|
required=1
|
|
fi
|
|
if [[ "$required" -eq 1 ]]; then
|
|
source_path="$(resolve_runtime_dll_path "$arch" "$dll" || true)"
|
|
[[ -n "$source_path" ]] || die "missing runtime dependency $dll for $arch"
|
|
copy_dll_to_targets "$source_path" "${targets[@]}"
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
write_build_metadata() {
|
|
local variant="$1"
|
|
local tool_name="$2"
|
|
local tool_root="$3"
|
|
local dxvk_channel="$4"
|
|
local dxvk_tag="$5"
|
|
local dxvk_commit="$6"
|
|
local dxvk_describe="$7"
|
|
local build_time="$8"
|
|
local archive_name="$9"
|
|
local archive_alias="${10}"
|
|
|
|
cat > "$(metadata_file_for_variant "$variant")" <<EOF
|
|
VARIANT='${variant}'
|
|
TOOL_NAME='${tool_name}'
|
|
PROTON_TAG='${PROTON_TAG}'
|
|
PROTON_ASSET='${PROTON_ASSET}'
|
|
WINE_SERIES='${WINE_SERIES}'
|
|
RUNTIME_APPID='${RUNTIME_APPID}'
|
|
DXVK_CHANNEL='${dxvk_channel}'
|
|
DXVK_TAG='${dxvk_tag}'
|
|
DXVK_COMMIT='${dxvk_commit}'
|
|
DXVK_DESCRIBE='${dxvk_describe}'
|
|
BUILD_TIME='${build_time}'
|
|
TOOL_DIR='${tool_root}'
|
|
ARCHIVE_NAME='${archive_name}'
|
|
ARCHIVE_ALIAS='${archive_alias}'
|
|
SAFE_LAUNCH_OPTIONS='${SAFE_LAUNCH_OPTIONS}'
|
|
PERFORMANCE_LAUNCH_OPTIONS='${PERF_LAUNCH_OPTIONS}'
|
|
EOF
|
|
}
|