fix: path discovery of cosmoteer for manjaro linux
This commit is contained in:
@@ -98,6 +98,7 @@ No stat or number is changed. Only *when* radiators run.
|
|||||||
mod.rules manifest + patch actions
|
mod.rules manifest + patch actions
|
||||||
components/radiator_auto.rules measures network headroom, gates the radiator
|
components/radiator_auto.rules measures network headroom, gates the radiator
|
||||||
tests/ offline validation suite
|
tests/ offline validation suite
|
||||||
|
tests/steam_paths.py finds the game via Steam's libraryfolders.vdf
|
||||||
tools/dotnet_enum.py reads component schemas out of Cosmoteer.dll
|
tools/dotnet_enum.py reads component schemas out of Cosmoteer.dll
|
||||||
tools/ildump.py disassembles component methods to readable CIL
|
tools/ildump.py disassembles component methods to readable CIL
|
||||||
install.sh validate + copy into the Mods folder
|
install.sh validate + copy into the Mods folder
|
||||||
@@ -110,12 +111,18 @@ install.sh validate + copy into the Mods folder
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then enable **Auto Radiator** in the game's Mods menu. `./install.sh --uninstall`
|
Then enable **Auto Radiator** in the game's Mods menu. `./install.sh --uninstall`
|
||||||
removes it. If the script cannot find your Mods folder, set `COSMOTEER_MODS_DIR`.
|
removes it.
|
||||||
|
|
||||||
|
The script finds the game by reading Steam's `libraryfolders.vdf`, so a library
|
||||||
|
on a second disk and a Proton prefix are both handled. If it still comes up
|
||||||
|
empty it prints the libraries it searched; set `COSMOTEER_MODS_DIR` to override.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 tests/test_mod.py
|
python3 tests/test_mod.py # the mod, against installed game data
|
||||||
|
python3 tests/test_steam_paths.py # game discovery
|
||||||
|
./tests/test_install.sh # Mods folder discovery
|
||||||
```
|
```
|
||||||
|
|
||||||
Cosmoteer applies mod actions to vanilla data at load time. Some mistakes crash
|
Cosmoteer applies mod actions to vanilla data at load time. Some mistakes crash
|
||||||
|
|||||||
+92
-38
@@ -3,56 +3,110 @@
|
|||||||
# Installs the Auto Radiator mod into Cosmoteer's user Mods folder.
|
# Installs the Auto Radiator mod into Cosmoteer's user Mods folder.
|
||||||
#
|
#
|
||||||
# Why it exists: Cosmoteer loads mods from the user profile directory, which on
|
# Why it exists: Cosmoteer loads mods from the user profile directory, which on
|
||||||
# a Proton install lives deep inside the Steam compatdata prefix and is awkward
|
# a Proton install lives deep inside the Steam compatdata prefix of whichever
|
||||||
# to find by hand. This script locates it, validates the mod against the
|
# library the game was installed to. That library can sit on any disk, so this
|
||||||
# installed game data first, and copies only the files the game needs.
|
# script reads Steam's own libraryfolders.vdf to find it, validates the mod
|
||||||
|
# against the installed game data, and copies only the files the game needs.
|
||||||
#
|
#
|
||||||
# Usage: ./install.sh install (validates first)
|
# Usage: ./install.sh install (validates first)
|
||||||
# ./install.sh --uninstall remove the installed copy
|
# ./install.sh --uninstall remove the installed copy
|
||||||
|
#
|
||||||
set -euo pipefail
|
# Sourcing this file defines the discovery functions without installing, and
|
||||||
|
# leaves the sourcing shell's options untouched.
|
||||||
|
|
||||||
MOD_ID="auto_radiator"
|
MOD_ID="auto_radiator"
|
||||||
|
COSMOTEER_APPID=799600
|
||||||
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
steam_roots() {
|
||||||
|
# The Steam client directories, one per install flavour: the modern default,
|
||||||
|
# the two legacy symlinks, and Flatpak's sandboxed home.
|
||||||
|
printf '%s\n' \
|
||||||
|
"$HOME/.local/share/Steam" \
|
||||||
|
"$HOME/.steam/steam" \
|
||||||
|
"$HOME/.steam/root" \
|
||||||
|
"$HOME/.var/app/com.valvesoftware.Steam/data/Steam"
|
||||||
|
}
|
||||||
|
|
||||||
|
steam_libraries() {
|
||||||
|
# Every library folder Steam knows about. libraryfolders.vdf lists one
|
||||||
|
# "path" per library; the app data sits in <path>/steamapps. Steam's own
|
||||||
|
# root is listed there too, but emit it regardless so a missing or
|
||||||
|
# unreadable manifest still leaves the default library searchable.
|
||||||
|
local root vdf
|
||||||
|
steam_roots
|
||||||
|
while read -r root; do
|
||||||
|
vdf="$root/steamapps/libraryfolders.vdf"
|
||||||
|
[ -f "$vdf" ] || continue
|
||||||
|
sed -n 's/^[[:space:]]*"path"[[:space:]]*"\(.*\)"[[:space:]]*$/\1/p' "$vdf"
|
||||||
|
done < <(steam_roots)
|
||||||
|
}
|
||||||
|
|
||||||
find_mods_dir() {
|
find_mods_dir() {
|
||||||
# A Cosmoteer profile folder is named after the Steam ID, so glob for it.
|
local lib path
|
||||||
local candidates=(
|
local -a libraries
|
||||||
"$HOME"/.local/share/Steam/steamapps/compatdata/799600/pfx/drive_c/users/steamuser/"Saved Games"/Cosmoteer/*/Mods
|
readarray -t libraries < <(steam_libraries)
|
||||||
/mnt/*/SteamLibrary/steamapps/compatdata/799600/pfx/drive_c/users/steamuser/"Saved Games"/Cosmoteer/*/Mods
|
|
||||||
"$HOME"/"Saved Games"/Cosmoteer/*/Mods
|
# Proton keeps the profile inside the game's compat prefix. The profile
|
||||||
"$HOME"/Documents/"My Games"/Cosmoteer/*/Mods
|
# folder is named after the Steam ID, so glob for it.
|
||||||
)
|
for lib in "${libraries[@]}"; do
|
||||||
for path in "${candidates[@]}"; do
|
for path in "$lib/steamapps/compatdata/$COSMOTEER_APPID/pfx/drive_c/users/steamuser/Saved Games/Cosmoteer"/*/Mods; do
|
||||||
|
[ -d "$path" ] && printf '%s\n' "$path" && return 0
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# A native or Windows install keeps the profile in the user's home.
|
||||||
|
for path in "$HOME/Saved Games/Cosmoteer"/*/Mods "$HOME/Documents/My Games/Cosmoteer"/*/Mods; do
|
||||||
[ -d "$path" ] && printf '%s\n' "$path" && return 0
|
[ -d "$path" ] && printf '%s\n' "$path" && return 0
|
||||||
done
|
done
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
MODS_DIR="${COSMOTEER_MODS_DIR:-$(find_mods_dir || true)}"
|
resolve_mods_dir() {
|
||||||
|
# An explicit COSMOTEER_MODS_DIR wins, so an unusual install can be pointed
|
||||||
|
# at by hand when discovery comes up empty.
|
||||||
|
if [ -n "${COSMOTEER_MODS_DIR:-}" ]; then
|
||||||
|
printf '%s\n' "$COSMOTEER_MODS_DIR"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
find_mods_dir
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z "$MODS_DIR" ]; then
|
main() {
|
||||||
echo "Could not find the Cosmoteer Mods folder." >&2
|
set -euo pipefail
|
||||||
echo "Set COSMOTEER_MODS_DIR to it and run again." >&2
|
local mods_dir target
|
||||||
exit 1
|
mods_dir="$(resolve_mods_dir || true)"
|
||||||
|
|
||||||
|
if [ -z "$mods_dir" ]; then
|
||||||
|
echo "Could not find the Cosmoteer Mods folder." >&2
|
||||||
|
echo "Searched these Steam libraries:" >&2
|
||||||
|
steam_libraries | sort -u | sed 's/^/ /' >&2
|
||||||
|
echo "Set COSMOTEER_MODS_DIR to it and run again." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
target="$mods_dir/$MOD_ID"
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--uninstall" ]; then
|
||||||
|
rm -rf "$target"
|
||||||
|
echo "Removed $target"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Validating against installed game data..."
|
||||||
|
python3 "$SOURCE_DIR/tests/test_mod.py"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Installing to $target"
|
||||||
|
rm -rf "$target"
|
||||||
|
mkdir -p "$target"
|
||||||
|
cp "$SOURCE_DIR/mod.rules" "$target/"
|
||||||
|
cp -r "$SOURCE_DIR/components" "$target/"
|
||||||
|
|
||||||
|
echo "Done. Enable \"Auto Radiator\" in the game's Mods menu."
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||||
|
main "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TARGET="$MODS_DIR/$MOD_ID"
|
|
||||||
|
|
||||||
if [ "${1:-}" = "--uninstall" ]; then
|
|
||||||
rm -rf "$TARGET"
|
|
||||||
echo "Removed $TARGET"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Validating against installed game data..."
|
|
||||||
python3 "$SOURCE_DIR/tests/test_mod.py"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Installing to $TARGET"
|
|
||||||
rm -rf "$TARGET"
|
|
||||||
mkdir -p "$TARGET"
|
|
||||||
cp "$SOURCE_DIR/mod.rules" "$TARGET/"
|
|
||||||
cp -r "$SOURCE_DIR/components" "$TARGET/"
|
|
||||||
|
|
||||||
echo "Done. Enable \"Auto Radiator\" in the game's Mods menu."
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# ABOUT ME
|
||||||
|
# Locates Cosmoteer's installed Data folder by asking Steam where its libraries
|
||||||
|
# are.
|
||||||
|
#
|
||||||
|
# Why this exists: the mod tests validate against vanilla game data, and the
|
||||||
|
# game can be installed to any Steam library on any disk. A fixed list of
|
||||||
|
# guessed paths misses real installs, so this reads the library roots out of
|
||||||
|
# Steam's own libraryfolders.vdf and looks for the game under each one.
|
||||||
|
#
|
||||||
|
# install.sh carries the same logic in bash for finding the Mods folder.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
COSMOTEER_APPID = 799600
|
||||||
|
|
||||||
|
# One "path" entry per library root; the app data sits in <path>/steamapps.
|
||||||
|
_LIBRARY_PATH_RE = re.compile(r'^\s*"path"\s*"(.*)"\s*$')
|
||||||
|
|
||||||
|
|
||||||
|
def steam_roots(home):
|
||||||
|
"""The Steam client directories, one per install flavour."""
|
||||||
|
return [
|
||||||
|
os.path.join(home, ".local/share/Steam"),
|
||||||
|
os.path.join(home, ".steam/steam"),
|
||||||
|
os.path.join(home, ".steam/root"),
|
||||||
|
os.path.join(home, ".var/app/com.valvesoftware.Steam/data/Steam"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def steam_libraries(home):
|
||||||
|
"""Every Steam library root, in search order and without duplicates.
|
||||||
|
|
||||||
|
Steam's own roots come first so the default library stays searchable even
|
||||||
|
when libraryfolders.vdf is missing or unreadable.
|
||||||
|
"""
|
||||||
|
libraries = list(steam_roots(home))
|
||||||
|
for root in steam_roots(home):
|
||||||
|
vdf = os.path.join(root, "steamapps", "libraryfolders.vdf")
|
||||||
|
try:
|
||||||
|
with open(vdf, encoding="utf-8", errors="replace") as handle:
|
||||||
|
lines = handle.readlines()
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
for line in lines:
|
||||||
|
match = _LIBRARY_PATH_RE.match(line)
|
||||||
|
if match:
|
||||||
|
libraries.append(match.group(1).replace("\\\\", "\\"))
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
unique = []
|
||||||
|
for library in libraries:
|
||||||
|
if library not in seen:
|
||||||
|
seen.add(library)
|
||||||
|
unique.append(library)
|
||||||
|
return unique
|
||||||
|
|
||||||
|
|
||||||
|
def find_data_dir(home=None, environ=None):
|
||||||
|
"""The Cosmoteer Data folder, or None when the game is not installed.
|
||||||
|
|
||||||
|
COSMOTEER_DATA overrides discovery so an unusual install can be pointed at
|
||||||
|
by hand.
|
||||||
|
"""
|
||||||
|
environ = os.environ if environ is None else environ
|
||||||
|
override = environ.get("COSMOTEER_DATA")
|
||||||
|
if override:
|
||||||
|
return override
|
||||||
|
|
||||||
|
home = os.path.expanduser("~") if home is None else home
|
||||||
|
for library in steam_libraries(home):
|
||||||
|
data = os.path.join(library, "steamapps", "common", "Cosmoteer", "Data")
|
||||||
|
if os.path.isdir(data):
|
||||||
|
return data
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_bin_dir(home=None, environ=None):
|
||||||
|
"""The Cosmoteer Bin folder, which sits beside Data."""
|
||||||
|
data = find_data_dir(home=home, environ=environ)
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
return os.path.join(os.path.dirname(data), "Bin")
|
||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ABOUT ME
|
||||||
|
# Tests install.sh's discovery of the Cosmoteer Mods folder.
|
||||||
|
#
|
||||||
|
# Why it exists: the Mods folder sits inside a Proton prefix under whichever
|
||||||
|
# Steam library the game was installed to, and that library can live anywhere.
|
||||||
|
# Guessing paths misses real installs, so discovery reads Steam's own
|
||||||
|
# libraryfolders.vdf. These tests build fake Steam trees and check that the
|
||||||
|
# discovery finds the Mods folder in each layout.
|
||||||
|
#
|
||||||
|
# Usage: ./tests/test_install.sh
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
APPID=799600
|
||||||
|
PROFILE=76561198000495479
|
||||||
|
|
||||||
|
# Sourcing install.sh must define the functions without installing anything.
|
||||||
|
source "$REPO_DIR/install.sh"
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
tmp_root="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_root"' EXIT
|
||||||
|
|
||||||
|
check() {
|
||||||
|
# check <name> <expected> <actual>
|
||||||
|
if [ "$2" = "$3" ]; then
|
||||||
|
echo "PASS: $1"
|
||||||
|
else
|
||||||
|
echo "FAIL: $1"
|
||||||
|
echo " expected: $2"
|
||||||
|
echo " actual: $3"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Builds a Steam client dir at <root>/.local/share/Steam whose
|
||||||
|
# libraryfolders.vdf lists every library path passed after the root.
|
||||||
|
make_steam_home() {
|
||||||
|
local home="$1"
|
||||||
|
shift
|
||||||
|
local vdf="$home/.local/share/Steam/steamapps/libraryfolders.vdf"
|
||||||
|
mkdir -p "$(dirname "$vdf")"
|
||||||
|
{
|
||||||
|
echo '"libraryfolders"'
|
||||||
|
echo '{'
|
||||||
|
local i=0
|
||||||
|
for lib in "$@"; do
|
||||||
|
printf '\t"%s"\n\t{\n\t\t"path"\t\t"%s"\n\t}\n' "$i" "$lib"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
echo '}'
|
||||||
|
} >"$vdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creates the Proton-prefix Mods folder for Cosmoteer inside a library root.
|
||||||
|
make_proton_mods() {
|
||||||
|
local lib="$1"
|
||||||
|
local mods="$lib/steamapps/compatdata/$APPID/pfx/drive_c/users/steamuser/Saved Games/Cosmoteer/$PROFILE/Mods"
|
||||||
|
mkdir -p "$mods"
|
||||||
|
printf '%s\n' "$mods"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. The game lives in a secondary library on another disk, which is the case
|
||||||
|
# whenever Steam is told to install to a second drive.
|
||||||
|
case1="$tmp_root/case1"
|
||||||
|
mkdir -p "$case1/home"
|
||||||
|
lib="$case1/disk2/steamapps"
|
||||||
|
make_steam_home "$case1/home" "$case1/home/.local/share/Steam" "$lib"
|
||||||
|
expected="$(make_proton_mods "$lib")"
|
||||||
|
actual="$(HOME="$case1/home" find_mods_dir)"
|
||||||
|
check "finds Mods in a secondary library from libraryfolders.vdf" "$expected" "$actual"
|
||||||
|
|
||||||
|
# 2. The game lives in the default library inside the Steam client dir.
|
||||||
|
case2="$tmp_root/case2"
|
||||||
|
mkdir -p "$case2/home"
|
||||||
|
make_steam_home "$case2/home" "$case2/home/.local/share/Steam"
|
||||||
|
expected="$(make_proton_mods "$case2/home/.local/share/Steam")"
|
||||||
|
actual="$(HOME="$case2/home" find_mods_dir)"
|
||||||
|
check "finds Mods in the default Steam library" "$expected" "$actual"
|
||||||
|
|
||||||
|
# 3. Library paths may contain spaces, so discovery must not word-split them.
|
||||||
|
case3="$tmp_root/case3"
|
||||||
|
mkdir -p "$case3/home"
|
||||||
|
lib="$case3/my games/steamapps"
|
||||||
|
make_steam_home "$case3/home" "$case3/home/.local/share/Steam" "$lib"
|
||||||
|
expected="$(make_proton_mods "$lib")"
|
||||||
|
actual="$(HOME="$case3/home" find_mods_dir)"
|
||||||
|
check "finds Mods under a library path containing spaces" "$expected" "$actual"
|
||||||
|
|
||||||
|
# 4. A Windows-style profile in the user's home, for a non-Proton install.
|
||||||
|
case4="$tmp_root/case4"
|
||||||
|
mkdir -p "$case4/home/Saved Games/Cosmoteer/$PROFILE/Mods"
|
||||||
|
expected="$case4/home/Saved Games/Cosmoteer/$PROFILE/Mods"
|
||||||
|
actual="$(HOME="$case4/home" find_mods_dir)"
|
||||||
|
check "finds Mods in the home Saved Games folder" "$expected" "$actual"
|
||||||
|
|
||||||
|
# 5. Nothing installed at all: discovery reports failure instead of guessing.
|
||||||
|
case5="$tmp_root/case5"
|
||||||
|
mkdir -p "$case5/home"
|
||||||
|
actual="$(HOME="$case5/home" find_mods_dir)"
|
||||||
|
status=$?
|
||||||
|
check "reports no Mods folder when there is no install" "" "$actual"
|
||||||
|
check "exits non-zero when there is no install" "1" "$status"
|
||||||
|
|
||||||
|
# 6. COSMOTEER_MODS_DIR overrides discovery, so an unusual install can be
|
||||||
|
# pointed at by hand.
|
||||||
|
case6="$tmp_root/case6"
|
||||||
|
mkdir -p "$case6/elsewhere/Mods"
|
||||||
|
make_steam_home "$case6/home" "$case6/home/.local/share/Steam"
|
||||||
|
make_proton_mods "$case6/home/.local/share/Steam" >/dev/null
|
||||||
|
actual="$(HOME="$case6/home" COSMOTEER_MODS_DIR="$case6/elsewhere/Mods" resolve_mods_dir)"
|
||||||
|
check "COSMOTEER_MODS_DIR overrides discovery" "$case6/elsewhere/Mods" "$actual"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ "$failures" -eq 0 ]; then
|
||||||
|
echo "All install.sh discovery tests passed."
|
||||||
|
else
|
||||||
|
echo "$failures test(s) failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
+2
-17
@@ -17,15 +17,10 @@ import sys
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
import rules_parser
|
import rules_parser
|
||||||
|
import steam_paths
|
||||||
|
|
||||||
MOD_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
MOD_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
CANDIDATE_DATA_DIRS = [
|
|
||||||
"/mnt/SSD4TB/SteamLibrary/steamapps/common/Cosmoteer/Data",
|
|
||||||
os.path.expanduser("~/.local/share/Steam/steamapps/common/Cosmoteer/Data"),
|
|
||||||
os.path.expanduser("~/.steam/steam/steamapps/common/Cosmoteer/Data"),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Component fields whose value names another component in the same part.
|
# Component fields whose value names another component in the same part.
|
||||||
COMPONENT_REF_FIELDS = {
|
COMPONENT_REF_FIELDS = {
|
||||||
"OperationalToggle",
|
"OperationalToggle",
|
||||||
@@ -55,17 +50,7 @@ def check(condition, message):
|
|||||||
_failures.append(message)
|
_failures.append(message)
|
||||||
|
|
||||||
|
|
||||||
def find_data_dir():
|
DATA = steam_paths.find_data_dir()
|
||||||
override = os.environ.get("COSMOTEER_DATA")
|
|
||||||
if override:
|
|
||||||
return override
|
|
||||||
for path in CANDIDATE_DATA_DIRS:
|
|
||||||
if os.path.isdir(path):
|
|
||||||
return path
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
DATA = find_data_dir()
|
|
||||||
|
|
||||||
_vanilla_cache = {}
|
_vanilla_cache = {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# ABOUT ME
|
||||||
|
# Tests steam_paths.find_data_dir, which locates Cosmoteer's Data folder.
|
||||||
|
#
|
||||||
|
# Why this exists: the game can be installed to any Steam library on any disk,
|
||||||
|
# so the Data folder cannot be guessed from a fixed list of paths. Discovery
|
||||||
|
# reads Steam's own libraryfolders.vdf instead. These tests build fake Steam
|
||||||
|
# trees and check that each realistic layout is found.
|
||||||
|
#
|
||||||
|
# Usage: python3 tests/test_steam_paths.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
import steam_paths
|
||||||
|
|
||||||
|
_failures = []
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, expected, actual):
|
||||||
|
if expected == actual:
|
||||||
|
print(f"PASS: {name}")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: {name}")
|
||||||
|
print(f" expected: {expected}")
|
||||||
|
print(f" actual: {actual}")
|
||||||
|
_failures.append(name)
|
||||||
|
|
||||||
|
|
||||||
|
def make_steam_home(home, libraries):
|
||||||
|
"""Write a libraryfolders.vdf under `home` listing the given library roots."""
|
||||||
|
vdf = os.path.join(home, ".local/share/Steam/steamapps/libraryfolders.vdf")
|
||||||
|
os.makedirs(os.path.dirname(vdf), exist_ok=True)
|
||||||
|
with open(vdf, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write('"libraryfolders"\n{\n')
|
||||||
|
for index, library in enumerate(libraries):
|
||||||
|
handle.write(f'\t"{index}"\n\t{{\n\t\t"path"\t\t"{library}"\n\t}}\n')
|
||||||
|
handle.write("}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def make_data_dir(library):
|
||||||
|
"""Create the Cosmoteer Data folder inside a library root."""
|
||||||
|
data = os.path.join(library, "steamapps/common/Cosmoteer/Data")
|
||||||
|
os.makedirs(data, exist_ok=True)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tmp_root = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
# 1. The game lives in a secondary library on another disk.
|
||||||
|
case = os.path.join(tmp_root, "case1")
|
||||||
|
home = os.path.join(case, "home")
|
||||||
|
library = os.path.join(case, "disk2/steamapps")
|
||||||
|
make_steam_home(home, [os.path.join(home, ".local/share/Steam"), library])
|
||||||
|
expected = make_data_dir(library)
|
||||||
|
check(
|
||||||
|
"finds Data in a secondary library from libraryfolders.vdf",
|
||||||
|
expected,
|
||||||
|
steam_paths.find_data_dir(home=home, environ={}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. The game lives in the default library inside the Steam client dir.
|
||||||
|
case = os.path.join(tmp_root, "case2")
|
||||||
|
home = os.path.join(case, "home")
|
||||||
|
steam = os.path.join(home, ".local/share/Steam")
|
||||||
|
make_steam_home(home, [steam])
|
||||||
|
expected = make_data_dir(steam)
|
||||||
|
check(
|
||||||
|
"finds Data in the default Steam library",
|
||||||
|
expected,
|
||||||
|
steam_paths.find_data_dir(home=home, environ={}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Library paths may contain spaces.
|
||||||
|
case = os.path.join(tmp_root, "case3")
|
||||||
|
home = os.path.join(case, "home")
|
||||||
|
library = os.path.join(case, "my games/steamapps")
|
||||||
|
make_steam_home(home, [os.path.join(home, ".local/share/Steam"), library])
|
||||||
|
expected = make_data_dir(library)
|
||||||
|
check(
|
||||||
|
"finds Data under a library path containing spaces",
|
||||||
|
expected,
|
||||||
|
steam_paths.find_data_dir(home=home, environ={}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Nothing installed: discovery reports None instead of guessing.
|
||||||
|
case = os.path.join(tmp_root, "case4")
|
||||||
|
home = os.path.join(case, "home")
|
||||||
|
os.makedirs(home, exist_ok=True)
|
||||||
|
check(
|
||||||
|
"returns None when there is no install",
|
||||||
|
None,
|
||||||
|
steam_paths.find_data_dir(home=home, environ={}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. COSMOTEER_DATA overrides discovery.
|
||||||
|
case = os.path.join(tmp_root, "case5")
|
||||||
|
home = os.path.join(case, "home")
|
||||||
|
steam = os.path.join(home, ".local/share/Steam")
|
||||||
|
make_steam_home(home, [steam])
|
||||||
|
make_data_dir(steam)
|
||||||
|
override = os.path.join(case, "elsewhere/Data")
|
||||||
|
os.makedirs(override, exist_ok=True)
|
||||||
|
check(
|
||||||
|
"COSMOTEER_DATA overrides discovery",
|
||||||
|
override,
|
||||||
|
steam_paths.find_data_dir(home=home, environ={"COSMOTEER_DATA": override}),
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmp_root, ignore_errors=True)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if _failures:
|
||||||
|
print(f"{len(_failures)} test(s) failed.")
|
||||||
|
return 1
|
||||||
|
print("All Steam path discovery tests passed.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user