feat: add Elsword GE build project
build-elsword-ge / build (push) Has been cancelled

This commit is contained in:
magges
2026-06-27 11:23:40 +02:00
parent f3817f7c82
commit 23e0101e20
14 changed files with 1378 additions and 667 deletions
+55 -38
View File
@@ -1,15 +1,18 @@
name: build-elsword-ge
on:
schedule:
- cron: "0 4 * * *"
push:
branches:
- main
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
jobs:
build-release:
build:
runs-on: self-hosted
steps:
- name: Checkout repository
@@ -20,61 +23,71 @@ jobs:
git -c http.extraHeader="Authorization: token ${{ secrets.GITEA_TOKEN }}" fetch --depth 1 origin "${{ gitea.sha }}"
git checkout --detach FETCH_HEAD
- name: Build Elsword Proton-GE 10-34 tool
- name: Build both Elsword Proton variants
run: |
nix develop -c ./build.sh --no-install
nix develop -c ./scripts/build.sh all
- name: Package release archives
run: |
nix develop -c ./scripts/create-release-archive.sh
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: elsword-ge-builds
path: |
dist/*.tar.zst
dist/*.sha256
dist/SHA256SUMS.txt
dist/release-notes.md
profiles/elsword-launch-options.md
if-no-files-found: error
- name: Create or update Gitea release
if: startsWith(gitea.ref, 'refs/tags/v')
env:
GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_TAG: ${{ gitea.ref_name }}
GITEA_SHA: ${{ gitea.sha }}
run: |
nix develop -c bash -lc '
set -euo pipefail
set -a
. dist/package-info.env
set +a
RELEASE_API="${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases"
RELEASE_TAG_URL="$(python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))" "$RELEASE_TAG")"
ARCHIVE_URL_NAME="$(python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))" "$ARCHIVE_FILE")"
RELEASE_TAG_URL="$(python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))" "$GITEA_TAG")"
HTTP_CODE="$(curl -s -o /tmp/release.json -w "%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${RELEASE_API}/tags/${RELEASE_TAG_URL}")"
if [ "$HTTP_CODE" = "200" ]; then
RELEASE_ID="$(python3 -c "import json; print(json.load(open(\"/tmp/release.json\"))[\"id\"])" )"
RELEASE_ID="$(jq -r ".id" /tmp/release.json)"
elif [ "$HTTP_CODE" = "404" ]; then
python3 -c "import json, pathlib, os; pathlib.Path(\"/tmp/release-create.json\").write_text(json.dumps({
\"tag_name\": os.environ[\"RELEASE_TAG\"],
\"target_commitish\": os.environ[\"GITEA_SHA\"],
\"name\": os.environ[\"RELEASE_TAG\"],
\"body\": pathlib.Path(\"dist/release-notes.md\").read_text(),
\"draft\": False,
\"prerelease\": False,
}))"
jq -n \
--arg tag "$GITEA_TAG" \
--arg sha "$GITEA_SHA" \
--arg body "$(cat dist/release-notes.md)" \
"{tag_name: \$tag, target_commitish: \$sha, name: \$tag, body: \$body, draft: false, prerelease: false}" \
> /tmp/release-create.json
curl --fail-with-body \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @/tmp/release-create.json \
"${RELEASE_API}" > /tmp/release.json
RELEASE_ID="$(python3 -c "import json; print(json.load(open(\"/tmp/release.json\"))[\"id\"])" )"
RELEASE_ID="$(jq -r ".id" /tmp/release.json)"
else
cat /tmp/release.json
exit 1
fi
python3 -c "import json, pathlib, os; pathlib.Path(\"/tmp/release-update.json\").write_text(json.dumps({
\"tag_name\": os.environ[\"RELEASE_TAG\"],
\"target_commitish\": os.environ[\"GITEA_SHA\"],
\"name\": os.environ[\"RELEASE_TAG\"],
\"body\": pathlib.Path(\"dist/release-notes.md\").read_text(),
\"draft\": False,
\"prerelease\": False,
}))"
jq -n \
--arg tag "$GITEA_TAG" \
--arg sha "$GITEA_SHA" \
--arg body "$(cat dist/release-notes.md)" \
"{tag_name: \$tag, target_commitish: \$sha, name: \$tag, body: \$body, draft: false, prerelease: false}" \
> /tmp/release-update.json
curl --fail-with-body \
-X PATCH \
-H "Authorization: token ${GITEA_TOKEN}" \
@@ -86,16 +99,20 @@ jobs:
-H "Authorization: token ${GITEA_TOKEN}" \
"${RELEASE_API}/${RELEASE_ID}/assets" > /tmp/assets.json
EXISTING_ASSET_ID="$(python3 -c "import json, os; data=json.load(open(\"/tmp/assets.json\")); target=os.environ[\"ARCHIVE_FILE\"]; print(next((str(item[\"id\"]) for item in data if item.get(\"name\") == target), \"\"))")"
if [ -n "$EXISTING_ASSET_ID" ]; then
for asset in dist/*.tar.zst dist/*.sha256 dist/SHA256SUMS.txt dist/release-notes.md; do
[ -f "$asset" ] || continue
name="$(basename "$asset")"
existing_id="$(jq -r --arg name "$name" ".[] | select(.name == \$name) | .id" /tmp/assets.json | head -n1)"
if [ -n "$existing_id" ]; then
curl --fail-with-body \
-X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${RELEASE_API}/${RELEASE_ID}/assets/${existing_id}"
fi
encoded_name="$(python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))" "$name")"
curl --fail-with-body \
-X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${RELEASE_API}/${RELEASE_ID}/assets/${EXISTING_ASSET_ID}"
fi
curl --fail-with-body \
-H "Authorization: token ${GITEA_TOKEN}" \
--data-binary @"dist/${ARCHIVE_FILE}" \
"${RELEASE_API}/${RELEASE_ID}/assets?name=${ARCHIVE_URL_NAME}"
--data-binary @"${asset}" \
"${RELEASE_API}/${RELEASE_ID}/assets?name=${encoded_name}"
done
'
+2 -1
View File
@@ -1,2 +1,3 @@
.cache/
dist/
dist/*
!dist/.gitkeep
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+242 -56
View File
@@ -1,95 +1,281 @@
# Elsword Proton-GE Builder
# Elsword-GE Build Project
Dieses Repo baut per Gitea Actions versionierte Proton-GE-Kompatibilitätstools
für Elsword mit `dxvk-git` und Steam Linux Runtime 3.0 (`sniper`).
This repository builds two custom Proton-GE 11 compatibility tools for Elsword on Linux/Steam:
## Was gebaut wird
- `Elsword-GE-11-1`
- `Elsword-GE-11-1+dxvk-git`
- Tool-Name: `Elsword-GE-(Version)`
- Beispiel: `Elsword-GE-11-1`
- Runtime: `1628350` (`Steam Linux Runtime 3.0`)
- DXVK: standardmäßig `dxvk-git`
- Ausgabe: versionierte `.tar.xz`-Archive in `dist/`
It is designed for NixOS and for hosting on a private or public Gitea server with Gitea Actions.
Ältere lokal installierte Versionen wie `Elsword-GE-10-30` bleiben bestehen,
weil jede Build ihren eigenen Tool-Namen bekommt.
## What this project does
## Ablauf
- Builds a Proton-GE 11 / Wine 11 based compatibility tool for Elsword.
- Uses Steam Linux Runtime 3.0 (`sniper`, appid `1628350`).
- Builds DXVK for both x86 and x64.
- Produces release archives that other users can install into Steam.
- Disables optional extras that are known to be risky for game stability by default:
- `PROTON_DISCORD_BRIDGE=0`
- `PROTON_USE_OPTISCALER=0`
- `PROTON_USE_D7VK=0`
- `MANGOHUD=0`
- `ENABLE_VKBASALT=0`
1. Das Repo nutzt standardmäßig `GE-Proton10-34`.
2. DXVK wird aus Upstream gebaut, standardmäßig als `dxvk-git`.
3. Die GE-Basis wird auf `sniper` umgestellt.
4. Die DXVK-DLLs in Proton-GE werden ersetzt.
5. Das Ergebnis wird als eigenes Steam-Compatibility-Tool paketiert.
6. Die Gitea Action erstellt oder aktualisiert ein Release und lädt das Archiv hoch.
## What this project does not do
## Standardverhalten
- No GameGuard bypass.
- No anti-cheat bypass.
- No patching or hooking of GameGuard.
- No hard override of `LOW_LATENCY_LAYER`.
- No global hard override of `LD_PRELOAD`.
Ohne Parameter baut `build.sh`:
## Variant difference
- `GE-Proton10-34`
- mit `dxvk-git`
- als Tool `Elsword-GE-(Version)`
- mit `sniper`
- `Elsword-GE-11-1`
- Proton-GE 11 base
- DXVK stable/latest release tag
## Lokaler Build
- `Elsword-GE-11-1+dxvk-git`
- Proton-GE 11 base
- DXVK from current upstream Git
```bash
nix develop -c ./build.sh
## Repository layout
```text
.
├── flake.nix
├── README.md
├── LICENSE
├── .gitignore
├── .gitea/
│ └── workflows/
│ └── build.yml
├── scripts/
│ ├── common.sh
│ ├── build.sh
│ ├── package.sh
│ ├── install-to-steam.sh
│ ├── validate-build.sh
│ └── create-release-archive.sh
├── patches/
│ └── proton-defaults-elsword.patch
├── profiles/
│ └── elsword-launch-options.md
└── dist/
```
Nur Archiv bauen, nicht lokal installieren:
## NixOS prerequisites
Use the provided flake:
```bash
nix develop -c ./build.sh --no-install
nix develop
```
Einen bestimmten GE-Tag bauen:
The shell includes build dependencies such as:
- git
- bash
- coreutils
- findutils
- gnused
- gnugrep
- gawk
- jq
- curl
- wget
- tar
- zstd
- meson
- ninja
- python3
- pkg-config
- cmake
- perl
- patchelf
- binutils
- mingw-w64 toolchains for `i686` and `x86_64`
- Wine / wow64 support
## Local build
Build the stable DXVK variant:
```bash
nix develop -c ./build.sh --proton-tag GE-Proton11-1
nix develop -c ./scripts/build.sh stable
```
Eigenen Tool-Namen setzen:
Build the DXVK Git variant:
```bash
nix develop -c ./build.sh --tool-name Elsword-GE-test
nix develop -c ./scripts/build.sh dxvk-git
```
## Wichtige Optionen
Build both:
- `--proton-tag TAG`
- `--proton-asset x86_64|aarch64`
- `--dxvk-channel git|stable`
- `--dxvk-tag TAG`
- `--tool-prefix PREFIX`
- `--tool-name NAME`
- `--runtime-appid APPID`
- `--no-install`
```bash
nix develop -c ./scripts/build.sh all
```
## Gitea Actions
Package both into release archives:
Der Workflow liegt unter:
```bash
nix develop -c ./scripts/create-release-archive.sh
```
- `.gitea/workflows/build.yml`
## Output files
Er läuft bei:
After packaging, the main release files are:
- manuellem Start per `workflow_dispatch`
- automatischem Zeitplan täglich
```text
dist/Elsword-GE-11-1.tar.zst
dist/Elsword-GE-11-1+dxvk-git.tar.zst
dist/Elsword-GE-11-1-dxvk-git.tar.zst
dist/SHA256SUMS.txt
dist/release-notes.md
```
Für Release-Uploads braucht der Workflow ein Secret:
If `+` causes issues in a filesystem or upload target, the safe alias is:
```text
Elsword-GE-11-1-dxvk-git.tar.zst
```
The Steam compatibility tool name still remains exactly:
```text
Elsword-GE-11-1+dxvk-git
```
## Install into Steam
Install a built archive:
```bash
./scripts/install-to-steam.sh dist/Elsword-GE-11-1.tar.zst
./scripts/install-to-steam.sh dist/Elsword-GE-11-1+dxvk-git.tar.zst
```
Target path:
```text
~/.local/share/Steam/compatibilitytools.d/
```
After install:
1. Fully restart Steam.
2. Open Elsword properties in Steam.
3. Select the matching compatibility tool.
## Recommended Elsword launch options
The documented profiles live in [profiles/elsword-launch-options.md](/home/magges/git/Elsword-GE/profiles/elsword-launch-options.md:1).
Safe / GameGuard test:
```bash
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%
```
Performance / low-latency test:
```bash
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%
```
Operational advice:
- Disable Steam Overlay for Elsword if possible.
- Do not launch with `gamemoderun`.
- Keep MangoHud disabled.
- Keep vkBasalt disabled.
- Keep Discord Bridge disabled.
- Keep OptiScaler disabled.
- Keep D7VK disabled.
- Test `LOW_LATENCY_LAYER=1` separately.
- If GameGuard kicks the game, go back to the Safe profile first.
## Debugging
For Proton logs:
```bash
PROTON_LOG=1 %command%
```
## Gitea Actions behavior
Workflow file:
- [.gitea/workflows/build.yml](/home/magges/git/Elsword-GE/.gitea/workflows/build.yml:1)
Behavior:
- Push to `main`: builds both variants and stores build artifacts.
- Tag matching `v*`: builds both variants, packages them, generates checksums, and creates or updates a Gitea release with uploaded assets.
Expected secrets:
- `GITEA_TOKEN`
- `GITEA_SERVER_URL`
- `GITEA_REPOSITORY`
Dieses Token muss Release-Schreibrechte auf das Repo haben.
No tokens or passwords are stored in the repository.
## Release-Ausgabe
## Manual fallback if release upload needs to be done by hand
Nach einem Build schreibt das Skript nach `dist/`:
If your Gitea release upload path differs or the API step needs adjustment for your instance, build locally or through Actions and upload these files manually in the release UI:
- das fertige `.tar.xz`-Archiv
- `package-info.env`
- `release-notes.md`
- `dist/Elsword-GE-11-1.tar.zst`
- `dist/Elsword-GE-11-1+dxvk-git.tar.zst`
- `dist/Elsword-GE-11-1-dxvk-git.tar.zst`
- `dist/SHA256SUMS.txt`
- `dist/*.sha256`
- `dist/release-notes.md`
Die Gitea Action nutzt diese Dateien direkt für das Release.
## Push to Gitea
```bash
git init
git add .
git commit -m "Initial Elsword-GE build project"
git remote add origin ssh://git@DEIN-GITEA-SERVER/USER/elsword-ge.git
git push -u origin main
```
## Create a release by tag
```bash
git tag v11-1
git push origin v11-1
```
## Gitea attachment size note
Built Proton archives are usually much larger than 50 MB. Your Gitea instance may need larger release attachment limits.
Example configuration hint only; exact keys can vary by Gitea version:
```ini
[repository.upload]
FILE_MAX_SIZE = 2048
MAX_FILES = 20
[attachment]
MAX_SIZE = 2048
MAX_FILES = 20
[actions]
ENABLED = true
```
## Security and anti-cheat note
This project intentionally avoids:
- GameGuard bypasses
- anti-cheat bypasses
- GameGuard patches
- GameGuard hooks
The goal is only a clean Proton-GE build with complete DXVK payloads and conservative defaults for optional extras.
-570
View File
@@ -1,570 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CACHE_DIR="$REPO_ROOT/.cache"
DIST_DIR="$REPO_ROOT/dist"
WORK_BASE="$CACHE_DIR/work"
DXVK_CACHE_DIR="$CACHE_DIR/dxvk"
STEAM_COMPAT_DIR="${HOME}/.local/share/Steam/compatibilitytools.d"
UPSTREAM_REPO="GloriousEggroll/proton-ge-custom"
PROTON_TAG="GE-Proton10-34"
PROTON_ASSET="x86_64"
DXVK_CHANNEL="git"
DXVK_TAG="latest"
RUNTIME_APPID="1628350"
TOOL_PREFIX="Elsword-GE"
TOOL_NAME=""
KEEP_WORK=0
DXVK_SRC_OVERRIDE=""
MINGW_PTHREAD_ROOT="${MINGW_PTHREAD_ROOT:-}"
INSTALL_TOOL=1
DXVK_DESCRIBE=""
DXVK_FULL_SHA=""
DXVK_SHORT_SHA=""
usage() {
cat <<'EOF'
Usage: ./build.sh [options]
Build a custom Proton-GE compatibility tool for Elsword and replace its bundled
DXVK with a chosen DXVK build, defaulting to GE-Proton10-34 plus dxvk-git on
Steam Linux Runtime 3.0 (sniper).
Options:
--proton-tag TAG Build a specific Proton-GE release tag, default: GE-Proton10-34
--proton-asset x86_64|aarch64 Choose which Proton-GE asset to use
--dxvk-channel git|stable Choose DXVK source channel, default: git
--dxvk-tag TAG Stable DXVK tag to use when --dxvk-channel stable
--tool-prefix PREFIX Tool name prefix, default: Elsword-GE
--tool-name NAME Override final tool name completely
--runtime-appid APPID Steam Runtime appid, default: 1628350 (sniper)
--steam-compat-dir PATH Override compatibilitytools.d path
--dxvk-src PATH Use an existing dxvk checkout instead of cache
--mingw-pthread-root PATH Path with x32/libpthread.a and x64/libpthread.a
--dist-dir PATH Output directory for sendable archives
--no-install Build/package only, do not install into Steam
--keep-work Keep temporary work directory
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--proton-tag)
PROTON_TAG="${2:?missing value for --proton-tag}"
shift 2
;;
--proton-asset)
PROTON_ASSET="${2:?missing value for --proton-asset}"
shift 2
;;
--dxvk-channel)
DXVK_CHANNEL="${2:?missing value for --dxvk-channel}"
shift 2
;;
--dxvk-tag)
DXVK_TAG="${2:?missing value for --dxvk-tag}"
shift 2
;;
--tool-prefix)
TOOL_PREFIX="${2:?missing value for --tool-prefix}"
shift 2
;;
--tool-name)
TOOL_NAME="${2:?missing value for --tool-name}"
shift 2
;;
--runtime-appid)
RUNTIME_APPID="${2:?missing value for --runtime-appid}"
shift 2
;;
--steam-compat-dir)
STEAM_COMPAT_DIR="${2:?missing value for --steam-compat-dir}"
shift 2
;;
--dxvk-src)
DXVK_SRC_OVERRIDE="${2:?missing value for --dxvk-src}"
shift 2
;;
--mingw-pthread-root)
MINGW_PTHREAD_ROOT="${2:?missing value for --mingw-pthread-root}"
shift 2
;;
--dist-dir)
DIST_DIR="${2:?missing value for --dist-dir}"
shift 2
;;
--no-install)
INSTALL_TOOL=0
shift
;;
--keep-work)
KEEP_WORK=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
case "$PROTON_ASSET" in
x86_64|aarch64) ;;
*)
echo "Unsupported --proton-asset: $PROTON_ASSET" >&2
exit 1
;;
esac
case "$DXVK_CHANNEL" in
git|stable) ;;
*)
echo "Unsupported --dxvk-channel: $DXVK_CHANNEL" >&2
exit 1
;;
esac
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
for cmd in \
curl \
git \
nix \
tar \
meson \
ninja \
python3 \
realpath \
glslangValidator \
x86_64-w64-mingw32-gcc \
x86_64-w64-mingw32-g++ \
i686-w64-mingw32-gcc \
i686-w64-mingw32-g++ \
wine; do
require_cmd "$cmd"
done
mkdir -p "$CACHE_DIR" "$WORK_BASE" "$DIST_DIR"
if [[ "$INSTALL_TOOL" -eq 1 ]]; then
mkdir -p "$STEAM_COMPAT_DIR"
fi
WORK_DIR="$(mktemp -d "$WORK_BASE/build.XXXXXX")"
cleanup() {
if [[ "$KEEP_WORK" -ne 1 ]]; then
rm -rf "$WORK_DIR"
fi
}
trap cleanup EXIT
say() {
printf '\n==> %s\n' "$1" >&2
}
tool_version_from_tag() {
local tag="$1"
local version="${tag#GE-Proton}"
if [[ "$version" == "$tag" ]]; then
printf '%s\n' "$tag"
else
printf '%s\n' "$version"
fi
}
get_latest_proton_tag() {
curl -fsSL "https://api.github.com/repos/${UPSTREAM_REPO}/releases/latest" \
| grep -oP '"tag_name":\s*"\K[^"]+' \
| head -n1
}
get_latest_dxvk_tag() {
curl -fsSL https://api.github.com/repos/doitsujin/dxvk/releases/latest \
| grep -oP '"tag_name":\s*"\K[^"]+' \
| head -n1
}
prepare_dxvk_cache() {
if [[ ! -d "$DXVK_CACHE_DIR/.git" ]]; then
say "Clone dxvk"
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 origin/master >&2
git -C "$DXVK_CACHE_DIR" submodule update --init --recursive >&2
git -C "$DXVK_CACHE_DIR" clean -fdx >&2
fi
}
prepare_dxvk_repo() {
local source_repo ref_name checkout_dir
checkout_dir="$WORK_DIR/dxvk-src"
if [[ -n "$DXVK_SRC_OVERRIDE" ]]; then
source_repo="$DXVK_SRC_OVERRIDE"
else
prepare_dxvk_cache
source_repo="$DXVK_CACHE_DIR"
fi
if [[ "$DXVK_CHANNEL" == "stable" ]]; then
ref_name="$DXVK_TAG"
if [[ -z "$ref_name" || "$ref_name" == "latest" ]]; then
ref_name="$(get_latest_dxvk_tag)"
fi
if [[ -z "$ref_name" ]]; then
echo "Failed to resolve DXVK stable tag" >&2
exit 1
fi
else
ref_name="HEAD"
fi
say "Prepare DXVK source ($DXVK_CHANNEL)"
git clone --recursive "$source_repo" "$checkout_dir" >&2
if [[ "$DXVK_CHANNEL" == "stable" ]]; then
git -C "$checkout_dir" fetch --tags https://github.com/doitsujin/dxvk.git >&2
git -C "$checkout_dir" checkout "refs/tags/$ref_name" >&2
git -C "$checkout_dir" submodule update --init --recursive >&2
fi
echo "$checkout_dir"
}
build_dxvk() {
local dxvk_src="$1"
local dxvk_build_dir="$WORK_DIR/dxvk-out/dxvk-${DXVK_CHANNEL}"
local toolchain_dir="$WORK_DIR/toolchain"
local pthread_dir="$WORK_DIR/mingw-pthread"
DXVK_SHORT_SHA="$(git -C "$dxvk_src" rev-parse --short HEAD)"
DXVK_FULL_SHA="$(git -C "$dxvk_src" rev-parse HEAD)"
DXVK_DESCRIBE="$(git -C "$dxvk_src" describe --always --tags --dirty)"
say "Build DXVK ($DXVK_DESCRIBE)"
rm -rf "$WORK_DIR/dxvk-out" "$toolchain_dir" "$pthread_dir"
mkdir -p "$dxvk_build_dir" "$toolchain_dir" "$pthread_dir/x32" "$pthread_dir/x64"
if [[ -n "$MINGW_PTHREAD_ROOT" \
&& -f "$MINGW_PTHREAD_ROOT/x32/libpthread.a" \
&& -f "$MINGW_PTHREAD_ROOT/x64/libpthread.a" ]]; then
ln -sf "$MINGW_PTHREAD_ROOT/x32/libpthread.a" "$pthread_dir/x32/libpthread.a"
ln -sf "$MINGW_PTHREAD_ROOT/x64/libpthread.a" "$pthread_dir/x64/libpthread.a"
else
local mcfg32 mcfg64
mcfg32="$(NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix path-info --impure nixpkgs#pkgsCross.mingw32.windows.mcfgthreads)"
mcfg64="$(NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix path-info --impure nixpkgs#pkgsCross.mingwW64.windows.mcfgthreads)"
ln -sf "$mcfg32/lib/libmcfgthread.a" "$pthread_dir/x32/libpthread.a"
ln -sf "$mcfg64/lib/libmcfgthread.a" "$pthread_dir/x64/libpthread.a"
fi
cat > "$toolchain_dir/x86_64-w64-mingw32-gcc" <<EOF
#!/usr/bin/env bash
exec "$(command -v x86_64-w64-mingw32-gcc)" "\$@" -L"$pthread_dir/x64" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/x86_64-w64-mingw32-g++" <<EOF
#!/usr/bin/env bash
exec "$(command -v x86_64-w64-mingw32-g++)" "\$@" -L"$pthread_dir/x64" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/i686-w64-mingw32-gcc" <<EOF
#!/usr/bin/env bash
exec "$(command -v i686-w64-mingw32-gcc)" "\$@" -L"$pthread_dir/x32" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/i686-w64-mingw32-g++" <<EOF
#!/usr/bin/env bash
exec "$(command -v i686-w64-mingw32-g++)" "\$@" -L"$pthread_dir/x32" -lmcfgthread -lpthread
EOF
chmod +x \
"$toolchain_dir/x86_64-w64-mingw32-gcc" \
"$toolchain_dir/x86_64-w64-mingw32-g++" \
"$toolchain_dir/i686-w64-mingw32-gcc" \
"$toolchain_dir/i686-w64-mingw32-g++"
cat > "$toolchain_dir/build-win64-mcfg.txt" <<EOF
[binaries]
c = '$toolchain_dir/x86_64-w64-mingw32-gcc'
cpp = '$toolchain_dir/x86_64-w64-mingw32-g++'
ar = '$(command -v x86_64-w64-mingw32-ar)'
strip = '$(command -v x86_64-w64-mingw32-strip)'
windres = '$(command -v x86_64-w64-mingw32-windres)'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
EOF
cat > "$toolchain_dir/build-win32-mcfg.txt" <<EOF
[binaries]
c = '$toolchain_dir/i686-w64-mingw32-gcc'
cpp = '$toolchain_dir/i686-w64-mingw32-g++'
ar = '$(command -v i686-w64-mingw32-ar)'
strip = '$(command -v i686-w64-mingw32-strip)'
windres = '$(command -v i686-w64-mingw32-windres)'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'x86'
endian = 'little'
EOF
(
cd "$dxvk_src"
meson setup --cross-file "$toolchain_dir/build-win64-mcfg.txt" \
--buildtype release \
--prefix "$dxvk_build_dir" \
--strip \
--bindir x64 \
--libdir x64 \
-Db_ndebug=if-release \
"$dxvk_build_dir/build.64" >&2
ninja -C "$dxvk_build_dir/build.64" install >&2
meson setup --cross-file "$toolchain_dir/build-win32-mcfg.txt" \
--buildtype release \
--prefix "$dxvk_build_dir" \
--strip \
--bindir x32 \
--libdir x32 \
-Db_ndebug=if-release \
"$dxvk_build_dir/build.32" >&2
ninja -C "$dxvk_build_dir/build.32" install >&2
)
rm -f "$dxvk_build_dir/x64/"*.a "$dxvk_build_dir/x32/"*.a
rm -rf "$dxvk_build_dir/build.64" "$dxvk_build_dir/build.32"
echo "$DXVK_SHORT_SHA"
}
download_proton() {
local proton_tag="$1"
local asset_file
local proton_tarball
local proton_url
if [[ "$PROTON_ASSET" == "aarch64" ]]; then
asset_file="${proton_tag}-aarch64.tar.gz"
else
asset_file="${proton_tag}.tar.gz"
fi
proton_tarball="$CACHE_DIR/${asset_file}"
proton_url="https://github.com/${UPSTREAM_REPO}/releases/download/${proton_tag}/${asset_file}"
if [[ ! -f "$proton_tarball" ]]; then
say "Download Proton-GE ${proton_tag} (${PROTON_ASSET})"
curl -fL "$proton_url" -o "$proton_tarball"
else
say "Reuse cached Proton tarball"
fi
echo "$proton_tarball"
}
replace_tree() {
local source_dir="$1"
local target_dir="$2"
mkdir -p "$target_dir"
for dll in d3d8.dll d3d9.dll d3d10core.dll d3d11.dll dxgi.dll openvr_api_dxvk.dll; do
if [[ -f "$source_dir/$dll" ]]; then
cp -f "$source_dir/$dll" "$target_dir/$dll"
fi
done
}
replace_base_dlls() {
local source_dir="$1"
local target_dir="$2"
mkdir -p "$target_dir"
for dll in d3d8.dll d3d9.dll d3d10core.dll d3d11.dll ddraw.dll dxgi.dll; do
if [[ -f "$source_dir/$dll" ]]; then
cp -f "$source_dir/$dll" "$target_dir/$dll"
fi
done
}
patch_tool() {
local tool_root="$1"
local tool_name="$2"
local proton_tag="$3"
local dxvk_src="$4"
local dxvk_short_dir="$WORK_DIR/dxvk-out/dxvk-${DXVK_CHANNEL}"
local timestamp
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
say "Replace DXVK payload"
replace_tree "$dxvk_short_dir/x64" "$tool_root/files/lib/wine/dxvk/x86_64-windows"
replace_tree "$dxvk_short_dir/x32" "$tool_root/files/lib/wine/dxvk/i386-windows"
replace_base_dlls "$dxvk_short_dir/x64" "$tool_root/files/lib/wine/x86_64-windows"
replace_base_dlls "$dxvk_short_dir/x32" "$tool_root/files/lib/wine/i386-windows"
replace_base_dlls "$dxvk_short_dir/x64" "$tool_root/files/share/default_pfx/drive_c/windows/system32"
replace_base_dlls "$dxvk_short_dir/x32" "$tool_root/files/share/default_pfx/drive_c/windows/syswow64"
chmod u+w \
"$tool_root/files/lib/wine/dxvk/version" \
"$tool_root/version" \
"$tool_root/toolmanifest.vdf" 2>/dev/null || true
printf '%s dxvk (%s)\n' "$DXVK_FULL_SHA" "$DXVK_DESCRIBE" > "$tool_root/files/lib/wine/dxvk/version"
printf '%s %s\n' "$(date +%s)" "$tool_name" > "$tool_root/version"
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
cat > "$tool_root/BUILD-INFO.txt" <<EOF
Tool name: ${tool_name}
Built at: ${timestamp}
Upstream repo: ${UPSTREAM_REPO}
Proton-GE tag: ${proton_tag}
Proton asset: ${PROTON_ASSET}
DXVK source: ${dxvk_src}
DXVK commit: ${DXVK_FULL_SHA}
DXVK describe: ${DXVK_DESCRIBE}
DXVK channel: ${DXVK_CHANNEL}
Runtime appid: ${RUNTIME_APPID}
EOF
}
install_tool() {
local tool_root="$1"
local tool_name="$2"
local destination="$STEAM_COMPAT_DIR/$tool_name"
say "Install tool"
rm -rf "$destination"
mv "$tool_root" "$destination"
echo "$destination"
}
package_tool() {
local tool_root="$1"
local tool_name="$2"
local archive_path="$DIST_DIR/${tool_name}-dxvk-${DXVK_SHORT_SHA}.tar.xz"
say "Create shareable archive"
mkdir -p "$DIST_DIR"
rm -f "$archive_path"
tar -C "$(dirname "$tool_root")" -cJf "$archive_path" "$(basename "$tool_root")"
echo "$archive_path"
}
write_dist_metadata() {
local archive_path="$1"
local tool_name="$2"
local proton_tag="$3"
mkdir -p "$DIST_DIR"
cat > "$DIST_DIR/package-info.env" <<EOF
TOOL_NAME=${tool_name}
PROTON_TAG=${proton_tag}
PROTON_VERSION=$(tool_version_from_tag "$proton_tag")
PROTON_ASSET=${PROTON_ASSET}
DXVK_CHANNEL=${DXVK_CHANNEL}
DXVK_TAG=${DXVK_TAG}
DXVK_COMMIT=${DXVK_FULL_SHA}
DXVK_DESCRIBE=${DXVK_DESCRIBE}
RUNTIME_APPID=${RUNTIME_APPID}
ARCHIVE_FILE=$(basename "$archive_path")
RELEASE_TAG=${tool_name}
EOF
cat > "$DIST_DIR/release-notes.md" <<EOF
# ${tool_name}
- Proton-GE: \`${proton_tag}\`
- DXVK: \`${DXVK_DESCRIBE}\`
- Runtime appid: \`${RUNTIME_APPID}\`
- Archive: \`$(basename "$archive_path")\`
This release is built for Elsword and uses Steam Linux Runtime 3.0 (sniper).
EOF
}
if [[ -z "$TOOL_NAME" ]]; then
TOOL_NAME="${TOOL_PREFIX}-$(tool_version_from_tag "$PROTON_TAG")"
fi
DXVK_SRC="$(prepare_dxvk_repo)"
build_dxvk "$DXVK_SRC" >/dev/null
PROTON_TARBALL="$(download_proton "$PROTON_TAG")"
say "Extract Proton-GE"
mkdir -p "$WORK_DIR/proton"
tar -xf "$PROTON_TARBALL" -C "$WORK_DIR/proton"
TOOL_ROOT="$(find "$WORK_DIR/proton" -mindepth 1 -maxdepth 1 -type d | head -n1)"
if [[ -z "$TOOL_ROOT" ]]; then
echo "Could not find extracted Proton tool root" >&2
exit 1
fi
patch_tool "$TOOL_ROOT" "$TOOL_NAME" "$PROTON_TAG" "$DXVK_SRC"
ARCHIVE_PATH="$(package_tool "$TOOL_ROOT" "$TOOL_NAME")"
write_dist_metadata "$ARCHIVE_PATH" "$TOOL_NAME" "$PROTON_TAG"
if [[ "$INSTALL_TOOL" -eq 1 ]]; then
INSTALLED_PATH="$(install_tool "$TOOL_ROOT" "$TOOL_NAME")"
fi
say "Done"
printf 'Archive: %s\n' "$ARCHIVE_PATH"
if [[ "$INSTALL_TOOL" -eq 1 ]]; then
printf 'Installed: %s\n' "$INSTALLED_PATH"
fi
printf 'Proton-GE: %s (%s)\n' "$PROTON_TAG" "$PROTON_ASSET"
printf 'DXVK: %s\n' "$DXVK_DESCRIBE"
printf 'Runtime: %s\n' "$RUNTIME_APPID"
if [[ "$INSTALL_TOOL" -eq 1 ]]; then
printf '\nRestart Steam and choose "%s" as compatibility tool.\n' "$TOOL_NAME"
else
printf '\nShare the archive or unpack it into compatibilitytools.d on another system.\n'
fi
+1
View File
@@ -0,0 +1 @@
+18 -2
View File
@@ -1,9 +1,9 @@
{
description = "Build versioned Elsword Proton-GE tools with DXVK and sniper runtime";
description = "Gitea-ready custom Proton-GE 11 build project for Elsword";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
outputs = { nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
@@ -11,13 +11,29 @@
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
bash
binutils
cmake
coreutils
curl
findutils
gawk
git
gnugrep
gnused
gnutar
glslang
jq
meson
ninja
patchelf
perl
pkg-config
python3
wget
which
xz
zstd
wineWow64Packages.stable
pkgsCross.mingw32.stdenv.cc
pkgsCross.mingwW64.stdenv.cc
+36
View File
@@ -0,0 +1,36 @@
--- a/proton
+++ b/proton
@@
import os
+
+os.environ.setdefault("PROTON_DISCORD_BRIDGE", "0")
+os.environ.setdefault("PROTON_USE_OPTISCALER", "0")
+os.environ.setdefault("PROTON_USE_D7VK", "0")
+os.environ.setdefault("MANGOHUD", "0")
+os.environ.setdefault("ENABLE_VKBASALT", "0")
*** Add File: /home/magges/git/Elsword-GE/profiles/elsword-launch-options.md
# Elsword Launch Profiles
## Safe / GameGuard Test
```bash
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%
```
## Performance / Low-Latency Test
```bash
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%
```
## Notes
- Disable the Steam Overlay for Elsword if possible.
- Do not start Elsword through `gamemoderun`.
- Keep MangoHud disabled.
- Keep vkBasalt disabled.
- Keep the Discord bridge disabled.
- Keep OptiScaler disabled.
- Keep D7VK disabled.
- Test `LOW_LATENCY_LAYER=1` on its own before mixing other changes in.
- If GameGuard kicks or closes the game, return to the Safe profile first.
+238
View File
@@ -0,0 +1,238 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
Usage:
./scripts/build.sh stable
./scripts/build.sh dxvk-git
./scripts/build.sh all
Builds one or both Elsword Proton-GE compatibility tool directories in dist/
and validates them before packaging.
EOF
}
prepare_toolchain_wrappers() {
local workdir="$1"
local toolchain_dir="$workdir/toolchain"
local pthread_dir="$workdir/mingw-pthread"
local mcfg32 mcfg64
mkdir -p "$toolchain_dir" "$pthread_dir/x32" "$pthread_dir/x64"
if [[ -n "$MINGW_PTHREAD_ROOT" \
&& -f "$MINGW_PTHREAD_ROOT/x32/libpthread.a" \
&& -f "$MINGW_PTHREAD_ROOT/x64/libpthread.a" ]]; then
ln -sf "$MINGW_PTHREAD_ROOT/x32/libpthread.a" "$pthread_dir/x32/libpthread.a"
ln -sf "$MINGW_PTHREAD_ROOT/x64/libpthread.a" "$pthread_dir/x64/libpthread.a"
else
mcfg32="$(NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix path-info --impure nixpkgs#pkgsCross.mingw32.windows.mcfgthreads)"
mcfg64="$(NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix path-info --impure nixpkgs#pkgsCross.mingwW64.windows.mcfgthreads)"
ln -sf "$mcfg32/lib/libmcfgthread.a" "$pthread_dir/x32/libpthread.a"
ln -sf "$mcfg64/lib/libmcfgthread.a" "$pthread_dir/x64/libpthread.a"
fi
cat > "$toolchain_dir/x86_64-w64-mingw32-gcc" <<EOF
#!/usr/bin/env bash
exec "$(command -v x86_64-w64-mingw32-gcc)" "\$@" -L"$pthread_dir/x64" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/x86_64-w64-mingw32-g++" <<EOF
#!/usr/bin/env bash
exec "$(command -v x86_64-w64-mingw32-g++)" "\$@" -L"$pthread_dir/x64" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/i686-w64-mingw32-gcc" <<EOF
#!/usr/bin/env bash
exec "$(command -v i686-w64-mingw32-gcc)" "\$@" -L"$pthread_dir/x32" -lmcfgthread -lpthread
EOF
cat > "$toolchain_dir/i686-w64-mingw32-g++" <<EOF
#!/usr/bin/env bash
exec "$(command -v i686-w64-mingw32-g++)" "\$@" -L"$pthread_dir/x32" -lmcfgthread -lpthread
EOF
chmod +x \
"$toolchain_dir/x86_64-w64-mingw32-gcc" \
"$toolchain_dir/x86_64-w64-mingw32-g++" \
"$toolchain_dir/i686-w64-mingw32-gcc" \
"$toolchain_dir/i686-w64-mingw32-g++"
cat > "$toolchain_dir/build-win64.txt" <<EOF
[binaries]
c = '$toolchain_dir/x86_64-w64-mingw32-gcc'
cpp = '$toolchain_dir/x86_64-w64-mingw32-g++'
ar = '$(command -v x86_64-w64-mingw32-ar)'
strip = '$(command -v x86_64-w64-mingw32-strip)'
windres = '$(command -v x86_64-w64-mingw32-windres)'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
EOF
cat > "$toolchain_dir/build-win32.txt" <<EOF
[binaries]
c = '$toolchain_dir/i686-w64-mingw32-gcc'
cpp = '$toolchain_dir/i686-w64-mingw32-g++'
ar = '$(command -v i686-w64-mingw32-ar)'
strip = '$(command -v i686-w64-mingw32-strip)'
windres = '$(command -v i686-w64-mingw32-windres)'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'x86'
endian = 'little'
EOF
printf "%s\n" "$toolchain_dir"
}
build_dxvk_variant() {
local variant="$1"
local dxvk_src="$2"
local workdir="$3"
local toolchain_dir="$4"
local dxvk_build_dir="$workdir/dxvk-out/$variant"
mkdir -p "$dxvk_build_dir"
say "Build DXVK for $variant"
(
cd "$dxvk_src"
meson setup --cross-file "$toolchain_dir/build-win64.txt" \
--buildtype release \
--prefix "$dxvk_build_dir" \
--strip \
--bindir x64 \
--libdir x64 \
-Db_ndebug=if-release \
"$dxvk_build_dir/build.64" >&2
ninja -C "$dxvk_build_dir/build.64" install >&2
meson setup --cross-file "$toolchain_dir/build-win32.txt" \
--buildtype release \
--prefix "$dxvk_build_dir" \
--strip \
--bindir x32 \
--libdir x32 \
-Db_ndebug=if-release \
"$dxvk_build_dir/build.32" >&2
ninja -C "$dxvk_build_dir/build.32" install >&2
)
rm -f "$dxvk_build_dir"/x64/*.a "$dxvk_build_dir"/x32/*.a
rm -rf "$dxvk_build_dir/build.64" "$dxvk_build_dir/build.32"
printf "%s\n" "$dxvk_build_dir"
}
finalize_tool_tree() {
local variant="$1"
local tool_root="$2"
local tool_name="$3"
local dxvk_build_dir="$4"
local build_time="$5"
local dxvk_src="$6"
local tool_dir="$DIST_DIR/$tool_name"
local metadata_log="$tool_root/runtime-deps.log"
local dxvk_channel dxvk_tag dxvk_commit dxvk_describe
dxvk_channel="$(dxvk_channel_for_variant "$variant")"
dxvk_tag="$(dxvk_tag_for_variant "$variant")"
dxvk_commit="$(git -C "$dxvk_src" rev-parse HEAD)"
dxvk_describe="$(git -C "$dxvk_src" describe --always --tags --dirty)"
say "Patch tool tree for $tool_name"
ensure_tool_tree_writable "$tool_root"
replace_dxvk_payload "$dxvk_build_dir" "$tool_root"
mkdir -p "$tool_dir"
copy_required_runtime_dlls "$tool_root" "$dxvk_build_dir" "$metadata_log"
inject_wrapper_defaults "$tool_root/proton"
patch_tool_manifests "$tool_root" "$tool_name"
printf "%s dxvk (%s)\n" "$dxvk_commit" "$dxvk_describe" > "$tool_root/files/lib/wine/dxvk/version"
cat > "$tool_root/BUILD-INFO.txt" <<EOF
Tool name: ${tool_name}
Proton base: ${PROTON_TAG}
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}
EOF
rm -rf "$tool_dir"
mv "$tool_root" "$tool_dir"
write_build_metadata \
"$variant" \
"$tool_name" \
"$tool_dir" \
"$dxvk_channel" \
"$dxvk_tag" \
"$dxvk_commit" \
"$dxvk_describe" \
"$build_time" \
"$(archive_name_for_variant "$variant")" \
"$(archive_alias_for_variant "$variant")"
"$SCRIPT_DIR/validate-build.sh" "$tool_dir" "$tool_name"
}
build_variant() {
local variant="$1"
local workdir proton_tarball proton_root dxvk_src toolchain_dir dxvk_build_dir tool_name build_time
tool_name="$(tool_name_for_variant "$variant")"
build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
workdir="$(workdir_for_variant "$variant")"
trap "cleanup_workdir '$workdir'" RETURN
proton_tarball="$(download_proton_tarball)"
proton_root="$(extract_proton_tool "$workdir" "$proton_tarball")"
dxvk_src="$(prepare_dxvk_source "$variant" "$workdir")"
toolchain_dir="$(prepare_toolchain_wrappers "$workdir")"
dxvk_build_dir="$(build_dxvk_variant "$variant" "$dxvk_src" "$workdir" "$toolchain_dir")"
finalize_tool_tree "$variant" "$proton_root" "$tool_name" "$dxvk_build_dir" "$build_time" "$dxvk_src"
}
main() {
local mode="${1:-all}"
case "$mode" in
stable|dxvk-git|all) ;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 1
;;
esac
require_toolchain_commands
ensure_repo_layout
if [[ "$mode" == "all" ]]; then
build_variant stable
build_variant dxvk-git
else
build_variant "$mode"
fi
}
main "$@"
+460
View File
@@ -0,0 +1,460 @@
#!/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:-1628350}"
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
}
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
main() {
local stable_meta git_meta now stable_alias git_alias
ensure_repo_layout
"$SCRIPT_DIR/package.sh" all
stable_meta="$(metadata_file_for_variant stable)"
git_meta="$(metadata_file_for_variant dxvk-git)"
[[ -f "$stable_meta" && -f "$git_meta" ]] || die "build metadata missing; run ./scripts/build.sh all first"
source "$stable_meta"
local stable_tool_name="$TOOL_NAME"
local stable_dxvk="$DXVK_DESCRIBE"
local stable_commit="$DXVK_COMMIT"
local stable_archive="$ARCHIVE_NAME"
stable_alias="$ARCHIVE_ALIAS"
source "$git_meta"
local git_tool_name="$TOOL_NAME"
local git_dxvk="$DXVK_DESCRIBE"
local git_commit="$DXVK_COMMIT"
local git_archive="$ARCHIVE_NAME"
git_alias="$ARCHIVE_ALIAS"
say "Generate checksums"
(
cd "$DIST_DIR"
sha256sum \
"$stable_archive" \
"$git_archive" \
"$stable_alias" \
"$git_alias" > SHA256SUMS.txt
awk '{print $1 " " $2}' SHA256SUMS.txt | while read -r sum file; do
printf "%s %s\n" "$sum" "$file" > "${file}.sha256"
done
)
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
cat > "$DIST_DIR/release-notes.md" <<EOF
# $(release_title)
Build timestamp: \`${now}\`
## Variants
- \`${stable_tool_name}\`
- Proton base: \`${PROTON_TAG}\`
- Wine series: \`${WINE_SERIES}\`
- DXVK: \`${stable_dxvk}\`
- DXVK commit: \`${stable_commit}\`
- Archive: \`${stable_archive}\`
- \`${git_tool_name}\`
- Proton base: \`${PROTON_TAG}\`
- Wine series: \`${WINE_SERIES}\`
- DXVK: \`${git_dxvk}\`
- DXVK commit: \`${git_commit}\`
- Archive: \`${git_archive}\`
- Alias: \`${git_alias}\`
## Checksums
See \`SHA256SUMS.txt\` and the matching \`.sha256\` files in this release.
## Recommended Elsword launch options
### Safe / GameGuard Test
\`\`\`bash
${SAFE_LAUNCH_OPTIONS}
\`\`\`
### Performance / Low-Latency Test
\`\`\`bash
${PERF_LAUNCH_OPTIONS}
\`\`\`
EOF
}
main "$@"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
STEAM_COMPAT_DIR="${STEAM_COMPAT_DIR:-$HOME/.local/share/Steam/compatibilitytools.d}"
usage() {
cat <<'EOF'
Usage:
./scripts/install-to-steam.sh dist/Elsword-GE-11-1.tar.zst
./scripts/install-to-steam.sh dist/Elsword-GE-11-1+dxvk-git.tar.zst
EOF
}
archive="${1:-}"
[[ -n "$archive" ]] || {
usage >&2
exit 1
}
[[ -f "$archive" ]] || {
printf "Archive not found: %s\n" "$archive" >&2
exit 1
}
mkdir -p "$STEAM_COMPAT_DIR"
mapfile -t archive_entries < <(tar -tf "$archive")
tool_name="${archive_entries[0]%%/*}"
[[ -n "$tool_name" ]] || {
printf "Could not determine tool name from archive: %s\n" "$archive" >&2
exit 1
}
install_path="$STEAM_COMPAT_DIR/$tool_name"
rm -rf "$install_path"
tar --zstd -xf "$archive" -C "$STEAM_COMPAT_DIR"
printf "Installed tool: %s\n" "$tool_name"
printf "Installed path: %s\n" "$install_path"
printf "Steam fully restart required.\n"
printf "In Steam, open Elsword and select this compatibility tool.\n"
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
Usage:
./scripts/package.sh stable
./scripts/package.sh dxvk-git
./scripts/package.sh all
EOF
}
package_variant() {
local variant="$1"
local metadata_file tool_name tool_dir archive_name archive_alias archive_path alias_path
metadata_file="$(metadata_file_for_variant "$variant")"
[[ -f "$metadata_file" ]] || die "missing metadata for variant $variant, run build first"
source "$metadata_file"
tool_name="$(tool_name_for_variant "$variant")"
tool_dir="$DIST_DIR/$tool_name"
archive_name="$(archive_name_for_variant "$variant")"
archive_alias="$(archive_alias_for_variant "$variant")"
archive_path="$DIST_DIR/$archive_name"
alias_path="$DIST_DIR/$archive_alias"
[[ -d "$tool_dir" ]] || die "missing built tool dir: $tool_dir"
say "Package $tool_name"
rm -f "$archive_path" "$alias_path"
tar --zstd -cf "$archive_path" -C "$DIST_DIR" "$tool_name"
if [[ "$archive_alias" != "$archive_name" ]]; then
cp -f "$archive_path" "$alias_path"
fi
}
main() {
local mode="${1:-all}"
ensure_repo_layout
case "$mode" in
stable) package_variant stable ;;
dxvk-git) package_variant dxvk-git ;;
all)
package_variant stable
package_variant dxvk-git
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 1
;;
esac
}
main "$@"
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
usage() {
cat <<'EOF'
Usage:
./scripts/validate-build.sh dist/Elsword-GE-11-1 Elsword-GE-11-1
EOF
}
tool_dir="${1:-}"
tool_name="${2:-}"
[[ -n "$tool_dir" && -n "$tool_name" ]] || {
usage >&2
exit 1
}
pass() {
printf "[ok] %s\n" "$1"
}
fail() {
printf "[fail] %s\n" "$1" >&2
exit 1
}
check_exists() {
local path="$1"
local label="$2"
[[ -e "$path" ]] && pass "$label" || fail "$label"
}
check_contains() {
local path="$1"
local needle="$2"
local label="$3"
grep -Fq "$needle" "$path" && pass "$label" || fail "$label"
}
dump_dll_report() {
local dll_path="$1"
local label="$2"
local deps arch
arch="$(objdump -f "$dll_path" | awk -F'file format ' '/file format/ {print $2}' | head -n1)"
deps="$(objdump -p "$dll_path" | awk '/DLL Name:/ {print $3}' | sort -u | paste -sd',' -)"
printf "[dll] %s arch=%s deps=%s\n" "$label" "${arch:-unknown}" "${deps:-none}"
}
check_packaged_runtime_deps() {
local dll_path="$1"
local search_dir="$2"
local dep
while read -r dep; do
[[ -n "$dep" ]] || continue
if ! printf '%s\n' "${runtime_dll_names[@]}" | grep -Fxq "$dep"; then
printf "[dep] %s requires %s ... provided by Wine/Windows runtime\n" "$(basename "$dll_path")" "$dep"
continue
fi
printf "[dep] %s requires %s ... " "$(basename "$dll_path")" "$dep"
if [[ -f "$search_dir/$dep" || -f "$(dirname "$dll_path")/$dep" ]]; then
printf "present\n"
else
printf "missing\n" >&2
exit 1
fi
done < <(objdump -p "$dll_path" | awk '/DLL Name:/ {print $3}')
}
check_exists "$tool_dir/compatibilitytool.vdf" "compatibilitytool.vdf exists"
check_exists "$tool_dir/proton" "proton wrapper exists"
[[ -x "$tool_dir/proton" ]] && pass "proton wrapper executable" || fail "proton wrapper executable"
check_contains "$tool_dir/compatibilitytool.vdf" "\"$tool_name\"" "tool name in compatibilitytool.vdf matches"
check_contains "$tool_dir/compatibilitytool.vdf" "\"display_name\" \"$tool_name\"" "display_name in compatibilitytool.vdf matches"
for dll in d3d9.dll dxgi.dll d3d10core.dll d3d11.dll; do
check_exists "$tool_dir/files/lib/wine/dxvk/i386-windows/$dll" "$dll x86 exists"
check_exists "$tool_dir/files/lib/wine/dxvk/x86_64-windows/$dll" "$dll x64 exists"
done
check_contains "$tool_dir/proton" 'os.environ.setdefault("PROTON_DISCORD_BRIDGE", "0")' "PROTON_DISCORD_BRIDGE default 0 in wrapper"
check_contains "$tool_dir/proton" 'os.environ.setdefault("PROTON_USE_OPTISCALER", "0")' "PROTON_USE_OPTISCALER default 0 in wrapper"
check_contains "$tool_dir/proton" 'os.environ.setdefault("PROTON_USE_D7VK", "0")' "PROTON_USE_D7VK default 0 in wrapper"
check_contains "$tool_dir/proton" 'os.environ.setdefault("MANGOHUD", "0")' "MANGOHUD default 0 in wrapper"
check_contains "$tool_dir/proton" 'os.environ.setdefault("ENABLE_VKBASALT", "0")' "ENABLE_VKBASALT default 0 in wrapper"
if grep -Fq 'LOW_LATENCY_LAYER' "$tool_dir/proton" && grep -Fq 'LOW_LATENCY_LAYER", "0"' "$tool_dir/proton"; then
fail "LOW_LATENCY_LAYER must not be hard-disabled"
else
pass "LOW_LATENCY_LAYER not hard-disabled"
fi
for dll in d3d9.dll dxgi.dll d3d10core.dll d3d11.dll; do
dump_dll_report "$tool_dir/files/lib/wine/dxvk/i386-windows/$dll" "x86 $dll"
dump_dll_report "$tool_dir/files/lib/wine/dxvk/x86_64-windows/$dll" "x64 $dll"
check_packaged_runtime_deps "$tool_dir/files/lib/wine/dxvk/i386-windows/$dll" "$tool_dir/files/share/default_pfx/drive_c/windows/syswow64"
check_packaged_runtime_deps "$tool_dir/files/lib/wine/dxvk/x86_64-windows/$dll" "$tool_dir/files/share/default_pfx/drive_c/windows/system32"
done
for runtime_dll in "${runtime_dll_names[@]}"; do
if objdump -p "$tool_dir/files/lib/wine/dxvk/i386-windows/"*.dll | grep -Fq "$runtime_dll"; then
check_exists "$tool_dir/files/share/default_pfx/drive_c/windows/syswow64/$runtime_dll" "$runtime_dll present for x86 if needed"
fi
if objdump -p "$tool_dir/files/lib/wine/dxvk/x86_64-windows/"*.dll | grep -Fq "$runtime_dll"; then
check_exists "$tool_dir/files/share/default_pfx/drive_c/windows/system32/$runtime_dll" "$runtime_dll present for x64 if needed"
fi
done