mirror of
https://github.com/aclist/dztui.git
synced 2024-12-28 21:32:36 +01:00
2126 lines
67 KiB
Bash
Executable file
2126 lines
67 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -o pipefail
|
|
version=4.1.0.rc-4
|
|
|
|
aid=221100
|
|
game="dayz"
|
|
workshop="steam://url/CommunityFilePage/"
|
|
api="https://api.battlemetrics.com/servers"
|
|
sd_res="--width=1280 --height=800"
|
|
config_path="$HOME/.config/dztui/"
|
|
config_file="${config_path}dztuirc"
|
|
hist_file="${config_path}history"
|
|
tmp=/tmp/dzgui.tmp
|
|
fifo=/tmp/table.tmp
|
|
debug_log="$PWD/DZGUI_DEBUG.log"
|
|
separator="%%"
|
|
check_config_msg="Check config values and restart."
|
|
issues_url="https://github.com/aclist/dztui/issues"
|
|
url_prefix="https://raw.githubusercontent.com/aclist/dztui"
|
|
stable_url="$url_prefix/dzgui"
|
|
testing_url="$url_prefix/testing"
|
|
releases_url="https://github.com/aclist/dztui/releases/download/browser"
|
|
help_url="https://aclist.github.io/dzgui/dzgui"
|
|
sponsor_url="https://github.com/sponsors/aclist"
|
|
freedesktop_path="$HOME/.local/share/applications"
|
|
sd_install_path="$HOME/.local/share/dzgui"
|
|
helpers_path="$sd_install_path/helpers"
|
|
geo_file="$helpers_path/ips.csv"
|
|
km_helper="$helpers_path/latlon"
|
|
sums_path="$helpers_path/sums.md5"
|
|
scmd_file="$helpers_path/scmd.sh"
|
|
km_helper_url="$releases_url/latlon"
|
|
db_file="$releases_url/ips.csv.gz"
|
|
sums_url="$testing_url/helpers/sums.md5"
|
|
scmd_url="$testing_url/helpers/scmd.sh"
|
|
vdf2json_url="$testing_url/helpers/vdf2json.py"
|
|
forum_url="https://github.com/aclist/dztui/discussions"
|
|
version_file="$config_path/versions"
|
|
steamsafe_zenity="/usr/bin/zenity"
|
|
|
|
update_last_seen(){
|
|
mv $config_file ${config_path}dztuirc.old
|
|
nr=$(awk '/seen_news=/ {print NR}' ${config_path}dztuirc.old)
|
|
seen_news="seen_news=\"$sum\""
|
|
awk -v "var=$seen_news" -v "nr=$nr" 'NR==nr {$0=var}{print}' ${config_path}dztuirc.old > $config_file
|
|
source $config_file
|
|
}
|
|
check_news(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
echo "# Checking news"
|
|
[[ $branch == "stable" ]] && news_url="$stable_url/news"
|
|
[[ $branch == "testing" ]] && news_url="$testing_url/news"
|
|
local result=$(curl -Ls "$news_url")
|
|
sum=$(echo -n "$result" | md5sum | awk '{print $1}')
|
|
logger INFO "News: $result"
|
|
}
|
|
print_news(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ $sum == $seen_news || -z $result ]]; then
|
|
hchar=""
|
|
news=""
|
|
else
|
|
hchar="─"
|
|
news="$result\n$(awk -v var="$hchar" 'BEGIN{for(c=0;c<90;c++) printf var;}')\n"
|
|
update_last_seen
|
|
fi
|
|
}
|
|
|
|
declare -A deps
|
|
deps=([awk]="5.1.1" [curl]="7.80.0" [jq]="1.6" [tr]="9.0" [$steamsafe_zenity]="3.42.1" [fold]="9.0")
|
|
changelog(){
|
|
build(){
|
|
local mdbranch
|
|
case "$branch" in
|
|
"stable")
|
|
mdbranch="dzgui"
|
|
;;
|
|
*)
|
|
mdbranch="testing"
|
|
;;
|
|
esac
|
|
local md="https://raw.githubusercontent.com/aclist/dztui/${mdbranch}/CHANGELOG.md"
|
|
prefix="This window can be scrolled."
|
|
echo $prefix
|
|
echo ""
|
|
curl -Ls "$md"
|
|
}
|
|
build | $steamsafe_zenity --text-info $sd_res --title="DZGUI" 2>/dev/null
|
|
}
|
|
|
|
depcheck(){
|
|
for dep in "${!deps[@]}"; do
|
|
command -v "$dep" 2>&1>/dev/null || (printf "Requires %s >=%s\n" "$dep" ${deps[$dep]}; exit 1)
|
|
done
|
|
}
|
|
watcher_deps(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ ! $(command -v wmctrl) ]] && [[ ! $(command -v xdotool) ]]; then
|
|
echo "100"
|
|
warn "Missing dependency: requires 'wmctrl' or 'xdotool'.\nInstall from your system's package manager."
|
|
logger ERROR "Missing watcher dependencies"
|
|
exit 1
|
|
fi
|
|
}
|
|
init_items(){
|
|
#array order determines menu selector; this is destructive
|
|
#change favorite index affects setup() and add_by_fav()
|
|
items=(
|
|
"[Connect]"
|
|
" Server browser"
|
|
" My servers"
|
|
" Quick connect to favorite server"
|
|
" Connect by ID"
|
|
" Connect by IP"
|
|
" Recent servers (last 10)"
|
|
"[Manage servers]"
|
|
" Add server by ID"
|
|
" Add server by IP"
|
|
" Add favorite server"
|
|
" Delete server"
|
|
"[Options]"
|
|
" List installed mods"
|
|
" View changelog"
|
|
" Advanced options"
|
|
"[Help]"
|
|
" Help file ⧉"
|
|
" Report bug ⧉"
|
|
" Forum ⧉"
|
|
" Sponsor ⧉"
|
|
" Hall of fame ⧉"
|
|
)
|
|
}
|
|
warn(){
|
|
logger WARN "$1"
|
|
$steamsafe_zenity --info --title="DZGUI" --text="$1" --width=500 --icon-name="dialog-warning" 2>/dev/null
|
|
}
|
|
info(){
|
|
$steamsafe_zenity --info --title="DZGUI" --text="$1" --width=500 2>/dev/null
|
|
}
|
|
set_api_params(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
response=$(curl -s "$api" -H "Authorization: Bearer "$api_key"" -G -d "sort=-players" \
|
|
-d "filter[game]=$game" -d "filter[ids][whitelist]=$list_of_ids")
|
|
list_response=$response
|
|
first_entry=1
|
|
}
|
|
write_config(){
|
|
cat <<-END
|
|
#Path to DayZ installation
|
|
steam_path="$steam_path"
|
|
|
|
#Your unique API key
|
|
api_key="$api_key"
|
|
|
|
#Favorited server IP:PORT array
|
|
ip_list=(
|
|
$(print_ip_list)
|
|
)
|
|
|
|
#Favorite server to fast-connect to (limit one)
|
|
fav_server="$fav_server"
|
|
|
|
#Favorite server label (human readable)
|
|
fav_label="$fav_label"
|
|
|
|
#Custom player name (optional, required by some servers)
|
|
name="$name"
|
|
|
|
#Set to 1 to perform dry-run and print launch options
|
|
debug="$debug"
|
|
|
|
#Toggle stable/testing branch
|
|
branch="$branch"
|
|
|
|
#Last seen news item
|
|
seen_news="$seen_news"
|
|
|
|
#Steam API key
|
|
steam_api="$steam_api"
|
|
|
|
#Auto-install mods
|
|
auto_install="$auto_install"
|
|
|
|
#Automod staging directory
|
|
staging_dir="$staging_dir"
|
|
|
|
#Path to default Steam client
|
|
default_steam_path="$default_steam_path"
|
|
|
|
#Preferred Steam launch command (for Flatpak support)
|
|
preferred_client="$preferred_client"
|
|
END
|
|
}
|
|
write_desktop_file(){
|
|
cat <<-END
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Terminal=false
|
|
Exec=$sd_install_path/dzgui.sh
|
|
Name=DZGUI
|
|
Comment=dzgui
|
|
Icon=$sd_install_path/dzgui
|
|
Categories=Game
|
|
END
|
|
}
|
|
freedesktop_dirs(){
|
|
mkdir -p "$sd_install_path"
|
|
mkdir -p "$freedesktop_path"
|
|
curl -s "$version_url" > "$sd_install_path/dzgui.sh"
|
|
chmod +x "$sd_install_path/dzgui.sh"
|
|
img_url="$testing_url/images"
|
|
for i in dzgui grid.png hero.png logo.png; do
|
|
curl -s "$img_url/$i" > "$sd_install_path/$i"
|
|
done
|
|
write_desktop_file > "$freedesktop_path/dzgui.desktop"
|
|
if [[ $is_steam_deck -eq 1 ]]; then
|
|
write_desktop_file > "$HOME/Desktop/dzgui.desktop"
|
|
fi
|
|
}
|
|
find_library_folder(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
logger INFO "User picked directory: '$1'"
|
|
steam_path="$(python3 "$helpers_path/vdf2json.py" -i "$1/steamapps/libraryfolders.vdf" | jq -r '.libraryfolders[]|select(.apps|has("221100")).path')"
|
|
logger INFO "Steam path resolved to: $steam_path"
|
|
}
|
|
file_picker(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
local path=$($steamsafe_zenity --file-selection --directory 2>/dev/null)
|
|
logger INFO "File picker path resolve to: $path"
|
|
if [[ -z "$path" ]]; then
|
|
logger INFO "Path was empty"
|
|
return
|
|
else
|
|
default_steam_path="$path"
|
|
find_library_folder "$default_steam_path"
|
|
fi
|
|
}
|
|
create_config(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
check_pyver
|
|
write_to_config(){
|
|
mkdir -p $config_path
|
|
write_config > $config_file
|
|
info "Config file created at $config_file."
|
|
source $config_file
|
|
}
|
|
while true; do
|
|
player_input="$($steamsafe_zenity --forms --add-entry="Player name (required for some servers)" --add-entry="Steam API key" --add-entry="BattleMetrics API key (optional)" --title="DZGUI" --text="DZGUI" $sd_res --separator="@" 2>/dev/null)"
|
|
#explicitly setting IFS crashes $steamsafe_zenity in loop
|
|
#and mapfile does not support high ascii delimiters
|
|
#so split fields with newline
|
|
readarray -t args < <(echo "$player_input" | sed 's/@/\n/g')
|
|
name="${args[0]}"
|
|
steam_api="${args[1]}"
|
|
api_key="${args[2]}"
|
|
|
|
[[ -z $player_input ]] && exit
|
|
if [[ -z $steam_api ]]; then
|
|
warn "Steam API key cannot be empty"
|
|
continue
|
|
elif [[ $(test_steam_api) -eq 1 ]]; then
|
|
warn "Invalid Steam API key"
|
|
continue
|
|
fi
|
|
if [[ -n $api_key ]] && [[ $(test_bm_api $api_key) -eq 1 ]]; then
|
|
warn "Invalid BM API key"
|
|
continue
|
|
fi
|
|
while true; do
|
|
logger INFO "steamsafe_zenity is $steamsafe_zenity"
|
|
if [[ -n $steam_path ]]; then
|
|
write_to_config
|
|
return
|
|
fi
|
|
find_default_path
|
|
find_library_folder "$default_steam_path"
|
|
if [[ -z $steam_path ]]; then
|
|
logger WARN "Steam path was empty"
|
|
zenity --question --text="DayZ not found or not installed at the chosen path." --ok-label="Choose path manually" --cancel-label="Exit"
|
|
if [[ $? -eq 0 ]]; then
|
|
logger INFO "User selected file picker"
|
|
file_picker
|
|
else
|
|
exit
|
|
fi
|
|
else
|
|
write_to_config
|
|
return
|
|
fi
|
|
done
|
|
done
|
|
|
|
}
|
|
err(){
|
|
printf "[ERROR] %s\n" "$1"
|
|
}
|
|
varcheck(){
|
|
if [[ ! -d $steam_path ]] || [[ ! -d $game_dir ]]; then
|
|
echo 1
|
|
fi
|
|
}
|
|
run_depcheck(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ -n $(depcheck) ]]; then
|
|
echo "100"
|
|
logger ERROR "Missing dependencies, quitting"
|
|
$steamsafe_zenity --warning --ok-label="Exit" --title="DZGUI" --text="$(depcheck)"
|
|
exit
|
|
fi
|
|
}
|
|
logger(){
|
|
local date="$(date "+%F %T")"
|
|
local tag="$1"
|
|
local string="$2"
|
|
printf "[%s] [%s] %s\n" "$date" "$tag" "$string" >> "$debug_log"
|
|
}
|
|
check_pyver(){
|
|
local pyver=$(python3 --version | awk '{print $2}')
|
|
local minor=$(<<< $pyver awk -F. '{print $2}')
|
|
if [[ -z $pyver ]] || [[ ${pyver:0:1} -lt 3 ]] || [[ $minor -lt 10 ]]; then
|
|
warn "Requires python >=3.10" &&
|
|
exit
|
|
fi
|
|
}
|
|
run_varcheck(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
source $config_file
|
|
workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
|
game_dir="$steam_path/steamapps/common/DayZ"
|
|
if [[ $(varcheck) -eq 1 ]]; then
|
|
$steamsafe_zenity --question --cancel-label="Exit" --text="Malformed config file. This is probably user error.\nStart first-time setup process again?" --width=500 2>/dev/null
|
|
code=$?
|
|
if [[ $code -eq 1 ]]; then
|
|
logger ERROR "Malformed config vars"
|
|
exit
|
|
else
|
|
create_config
|
|
fi
|
|
fi
|
|
}
|
|
config(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ ! -f $config_file ]]; then
|
|
logger WARN "Config file missing"
|
|
logger INFO "steamsafe_zenity is $steamsafe_zenity"
|
|
$steamsafe_zenity --width=500 --info --text="Config file not found. Click OK to proceed to first-time setup." 2>/dev/null
|
|
code=$?
|
|
logger INFO "Return code $code"
|
|
#TODO: prevent progress if user hits ESC
|
|
if [[ $code -eq 1 ]]; then
|
|
exit
|
|
else
|
|
create_config
|
|
fi
|
|
else
|
|
source $config_file
|
|
fi
|
|
|
|
}
|
|
steam_deck_mods(){
|
|
until [[ -z $diff ]]; do
|
|
next=$(echo -e "$diff" | head -n1)
|
|
$steamsafe_zenity --question --ok-label="Open" --cancel-label="Cancel" --title="DZGUI" --text="Missing mods. Click [Open] to open mod $next in Steam Workshop and subscribe to it by clicking the green Subscribe button. After the mod is downloaded, return to this menu to continue validation." --width=500 2>/dev/null
|
|
rc=$?
|
|
if [[ $rc -eq 0 ]]; then
|
|
echo "[DZGUI] Opening ${workshop}$next"
|
|
$steam_cmd steam://url/CommunityFilePage/$next 2>/dev/null &
|
|
$steamsafe_zenity --info --title="DZGUI" --ok-label="Next" --text="Click [Next] to continue mod check." --width=500 2>/dev/null
|
|
else
|
|
return 1
|
|
fi
|
|
compare
|
|
done
|
|
}
|
|
test_display_mode(){
|
|
pgrep -a gamescope | grep -q "generate-drm-mode"
|
|
if [[ $? -eq 0 ]]; then
|
|
echo gm
|
|
else
|
|
echo dm
|
|
fi
|
|
}
|
|
foreground(){
|
|
if [[ $(command -v wmctrl) ]]; then
|
|
wmctrl -a "DZG Watcher"
|
|
else
|
|
local window_id=$(xdotool search --name "DZG Watcher")
|
|
xdotool windowactivate $window_id
|
|
fi
|
|
}
|
|
manual_mod_install(){
|
|
local ip="$1"
|
|
local gameport="$2"
|
|
|
|
local ex="/tmp/dzc.tmp"
|
|
[[ -f $ex ]] && rm $ex
|
|
watcher(){
|
|
readarray -t stage_mods <<< "$diff"
|
|
for((i=0;i<${#stage_mods[@]};i++)); do
|
|
[[ -f $ex ]] && return 1
|
|
local downloads_dir="$steam_path/steamapps/workshop/downloads/$aid"
|
|
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
|
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}"
|
|
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."
|
|
sleep 1s
|
|
foreground
|
|
until [[ -d $downloads_dir/${stage_mods[$i]} ]]; do
|
|
[[ -f $ex ]] && return 1
|
|
sleep 0.1s
|
|
if [[ -d $workshop_dir/${stage_mods[$i]} ]]; then
|
|
break
|
|
fi
|
|
done
|
|
foreground
|
|
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]})"
|
|
until [[ -d $workshop_dir/${stage_mods[$i]} ]]; do
|
|
[[ -f $ex ]] && return 1
|
|
sleep 0.1s
|
|
done
|
|
foreground
|
|
echo "# ${stage_mods[$i]} moved to mods dir"
|
|
done
|
|
echo "100"
|
|
}
|
|
watcher > >($steamsafe_zenity --pulsate --progress --auto-close --title="DZG Watcher" --width=500 2>/dev/null; rc=$?; [[ $rc -eq 1 ]] && touch $ex)
|
|
compare
|
|
if [[ -z $diff ]]; then
|
|
passed_mod_check > >($steamsafe_zenity --pulsate --progress --auto-close --width=500 2>/dev/null)
|
|
launch "$ip" "$gameport"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
encode(){
|
|
echo "$1" | md5sum | cut -c -8
|
|
}
|
|
stale_symlinks(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
for l in $(find "$game_dir" -xtype l); do
|
|
unlink $l
|
|
done
|
|
}
|
|
legacy_symlinks(){
|
|
for d in "$game_dir"/*; do
|
|
if [[ $d =~ @[0-9]+-.+ ]]; then
|
|
unlink "$d"
|
|
fi
|
|
done
|
|
for d in "$workshop_dir"/*; do
|
|
local id=$(awk -F"= " '/publishedid/ {print $2}' "$d"/meta.cpp | awk -F\; '{print $1}')
|
|
local encoded_id=$(echo "$id" | awk '{printf("%c",$1)}' | base64 | sed 's/\//_/g; s/=//g; s/+/]/g')
|
|
if [[ -h "$game_dir/@$encoded_id" ]]; then
|
|
unlink "$game_dir/@$encoded_id"
|
|
fi
|
|
done
|
|
}
|
|
symlinks(){
|
|
for d in "$workshop_dir"/*; do
|
|
id=$(awk -F"= " '/publishedid/ {print $2}' "$d"/meta.cpp | awk -F\; '{print $1}')
|
|
encoded_id=$(encode "$id")
|
|
mod=$(awk -F\" '/name/ {print $2}' "$d"/meta.cpp | sed -E 's/[^[:alpha:]0-9]+/_/g; s/^_|_$//g')
|
|
link="@$encoded_id"
|
|
if [[ -h "$game_dir/$link" ]]; then
|
|
:
|
|
else
|
|
printf "[DZGUI] Creating symlink for $mod\n"
|
|
ln -fs "$d" "$game_dir/$link"
|
|
fi
|
|
done
|
|
}
|
|
passed_mod_check(){
|
|
echo "[DZGUI] Passed mod check"
|
|
echo "# Preparing symlinks"
|
|
legacy_symlinks
|
|
symlinks
|
|
echo "100"
|
|
|
|
}
|
|
auto_mod_install(){
|
|
local ip="$1"
|
|
local gameport="$2"
|
|
popup 300
|
|
rc=$?
|
|
if [[ $rc -eq 1 ]]; then
|
|
manual_mod_install "$ip" "$gameport"
|
|
return
|
|
fi
|
|
log="$default_steam_path/logs/content_log.txt"
|
|
[[ -f "/tmp/dz.status" ]] && rm "/tmp/dz.status"
|
|
touch "/tmp/dz.status"
|
|
console_dl "$diff" &&
|
|
$steam_cmd steam://open/downloads && 2>/dev/null 1>&2
|
|
foreground
|
|
until [[ -z $(comm -23 <(printf "%s\n" "${modids[@]}" | sort) <(ls -1 $workshop_dir | sort)) ]]; do
|
|
local missing=$(comm -23 <(printf "%s\n" "${modids[@]}" | sort) <(ls -1 $workshop_dir | sort) | wc -l)
|
|
echo "# Downloaded $((${#modids[@]}-missing)) of ${#modids[@]} mods. ESC cancels"
|
|
done | $steamsafe_zenity --pulsate --progress --title="DZG Watcher" --auto-close --no-cancel --width=500 2>/dev/null
|
|
compare
|
|
[[ $force_update -eq 1 ]] && { unset force_update; return; }
|
|
if [[ -z $diff ]]; then
|
|
check_timestamps
|
|
passed_mod_check > >($steamsafe_zenity --pulsate --progress --title="DZGUI" --auto-close --width=500 2>/dev/null)
|
|
launch "$ip" "$gameport"
|
|
else
|
|
manual_mod_install "$ip" "$gameport"
|
|
fi
|
|
}
|
|
get_local_stamps(){
|
|
concat(){
|
|
for ((i=0;i<$max;i++)); do
|
|
echo "publishedfileids[$i]=${local_modlist[$i]}&"
|
|
done | awk '{print}' ORS=''
|
|
}
|
|
payload(){
|
|
echo -e "itemcount=${max}&$(concat)"
|
|
}
|
|
post(){
|
|
curl -s -X POST -H "Content-Type:application/x-www-form-urlencoded" \
|
|
-d "$(payload)" 'https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json'
|
|
}
|
|
post
|
|
}
|
|
update_stamps(){
|
|
for((i=0;i<${#local_modlist[@]};i++)); do
|
|
mod=${local_modlist[$i]}
|
|
stamp=${stamps[$i]}
|
|
printf "%s\t%s\n" "$mod" "$stamp" >> $version_file
|
|
done
|
|
}
|
|
check_timestamps(){
|
|
readarray -t local_modlist < <(ls -1 $workshop_dir)
|
|
max=${#local_modlist[@]}
|
|
[[ $max -eq 0 ]] && return
|
|
readarray -t stamps < <(get_local_stamps | jq -r '.response.publishedfiledetails[].time_updated')
|
|
if [[ ! -f $version_file ]]; then
|
|
update_stamps
|
|
return
|
|
else
|
|
needs_update=()
|
|
for((i=0;i<${#local_modlist[@]};i++)); do
|
|
mod=${local_modlist[$i]}
|
|
stamp=${stamps[$i]}
|
|
if [[ ! $(awk -v var=$mod '$1 == var' $version_file) ]]; then
|
|
echo -e "$mod\t$stamp" >> $version_file
|
|
elif [[ $(awk -v var=$mod -v var2=$stamp '$1 == var && $2 == var2' $version_file) ]]; then
|
|
:
|
|
else
|
|
awk -v var=$mod -v var2=$stamp '$1 == var {$2=var2;print $1"\t"$2; next;};{print}' $version_file > $version_file.new
|
|
mv $version_file.new $version_file
|
|
needs_update+=($mod)
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
merge_modlists(){
|
|
echo "# Aligning modlists"
|
|
[[ $force_update -eq 1 ]] && echo "# Checking mod versions"
|
|
check_timestamps
|
|
if [[ -z "$diff" ]] && [[ ${#needs_update[@]} -gt 0 ]]; then
|
|
diff=$(printf "%s\n" "${needs_update[@]}")
|
|
elif [[ -z "$diff" ]] && [[ ${#needs_update[@]} -eq 0 ]]; then
|
|
diff=
|
|
elif [[ -n "$diff" ]] && [[ ${#needs_update[@]} -eq 0 ]]; then
|
|
:
|
|
else
|
|
diff="$(printf "%s\n%s\n" "$diff" "${needs_update[@]}")"
|
|
fi
|
|
[[ $force_update -eq 1 ]] && echo "100"
|
|
}
|
|
update_history(){
|
|
local ip="$1"
|
|
local gameport="$2"
|
|
local qport="$3"
|
|
[[ -n $(grep "$ip:$gameport:$qport" $hist_file) ]] && return
|
|
if [[ -f $hist_file ]]; then
|
|
old=$(tail -n9 "$hist_file")
|
|
old="$old\n"
|
|
fi
|
|
echo -e "${old}${ip}:${gameport}:${qport}" > "$hist_file"
|
|
}
|
|
connect(){
|
|
local ip=$1
|
|
local gameport=$2
|
|
local qport=$3
|
|
logger INFO "Querying $ip:$gameport:$qport"
|
|
connect_dialog(){
|
|
echo "# Querying modlist"
|
|
local remote
|
|
remote=$(a2s "$ip" "$qport" rules)
|
|
if [[ $? -eq 1 ]]; then
|
|
echo "100"
|
|
popup 1200
|
|
return 1
|
|
fi
|
|
logger INFO "Server returned modlist: $(<<< $remote tr '\n' ' ')"
|
|
echo "# Checking for defunct mods"
|
|
query_defunct "$remote"
|
|
}
|
|
(connect_dialog "$ip" "$qport") | pdialog
|
|
rc=$?
|
|
[[ $rc -eq 1 ]] && return
|
|
readarray -t newlist < /tmp/dz.modlist
|
|
compare
|
|
[[ $auto_install -eq 2 ]] && merge_modlists > >(pdialog)
|
|
if [[ -n $diff ]]; then
|
|
if [[ $is_steam_deck -eq 1 ]] && [[ $(test_display_mode) == "gm" ]]; then
|
|
popup 1400
|
|
return 1
|
|
fi
|
|
case $auto_install in
|
|
1|2) auto_mod_install "$ip" "$gameport" ;;
|
|
*) manual_mod_install "$ip" "$gameport" ;;
|
|
esac
|
|
else
|
|
passed_mod_check > >(pdialog)
|
|
update_history "$ip" "$gameport" "$qport"
|
|
launch "$ip" "$gameport" "$qport"
|
|
fi
|
|
}
|
|
update_config(){
|
|
mv $config_file ${config_file}.old
|
|
write_config > $config_file
|
|
source $config_file
|
|
}
|
|
prepare_ip_list(){
|
|
local res="$1"
|
|
local ct=$(<<< "$res" jq '[.response.servers[]]|length' 2>/dev/null)
|
|
if [[ -n $ct ]]; then
|
|
for((i=0;i<$ct;i++));do
|
|
readarray -t json_arr < <(<<< $res jq --arg i $i -r '[.response.servers[]][($i|tonumber)]|"\(.name)\n\(.addr)\n\(.players)\n\(.max_players)\n\(.gameport)\n\(.gametype)"')
|
|
local name=${json_arr[0]}
|
|
local addr=${json_arr[1]}
|
|
local ip=$(<<< $addr awk -F: '{print $1}')
|
|
local qport=$(<<< $addr awk -F: '{print $2}')
|
|
local current=${json_arr[2]}
|
|
local max=${json_arr[3]}
|
|
local players="${current}/${max}"
|
|
local gameport="${json_arr[4]}"
|
|
local gametime="${json_arr[5]}"
|
|
gametime=$(<<< "$gametime" grep -o '[0-9][0-9]:[0-9][0-9]')
|
|
|
|
echo "$name"
|
|
echo "${ip}:${gameport}"
|
|
echo "$players"
|
|
echo "$gametime"
|
|
echo "$qport"
|
|
done
|
|
fi
|
|
}
|
|
ip_table(){
|
|
local sel
|
|
local res="$1"
|
|
while true; do
|
|
sel=$(prepare_ip_list "$res" | $steamsafe_zenity --width 1200 --height 800 --text="Multiple maps found at this server. Select map from the list below" --title="DZGUI" --list --column=Name --column=IP --column=Players --column=Gametime --column=Qport --print-column=1,2,5 --separator=%% 2>/dev/null)
|
|
[[ $? -eq 1 ]] && return 1
|
|
echo "$sel"
|
|
return 0
|
|
done
|
|
}
|
|
fetch_ip_metadata(){
|
|
local ip="$1"
|
|
source $config_file
|
|
local url="https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\appid\221100\gameaddr\\$ip&key=$steam_api"
|
|
curl -Ls "$url"
|
|
}
|
|
|
|
validate_local_ip(){
|
|
<<< "$1" grep -qP '(^127.\d+.\d+.\d+:\d+$)|(^10\.\d+.\d+.\d+:\d+$)|(^172\.1[6-9]\.\d+.\d+:\d+$)|(^172\.2[0-9]\.\d+.\d+:\d+$)|(^172\.3[0-1]\.\d+.\d+:\d+$)|(^192\.168\.\d+.\d+:\d+$)'
|
|
}
|
|
test_steam_api(){
|
|
local url="https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\appid\221100&limit=10&key=$steam_api"
|
|
local code=$(curl -ILs "$url" | grep -E "^HTTP")
|
|
[[ $code =~ 403 ]] && echo 1
|
|
[[ $code =~ 200 ]] && echo 0
|
|
}
|
|
test_bm_api(){
|
|
local api_key="$1"
|
|
[[ -z $api_key ]] && return 1
|
|
local code=$(curl -ILs "$api" -H "Authorization: Bearer "$api_key"" -G \
|
|
-d "filter[game]=$game" | grep -E "^HTTP")
|
|
[[ $code =~ 401 ]] && echo 1
|
|
[[ $code =~ 200 ]] && echo 0
|
|
}
|
|
add_steam_api(){
|
|
[[ $(test_steam_api) -eq 1 ]] && return 1
|
|
mv $config_file ${config_path}dztuirc.old
|
|
nr=$(awk '/steam_api=/ {print NR}' ${config_path}dztuirc.old)
|
|
steam_api="steam_api=\"$steam_api\""
|
|
awk -v "var=$steam_api" -v "nr=$nr" 'NR==nr {$0=var}{print}' ${config_path}dztuirc.old > ${config_path}dztuirc
|
|
echo "[DZGUI] Added Steam API key"
|
|
$steamsafe_zenity --info --title="DZGUI" --text="Added Steam API key to:\n\n${config_path}dztuirc\nIf errors occur, you can restore the file:\n${config_path}dztuirc.old" --width=500 2>/dev/null
|
|
source $config_file
|
|
}
|
|
check_steam_api(){
|
|
if [[ -z $steam_api ]]; then
|
|
steam_api=$($steamsafe_zenity --entry --text="Key 'steam_api' not present in config file. Enter Steam API key:" --title="DZGUI" 2>/dev/null)
|
|
if [[ $? -eq 1 ]] ; then
|
|
return
|
|
elif [[ ${#steam_api} -lt 32 ]] || [[ $(test_steam_api) -eq 1 ]]; then
|
|
$steamsafe_zenity --warning --title="DZGUI" --text="Check API key and try again." 2>/dev/null
|
|
return 1
|
|
else
|
|
add_steam_api
|
|
fi
|
|
fi
|
|
}
|
|
validate_ip(){
|
|
echo "$1" | grep -qP '^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$'
|
|
}
|
|
connect_by_id(){
|
|
local ip
|
|
ip=$(add_by_id "connect")
|
|
[[ $? -eq 1 ]] && return
|
|
readarray -t address < <(format_config_address "$ip")
|
|
local ip="${address[0]}"
|
|
local gameport="${address[1]}"
|
|
local qport="${address[2]}"
|
|
unset address
|
|
|
|
connect "$ip" "$gameport" "$qport"
|
|
}
|
|
connect_by_ip(){
|
|
local sel
|
|
sel=$(parse_ips)
|
|
[[ -z $sel ]] && return
|
|
|
|
readarray -t address < <(format_table_results "$sel")
|
|
local ip="${address[1]}"
|
|
local gameport="${address[2]}"
|
|
local qport="${address[3]}"
|
|
|
|
connect "$ip" "$gameport" "$qport"
|
|
}
|
|
parse_ips(){
|
|
local res
|
|
source $config_file
|
|
check_steam_api
|
|
[[ $? -eq 1 ]] && return
|
|
while true; do
|
|
local ip
|
|
ip=$(edialog "Enter server IP (for LAN servers, include query port in IP:PORT format)")
|
|
[[ $? -eq 1 ]] && return 1
|
|
if [[ $ip =~ ':' ]]; then
|
|
if ! validate_local_ip "$ip"; then
|
|
warn "Invalid local IP. Check IP:PORT combination and try again."
|
|
continue
|
|
fi
|
|
local lan_ip=$(<<< $ip awk -F: '{print $1}')
|
|
local lan_qport=$(<<< $ip awk -F: '{print $2}')
|
|
res=$(a2s $lan_ip $lan_qport info)
|
|
logger INFO "Local LAN response follows"
|
|
logger INFO "$res"
|
|
if [[ ! $? -eq 0 ]] || [[ $(<<< $res jq '.response|length') -eq 0 ]]; then
|
|
warn "Failed to retrieve server metadata. Check IP:PORT combination and try again."
|
|
return 1
|
|
fi
|
|
ip_table "$res"
|
|
return 0
|
|
else
|
|
if validate_ip "$ip"; then
|
|
res=$(fetch_ip_metadata "$ip")
|
|
if [[ ! $? -eq 0 ]] || [[ $(<<< $res jq '.response|length') -eq 0 ]]; then
|
|
warn "Failed to retrieve server metadata. Check IP or API key and try again."
|
|
return 1
|
|
fi
|
|
local ct=$(<<< "$res" jq '.response.servers|length')
|
|
if [[ $ct -eq 1 ]]; then
|
|
local name=$(<<< $res jq -r '.response.servers[].name')
|
|
local address=$(<<< $res jq -r '.response.servers[].addr')
|
|
local ip=$(<<< "$address" awk -F: '{print $1}')
|
|
local qport=$(<<< "$address" awk -F: '{print $2}')
|
|
local gameport=$(<<< $res jq -r '.response.servers[].gameport')
|
|
echo "${name}%%${ip}:${gameport}%%${qport}"
|
|
return 0
|
|
fi
|
|
ip_table "$res"
|
|
return 0
|
|
else
|
|
warn "Invalid IP"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
query_defunct(){
|
|
readarray -t modlist <<< "$@"
|
|
local max=${#modlist[@]}
|
|
concat(){
|
|
for ((i=0;i<$max;i++)); do
|
|
echo "publishedfileids[$i]=${modlist[$i]}&"
|
|
done | awk '{print}' ORS=''
|
|
}
|
|
payload(){
|
|
echo -e "itemcount=${max}&$(concat)"
|
|
}
|
|
post(){
|
|
curl -s \
|
|
-X POST \
|
|
-H "Content-Type:application/x-www-form-urlencoded"\
|
|
-d "$(payload)" 'https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json'
|
|
}
|
|
local result=$(post | jq -r '.[].publishedfiledetails[] | select(.result==1) | "\(.file_size) \(.publishedfileid)"')
|
|
<<< "$result" awk '{print $2}' > /tmp/dz.modlist
|
|
}
|
|
server_modlist(){
|
|
for i in "${newlist[@]}"; do
|
|
printf "$i\n"
|
|
done
|
|
}
|
|
compare(){
|
|
diff=$(comm -23 <(server_modlist | sort -u) <(installed_mods | sort))
|
|
}
|
|
|
|
installed_mods(){
|
|
ls -1 "$workshop_dir"
|
|
}
|
|
concat_mods(){
|
|
readarray -t serv <<< "$(server_modlist)"
|
|
for i in "${serv[@]}"; do
|
|
id=$(awk -F"= " '/publishedid/ {print $2}' "$workshop_dir"/$i/meta.cpp | awk -F\; '{print $1}')
|
|
encoded_id=$(encode $id)
|
|
link="@$encoded_id;"
|
|
echo -e "$link"
|
|
done | tr -d '\n' | perl -ple 'chop'
|
|
}
|
|
launch(){
|
|
local ip="$1"
|
|
local gameport="$2"
|
|
local qport="$3"
|
|
source $config_file
|
|
mods=$(concat_mods)
|
|
if [[ ! ${ip_list[@]} =~ "$ip:$gameport:$qport" ]]; then
|
|
qdialog "Before connecting, add this server to My Servers?"
|
|
if [[ $? -eq 0 ]]; then
|
|
ip_list+=("$ip:$gameport:$qport")
|
|
update_config
|
|
fi
|
|
fi
|
|
if [[ $debug -eq 1 ]]; then
|
|
launch_options="$steam_cmd -applaunch $aid -connect=$ip:$gameport -nolauncher -nosplash -name=$name -skipintro \"-mod=$mods\""
|
|
print_launch_options="$(printf "This is a dry run.\nThese options would have been used to launch the game:\n\n$launch_options\n" | fold -w 60)"
|
|
$steamsafe_zenity --question --title="DZGUI" --ok-label="Write to file" --cancel-label="Back"\
|
|
--text="$print_launch_options" 2>/dev/null
|
|
if [[ $? -eq 0 ]]; then
|
|
source_script=$(realpath "$0")
|
|
source_dir=$(dirname "$source_script")
|
|
echo "$launch_options" > "$source_dir"/options.log
|
|
echo "[DZGUI] Wrote launch options to $source_dir/options.log"
|
|
$steamsafe_zenity --info --width=500 --title="DZGUI" --text="Wrote launch options to \n$source_dir/options.log" 2>/dev/null
|
|
fi
|
|
else
|
|
$steamsafe_zenity --width=500 --title="DZGUI" --info --text="Launch conditions satisfied.\nDayZ will now launch after clicking [OK]." 2>/dev/null
|
|
$steam_cmd -applaunch $aid -connect=$ip:$gameport -nolauncher -nosplash -skipintro -name=$name \"-mod=$mods\"
|
|
fi
|
|
}
|
|
browser(){
|
|
if [[ $is_steam_deck -eq 1 ]]; then
|
|
steam steam://openurl/"$1" 2>/dev/null
|
|
elif [[ $is_steam_deck -eq 0 ]]; then
|
|
if [[ -n "$BROWSER" ]]; then
|
|
"$BROWSER" "$1" 2>/dev/null
|
|
else
|
|
xdg-open "$1" 2>/dev/null
|
|
fi
|
|
fi
|
|
}
|
|
report_bug(){
|
|
browser "$issues_url"
|
|
}
|
|
forum(){
|
|
browser "$forum_url"
|
|
}
|
|
help_file(){
|
|
browser "$help_url"
|
|
}
|
|
sponsor(){
|
|
browser "$sponsor_url"
|
|
}
|
|
hof(){
|
|
browser "${help_url}#_hall_of_fame"
|
|
}
|
|
set_mode(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ $debug -eq 1 ]]; then
|
|
mode=debug
|
|
else
|
|
mode=normal
|
|
fi
|
|
logger INFO "Mode is $mode"
|
|
}
|
|
delete_by_ip(){
|
|
local to_delete="$1"
|
|
for (( i=0; i<${#ip_list[@]}; ++i )); do
|
|
if [[ ${ip_list[$i]} == "$to_delete" ]]; then
|
|
unset ip_list[$i]
|
|
fi
|
|
done
|
|
if [[ ${#ip_list} -gt 0 ]]; then
|
|
readarray -t ip_list < <(printf "%s\n" "${ip_list[@]}")
|
|
fi
|
|
update_config
|
|
info "Removed $to_delete from:\n${config_path}dztuirc\nIf errors occur, you can restore the file:\n${config_path}dztuirc.old"
|
|
}
|
|
format_table_results(){
|
|
local sel="$1"
|
|
local name=$(<<< "$sel" awk -F"%%" '{print $1}')
|
|
local address=$(<<< "$sel" awk -F"%%" '{print $2}')
|
|
local ip=$(<<< "$address" awk -F":" '{print $1}')
|
|
local gameport=$(<<< "$address" awk -F":" '{print $2}')
|
|
local qport=$(<<< "$sel" awk -F"%%" '{print $3}')
|
|
printf "%s\n%s\n%s\n%s\n" "$name" "$ip" "$gameport" "$qport"
|
|
}
|
|
delete_or_connect(){
|
|
local sel="$1"
|
|
local mode="$2"
|
|
|
|
readarray -t address < <(format_table_results "$sel")
|
|
local server_name="${address[0]}"
|
|
local ip="${address[1]}"
|
|
local gameport="${address[2]}"
|
|
local qport="${address[3]}"
|
|
unset address
|
|
|
|
case "$mode" in
|
|
"delete")
|
|
qdialog "Delete this server?\n$server_name"
|
|
[[ $? -eq 1 ]] && return
|
|
|
|
delete_by_ip "$ip:$gameport:$qport"
|
|
source $config_file
|
|
|
|
local str="^$ip:$gameport$"
|
|
local nr=$(awk -v v="$str" '$1 ~ v {print NR}' $tmp)
|
|
local st=$((nr-1))
|
|
local en=$((st+5))
|
|
sed -i "${st},${en}d" $tmp
|
|
# if [[ ${#ip_list[@]} -eq 0 ]]; then
|
|
# return 1
|
|
# fi
|
|
;;
|
|
"connect"|"history")
|
|
connect "$ip" "$gameport" "$qport"
|
|
return
|
|
esac
|
|
}
|
|
populate(){
|
|
local switch="$1"
|
|
while true; do
|
|
cols="--column="Server" --column="IP" --column="Players" --column="Gametime" --column="Distance" --column="Qport""
|
|
set_header "$switch"
|
|
rc=$?
|
|
if [[ $rc -eq 0 ]]; then
|
|
if [[ -z $sel ]]; then
|
|
warn "No item was selected."
|
|
else
|
|
delete_or_connect "$sel" "$switch"
|
|
fi
|
|
else
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
list_mods(){
|
|
if [[ -z $(installed_mods) ]] || [[ -z $(find $workshop_dir -maxdepth 2 -name "*.cpp" | grep .cpp) ]]; then
|
|
$steamsafe_zenity --info --text="94: No mods currently installed or incorrect path given" $sd_res 2>/dev/null
|
|
else
|
|
for d in $(find $game_dir/* -maxdepth 1 -type l); do
|
|
dir=$(basename $d)
|
|
awk -v d=$dir -F\" '/name/ {printf "%s\t%s\t", $2,d}' "$gamedir"/$d/meta.cpp
|
|
printf "%s\n" "$(basename $(readlink -f $game_dir/$dir))"
|
|
done | sort -k1
|
|
fi
|
|
}
|
|
connect_to_fav(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
|
|
local fav="$1"
|
|
[[ -z $fav ]] && { popup 1300; return; }
|
|
|
|
readarray -t address < <(format_config_address "$fav")
|
|
local ip="${address[0]}"
|
|
local gameport="${address[1]}"
|
|
local qport="${address[2]}"
|
|
|
|
unset address
|
|
connect "$ip" "$gameport" "$qport"
|
|
[[ $? -eq 1 ]] && return 1
|
|
}
|
|
set_header(){
|
|
local switch="$1"
|
|
logger INFO "${FUNCNAME[0]}"
|
|
logger INFO "Header mode is $1"
|
|
print_news
|
|
[[ $auto_install -eq 2 ]] && install_mode="auto"
|
|
[[ $auto_install -eq 1 ]] && install_mode="headless"
|
|
[[ $auto_install -eq 0 ]] && install_mode=manual
|
|
case "$switch" in
|
|
"delete")
|
|
[[ -z $(< $tmp) ]] && return 1
|
|
sel=$(< $tmp $steamsafe_zenity $sd_res --list $cols --title="DZGUI" \
|
|
--text="DZGUI $version | Mode: $mode | Branch: $branch | Mods: $install_mode | Fav: $fav_label" \
|
|
--separator="$separator" --print-column=1,2,6 --ok-label="Delete" 2>/dev/null)
|
|
;;
|
|
|
|
"connect"|"history")
|
|
sel=$(< $tmp $steamsafe_zenity $sd_res --list $cols --title="DZGUI" \
|
|
--text="DZGUI $version | Mode: $mode | Branch: $branch | Mods: $install_mode | Fav: $fav_label" \
|
|
--separator="$separator" --print-column=1,2,6 --ok-label="Connect" 2>/dev/null)
|
|
;;
|
|
|
|
"main_menu")
|
|
sel=$($steamsafe_zenity $sd_res --list --title="DZGUI" \
|
|
--text="${news}DZGUI $version | Mode: $mode | Branch: $branch | Mods: $install_mode | Fav: $fav_label" \
|
|
--cancel-label="Exit" --ok-label="Select" --column="Select launch option" --hide-header "${items[@]}" 2>/dev/null)
|
|
;;
|
|
esac
|
|
}
|
|
toggle_branch(){
|
|
mv $config_file ${config_path}dztuirc.old
|
|
nr=$(awk '/branch=/ {print NR}' ${config_path}dztuirc.old)
|
|
if [[ $branch == "stable" ]]; then
|
|
branch="testing"
|
|
else
|
|
branch="stable"
|
|
fi
|
|
flip_branch="branch=\"$branch\""
|
|
awk -v "var=$flip_branch" -v "nr=$nr" 'NR==nr {$0=var}{print}' ${config_path}dztuirc.old > $config_file
|
|
source $config_file
|
|
}
|
|
generate_log(){
|
|
cat <<-DOC
|
|
Distro: $(< /etc/os-release grep -w NAME | awk -F\" '{print $2}')
|
|
Kernel: $(uname -mrs)
|
|
Version: $version
|
|
Branch: $branch
|
|
Mode: $mode
|
|
Auto: $auto_hr
|
|
Servers:
|
|
$(print_ip_list)
|
|
Steam path: $steam_path
|
|
Workshop path: $workshop_dir
|
|
Game path: $game_dir
|
|
|
|
Mods:
|
|
$(list_mods)
|
|
DOC
|
|
}
|
|
focus_beta_client(){
|
|
steam steam://open/library 2>/dev/null 1>&2 &&
|
|
steam steam://open/console 2>/dev/null 1>&2 &&
|
|
sleep 1s
|
|
wid(){
|
|
wmctrl -ilx |\
|
|
awk 'tolower($3) == "steamwebhelper.steam"' |\
|
|
awk '$5 ~ /^Steam|Steam Games List/' |\
|
|
awk '{print $1}'
|
|
}
|
|
until [[ -n $(wid) ]]; do
|
|
:
|
|
done
|
|
wmctrl -ia $(wid)
|
|
sleep 0.1s
|
|
wid=$(xdotool getactivewindow)
|
|
local geo=$(xdotool getwindowgeometry $wid)
|
|
local pos=$(<<< "$geo" awk 'NR==2 {print $2}' | sed 's/,/ /')
|
|
local dim=$(<<< "$geo" awk 'NR==3 {print $2}' | sed 's/x/ /')
|
|
local pos1=$(<<< "$pos" awk '{print $1}')
|
|
local pos2=$(<<< "$pos" awk '{print $2}')
|
|
local dim1=$(<<< "$dim" awk '{print $1}')
|
|
local dim2=$(<<< "$dim" awk '{print $2}')
|
|
local dim1=$(((dim1/2)+pos1))
|
|
local dim2=$(((dim2/2)+pos2))
|
|
xdotool mousemove $dim1 $dim2
|
|
xdotool click 1
|
|
sleep 0.5s
|
|
xdotool key Tab
|
|
}
|
|
console_dl(){
|
|
readarray -t modids <<< "$@"
|
|
focus_beta_client
|
|
sleep 1.5s
|
|
for i in "${modids[@]}"; do
|
|
xdotool type --delay 0 "workshop_download_item $aid $i"
|
|
sleep 0.5s
|
|
xdotool key Return
|
|
sleep 0.5s
|
|
done
|
|
}
|
|
find_default_path(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
discover(){
|
|
echo "# Searching for Steam"
|
|
default_steam_path=$(find / -type d \( -path "/proc" -o -path "*/timeshift" -o -path \
|
|
"/tmp" -o -path "/usr" -o -path "/boot" -o -path "/proc" -o -path "/root" \
|
|
-o -path "/sys" -o -path "/etc" -o -path "/var" -o -path "/lost+found" \) -prune \
|
|
-o -regex ".*/Steam/ubuntu12_32$" -print -quit 2>/dev/null | sed 's@/ubuntu12_32@@')
|
|
}
|
|
if [[ $is_steam_deck -eq 1 ]]; then
|
|
default_steam_path="$HOME/.local/share/Steam"
|
|
else
|
|
local def_path
|
|
local ub_path
|
|
local flat_path
|
|
def_path="$HOME/.local/share/Steam"
|
|
ub_path="$HOME/.steam/steam"
|
|
flat_path="$HOME/.var/app/com.valvesoftware.Steam/data/Steam"
|
|
|
|
if [[ -d "$def_path" ]]; then
|
|
default_steam_path="$def_path"
|
|
elif [[ -d "$ub_path" ]]; then
|
|
default_steam_path="$ub_path"
|
|
elif [[ -d $flat_path ]]; then
|
|
default_steam_path="$flat_path"
|
|
else
|
|
local res=$(echo -e "Let DZGUI auto-discover Steam path (accurate, slower)\nSelect the Steam path manually (less accurate, faster)" | $steamsafe_zenity --list --column="Choice" --title="DZGUI" --hide-header --text="Steam is not installed in a standard location." $sd_res)
|
|
case "$res" in
|
|
*auto*) discover ;;
|
|
*manual*)
|
|
zenity --info --text="\nSelect the top-level entry point to the location where Steam (not DayZ)\nis installed and before entering the \"steamapps\" path.\n\nE.g., if Steam is installed at:\n\"/media/mydrive/Steam\"\n\nCorrect:\n- \"/media/mydrive/Steam\"\n\nIncorrect:\n- \"/media/mydrive/Steam/steamapps/common/DayZ\"\n- \"/media/mydrive/\"" --width=500 &&
|
|
file_picker ;;
|
|
esac
|
|
fi
|
|
fi
|
|
}
|
|
fold_message(){
|
|
echo "$1" | fold -s -w40
|
|
}
|
|
popup(){
|
|
pop(){
|
|
$steamsafe_zenity --info --text="$1" --title="DZGUI" --width=500 2>/dev/null
|
|
}
|
|
case "$1" in
|
|
100) pop "This feature requires xdotool and wmctrl.";;
|
|
200) pop "This feature is not supported on Gaming Mode.";;
|
|
300) pop "$(fold_message 'The Steam console will now open and briefly issue commands to download the workshop files, then return to the download progress page. Ensure that the Steam console has keyboard and mouse focus (keep hands off keyboard) while the commands are being issued. Depending on the number if mods, it may take some time to queue the downloads. If a popup or notification window steals focus, it could obstruct the process.')" ;;
|
|
400) pop "$(fold_message 'Automod install enabled. Auto-downloaded mods will not appear in your Steam Workshop subscriptions, but DZGUI will track the version number of downloaded mods internally and trigger an update if necessary.')" ;;
|
|
500) pop "$(fold_message 'Automod install disabled. Switched to manual mode.')" ;;
|
|
600) pop "No preferred servers set." ;;
|
|
700) pop "Toggled to Flatpak Steam." ;;
|
|
800) pop "Toggled to native Steam." ;;
|
|
900) pop "This feature is not supported on Steam Deck." ;;
|
|
1000) pop "No recent history." ;;
|
|
1100) pop "No results found." ;;
|
|
1200) pop "Timed out. Server may be temporarily offline or not responding to queries." ;;
|
|
1300) pop "No favorite server configured." ;;
|
|
1400) pop "To install missing mods, run DZGUI via Desktop Mode on Steam Deck, preferably via the desktop shortcut." ;;
|
|
esac
|
|
}
|
|
toggle_console_dl(){
|
|
[[ $is_steam_deck -eq 1 ]] && { popup 900; return; }
|
|
[[ ! $(command -v xdotool) ]] && { popup 100; return; }
|
|
[[ ! $(command -v wmctrl) ]] && { popup 100; return; }
|
|
mv $config_file ${config_path}dztuirc.old
|
|
local nr=$(awk '/auto_install=/ {print NR}' ${config_path}dztuirc.old)
|
|
if [[ $auto_install == "2" ]]; then
|
|
auto_install="0"
|
|
popup 500
|
|
else
|
|
auto_install="2"
|
|
popup 400
|
|
fi
|
|
local flip_state="auto_install=\"$auto_install\""
|
|
awk -v "var=$flip_state" -v "nr=$nr" 'NR==nr {$0=var}{print}' ${config_path}dztuirc.old > $config_file
|
|
source $config_file
|
|
}
|
|
force_update_mods(){
|
|
if [[ -f $version_file ]]; then
|
|
awk '{OFS="\t"}{$2="000"}1' $version_file > /tmp/versions
|
|
mv /tmp/versions $version_file
|
|
fi
|
|
}
|
|
toggle_steam_binary(){
|
|
case "$steam_cmd" in
|
|
steam)
|
|
steam_cmd="flatpak run com.valvesoftware.Steam"
|
|
update_steam_cmd
|
|
popup 700
|
|
;;
|
|
flatpak*)
|
|
steam_cmd="steam"
|
|
update_steam_cmd
|
|
popup 800;;
|
|
esac
|
|
}
|
|
options_menu(){
|
|
init_options_list(){
|
|
source $config_file
|
|
set_mode
|
|
case "$auto_install" in
|
|
0|1|"") auto_hr="OFF"; ;;
|
|
2) auto_hr="ON"; ;;
|
|
esac
|
|
[[ -z $name ]] && name="null"
|
|
debug_list=(
|
|
"Toggle branch [current: $branch]"
|
|
"Toggle debug mode [current: $mode]"
|
|
"Toggle auto mod install [current: $auto_hr]"
|
|
"Change player name [current: $name]"
|
|
"Output system info"
|
|
)
|
|
#TODO: tech debt: drop old flags
|
|
[[ $auto_install -eq 2 ]] || [[ $auto_install -eq 1 ]] && debug_list+=("Force update local mods")
|
|
case "$steam_cmd" in
|
|
steam) steam_hr=Steam ;;
|
|
flatpak*) steam_hr=Flatpak ;;
|
|
esac
|
|
[[ $toggle_steam -eq 1 ]] && debug_list+=("Toggle native Steam or Flatpak [$steam_hr]")
|
|
}
|
|
while true; do
|
|
init_options_list
|
|
debug_sel=$($steamsafe_zenity --list --width=1280 --height=800 --column="Options" --title="DZGUI" --hide-header "${debug_list[@]}" 2>/dev/null)
|
|
[[ -z $debug_sel ]] && return
|
|
case "$debug_sel" in
|
|
Toggle[[:space:]]branch*)
|
|
enforce_dl=1
|
|
toggle_branch &&
|
|
check_version
|
|
;;
|
|
Toggle[[:space:]]debug*) toggle_debug ;;
|
|
"Output system info")
|
|
source_script=$(realpath "$0")
|
|
source_dir=$(dirname "$source_script")
|
|
output(){
|
|
echo "# Generating log"
|
|
generate_log > "$source_dir/DZGUI.log"
|
|
}
|
|
(output) | pdialog
|
|
[[ $? -eq 1 ]] && return
|
|
info_dialog "Wrote log file to: $source_dir/DZGUI.log"
|
|
;;
|
|
Toggle[[:space:]]auto*) toggle_console_dl ;;
|
|
"Force update local mods")
|
|
force_update=1
|
|
force_update_mods
|
|
(merge_modlists) | pdialog
|
|
auto_mod_install
|
|
;;
|
|
Toggle[[:space:]]native*) toggle_steam_binary ;;
|
|
Change[[:space:]]player[[:space:]]name*) change_name
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
info_dialog(){
|
|
local title="DZGUI"
|
|
$steamsafe_zenity --info --width=500 --title="$title" --text="$1" 2>/dev/null
|
|
}
|
|
a2s(){
|
|
local ip="$1"
|
|
local qport="$2"
|
|
local mode="$3"
|
|
python3 $helpers_path/query.py "$ip" "$qport" "$mode"
|
|
}
|
|
format_config_address(){
|
|
local address="$1"
|
|
parse(){
|
|
local ind="$1"
|
|
<<< $address awk -F: "{print \$$ind}"
|
|
}
|
|
local ip=$(parse 1)
|
|
local gameport=$(parse 2)
|
|
local qport=$(parse 3)
|
|
printf "%s\n%s\n%s" "$ip" "$gameport" "$qport"
|
|
}
|
|
query_and_connect(){
|
|
source $config_file
|
|
local switch="$1"
|
|
local ips="$2"
|
|
case "$switch" in
|
|
"history")
|
|
if [[ -z $2 ]]; then
|
|
warn "No recent servers in history"
|
|
return 1
|
|
fi
|
|
readarray -t ip_arr <<< "$ips"
|
|
;;
|
|
"connect"|"delete")
|
|
if [[ ${#ip_list[@]} -eq 0 ]]; then
|
|
warn "No servers currently saved"
|
|
return 1
|
|
fi
|
|
ips="$(printf "%s\n" "${ip_list[@]}")"
|
|
readarray -t ip_arr <<< "$ips"
|
|
;;
|
|
esac
|
|
[[ ${#ip_arr[@]} -lt 1 ]] && { popup 600; return; }
|
|
> $tmp
|
|
q(){
|
|
for (( i = 0; i < ${#ip_arr[@]}; ++i )); do
|
|
|
|
local address="${ip_arr[$i]}"
|
|
readarray -t address < <(format_config_address "$address")
|
|
local ip="${address[0]}"
|
|
local gameport="${address[1]}"
|
|
local qport="${address[2]}"
|
|
unset address
|
|
|
|
local info
|
|
echo "# Querying $ip:$qport"
|
|
info=$(a2s "$ip" "$qport" info)
|
|
[[ $? -eq 1 ]] && continue
|
|
local keywords=$(<<< $info jq -r '.keywords')
|
|
local vars=("name" "address" "count" "time" "dist" "qport")
|
|
for j in ${vars[@]}; do
|
|
local -n var=$j
|
|
case "$j" in
|
|
"time")
|
|
var=$(<<< "$keywords" grep -o '[0-9][0-9]:[0-9][0-9]')
|
|
;;
|
|
"name")
|
|
var=$(<<< "$info" jq -r --arg arg $j '.[$arg]')
|
|
if [[ "${#var}" -gt 50 ]]; then
|
|
var="$(<<< "$var" awk '{print substr($0,1,50) "..."}')"
|
|
fi
|
|
;;
|
|
"dist")
|
|
check_geo_file
|
|
local_latlon
|
|
var=$(get_dist $(<<< $address awk -F: '{print $1}'))
|
|
;;
|
|
*)
|
|
var=$(<<< "$info" jq -r --arg arg $j '.[$arg]')
|
|
;;
|
|
esac
|
|
printf "%s\n" "$var" >> $tmp
|
|
done
|
|
unset $j
|
|
done
|
|
}
|
|
|
|
(q) | pdialog
|
|
[[ $? -eq 1 ]] && return
|
|
populate "$switch"
|
|
}
|
|
exclude_fpp(){
|
|
response=$(<<< "$response" jq '[.[]|select(.gametype|split(",")|any(. == "no3rd")|not)]')
|
|
}
|
|
exclude_tpp(){
|
|
response=$(<<< "$response" jq '[.[]|select(.gametype|split(",")|any(. == "no3rd"))]')
|
|
}
|
|
exclude_full(){
|
|
response=$(echo "$response" | jq '[.[]|select(.players!=.max_players)]')
|
|
}
|
|
exclude_empty(){
|
|
response=$(echo "$response" | jq '[.[]|select(.players!=0)]')
|
|
}
|
|
filter_maps(){
|
|
echo "# Filtering maps"
|
|
[[ $ret -eq 98 ]] && return
|
|
local maps=$(echo "$response" | jq -r '.[].map//empty|ascii_downcase' | sort -u)
|
|
local map_ct=$(echo "$maps" | wc -l)
|
|
local map_sel=$(echo "$maps" | $steamsafe_zenity --list --column="Check" --width=1200 --height=800 2>/dev/null --title="DZGUI" --text="Found $map_ct map types")
|
|
echo "[DZGUI] Selected '$map_sel'"
|
|
if [[ -z $map_sel ]]; then
|
|
ret=97
|
|
return
|
|
fi
|
|
echo "100"
|
|
response=$(echo "$response" | jq --arg map "$map_sel" '[.[]|select(.map)//empty|select(.map|ascii_downcase == $map)]')
|
|
}
|
|
exclude_daytime(){
|
|
response=$(echo "$response" | jq '[.[]|select(.gametype|test(",[0][6-9]:|,[1][0-6]:")|not)]')
|
|
}
|
|
exclude_nighttime(){
|
|
response=$(echo "$response" | jq '[.[]|select(.gametype|test(",[1][7-9]:|,[2][0-4]:|[0][0-5]:")|not)]')
|
|
}
|
|
keyword_filter(){
|
|
response=$(echo "$response" | jq --arg search "$search" '[.[]|select(.name|ascii_downcase | contains($search))]')
|
|
}
|
|
exclude_lowpop(){
|
|
response=$(echo "$response" | jq '[.[]|select(.players > 9)]')
|
|
}
|
|
exclude_nonascii(){
|
|
response=$(echo "$response" | jq -r '[.[]|select(.name|test("^([[:ascii:]])*$"))]')
|
|
}
|
|
strip_null(){
|
|
response=$(echo "$response" | jq -r '[.[]|select(.map//empty)]')
|
|
}
|
|
local_latlon(){
|
|
if [[ -z $(command -v dig) ]]; then
|
|
local local_ip=$(curl -Ls "https://ipecho.net/plain")
|
|
else
|
|
local local_ip=$(dig -4 +short myip.opendns.com @resolver1.opendns.com)
|
|
fi
|
|
local url="http://ip-api.com/json/$local_ip"
|
|
local res=$(curl -Ls "$url" | jq -r '"\(.lat),\(.lon)"')
|
|
local_lat=$(echo "$res" | awk -F, '{print $1}')
|
|
local_lon=$(echo "$res" | awk -F, '{print $2}')
|
|
}
|
|
disabled(){
|
|
if [[ -z ${disabled[@]} ]]; then
|
|
printf "%s" "-"
|
|
else
|
|
for((i=0;i<${#disabled[@]};i++)); do
|
|
if [[ $i < $((${#disabled[@]}-1)) ]]; then
|
|
printf "%s, " "${disabled[$i]}"
|
|
else
|
|
printf "%s" "${disabled[$i]}"
|
|
fi
|
|
|
|
done
|
|
fi
|
|
}
|
|
pagination(){
|
|
if [[ ${#qport[@]} -eq 1 ]]; then
|
|
entry=server
|
|
else
|
|
entry=servers
|
|
fi
|
|
printf "DZGUI %s | " "$version"
|
|
printf "Mode: %s |" "$mode"
|
|
printf "Fav: %s " "$fav_label"
|
|
printf "\nIncluded: %s | " "$filters"
|
|
printf "Excluded: %s " "$(disabled)"
|
|
if [[ -n $search ]]; then
|
|
printf "| Keyword: %s " "$search"
|
|
fi
|
|
printf "\nReturned: %s %s of %s | " "${#qport[@]}" "$entry" "$total_servers"
|
|
printf "Players in-game: %s" "$players_online"
|
|
}
|
|
check_geo_file(){
|
|
local gzip="$helpers_path/ips.csv.gz"
|
|
curl -Ls "$sums_url" > "$sums_path"
|
|
cd "$helpers_path"
|
|
md5sum -c "$sums_path" 2>/dev/null 1>&2
|
|
local res=$?
|
|
cd $OLDPWD
|
|
[[ $res -eq 0 ]] && return
|
|
update(){
|
|
mkdir -p "$helpers_path"
|
|
echo "# Fetching new geolocation DB"
|
|
curl -Ls "$db_file" > "$gzip"
|
|
echo "# Extracting coordinates"
|
|
#force overwrite
|
|
gunzip -f "$gzip"
|
|
echo "# Preparing helper file"
|
|
curl -Ls "$km_helper_url" > "$km_helper"
|
|
chmod +x $km_helper
|
|
echo "100"
|
|
}
|
|
update > >(pdialog)
|
|
}
|
|
choose_filters(){
|
|
if [[ $is_steam_deck -eq 0 ]]; then
|
|
sd_res="--width=1920 --height=1080"
|
|
fi
|
|
sels=$($steamsafe_zenity --title="DZGUI" --text="Server search" --list --checklist --column "Check" --column "Option" --hide-header TRUE "All maps (untick to select from map list)" TRUE "Daytime" TRUE "Nighttime" TRUE "1PP" TRUE "3PP" False "Empty" False "Full" TRUE "Low population" FALSE "Non-ASCII titles" FALSE "Keyword search" $sd_res 2>/dev/null)
|
|
if [[ $sels =~ Keyword ]]; then
|
|
local search
|
|
while true; do
|
|
search=$(edialog "Search (case insensitive)" | awk '{print tolower($0)}')
|
|
[[ $? -eq 1 ]] && return 1
|
|
[[ -z $search ]] && warn "Cannot submit an empty keyword"
|
|
[[ -n $search ]] && break
|
|
done
|
|
fi
|
|
[[ -z $sels ]] && return
|
|
echo "$sels" | sed 's/|/, /g;s/ (untick to select from map list)//'
|
|
echo "$search"
|
|
}
|
|
get_dist(){
|
|
local given_ip="$1"
|
|
local network="$(<<< "$given_ip" awk -F. '{OFS="."}{print $1"."$2}')"
|
|
local binary=$(grep -E "^$network\." $geo_file)
|
|
local three=$(<<< $given_ip awk -F. '{print $3}')
|
|
local host=$(<<< $given_ip awk -F. '{print $4}')
|
|
local res=$(<<< "$binary" awk -F[.,] -v three=$three -v host=$host '$3 <=three && $7 >= three{if($3>three || ($3==three && $4 > host) || $7 < three || ($7==three && $8 < host)){next}{print}}' | awk -F, '{print $7,$8}')
|
|
local remote_lat=$(<<< "$res" awk '{print $1}')
|
|
local remote_lon=$(<<< "$res" awk '{print $2}')
|
|
if [[ -z $remote_lat ]]; then
|
|
local dist="Unknown"
|
|
echo "$dist"
|
|
else
|
|
local dist=$($km_helper "$local_lat" "$local_lon" "$remote_lat" "$remote_lon")
|
|
LC_NUMERIC=C printf "%05.0f %s" "$dist" "km"
|
|
fi
|
|
}
|
|
prepare_filters(){
|
|
local sels="$1"
|
|
local search="$2"
|
|
[[ ! "$sels" =~ "Full" ]] && { exclude_full; disabled+=("Full") ; }
|
|
[[ ! "$sels" =~ "Empty" ]] && { exclude_empty; disabled+=("Empty") ; }
|
|
[[ ! "$sels" =~ "Daytime" ]] && { exclude_daytime; disabled+=("Daytime") ; }
|
|
[[ ! "$sels" =~ "Nighttime" ]] && { exclude_nighttime; disabled+=("Nighttime") ; }
|
|
[[ ! "$sels" =~ "Low population" ]] && { exclude_lowpop; disabled+=("Low-pop") ; }
|
|
[[ ! "$sels" =~ "Non-ASCII titles" ]] && { exclude_nonascii; disabled+=("Non-ASCII") ; }
|
|
[[ ! "$sels" =~ "1PP" ]] && { exclude_fpp; disabled+=("FPP") ; }
|
|
[[ ! "$sels" =~ "3PP" ]] && { exclude_tpp; disabled+=("TPP") ; }
|
|
[[ -n "$search" ]] && keyword_filter
|
|
strip_null
|
|
}
|
|
munge_servers(){
|
|
local sels="$1"
|
|
local search="$2"
|
|
write_fifo(){
|
|
[[ -p $fifo ]] && rm $fifo
|
|
mkfifo $fifo
|
|
local dist
|
|
for((i=0;i<${#qport[@]};i++)); do
|
|
dist=$(get_dist ${addr[$i]})
|
|
|
|
printf "%s\n%s\n%s\n%s\n%03d\n%03d\n%s\n%s:%s\n%s\n" "${name[$i]}" "${map[$i]}" "${fpp[$i]}" "${gametime[$i]}" \
|
|
"${players[$i]}" "${max[$i]}" "$dist" "${addr[$i]}" "${gameport[$i]}" "${qport[$i]}" >> $fifo
|
|
done
|
|
}
|
|
response="$(cat /tmp/dz.servers)"
|
|
if [[ ! "$sels" =~ "All maps" ]]; then
|
|
filter_maps > >(pdialog)
|
|
[[ $? -eq 1 ]] && return
|
|
disabled+=("All maps")
|
|
fi
|
|
[[ $ret -eq 97 ]] && return
|
|
prepare_filters "$sels" "$search"
|
|
[[ $? -eq 1 ]] && return
|
|
if [[ $(echo "$response" | jq 'length') -eq 0 ]]; then
|
|
$steamsafe_zenity --error --text="No matching servers" 2>/dev/null
|
|
return
|
|
fi
|
|
#jq bug #1788, raw output (-r) cannot be used with ASCII
|
|
local name=$(<<< "$response" jq -a '.[].name' | sed 's/\\u[0-9a-z]\{4\}//g;s/^"//;s/"$//')
|
|
local map=$(<<< "$response" jq -r '.[].map|if type == "string" then ascii_downcase else "null" end')
|
|
local gametime=$(<<< "$response" jq -r '.[]|(if .gametype == null then "null" else .gametype end)|scan("[0-9]{2}:[0-9]{2}$")')
|
|
local fpp=$(<<< "$response" jq -r '.[].gametype|split(",")|if any(. == "no3rd") then "1PP" else "3PP" end')
|
|
local players=$(<<< "$response" jq -r '.[].players')
|
|
local max=$(<<< "$response" jq -r '.[].max_players')
|
|
local addr=$(<<< "$response" jq -r '.[].addr|split(":")[0]')
|
|
local gameport=$(<<< "$response" jq -r '.[]|(if .gameport == null then "null" else .gameport end)')
|
|
local qport=$(<<< "$response" jq -r '.[].addr|split(":")[1]')
|
|
|
|
readarray -t qport <<< $qport
|
|
readarray -t gameport <<< $gameport
|
|
readarray -t addr <<< $addr
|
|
readarray -t name <<< $name
|
|
readarray -t fpp <<< $fpp
|
|
readarray -t players <<< $players
|
|
readarray -t map <<< $map
|
|
readarray -t max <<< $max
|
|
readarray -t gametime <<< $gametime
|
|
|
|
if [[ $is_steam_deck -eq 0 ]]; then
|
|
sd_res="--width=1920 --height=1080"
|
|
fi
|
|
write_fifo &
|
|
pid=$!
|
|
local sel=$($steamsafe_zenity --text="$(pagination)" --title="DZGUI" --list --column=Name --column=Map --column=PP --column=Gametime --column=Players --column=Max --column=Distance --column=IP --column=Qport $sd_res --print-column=1,8,9 --separator=%% 2>/dev/null < <(while true; do cat $fifo; done))
|
|
if [[ -z $sel ]]; then
|
|
rm $fifo
|
|
kill -9 $pid 2>/dev/null
|
|
return 1
|
|
else
|
|
rm $fifo
|
|
kill -9 $pid
|
|
echo $sel
|
|
fi
|
|
}
|
|
debug_servers(){
|
|
debug_res=$(curl -Ls "https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\appid\221100&limit=10&key=$steam_api")
|
|
local len=$(<<< "$debug_res" jq '[.response.servers[]]|length')
|
|
if [[ $len -eq 0 ]]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
server_browser(){
|
|
unset ret
|
|
local filters="$(<<< "$1" awk 'NR==1 {print $0}')"
|
|
local keywords="$(<<< "$1" awk 'NR==2 {print $0}')"
|
|
echo "# Checking Steam API"
|
|
check_steam_api
|
|
[[ $? -eq 1 ]] && return
|
|
echo "# Checking geolocation file"
|
|
check_geo_file
|
|
echo "# Calculating server distances"
|
|
local_latlon
|
|
[[ $ret -eq 97 ]] && return
|
|
|
|
local limit=20000
|
|
local url="https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\appid\221100&limit=$limit&key=$steam_api"
|
|
|
|
echo "# Getting server list"
|
|
curl -Ls "$url" | jq -r '.response.servers' > /tmp/dz.servers
|
|
total_servers=$(< /tmp/dz.servers jq 'length' | numfmt --grouping)
|
|
players_online=$(curl -Ls "https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=$aid" \
|
|
| jq '.response.player_count' | numfmt --grouping)
|
|
debug_servers
|
|
[[ $? -eq 1 ]] && { popup 1100; return 1; }
|
|
|
|
echo "100"
|
|
local sel=$(munge_servers "$filters" "$keywords")
|
|
if [[ -z $sel ]]; then
|
|
unset filters
|
|
unset search
|
|
ret=98
|
|
sd_res="--width=1280 --height=800"
|
|
return 1
|
|
fi
|
|
|
|
readarray -t address < <(format_table_results "$sel")
|
|
local ip="${address[1]}"
|
|
local gameport="${address[2]}"
|
|
local qport="${address[3]}"
|
|
unset address
|
|
|
|
connect "$ip" "$gameport" "$qport"
|
|
sd_res="--width=1280 --height=800"
|
|
}
|
|
mods_disk_size(){
|
|
printf "Total size on disk: %s | " $(du -sh "$workshop_dir" | awk '{print $1}')
|
|
printf "%s mods | " $(ls -1 "$workshop_dir" | wc -l)
|
|
printf "Location: %s/steamapps/workshop/content/221100" "$steam_path"
|
|
}
|
|
main_menu(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
logger INFO "Setting mode"
|
|
set_mode
|
|
while true; do
|
|
set_header "main_menu"
|
|
rc=$?
|
|
logger INFO "set_header rc is $rc"
|
|
if [[ $rc -eq 0 ]]; then
|
|
case "$sel" in
|
|
"") warn "No item was selected." ;;
|
|
" Server browser")
|
|
local filters=$(choose_filters)
|
|
[[ -z $filters ]] && continue
|
|
(server_browser "$filters") | pdialog ;;
|
|
" My servers") query_and_connect "connect" ;;
|
|
" Quick connect to favorite server") connect_to_fav "$fav_server" ;;
|
|
" Connect by ID") connect_by_id ;;
|
|
" Connect by IP") connect_by_ip ;;
|
|
" Recent servers (last 10)") query_and_connect "history" "$(cat $hist_file)" ;;
|
|
" Add server by ID") add_by_id ;;
|
|
" Add server by IP") add_by_ip ;;
|
|
" Add favorite server") add_by_fav ;;
|
|
" Change favorite server") add_by_fav ;;
|
|
" Delete server") query_and_connect "delete" ;;
|
|
" List installed mods")
|
|
list_mods | sed 's/\t/\n/g' | $steamsafe_zenity --list --column="Mod" --column="Symlink" --column="Dir" \
|
|
--title="DZGUI" $sd_res --text="$(mods_disk_size)" \
|
|
--print-column="" 2>/dev/null
|
|
;;
|
|
" View changelog") changelog ;;
|
|
" Advanced options")
|
|
options_menu
|
|
main_menu
|
|
return
|
|
;;
|
|
" Help file ⧉") help_file ;;
|
|
" Report bug ⧉") report_bug ;;
|
|
" Forum ⧉") forum ;;
|
|
" Sponsor ⧉") sponsor ;;
|
|
" Hall of fame ⧉") hof ;;
|
|
esac
|
|
else
|
|
logger INFO "Returning from main menu"
|
|
return
|
|
fi
|
|
done
|
|
}
|
|
set_fav(){
|
|
local fav="$1"
|
|
logger INFO "${FUNCNAME[0]}"
|
|
|
|
readarray -t address < <(format_config_address "$fav")
|
|
local ip="${address[0]}"
|
|
local gameport="${address[1]}"
|
|
local qport="${address[2]}"
|
|
unset address
|
|
|
|
local info=$(a2s "$ip" "$qport" info)
|
|
local name=$(<<< $info jq -r '.name')
|
|
echo "'$name'"
|
|
}
|
|
check_unmerged(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ -f ${config_path}.unmerged ]]; then
|
|
merge_config
|
|
rm ${config_path}.unmerged
|
|
fi
|
|
}
|
|
merge_config(){
|
|
source $config_file
|
|
legacy_fav
|
|
legacy_ids
|
|
[[ -z $staging_dir ]] && staging_dir="/tmp"
|
|
update_config
|
|
tdialog "Wrote new config format to \n${config_file}\nIf errors occur, you can restore the file:\n${config_file}.old"
|
|
}
|
|
download_new_version(){
|
|
if [[ $is_steam_deck -eq 1 ]]; then
|
|
freedesktop_dirs
|
|
fi
|
|
source_script=$(realpath "$0")
|
|
source_dir=$(dirname "$source_script")
|
|
mv $source_script $source_script.old
|
|
echo "# Downloading version $upstream"
|
|
curl -Ls "$version_url" > $source_script
|
|
rc=$?
|
|
if [[ $rc -eq 0 ]]; then
|
|
echo "[DZGUI] Wrote $upstream to $source_script"
|
|
chmod +x $source_script
|
|
touch ${config_path}.unmerged
|
|
echo "100"
|
|
$steamsafe_zenity --question --width=500 --title="DZGUI" --text "DZGUI $upstream successfully downloaded.\nTo view the changelog, select Changelog.\nTo use the new version, select Exit and restart." --ok-label="Changelog" --cancel-label="Exit" 2>/dev/null
|
|
code=$?
|
|
if [[ $code -eq 0 ]]; then
|
|
changelog
|
|
exit
|
|
elif [[ $code -eq 1 ]]; then
|
|
exit
|
|
fi
|
|
else
|
|
echo "100"
|
|
mv $source_script.old $source_script
|
|
$steamsafe_zenity --info --title="DZGUI" --text "[ERROR] 99: Failed to download new version." 2>/dev/null
|
|
return
|
|
fi
|
|
|
|
}
|
|
check_branch(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
if [[ $branch == "stable" ]]; then
|
|
version_url="$stable_url/dzgui.sh"
|
|
elif [[ $branch == "testing" ]]; then
|
|
version_url="$testing_url/dzgui.sh"
|
|
fi
|
|
logger INFO "Branch is $branch"
|
|
upstream=$(curl -Ls "$version_url" | awk -F= '/^version=/ {print $2}')
|
|
logger INFO "Upstream version is $version"
|
|
}
|
|
enforce_dl(){
|
|
download_new_version > >(pdialog)
|
|
}
|
|
prompt_dl(){
|
|
$steamsafe_zenity --question --title="DZGUI" --text "Version conflict.\n\nYour branch:\t\t\t$branch\nYour version:\t\t\t$version\nUpstream version:\t\t$upstream\n\nVersion updates introduce important bug fixes and are encouraged.\n\nAttempt to download latest version?" --width=500 --ok-label="Yes" --cancel-label="No" 2>/dev/null
|
|
rc=$?
|
|
if [[ $rc -eq 1 ]]; then
|
|
return
|
|
else
|
|
echo "100"
|
|
download_new_version > >(pdialog)
|
|
fi
|
|
}
|
|
check_version(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
[[ -f $config_file ]] && source $config_file
|
|
[[ -z $branch ]] && branch="stable"
|
|
check_branch
|
|
[[ ! -f "$freedesktop_path/dzgui.desktop" ]] && freedesktop_dirs
|
|
if [[ $version == $upstream ]]; then
|
|
logger INFO "Local version is same as upstream"
|
|
check_unmerged
|
|
else
|
|
logger INFO "Local and remote version mismatch"
|
|
if [[ $enforce_dl -eq 1 ]]; then
|
|
enforce_dl
|
|
else
|
|
prompt_dl
|
|
fi
|
|
fi
|
|
}
|
|
check_architecture(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
cpu=$(cat /proc/cpuinfo | grep "AMD Custom APU 0405")
|
|
if [[ -n "$cpu" ]]; then
|
|
is_steam_deck=1
|
|
logger INFO "Setting architecture to 'Steam Deck'"
|
|
else
|
|
is_steam_deck=0
|
|
logger INFO "Setting architecture to 'desktop'"
|
|
fi
|
|
}
|
|
print_ip_list(){
|
|
[[ ${#ip_list} -eq 0 ]] && return
|
|
printf "\t\"%s\"\n" "${ip_list[@]}"
|
|
}
|
|
migrate_files(){
|
|
if [[ ! -f $config_path/dztuirc.oldapi ]]; then
|
|
cp $config_file $config_path/dztuirc.oldapi
|
|
rm $hist_file
|
|
fi
|
|
}
|
|
legacy_fav(){
|
|
source $config_file
|
|
[[ -z $fav ]] && return
|
|
local res=$(map_fav_to_ip "$fav")
|
|
source $config_file
|
|
}
|
|
legacy_ids(){
|
|
source $config_file
|
|
[[ -z $whitelist ]] && return
|
|
local res=$(map_id_to_ip "$whitelist")
|
|
source $config_file
|
|
}
|
|
map_fav_to_ip(){
|
|
local to_add="$1"
|
|
fav_server=$(curl -s "$api" -H "Authorization: Bearer "$api_key"" \
|
|
-G -d "sort=-players" \
|
|
-d "filter[game]=$game" \
|
|
-d "filter[ids][whitelist]=$to_add" \
|
|
| jq -r '.data[].attributes|"\(.ip):\(.port):\(.portQuery)"')
|
|
update_config
|
|
fav_label=$(set_fav "$fav_server")
|
|
}
|
|
map_id_to_ip(){
|
|
local to_add="$1"
|
|
local mode="$2"
|
|
local res=$(curl -s "$api" -H "Authorization: Bearer "$api_key"" \
|
|
-G -d "sort=-players" \
|
|
-d "filter[game]=$game" \
|
|
-d "filter[ids][whitelist]=$to_add")
|
|
local len=$(<<< "$res" jq '.data|length')
|
|
[[ $len -eq 0 ]] && return 1
|
|
local ip=$(<<< "$res" jq -r '.data[].attributes|"\(.ip):\(.port):\(.portQuery)"')
|
|
if [[ $mode == "connect" ]]; then
|
|
echo "$ip"
|
|
return 0
|
|
fi
|
|
for i in $ip; do
|
|
if [[ ${ip_list[@]} =~ $i ]]; then
|
|
[[ ! $len -eq 1 ]] && continue
|
|
warn "This server is already in your list"
|
|
return 2
|
|
fi
|
|
ip_list+=("$i")
|
|
update_config
|
|
done
|
|
echo $i
|
|
}
|
|
add_by_ip(){
|
|
local sel=$(parse_ips)
|
|
[[ -z $sel ]] && return
|
|
|
|
readarray -t address < <(format_table_results "$sel")
|
|
local ip="${address[1]}"
|
|
local gameport="${address[2]}"
|
|
local qport="${address[3]}"
|
|
unset address
|
|
|
|
if [[ ${ip_list[@]} =~ "$ip:$gameport:$qport" ]]; then
|
|
warn "This server is already in your favorites"
|
|
return
|
|
fi
|
|
|
|
ip_list+=("$ip:$gameport:$qport")
|
|
update_config
|
|
info "Added $ip:$gameport:$qport to:\n${config_path}dztuirc\nIf errors occurred, you can restore the file:\n${config_path}dztuirc.old"
|
|
}
|
|
pdialog(){
|
|
$steamsafe_zenity --progress --pulsate --auto-close --title="DZGUI" --width=500 2>/dev/null
|
|
}
|
|
edialog(){
|
|
if [[ $is_steam_deck -eq 1 ]] && [[ $(test_display_mode) == "gm" ]]; then
|
|
kdialog --inputbox "$1" --title "DZGUI" --geometry 500 2>/dev/null
|
|
else
|
|
$steamsafe_zenity --entry --text="$1" --width=500 --title="DZGUI" 2>/dev/null
|
|
fi
|
|
}
|
|
tdialog(){
|
|
$steamsafe_zenity --info --text="$1" --width=500 --title="DZGUI" 2>/dev/null
|
|
}
|
|
qdialog(){
|
|
$steamsafe_zenity --question --text="$1" --width=500 --title="DZGUI" 2>/dev/null
|
|
}
|
|
add_by_id(){
|
|
local mode="$1"
|
|
if [[ -z $api_key ]]; then
|
|
qdialog "Requires Battlemetrics API key. Set one now?"
|
|
[[ $? -eq 1 ]] && return 1
|
|
while true; do
|
|
api_key=$(edialog "Battlemetrics API key")
|
|
[[ $? -eq 1 ]] && return 1
|
|
[[ -z $api_key ]] && { warn "Invalid API key"; continue; }
|
|
if [[ $(test_bm_api $api_key) -eq 1 ]]; then
|
|
warn "Invalid API key"
|
|
unset api_key
|
|
continue
|
|
fi
|
|
update_config
|
|
break
|
|
done
|
|
fi
|
|
while true; do
|
|
local id
|
|
id=$(edialog "Enter server ID")
|
|
[[ $? -eq 1 ]] && return 1
|
|
if [[ ! $id =~ ^[0-9]+$ ]]; then
|
|
warn "Invalid ID"
|
|
else
|
|
local ip
|
|
ip=$(map_id_to_ip "$id" "$mode")
|
|
case "$?" in
|
|
1)
|
|
warn "Invalid ID"
|
|
continue
|
|
;;
|
|
2)
|
|
continue
|
|
;;
|
|
*)
|
|
if [[ $mode == "connect" ]]; then
|
|
echo "$ip"
|
|
return 0
|
|
fi
|
|
tdialog "Added $ip to:\n${config_path}dztuirc\nIf errors occurred, you can restore the file:\n${config_path}dztuirc.old"
|
|
return 0
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
}
|
|
toggle_debug(){
|
|
if [[ $debug -eq 1 ]]; then
|
|
debug=0
|
|
else
|
|
debug=1
|
|
fi
|
|
update_config
|
|
|
|
}
|
|
setup(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
[[ -z $fav_server ]] && return
|
|
items[10]=" Change favorite server"
|
|
[[ -n $fav_label ]] && return
|
|
fav_label=$(set_fav $fav_server)
|
|
update_config
|
|
}
|
|
check_map_count(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
[[ $is_steam_deck -eq 1 ]] && return
|
|
local count=1048576
|
|
logger INFO "Checking system map count"
|
|
echo "[DZGUI] Checking system map count"
|
|
if [[ ! -f /etc/sysctl.d/dayz.conf ]]; then
|
|
$steamsafe_zenity --question --width=500 --title="DZGUI" --cancel-label="Cancel" --ok-label="OK" --text "sudo password required to check system vm map count."
|
|
local rc=$?
|
|
logger INFO "Return code is $rc"
|
|
if [[ $rc -eq 0 ]]; then
|
|
local pass
|
|
logger INFO "Prompting user for sudo escalation"
|
|
pass=$($steamsafe_zenity --password)
|
|
local rc
|
|
logger INFO "Return code is $rc"
|
|
[[ $rc -eq 1 ]] && exit 1
|
|
local ct=$(sudo -S <<< "$pass" sh -c "sysctl -q vm.max_map_count | awk -F'= ' '{print \$2}'")
|
|
local new_ct
|
|
[[ $ct -lt $count ]] && ct=$count
|
|
logger INFO "Updating map count"
|
|
sudo -S <<< "$pass" sh -c "echo 'vm.max_map_count=$ct' > /etc/sysctl.d/dayz.conf"
|
|
sudo sysctl -p /etc/sysctl.d/dayz.conf
|
|
else
|
|
logger INFO "Zenity dialog failed or user exit"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
change_name(){
|
|
while true; do
|
|
local name=$($steamsafe_zenity --entry --text="Enter desired in-game name" --title="DZGUI" 2>/dev/null)
|
|
[[ -z "${name//[[:blank:]]/}" ]] && continue
|
|
update_config
|
|
info "Changed name to: '$name'.\nIf errors occur, you can restore the file '${config_path}dztuirc.old'."
|
|
return
|
|
done
|
|
}
|
|
add_by_fav(){
|
|
local sel=$(parse_ips)
|
|
[[ -z $sel ]] && return
|
|
|
|
readarray -t address < <(format_table_results "$sel")
|
|
local ip="${address[1]}"
|
|
local gameport="${address[2]}"
|
|
local qport="${address[3]}"
|
|
unset address
|
|
fav_server="$ip:$gameport:$qport"
|
|
fav_label=$(set_fav "$fav_server")
|
|
|
|
update_config
|
|
info "Added $fav_server to:\n${config_path}dztuirc\nIf errors occurred, you can restore the file:\n${config_path}dztuirc.old"
|
|
|
|
items[10]=" Change favorite server"
|
|
}
|
|
lock(){
|
|
[[ ! -d $config_path ]] && return
|
|
if [[ ! -f ${config_path}.lockfile ]]; then
|
|
touch ${config_path}.lockfile
|
|
fi
|
|
pid=$(cat ${config_path}.lockfile)
|
|
ps -p $pid -o pid= >/dev/null 2>&1
|
|
res=$?
|
|
if [[ $res -eq 0 ]]; then
|
|
info "DZGUI already running ($pid)"
|
|
exit
|
|
elif [[ $pid == $$ ]]; then
|
|
:
|
|
else
|
|
echo $$ > ${config_path}.lockfile
|
|
fi
|
|
}
|
|
fetch_a2s(){
|
|
[[ -d $helpers_path/a2s ]] && return
|
|
local sha=c7590ffa9a6d0c6912e17ceeab15b832a1090640
|
|
local author="yepoleb"
|
|
local repo="python-a2s"
|
|
local url="https://github.com/$author/$repo/tarball/$sha"
|
|
local prefix="${author^}-$repo-${sha:0:7}"
|
|
local file="$prefix.tar.gz"
|
|
curl -Ls "$url" > "$helpers_path/$file"
|
|
tar xf "$helpers_path/$file" -C "$helpers_path" "$prefix/a2s" --strip=1
|
|
rm "$helpers_path/$file"
|
|
}
|
|
fetch_dzq(){
|
|
[[ -f $helpers_path/dayzquery.py ]] && return
|
|
local sha=ccc4f71b48610a1885706c9d92638dbd8ca012a5
|
|
local author="yepoleb"
|
|
local repo="dayzquery"
|
|
local url="https://raw.githubusercontent.com/$author/$repo/$sha/$repo.py"
|
|
curl -Ls "$url" > $helpers_path/a2s/$repo.py
|
|
}
|
|
fetch_query(){
|
|
local sum="7cbae12ae68b526e7ff376b638123cc7"
|
|
local file="$helpers_path/query.py"
|
|
if [[ -f $file ]] && [[ $(md5sum $file | awk '{print $1}') == $sum ]]; then
|
|
return
|
|
fi
|
|
local author="aclist"
|
|
local repo="dzgui"
|
|
local url="https://raw.githubusercontent.com/$author/dztui/$repo/helpers/query.py"
|
|
curl -Ls "$url" > "$helpers_path/query.py"
|
|
}
|
|
fetch_helpers(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
mkdir -p "$helpers_path"
|
|
[[ ! -f "$helpers_path/vdf2json.py" ]] && curl -Ls "$vdf2json_url" > "$helpers_path/vdf2json.py"
|
|
fetch_a2s
|
|
fetch_dzq
|
|
fetch_query
|
|
}
|
|
update_steam_cmd(){
|
|
preferred_client="$steam_cmd"
|
|
update_config
|
|
}
|
|
steam_deps(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
local flatpak steam
|
|
[[ $(command -v flatpak) ]] && flatpak=$(flatpak list | grep valvesoftware.Steam)
|
|
steam=$(command -v steam)
|
|
if [[ -z "$steam" ]] && [[ -z "$flatpak" ]]; then
|
|
warn "Requires Steam or Flatpak Steam"
|
|
logger ERROR "Steam was missing"
|
|
exit
|
|
elif [[ -n "$steam" ]] && [[ -n "$flatpak" ]]; then
|
|
toggle_steam=1
|
|
steam_cmd="steam"
|
|
[[ -n $preferred_client ]] && steam_cmd="$preferred_client"
|
|
[[ -z $preferred_client ]] && update_steam_cmd
|
|
elif [[ -n "$steam" ]]; then
|
|
steam_cmd="steam"
|
|
else
|
|
steam_cmd="flatpak run com.valvesoftware.Steam"
|
|
fi
|
|
logger INFO "steam_cmd set to $steam_cmd"
|
|
}
|
|
update_steam_cmd(){
|
|
local new_cmd
|
|
preferred_client="$steam_cmd"
|
|
new_cmd="preferred_client=\"$preferred_client\""
|
|
mv $config_file ${config_path}dztuirc.old
|
|
nr=$(awk '/preferred_client=/ {print NR}' ${config_path}dztuirc.old)
|
|
awk -v "var=$new_cmd" -v "nr=$nr" 'NR==nr {$0=var}{print}' ${config_path}dztuirc.old > ${config_path}dztuirc
|
|
}
|
|
steam_deps(){
|
|
logger INFO "${FUNCNAME[0]}"
|
|
local flatpak steam
|
|
[[ $(command -v flatpak) ]] && flatpak=$(flatpak list | grep valvesoftware.Steam)
|
|
steam=$(command -v steam)
|
|
if [[ -z "$steam" ]] && [[ -z "$flatpak" ]]; then
|
|
warn "Requires Steam or Flatpak Steam"
|
|
logger ERROR "Steam was missing"
|
|
exit
|
|
elif [[ -n "$steam" ]] && [[ -n "$flatpak" ]]; then
|
|
toggle_steam=1
|
|
steam_cmd="steam"
|
|
[[ -n $preferred_client ]] && steam_cmd="$preferred_client"
|
|
[[ -z $preferred_client ]] && update_steam_cmd
|
|
elif [[ -n "$steam" ]]; then
|
|
steam_cmd="steam"
|
|
else
|
|
steam_cmd="flatpak run com.valvesoftware.Steam"
|
|
fi
|
|
logger INFO "steam_cmd set to $steam_cmd"
|
|
}
|
|
initial_setup(){
|
|
echo "# Initial setup"
|
|
run_depcheck
|
|
check_pyver
|
|
watcher_deps
|
|
check_architecture
|
|
check_version
|
|
check_map_count
|
|
fetch_helpers
|
|
config
|
|
steam_deps
|
|
run_varcheck
|
|
migrate_files
|
|
stale_symlinks
|
|
init_items
|
|
setup
|
|
check_news
|
|
echo "100"
|
|
}
|
|
main(){
|
|
local parent=$(cat /proc/$PPID/comm)
|
|
[[ -f "$debug_log" ]] && rm "$debug_log"
|
|
lock
|
|
local zenv=$(zenity --version 2>/dev/null)
|
|
[[ -z $zenv ]] && { echo "Failed to find zenity"; logger "Missing zenity"; exit 1; }
|
|
initial_setup > >(pdialog)
|
|
main_menu
|
|
#TODO: tech debt: cruddy handling for steam forking
|
|
[[ $? -eq 1 ]] && pkill -f dzgui.sh
|
|
}
|
|
main
|