diff --git a/src/launcher_main/neo_case_heal.cpp b/src/launcher_main/neo_case_heal.cpp index 375b9caca..2a7d42884 100644 --- a/src/launcher_main/neo_case_heal.cpp +++ b/src/launcher_main/neo_case_heal.cpp @@ -10,7 +10,7 @@ // Heal hack :: fix poisoned installations // Binaries the engine loads by exact mixed-case name. -// Keep this list in sync with src/devtools/check_depot_casing.sh. +// Keep this list in sync with tools/check-depot-casing.sh. // It's hacky but it works, and should be harmless to leave in // clearly marked, use `git blame` to find the commit to revert. static const char *k_pszCanonicalBinaryPaths[] = diff --git a/tools/check-depot-casing.sh b/tools/check-depot-casing.sh new file mode 100755 index 000000000..0e6878cf3 --- /dev/null +++ b/tools/check-depot-casing.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# ████ +# ████████ ██████████ ▓▓▓▓ +# ████████████ ███████████ ▓▓▓▓▓▓ +# █████░░░█████░ █████░░░░░░░ ▓▓▓▓▓▓░ +# █████░ █████░ █████░ ▓▓▓▓░░ +# █████░ █████░ █████░ ░░░░ +# █████░ █████░ ██████████ █████ +# █████░ █████░ ██████████░ █████░ +# ░░░░░ █████░ ░░░░░░░░░░ ████░░░ +# █████░ ███░░░ +# ░░░░░ ░░░ +# +# N E O T O K Y O ; R E B U I L D + +# Checks a depot staging tree for engine binaries staged under the wrong +# file name casing. The engine loads these binaries by exact mixed-case +# name, and Steam never repairs casing on installs, so a miscased file +# in one upload breaks fresh Linux installs until the next full fix. +# Run this against the staging root before every steamcmd upload: +# +# ./check-depot-casing.sh /path/to/staging/root +# +# Exits 0 when clean, 1 with one line per offending file otherwise. +# Keep the list below in sync with src/launcher_main/neo_case_heal.cpp +# (PR #2055, the launcher-side repair for already-broken installs). + +# Show the logo when running in a terminal. The helper lives next to this +# script in the repo; a standalone copy of this script skips it silently. +[ -f "$(dirname "$0")/nt-logo.sh" ] && . "$(dirname "$0")/nt-logo.sh" && nt_logo + +root="${1:?usage: check-depot-casing.sh }" + +canonical_paths=" +bin/GameUI.so +bin/ServerBrowser.so +bin/libMiles.so +bin/libTelemetryX64.so +bin/libTelemetryX86.so +bin/linux64/GameUI.so +bin/linux64/ServerBrowser.so +" + +status=0 +for canon in $canonical_paths; do + dir=$(dirname "$canon") + want=$(basename "$canon") + want_lower=$(printf '%s' "$want" | tr '[:upper:]' '[:lower:]') + [ -d "$root/$dir" ] || continue + for path in "$root/$dir"/*; do + [ -e "$path" ] || continue + have=$(basename "$path") + have_lower=$(printf '%s' "$have" | tr '[:upper:]' '[:lower:]') + if [ "$have_lower" = "$want_lower" ] && [ "$have" != "$want" ]; then + echo "WRONG CASING: $dir/$have (expected $dir/$want)" >&2 + status=1 + fi + done +done + +if [ "$status" -eq 0 ]; then + echo "Depot casing OK." +fi +exit $status diff --git a/tools/fix-linux-casing.sh b/tools/fix-linux-casing.sh new file mode 100755 index 000000000..d47f7e23b --- /dev/null +++ b/tools/fix-linux-casing.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# ████ +# ████████ ██████████ ▓▓▓▓ +# ████████████ ███████████ ▓▓▓▓▓▓ +# █████░░░█████░ █████░░░░░░░ ▓▓▓▓▓▓░ +# █████░ █████░ █████░ ▓▓▓▓░░ +# █████░ █████░ █████░ ░░░░ +# █████░ █████░ ██████████ █████ +# █████░ █████░ ██████████░ █████░ +# ░░░░░ █████░ ░░░░░░░░░░ ████░░░ +# █████░ ███░░░ +# ░░░░░ ░░░ +# +# N E O T O K Y O ; R E B U I L D + +# Repairs an NT;RE Linux install whose engine binaries have fully +# lowercased file names. The engine loads these binaries by exact +# mixed-case name, and Steam neither renames files on disk nor fails +# verification over casing, so a broken install stays broken until the +# files are renamed. Run from (or point at) the game install root, the +# directory that contains bin/ and neo/: +# +# ./fix-linux-casing.sh ~/.local/share/Steam/steamapps/common/NEOTOKYOREBUILD +# +# Safe to run repeatedly. Prints every change it makes. +# The canonical list matches src/launcher_main/neo_case_heal.cpp +# (PR #2055, the launcher-side repair that runs at every startup). + +set -u + +# Show the logo when running in a terminal. The helper lives next to this +# script in the repo; a standalone copy of this script skips it silently. +[ -f "$(dirname "$0")/nt-logo.sh" ] && . "$(dirname "$0")/nt-logo.sh" && nt_logo + +root="${1:-.}" + +if [ ! -d "$root/bin" ] || [ ! -d "$root/neo" ]; then + echo "error: $root does not look like an NT;RE install root (needs bin/ and neo/)" >&2 + exit 2 +fi + +canonical_paths=( + "bin/GameUI.so" + "bin/ServerBrowser.so" + "bin/libMiles.so" + "bin/libTelemetryX64.so" + "bin/libTelemetryX86.so" + "bin/linux64/GameUI.so" + "bin/linux64/ServerBrowser.so" +) + +repairs=0 +for canon in "${canonical_paths[@]}"; do + lower=$(printf '%s' "$canon" | tr '[:upper:]' '[:lower:]') + [ "$lower" = "$canon" ] && continue + lower_path="$root/$lower" + canon_path="$root/$canon" + [ -e "$lower_path" ] || [ -L "$lower_path" ] || continue + + if [ ! -e "$canon_path" ] && [ ! -L "$canon_path" ]; then + mv "$lower_path" "$canon_path" && echo "renamed: $lower -> $canon" && repairs=$((repairs+1)) + elif [ "$lower_path" -nt "$canon_path" ]; then + # The lowercased twin is newer, so Steam wrote it last and it + # carries the current content. Replace the canonical file. + mv -f "$lower_path" "$canon_path" && echo "replaced with newer: $canon" && repairs=$((repairs+1)) + else + rm -f "$lower_path" && echo "removed stale twin: $lower" && repairs=$((repairs+1)) + fi +done + +if [ "$repairs" -eq 0 ]; then + echo "Nothing to repair. File name casing is already correct." +else + echo "Done. $repairs file(s) repaired." +fi diff --git a/tools/nt-logo.sh b/tools/nt-logo.sh new file mode 100755 index 000000000..9f85bc9aa --- /dev/null +++ b/tools/nt-logo.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Prints the shadowed NT;RE logo, in color when the terminal supports it. +# Prints nothing when stdout is not a terminal, so piped and logged output +# stays clean. POSIX sh so both bash and sh scripts can source it: +# +# [ -f "$(dirname "$0")/nt-logo.sh" ] && . "$(dirname "$0")/nt-logo.sh" && nt_logo +# +# Running this file directly also prints the logo. + +nt_logo() { + [ -t 1 ] || return 0 + # Print once per process tree: child scripts inherit the mark, so a + # wrapper like toolbox.sh shows the logo and the tools it runs skip it. + [ "${NT_LOGO_SHOWN:-}" = 1 ] && return 0 + NT_LOGO_SHOWN=1 + export NT_LOGO_SHOWN + r=''; s=''; b=''; x='' + if [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ]; then + r='\033[31m' # red semicolon dot + s='\033[2m' # dim drop shadow + b='\033[1m' # bold title + x='\033[0m' + fi + printf '%b\n' \ + " ████" \ + " ████████ ██████████ ${r}▓▓▓▓${x}" \ + " ████████████ ███████████ ${r}▓▓▓▓▓▓${x}" \ + " █████${s}░░░${x}█████${s}░${x} █████${s}░░░░░░░${x} ${r}▓▓▓▓▓▓${x}${s}░${x}" \ + " █████${s}░${x} █████${s}░${x} █████${s}░${x} ${r}▓▓▓▓${x}${s}░░${x}" \ + " █████${s}░${x} █████${s}░${x} █████${s}░${x} ${s}░░░░${x}" \ + " █████${s}░${x} █████${s}░${x} ██████████ █████" \ + " █████${s}░${x} █████${s}░${x} ██████████${s}░${x} █████${s}░${x}" \ + " ${s}░░░░░${x} █████${s}░${x} ${s}░░░░░░░░░░${x} ████${s}░░░${x}" \ + " █████${s}░${x} ███${s}░░░${x}" \ + " ${s}░░░░░${x} ${s}░░░${x}" \ + "" \ + " ${b} N E O T O K Y O ; R E B U I L D${x}" \ + "" +} + +# Print right away when executed instead of sourced. +case "$0" in + *nt-logo.sh) nt_logo ;; +esac diff --git a/tools/toolbox.sh b/tools/toolbox.sh new file mode 100755 index 000000000..3da5081a9 --- /dev/null +++ b/tools/toolbox.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# ████ +# ████████ ██████████ ▓▓▓▓ +# ████████████ ███████████ ▓▓▓▓▓▓ +# █████░░░█████░ █████░░░░░░░ ▓▓▓▓▓▓░ +# █████░ █████░ █████░ ▓▓▓▓░░ +# █████░ █████░ █████░ ░░░░ +# █████░ █████░ ██████████ █████ +# █████░ █████░ ██████████░ █████░ +# ░░░░░ █████░ ░░░░░░░░░░ ████░░░ +# █████░ ███░░░ +# ░░░░░ ░░░ +# +# N E O T O K Y O ; R E B U I L D + +# Single entry point for the NT;RE devtools. Shows the tools as a numbered +# menu, prompts for the one path argument, and runs the selection. +# +# ./toolbox.sh +# +# To plug a new tool into the menu, add one line to the table below: +# "script name|path prompt|default path|one-line description". The script +# must live next to this file and take a single path argument. + +set -u + +here="$(cd "$(dirname "$0")" && pwd)" + +[ -t 0 ] || { echo "error: toolbox.sh is an interactive menu, run it from a terminal" >&2; exit 2; } + +[ -f "$here/nt-logo.sh" ] && . "$here/nt-logo.sh" && nt_logo + +tools=( + "check-depot-casing.sh|Staging root to check|.|Check a depot staging tree for wrongly cased binaries" + "fix-linux-casing.sh|Install root to repair|.|Repair a Linux install's binary file name casing" +) + +while :; do + echo + echo "Tools:" + for i in "${!tools[@]}"; do + IFS='|' read -r script _ _ desc <<< "${tools[$i]}" + printf ' %d) %-24s %s\n' "$((i + 1))" "$script" "$desc" + done + echo " Q) quit (default)" + read -rp "Select [# or Q]: " choice || exit 0 + + case "$choice" in + ''|q|Q) + exit 0 + ;; + *[!0-9]*) + echo "Pick a tool number or Q." >&2 + ;; + *) + idx=$(( choice - 1 )) + if [ "$idx" -lt 0 ] || [ "$idx" -ge "${#tools[@]}" ]; then + echo "No tool number $choice." >&2 + continue + fi + IFS='|' read -r script prompt default _ <<< "${tools[$idx]}" + read -rp "$prompt [$default]: " arg || exit 0 + echo + "$here/$script" "${arg:-$default}" + echo "($script exited $?)" + ;; + esac +done