Files
Jackify/binaries/omni-guides-testing.sh
2025-09-15 20:18:13 +01:00

1784 lines
71 KiB
Bash

#!/usr/bin/env bash
#
###################################################
# #
# A tool for running Wabbajack modlists on Linux #
# #
# Beta v0.69t - Omni 03/18/2025 #
# #
###################################################
# Full Changelog can be found here: https://github.com/Omni-guides/Wabbajack-Modlist-Linux/blob/main/binaries/omni-guides-sh.changelog.txt
# Parse --debug or -d flag before anything else
DEBUG=0
for arg in "$@"; do
if [[ "$arg" == "--debug" || "$arg" == "-d" ]]; then
DEBUG=1
export DEBUG
break
fi
# Do not shift here, just scan args
done
# Current Script Version (beta)
script_ver=0.69t
# Define modlist-specific configurations
declare -A modlist_configs=(
["wildlander"]="dotnet472"
["librum|apostasy"]="dotnet40 dotnet8"
["nordicsouls"]="dotnet40"
["livingskyrim|lsiv|ls4"]="dotnet40"
["lostlegacy"]="dotnet48"
)
# Set up and blank logs (simplified)
LOGFILE=$HOME/omni-guides-sh.log
echo "" >$HOME/omni-guides-sh.log
# Add our new logging function
log_status() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Always write to log file with timestamp but without color codes
echo "[$timestamp] [$level] $(echo "$message" | sed 's/\x1b\[[0-9;]*m//g')" >> "$LOGFILE"
# If DEBUG=1, print all messages to terminal, including DEBUG
if [[ "$DEBUG" == "1" ]]; then
echo -e "$message"
else
# Only display non-DEBUG messages to the user, preserving color codes
if [ "$level" != "DEBUG" ]; then
echo -e "$message"
fi
fi
}
# Display banner
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ Omni-Guides (beta) ║"
echo "║ ║"
echo "║ A tool for running Wabbajack modlists on Linux ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
#########
# Intro #
#########
echo ""
log_status "INFO" "Omni-Guides Wabbajack Post-Install Script v$script_ver"
echo "───────────────────────────────────────────────────────────────────"
log_status "INFO" "This script automates the post-install steps for Wabbajack modlists on Linux/Steam Deck."
log_status "INFO" "It will configure your modlist location, install required components, and apply necessary fixes."
echo ""
log_status "WARN" "⚠ IMPORTANT: Use this script at your own risk."
log_status "INFO" "Please report any issues via GitHub (Omni-guides/Wabbajack-Modlist-Linux)."
echo "───────────────────────────────────────────────────────────────────"
echo -e "\e[33mPress any key to continue...\e[0m"
read -n 1 -s -r -p ""
#############
# Functions #
#############
##########################
# Cleanup Wine Processes #
##########################
cleanup_wine_procs() {
# Find and kill processes containing various process names
processes=$(pgrep -f "win7|win10|ShowDotFiles|protontricks")
if [[ -n "$processes" ]]; then
echo "$processes" | xargs kill -9
echo "Processes killed successfully." >>$LOGFILE 2>&1
else
echo "No matching processes found." >>$LOGFILE 2>&1
fi
pkill -9 winetricks
}
#############
# Set APPID #
#############
set_appid() {
echo "DEBUG: Extracting APPID from choice: '$choice'" >>$LOGFILE 2>&1
APPID=$(echo "$choice" | awk -F'[()]' '{print $2}')
echo "DEBUG: Extracted APPID: '$APPID'" >>$LOGFILE 2>&1
#APPID=$(echo $choice | awk {'print $NF'} | sed 's:^.\(.*\).$:\1:')
echo "APPID=$APPID" >>$LOGFILE 2>&1
if [ -z "$APPID" ]; then
echo "Error: APPID cannot be empty, exiting... Please tell Omni :("
cleaner_exit
fi
}
#############################
# Detect if running on deck #
#############################
detect_steamdeck() {
# Steamdeck or nah?
if [ -f "/etc/os-release" ] && grep -q "steamdeck" "/etc/os-release"; then
steamdeck=1
echo "Running on Steam Deck" >>$LOGFILE 2>&1
else
steamdeck=0
echo "NOT A steamdeck" >>$LOGFILE 2>&1
fi
}
# Modlist-specific steps spinner wrapper
modlist_specific_steps_spinner() {
echo "[DEBUG] modlist_specific_steps_spinner: DEBUG=$DEBUG" >&2
if [ "$DEBUG" = "1" ]; then
modlist_specific_steps
else
local pid
local spinner=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
modlist_specific_steps >>"$LOGFILE" 2>&1 &
pid=$!
local i=0
local bar="=============== "
local msg="Progress: [${bar}] 30% - Running modlist-specific steps... "
while kill -0 "$pid" 2>/dev/null; do
printf "\r\033[K%s%s" "$msg" "${spinner[i]}"
i=$(( (i+1) % 10 ))
sleep 0.1
done
wait "$pid"
printf "\r\033[K"
fi
}
###########################################
# Detect Protontricks (flatpak or native) #
###########################################
detect_protontricks() {
echo -ne "\nDetecting if protontricks is installed..." >>$LOGFILE 2>&1
# Check if protontricks exists
if command -v protontricks >/dev/null 2>&1; then
protontricks_path=$(command -v protontricks)
# Check if the detected binary is actually a Flatpak wrapper
if [[ -f "$protontricks_path" ]] && grep -q "flatpak run" "$protontricks_path"; then
echo -e "Detected Protontricks is actually a Flatpak wrapper at $protontricks_path." >>$LOGFILE 2>&1
which_protontricks=flatpak
return 0
else
echo -e "Native Protontricks found at $protontricks_path." | tee -a $LOGFILE
which_protontricks=native
return 0 # Exit function since we confirmed native protontricks
fi
else
echo -e "Non-Flatpak Protontricks not found. Checking flatpak..." >>$LOGFILE 2>&1
if flatpak list | grep -iq protontricks; then
echo -e "Flatpak Protontricks is already installed." >>$LOGFILE 2>&1
which_protontricks=flatpak
else
echo -e "\e[31m\n** Protontricks not found. Do you wish to install it? (y/n): **\e[0m"
read -p " " answer
if [[ $answer =~ ^[Yy]$ ]]; then
if [[ $steamdeck -eq 1 ]]; then
if flatpak install -u -y --noninteractive flathub com.github.Matoking.protontricks; then
which_protontricks=flatpak
else
echo -e "\n\e[31mFailed to install Protontricks via Flatpak. Please install it manually and rerun this script.\e[0m" | tee -a $LOGFILE
exit 1
fi
else
read -p "Choose installation method: 1) Flatpak (preferred) 2) Native: " choice
if [[ $choice =~ 1 ]]; then
if flatpak install -u -y --noninteractive flathub com.github.Matoking.protontricks; then
which_protontricks=flatpak
else
echo -e "\n\e[31mFailed to install Protontricks via Flatpak. Please install it manually and rerun this script.\e[0m" | tee -a $LOGFILE
exit 1
fi
else
echo -e "\nSorry, there are too many distros to automate this!" | tee -a $LOGFILE
echo -e "Please check how to install Protontricks using your OS package manager (yum, dnf, apt, pacman, etc.)" | tee -a $LOGFILE
echo -e "\e[31mProtontricks is required for this script to function. Exiting.\e[0m" | tee -a $LOGFILE
exit 1
fi
fi
else
echo -e "\e[31mProtontricks is required for this script to function. Exiting.\e[0m" | tee -a $LOGFILE
exit 1
fi
fi
# After any install attempt, re-check for protontricks
if ! command -v protontricks >/dev/null 2>&1 && ! flatpak list | grep -iq protontricks; then
echo -e "\e[31mProtontricks is still not installed after attempted installation. Exiting.\e[0m" | tee -a $LOGFILE
exit 1
fi
fi
}
#############################
# Run protontricks commands #
#############################
run_protontricks() {
# Determine the protontricks binary path and create command array
if [ "$which_protontricks" = "flatpak" ]; then
local cmd=(flatpak run com.github.Matoking.protontricks)
else
local cmd=(protontricks)
fi
# Execute the command with all arguments
"${cmd[@]}" "$@"
}
###############################
# Detect Protontricks Version #
###############################
protontricks_version() {
# Get the current version of protontricks
protontricks_version=$(run_protontricks -V | cut -d ' ' -f 2 | sed 's/[()]//g')
# Remove any non-numeric characters from the version number
protontricks_version_cleaned=$(echo "$protontricks_version" | sed 's/[^0-9.]//g')
echo "Protontricks Version Cleaned = $protontricks_version_cleaned" >> "$LOGFILE" 2>&1
# Split the version into digits
IFS='.' read -r first_digit second_digit third_digit <<< "$protontricks_version_cleaned"
# Check if the second digit is defined and greater than or equal to 12
if [[ -n "$second_digit" && "$second_digit" -lt 12 ]]; then
echo "Your protontricks version is too old! Update to version 1.12 or newer and rerun this script. If 'flatpak run com.github.Matoking.protontricks -V' returns 'unknown', then please update via flatpak." | tee -a "$LOGFILE"
cleaner_exit
fi
}
#######################################
# Detect Skyrim or Fallout 4 Function #
#######################################
detect_game() {
# Define lookup table for games
declare -A game_lookup=(
["Skyrim"]="Skyrim Special Edition"
["Fallout 4"]="Fallout 4"
["Fallout New Vegas"]="Fallout New Vegas"
["FNV"]="Fallout New Vegas"
["Oblivion"]="Oblivion"
)
# Try direct match first
for pattern in "${!game_lookup[@]}"; do
if [[ $choice == *"$pattern"* ]]; then
gamevar="${game_lookup[$pattern]}"
which_game="${gamevar%% *}"
echo "Game variable set to $which_game." >>"$LOGFILE" 2>&1
echo "Game variable: $gamevar" >>"$LOGFILE" 2>&1
return 0
fi
done
# Handle generic "Fallout" case
if [[ $choice == *"Fallout"* ]]; then
PS3="Please select a Fallout game (enter the number): "
select fallout_opt in "Fallout 4" "Fallout New Vegas"; do
if [[ -n $fallout_opt ]]; then
gamevar="$fallout_opt"
which_game="${gamevar%% *}"
echo "Game variable set to $which_game." >>"$LOGFILE" 2>&1
echo "Game variable: $gamevar" >>"$LOGFILE" 2>&1
return 0
else
echo "Invalid option"
fi
done
fi
# If no match found, show selection menu
PS3="Please select a game (enter the number): "
select opt in "Skyrim" "Fallout 4" "Fallout New Vegas" "Oblivion"; do
if [[ -n $opt ]]; then
gamevar="${game_lookup[$opt]}"
which_game="${gamevar%% *}"
echo "Game variable set to $which_game." >>"$LOGFILE" 2>&1
echo "Game variable: $gamevar" >>"$LOGFILE" 2>&1
return 0
else
echo "Invalid option"
fi
done
}
###################################
# Try to detect the Steam Library #
###################################
detect_steam_library() {
local libraryfolders_vdf="$HOME/.steam/steam/config/libraryfolders.vdf"
if [[ ! -f "$libraryfolders_vdf" ]]; then
echo "libraryfolders.vdf not found in ~/.steam/steam/config/. Please ensure Steam is installed." | tee -a "$LOGFILE"
return 1
fi
local library_paths=()
while IFS='' read -r line; do
if [[ "$line" =~ \"path\" ]]; then
local path=$(echo "$line" | sed 's/.*"path"\s*"\(.*\)"/\1/')
if [[ -n "$path" ]]; then
library_paths+=("$path/steamapps/common")
fi
fi
done <"$libraryfolders_vdf"
local found=0
for library_path in "${library_paths[@]}"; do
if [[ -d "$library_path/$gamevar" ]]; then
steam_library="$library_path"
found=1
echo "Found '$gamevar' in $steam_library." >>$LOGFILE 2>&1
break
else
echo "Checking $library_path: '$gamevar' not found." >>$LOGFILE 2>&1
fi
done
if [[ "$found" -eq 0 ]]; then
echo "Vanilla game not found in Steam library locations." | tee -a "$LOGFILE"
while true; do
echo -e "\n** Enter the path to your Vanilla $gamevar directory manually (e.g. /data/SteamLibrary/steamapps/common/$gamevar): **"
read -e -r gamevar_input
steam_library_input="${gamevar_input%/*}/"
if [[ -d "$steam_library_input/$gamevar" ]]; then
steam_library="$steam_library_input"
echo "Found $gamevar in $steam_library_input." | tee -a "$LOGFILE"
echo "Steam Library set to: $steam_library" >>$LOGFILE 2>&1
break
else
echo "Game not found in $steam_library_input. Please enter a valid path to Vanilla $gamevar." | tee -a "$LOGFILE"
fi
done
fi
echo "Steam Library Location: $steam_library" >>$LOGFILE 2>&1
if [[ "$steamdeck" -eq 1 && "$steam_library" == "/run/media"* ]]; then
basegame_sdcard=1
fi
}
#################################
# Detect Modlist Directory Path #
#################################
detect_modlist_dir_path() {
log_status "DEBUG" "Detecting $MODLIST Install Directory..."
local modlist_paths=()
local choice modlist_ini_temp
local pattern=$(echo "$MODLIST" | sed 's/ /.*\|/g')
# Search for ModOrganizer.exe entries matching the modlist pattern
while IFS= read -r entry; do
modlist_paths+=("$(dirname "${entry//[\"\']/}")")
done < <(strings ~/.steam/steam/userdata/*/config/shortcuts.vdf | grep -iE "ModOrganizer.exe" | grep -iE "$pattern")
# If no exact matches, get all ModOrganizer.exe instances
if [[ ${#modlist_paths[@]} -eq 0 ]]; then
echo "No exact matches found. Searching for all ModOrganizer.exe instances..."
while IFS= read -r entry; do
modlist_paths+=("$(dirname "${entry//[\"\']/}")")
done < <(strings ~/.steam/steam/userdata/*/config/shortcuts.vdf | grep -iE "ModOrganizer.exe")
fi
# Handle different cases based on number of paths found
if [[ ${#modlist_paths[@]} -eq 0 ]]; then
# No paths found - must enter manually
echo -e "\e[34mNo ModOrganizer.exe entries found. Please enter the directory manually:\e[0m"
read -r -e modlist_dir
elif [[ ${#modlist_paths[@]} -eq 1 ]]; then
# Single path found - use it directly without output
modlist_dir="${modlist_paths[0]}"
else
# Multiple paths found - show selection menu
echo "Select the ModOrganizer directory:"
for i in "${!modlist_paths[@]}"; do
echo -e "\e[33m$((i + 1))) ${modlist_paths[i]}\e[0m"
done
echo -e "\e[34m$(( ${#modlist_paths[@]} + 1 ))) Enter path manually\e[0m"
while true; do
read -p "Enter your choice (1-$((${#modlist_paths[@]} + 1))): " choice
if [[ "$choice" =~ ^[0-9]+$ && "$choice" -ge 1 && "$choice" -le $(( ${#modlist_paths[@]} + 1 )) ]]; then
if [[ "$choice" -eq $(( ${#modlist_paths[@]} + 1 )) ]]; then
echo -ne "\e[34mEnter the ModOrganizer directory path: \e[0m"
read -r -e modlist_dir
else
modlist_dir="${modlist_paths[choice - 1]}"
fi
break
else
echo "Invalid selection. Please try again."
fi
done
fi
# Validate selection
modlist_ini_temp="$modlist_dir/ModOrganizer.ini"
while [[ ! -f "$modlist_ini_temp" ]]; do
echo "ModOrganizer.ini not found in $modlist_dir. Please enter a valid path."
echo -ne "\e[34mEnter the ModOrganizer directory path: \e[0m"
read -r -e modlist_dir
modlist_ini_temp="$modlist_dir/ModOrganizer.ini"
done
# Save and log results
modlist_ini="$modlist_ini_temp"
echo "Modlist directory: $modlist_dir" >> "$LOGFILE"
echo "Modlist INI location: $modlist_ini" >> "$LOGFILE"
}
#####################################################
# Set protontricks permissions on Modlist Directory #
#####################################################
set_protontricks_perms() {
if [ "$which_protontricks" = "flatpak" ]; then
log_status "INFO" "\nSetting Protontricks permissions..."
flatpak override --user com.github.Matoking.protontricks --filesystem="$modlist_dir"
log_status "SUCCESS" "Done!"
if [[ $steamdeck = 1 ]]; then
log_status "WARN" "\nChecking for SDCard and setting permissions appropriately.."
sdcard_path=$(df -h | grep "/run/media" | awk {'print $NF'})
echo "$sdcard_path" >>$LOGFILE 2>&1
flatpak override --user --filesystem=$sdcard_path com.github.Matoking.protontricks
flatpak override --user --filesystem=/run/media/mmcblk0p1 com.github.Matoking.protontricks
log_status "SUCCESS" "Done."
fi
else
log_status "DEBUG" "Using Native protontricks, skip setting permissions"
fi
}
#####################################
# Enable Visibility of (.)dot files #
#####################################
enable_dotfiles() {
log_status "DEBUG" "APPID=$APPID"
log_status "INFO" "\nEnabling visibility of (.)dot files..."
# Completely redirect all output to avoid any wine debug messages
if [[ "$DEBUG" == "1" ]]; then
dotfiles_check=$(run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID | grep ShowDotFiles | awk '{gsub(/\r/,""); print $NF}')
else
dotfiles_check=$(WINEDEBUG=-all run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID > /dev/null 2>&1; \
WINEDEBUG=-all run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID 2>/dev/null | grep ShowDotFiles | awk '{gsub(/\r/,""); print $NF}')
fi
log_status "DEBUG" "Current dotfiles setting: $dotfiles_check"
if [[ "$dotfiles_check" = "Y" ]]; then
log_status "INFO" "DotFiles already enabled via registry... skipping"
else
# Method 2: Set registry key (standard approach)
log_status "DEBUG" "Setting ShowDotFiles registry key..."
if [[ "$DEBUG" == "1" ]]; then
run_protontricks -c 'wine reg add "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles /d Y /f' $APPID
else
WINEDEBUG=-all run_protontricks -c 'wine reg add "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles /d Y /f' $APPID > /dev/null 2>&1
fi
# Method 3: Also try direct winecfg approach as backup
log_status "DEBUG" "Also setting via winecfg command..."
if [[ "$DEBUG" == "1" ]]; then
run_protontricks -c 'winecfg /v wine' $APPID
else
WINEDEBUG=-all run_protontricks -c 'winecfg /v wine' $APPID > /dev/null 2>&1
fi
# Method 4: Create user.reg entry if it doesn't exist
log_status "DEBUG" "Ensuring user.reg has correct entry..."
if [[ "$DEBUG" == "1" ]]; then
prefix_path=$(run_protontricks -c 'echo $WINEPREFIX' $APPID 2>/dev/null)
else
prefix_path=$(WINEDEBUG=-all run_protontricks -c 'echo $WINEPREFIX' $APPID 2>/dev/null)
fi
if [[ -n "$prefix_path" && -d "$prefix_path" ]]; then
if [[ -f "$prefix_path/user.reg" ]]; then
if ! grep -q "ShowDotFiles" "$prefix_path/user.reg" 2>/dev/null; then
echo '[Software\\Wine] 1603891765' >> "$prefix_path/user.reg" 2>/dev/null
echo '"ShowDotFiles"="Y"' >> "$prefix_path/user.reg" 2>/dev/null
fi
fi
fi
# Verify the setting took effect
if [[ "$DEBUG" == "1" ]]; then
dotfiles_verify=$(run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID | grep ShowDotFiles | awk '{gsub(/\r/,""); print $NF}')
else
dotfiles_verify=$(WINEDEBUG=-all run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID > /dev/null 2>&1; \
WINEDEBUG=-all run_protontricks -c 'wine reg query "HKEY_CURRENT_USER\Software\Wine" /v ShowDotFiles' $APPID 2>/dev/null | grep ShowDotFiles | awk '{gsub(/\r/,""); print $NF}')
fi
log_status "DEBUG" "Verification check: $dotfiles_verify"
log_status "SUCCESS" "Done!"
fi
}
###############################################
# Set Windows 10 version in the proton prefix #
###############################################
set_win10_prefix() {
if [[ "$DEBUG" == "1" ]]; then
run_protontricks --no-bwrap $APPID win10
else
WINEDEBUG=-all run_protontricks --no-bwrap $APPID win10 >/dev/null 2>&1
fi
}
######################################
# Install Wine Components & VCRedist #
######################################
install_wine_components() {
log_status "INFO" "Installing Wine Components... This can take some time, be patient!"
# Define game-specific component sets
local protontricks_appid="$APPID"
local protontricks_components=()
# Common components for all games
local common_components=("fontsmooth=rgb" "xact" "xact_x64" "vcrun2022")
# Game-specific configuration
case "$gamevar" in
"Skyrim Special Edition"|"Fallout 4")
protontricks_components=("${common_components[@]}" "d3dcompiler_47" "d3dx11_43" "d3dcompiler_43" "dotnet6" "dotnet7")
;;
"Fallout New Vegas")
protontricks_components=("${common_components[@]}" "d3dx9_43" "d3dx9")
protontricks_appid="22380" # Force appid for FNV
;;
"Oblivion")
protontricks_components=("${common_components[@]}" "d3dx9_43" "d3dx9")
;;
*)
echo "Unsupported game: $gamevar" | tee -a "$LOGFILE"
return 1
;;
esac
# Log the command we're about to run
echo "Installing components: ${protontricks_components[*]}" >>$LOGFILE 2>&1
# Run the installation with progress indicator
printf "Protontricks running... "
# Try up to 3 times to install components
local max_attempts=3
local attempt=1
local success=false
while [[ $attempt -le $max_attempts && $success == false ]]; do
if [[ $attempt -gt 1 ]]; then
echo "Retry attempt $attempt/$max_attempts..." | tee -a "$LOGFILE"
sleep 2
fi
if [[ "$DEBUG" == "1" ]]; then
run_protontricks --no-bwrap "$protontricks_appid" -q "${protontricks_components[@]}"
result=$?
else
WINEDEBUG=-all run_protontricks --no-bwrap "$protontricks_appid" -q "${protontricks_components[@]}" >/dev/null 2>&1
result=$?
fi
if [[ $result -eq 0 ]]; then
success=true
else
echo "Attempt $attempt failed, cleaning up wine processes before retry..." >>$LOGFILE 2>&1
cleanup_wine_procs
attempt=$((attempt+1))
fi
done
if [[ $success == true ]]; then
printf "Done.\n"
log_status "SUCCESS" "Wine Component installation completed."
else
printf "Failed.\n"
log_status "ERROR" "Component install failed after $max_attempts attempts."
return 1
fi
# Verify installation
log_status "DEBUG" "Verifying installed components..."
local output
output=$(run_protontricks --no-bwrap "$protontricks_appid" list-installed 2>/dev/null)
# Clean up and deduplicate the component list
local cleaned_output
cleaned_output=$(echo "$output" | grep -v "Using winetricks" | sort -u | grep -v '^$')
log_status "DEBUG" "Installed components (unique):"
echo "$cleaned_output" >> "$LOGFILE"
# Check for critical components only to avoid false negatives
local critical_components=("vcrun2022" "xact")
local missing_components=()
for component in "${critical_components[@]}"; do
if ! grep -q "$component" <<<"$output"; then
missing_components+=("$component")
fi
done
if [[ ${#missing_components[@]} -gt 0 ]]; then
echo -e "\nERROR: Some critical components are missing: ${missing_components[*]}" | tee -a "$LOGFILE"
return 1
else
echo "Critical components verified successfully." >>$LOGFILE 2>&1
fi
return 0
}
############################################
# Detect default compatdata Directory Path #
############################################
default_steam_compatdata_dir() {
# Prefer ~/.local/share/Steam if it exists
if [[ -d "$HOME/.local/share/Steam/steamapps/compatdata" ]]; then
echo "$HOME/.local/share/Steam/steamapps/compatdata"
elif [[ -d "$HOME/.steam/steam/steamapps/compatdata" ]]; then
echo "$HOME/.steam/steam/steamapps/compatdata"
else
# Do not create the directory; just return empty string
echo ""
fi
}
# Helper to get all Steam library folders from libraryfolders.vdf
get_all_steam_libraries() {
local vdf_file="$HOME/.steam/steam/config/libraryfolders.vdf"
local libraries=("$HOME/.local/share/Steam" "$HOME/.steam/steam")
if [[ -f "$vdf_file" ]]; then
while IFS='' read -r line; do
if [[ "$line" =~ "path" ]]; then
local path=$(echo "$line" | sed 's/.*"path"\s*"\(.*\)"/\1/')
if [[ -n "$path" ]]; then
libraries+=("$path")
fi
fi
done <"$vdf_file"
fi
echo "${libraries[@]}"
}
####################################
# Detect compatdata Directory Path #
####################################
detect_compatdata_path() {
local appid_to_check="$APPID"
if [[ "$gamevar" == "Fallout New Vegas" ]]; then
appid_to_check="22380"
local vdf_file="$HOME/.steam/steam/config/libraryfolders.vdf"
local libraries=("$HOME/.local/share/Steam" "$HOME/.steam/steam")
# Parse all additional libraries from the VDF
if [[ -f "$vdf_file" ]]; then
while IFS= read -r line; do
if [[ "$line" =~ "path" ]]; then
local path=$(echo "$line" | sed -E 's/.*"path"[ \t]*"([^"]+)".*/\1/')
if [[ "$path" == /* ]]; then
libraries+=("$path")
else
libraries+=("$HOME/$path")
fi
fi
done < "$vdf_file"
fi
compat_data_path=""
for lib in "${libraries[@]}"; do
local compat_path="$lib/steamapps/compatdata/$appid_to_check"
log_status "DEBUG" "Checking for compatdata at: $compat_path"
if [[ -d "$compat_path" ]]; then
compat_data_path="$compat_path"
log_status "DEBUG" "Found FNV compatdata: $compat_data_path"
break
fi
done
if [[ -z "$compat_data_path" ]]; then
log_status "ERROR" "Could not find compatdata directory for Fallout New Vegas (22380) in any Steam library."
log_status "ERROR" "Please ensure you have launched the vanilla Fallout New Vegas game at least once via Steam in the correct library."
return 1
fi
return 0
fi
# ... (existing logic for other games)
# Check common Steam library locations first
for path in "$HOME/.local/share/Steam/steamapps/compatdata" "$HOME/.steam/steam/steamapps/compatdata"; do
if [[ -d "$path/$appid_to_check" ]]; then
compat_data_path="$path/$appid_to_check"
log_status "DEBUG" "compatdata Path detected: $compat_data_path"
break
fi
done
# If not found in common locations, use find command
if [[ -z "$compat_data_path" ]]; then
find / -type d -name "compatdata" 2>/dev/null | while read -r compatdata_dir; do
if [[ -d "$compatdata_dir/$appid_to_check" ]]; then
compat_data_path="$compatdata_dir/$appid_to_check"
log_status "DEBUG" "compatdata Path detected: $compat_data_path"
break
fi
done
fi
if [[ -z "$compat_data_path" ]]; then
log_status "ERROR" "Directory named '$appid_to_check' not found in any compatdata directories."
log_status "ERROR" "Please ensure you have started the Steam entry for the modlist at least once, even if it fails.."
else
log_status "DEBUG" "Found compatdata directory with '$appid_to_check': $compat_data_path"
fi
}
#########################
# Detect Proton Version #
#########################
detect_proton_version() {
log_status "DEBUG" "Detecting Proton version..."
# Validate the compatdata path exists
if [[ ! -d "$compat_data_path" ]]; then
log_status "WARN" "Compatdata directory not found at '$compat_data_path'"
proton_ver="Unknown"
return 1
fi
# First try to get Proton version from the registry
if [[ -f "$compat_data_path/pfx/system.reg" ]]; then
local reg_output
reg_output=$(grep -A 3 "\"SteamClientProtonVersion\"" "$compat_data_path/pfx/system.reg" | grep "=" | cut -d= -f2 | tr -d '"' | tr -d ' ')
if [[ -n "$reg_output" ]]; then
# Keep GE versions as is, otherwise prefix with "Proton"
if [[ "$reg_output" == *"GE"* ]]; then
proton_ver="$reg_output" # Keep GE versions as is
else
proton_ver="Proton $reg_output"
fi
log_status "DEBUG" "Detected Proton version from registry: $proton_ver"
return 0
fi
fi
# Fallback to config_info if registry method fails
if [[ -f "$compat_data_path/config_info" ]]; then
local config_ver
config_ver=$(head -n 1 "$compat_data_path/config_info")
if [[ -n "$config_ver" ]]; then
# Keep GE versions as is, otherwise prefix with "Proton"
if [[ "$config_ver" == *"GE"* ]]; then
proton_ver="$config_ver" # Keep GE versions as is
else
proton_ver="Proton $config_ver"
fi
log_status "DEBUG" "Detected Proton version from config_info: $proton_ver"
return 0
fi
fi
proton_ver="Unknown"
log_status "WARN" "Could not detect Proton version"
return 1
}
###############################
# Confirmation before running #
###############################
confirmation_before_running() {
echo "" | tee -a $LOGFILE
echo -e "Detail Checklist:" | tee -a $LOGFILE
echo -e "=================" | tee -a $LOGFILE
echo -e "Modlist: $MODLIST .....\e[32m OK.\e[0m" | tee -a $LOGFILE
echo -e "Directory: $modlist_dir .....\e[32m OK.\e[0m" | tee -a $LOGFILE
echo -e "Proton Version: $proton_ver .....\e[32m OK.\e[0m" | tee -a $LOGFILE
echo -e "App ID: $APPID" | tee -a $LOGFILE
}
#################################
# chown/chmod modlist directory #
#################################
chown_chmod_modlist_dir() {
log_status "WARN" "Changing Ownership and Permissions of modlist directory (may require sudo password)"
user=$(whoami)
group=$(id -gn)
log_status "DEBUG" "User is $user and Group is $group"
sudo chown -R "$user:$group" "$modlist_dir"
sudo chmod -R 755 "$modlist_dir"
}
###############################################
# Backup ModOrganizer.ini and backup gamePath #
###############################################
backup_modorganizer() {
log_status "DEBUG" "Backing up ModOrganizer.ini: $modlist_ini"
cp "$modlist_ini" "$modlist_ini.$(date +"%Y%m%d_%H%M%S").bak"
grep gamePath "$modlist_ini" | sed '/^backupPath/! s/gamePath/backupPath/' >> "$modlist_ini"
}
########################################
# Blank or set MO2 Downloads Directory #
########################################
blank_downloads_dir() {
log_status "INFO" "\nEditing download_directory..."
sed -i "/download_directory/c\download_directory =" "$modlist_ini"
log_status "SUCCESS" "Done."
}
############################################
# Replace the gamePath in ModOrganizer.ini #
############################################
replace_gamepath() {
log_status "INFO" "Setting game path in ModOrganizer.ini..."
log_status "DEBUG" "Using Steam Library Path: $steam_library"
log_status "DEBUG" "Use SDCard?: $basegame_sdcard"
# Check if Modlist uses Game Root, Stock Game, etc.
game_path_line=$(grep '^gamePath' "$modlist_ini")
log_status "DEBUG" "Game Path Line: $game_path_line"
if [[ "$game_path_line" == *Stock\ Game* || "$game_path_line" == *STOCK\ GAME* || "$game_path_line" == *Stock\ Game\ Folder* || "$game_path_line" == *Stock\ Folder* || "$game_path_line" == *Skyrim\ Stock* || "$game_path_line" == *Game\ Root* || $game_path_line == *root\\\\Skyrim\ Special\ Edition* ]]; then
# Stock Game, Game Root or equivalent directory found
log_status "INFO" "Found Game Root/Stock Game or equivalent directory, editing Game Path..."
# Get the end of our path
if [[ $game_path_line =~ Stock\ Game\ Folder ]]; then
modlist_gamedir="$modlist_dir/Stock Game Folder"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
elif [[ $game_path_line =~ Stock\ Folder ]]; then
modlist_gamedir="$modlist_dir/Stock Folder"
elif [[ $game_path_line =~ Skyrim\ Stock ]]; then
modlist_gamedir="$modlist_dir/Skyrim Stock"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
elif [[ $game_path_line =~ Game\ Root ]]; then
modlist_gamedir="$modlist_dir/Game Root"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
elif [[ $game_path_line =~ STOCK\ GAME ]]; then
modlist_gamedir="$modlist_dir/STOCK GAME"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
elif [[ $game_path_line =~ Stock\ Game ]]; then
modlist_gamedir="$modlist_dir/Stock Game"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
elif [[ $game_path_line =~ root\\\\Skyrim\ Special\ Edition ]]; then
modlist_gamedir="$modlist_dir/root/Skyrim Special Edition"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
fi
if [[ "$modlist_sdcard" -eq "1" && "$steamdeck" -eq "1" ]]; then
log_status "DEBUG" "Using SDCard on Steam Deck"
modlist_gamedir_sdcard="${modlist_gamedir#*mmcblk0p1}"
sdcard_new_path="$modlist_gamedir_sdcard"
# Strip /run/media/deck/UUID if present
if [[ "$sdcard_new_path" == /run/media/deck/* ]]; then
sdcard_new_path="/${sdcard_new_path#*/run/media/deck/*/*}"
log_status "DEBUG" "SD Card Path after stripping: $sdcard_new_path"
fi
new_string="@ByteArray(D:${sdcard_new_path//\//\\\\})"
log_status "DEBUG" "New String: $new_string"
else
new_string="@ByteArray(Z:${modlist_gamedir//\//\\\\})"
log_status "DEBUG" "New String: $new_string"
fi
elif [[ "$game_path_line" == *steamapps* ]]; then
log_status "INFO" "Vanilla Game Directory required, editing Game Path..."
modlist_gamedir="$steam_library/$gamevar"
log_status "DEBUG" "Modlist Gamedir: $modlist_gamedir"
if [[ "$basegame_sdcard" -eq "1" && "$steamdeck" -eq "1" ]]; then
log_status "DEBUG" "Using SDCard on Steam Deck"
modlist_gamedir_sdcard="${modlist_gamedir#*mmcblk0p1}"
sdcard_new_path="$modlist_gamedir_sdcard/$gamevar"
new_string="@ByteArray(D:${sdcard_new_path//\//\\\\})"
log_status "DEBUG" "New String: $new_string"
else
new_string="@ByteArray(Z:${modlist_gamedir//\//\\\\})"
log_status "DEBUG" "New String: $new_string"
fi
else
log_status "WARN" "Neither Game Root, Stock Game or Vanilla Game directory found, Please launch MO and set path manually..."
return 1
fi
# Replace the string in the file
file_to_modify="$modlist_dir/ModOrganizer.ini"
escaped_new_string=$(printf '%s\n' "$new_string" | sed -e 's/[\/&]/\\&/g')
sed -i "/^gamePath/c\gamePath=$escaped_new_string" "$file_to_modify"
log_status "SUCCESS" "Game path set successfully"
}
##########################################
# Update Executables in ModOrganizer.ini #
##########################################
update_executables() {
# Take the line passed to the function
echo "Original Line: $orig_line_path" >>$LOGFILE 2>&1
skse_loc=$(echo "$orig_line_path" | cut -d '=' -f 2-)
echo "SKSE Loc: $skse_loc" >>$LOGFILE 2>&1
# Drive letter
if [[ "$modlist_sdcard" -eq 1 && "$steamdeck" -eq 1 ]]; then
echo "Using SDCard on Steam Deck" >>$LOGFILE 2>&1
drive_letter=" = D:"
else
drive_letter=" = Z:"
fi
# Find the workingDirectory number
binary_num=$(echo "$orig_line_path" | cut -d '=' -f -1)
echo "Binary Num: $binary_num" >>$LOGFILE 2>&1
# Find the equvalent workingDirectory
justnum=$(echo "$binary_num" | cut -d '\' -f 1)
bin_path_start=$(echo "$binary_num" | tr -d ' ' | sed 's/\\/\\\\/g')
path_start=$(echo "$justnum\\workingDirectory" | sed 's/\\/\\\\/g')
echo "Path Start: $path_start" >>$LOGFILE 2>&1
# Decide on steam apps or Stock Game etc
if [[ "$orig_line_path" == *"mods"* ]]; then
# mods path type found
echo -e "mods path Found" >>$LOGFILE 2>&1
# Path Middle / modlist_dr
if [[ "$modlist_sdcard" -eq 1 && "$steamdeck" -eq 1 ]]; then
echo "Using SDCard on Steam Deck" >>$LOGFILE 2>&1
drive_letter=" = D:"
echo "$modlist_dir" >>$LOGFILE 2>&1
path_middle="${modlist_dir#*mmcblk0p1}"
# Strip /run/media/deck/UUID
if [[ "$path_middle" == /run/media/*/* ]]; then
path_middle="/${path_middle#*/run/media/*/*/*}"
echo "Path Middle after stripping: $path_middle" >>$LOGFILE 2>&1
fi
else
path_middle="$modlist_dir"
fi
echo "Path Middle: $path_middle" >>$LOGFILE 2>&1
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/mods/\/mods/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/mods/\/mods/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif grep -q -E "(Stock Game|Game Root|STOCK GAME|Stock Game Folder|Stock Folder|Skyrim Stock|root/Skyrim Special Edition)" <<<"$orig_line_path"; then
# STOCK GAME ROOT FOUND
echo -e "Stock/Game Root Found" >>$LOGFILE 2>&1
# Path Middle / modlist_dr
if [[ "$modlist_sdcard" -eq 1 && "$steamdeck" -eq 1 ]]; then
echo "Using SDCard on Steam Deck" >>$LOGFILE 2>&1
drive_letter=" = D:"
echo "Modlist Dir: $modlist_dir" >>$LOGFILE 2>&1
path_middle="${modlist_dir#*mmcblk0p1}"
# Strip /run/media/deck/UUID
if [[ "$path_middle" == /run/media/*/* ]]; then
path_middle="/${path_middle#*/run/media/*/*/*}"
echo "Path Middle after stripping: $path_middle" >>$LOGFILE 2>&1
fi
else
path_middle="$modlist_dir"
fi
echo "Path Middle: $path_middle" >>$LOGFILE 2>&1
# Get the end of our path
if [[ $orig_line_path =~ Stock\ Game ]]; then
dir_type="stockgame"
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/Stock Game/\/Stock Game/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/Stock Game/\/Stock Game/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ Game\ Root ]]; then
dir_type="gameroot"
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/Game Root/\/Game Root/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/Game Root/\/Game Root/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ STOCK\ GAME ]]; then
dir_type="STOCKGAME"
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/STOCK GAME/\/STOCK GAME/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/STOCK GAME/\/STOCK GAME/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ Stock\ Folder ]]; then
dir_type="stockfolder"
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/Stock Folder/\/Stock Folder/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/Stock Folder/\/Stock Folder/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ Skyrim\ Stock ]]; then
dir_type="skyrimstock"
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/Skyrim Stock/\/Skyrim Stock/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/Skyrim Stock/\/Skyrim Stock/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ Stock\ Game\ Folder ]]; then
dir_type="stockgamefolder"
path_end=$(echo "$skse_loc" | sed 's/.*\/Stock Game Folder/\/Stock Game Folder/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
elif [[ $orig_line_path =~ root\/Skyrim\ Special\ Edition ]]; then
dir_type="rootskyrimse"
path_end="/${skse_loc# }"
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end="/${skse_loc# }"
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
fi
elif [[ "$orig_line_path" == *"steamapps"* ]]; then
# STEAMAPPS FOUND
echo -e "steamapps Found" >>$LOGFILE 2>&1
# Path Middle / modlist_dr
if [[ "$basegame_sdcard" -eq "1" && "$steamdeck" -eq "1" ]]; then
echo "Using SDCard on Steam Deck" >>$LOGFILE 2>&1
path_middle="${steam_library#*mmcblk0p1}"
drive_letter=" = D:"
else
echo "Steamapps Steam Library Path: $steam_library"
path_middle=${steam_library%%steamapps*}
fi
echo "Path Middle: $path_middle" >>$LOGFILE 2>&1
path_end=$(echo "${skse_loc%/*}" | sed 's/.*\/steamapps/\/steamapps/')
echo "Path End: $path_end" >>$LOGFILE 2>&1
bin_path_end=$(echo "$skse_loc" | sed 's/.*\/steamapps/\/steamapps/')
echo "Bin Path End: $bin_path_end" >>$LOGFILE 2>&1
else
echo "No matching pattern found in the path: $orig_line_path" >>$LOGFILE 2>&1
bail_out=1
echo $bail_out >>$LOGFILE 2>&1
fi
echo "Bail Out: $bail_out" >>$LOGFILE 2>&1
if [[ $bail_out -eq 1 ]]; then
echo "Exiting function due to bail_out" >>$LOGFILE 2>&1
return
else
# Combine them all together
full_bin_path="$bin_path_start$drive_letter$path_middle$bin_path_end"
echo "Full Bin Path: $full_bin_path" >>$LOGFILE 2>&1
full_path="$path_start$drive_letter$path_middle$path_end"
echo "Full Path: $full_path" >>$LOGFILE 2>&1
# Replace forwardslashes with double backslashes
new_path=${full_path//\//\\\\\\\\}
echo "New Path: $new_path" >>$LOGFILE 2>&1
# Convert the lines in ModOrganizer.ini, if it isn't already
sed -i "\|^${bin_path_start}|s|^.*$|${full_bin_path}|" "$modlist_ini"
# Convert workingDirectory entries
sed -i "\|^${path_start}|s|^.*$|${new_path}|" "$modlist_ini"
fi
}
#################################################
# Edit Custom binary and workingDirectory paths #
#################################################
edit_binary_working_paths() {
grep -E -e "skse64_loader\.exe" -e "f4se_loader\.exe" "$modlist_ini" | while IFS= read -r orig_line_path; do
update_executables
done
}
################################
# Set or Select the Resolution #
################################
select_resolution() {
if [ "$steamdeck" -eq 1 ]; then
set_res="1280x800"
else
while true; do
echo -e "\e[31m ** Enter your desired resolution in the format 1920x1200: ** \e[0m"
read -p " " user_res
# Validate the input format
if [[ "$user_res" =~ ^[0-9]+x[0-9]+$ ]]; then
# Ask for confirmation
echo -e "\e[31m \n** Is $user_res your desired resolution? (y/N): ** \e[0m"
read -p " " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
set_res="$user_res"
break
else
echo "Please enter the resolution again." | tee -a $LOGFILE
fi
else
echo "Invalid input format. Please enter the resolution in the format 1920x1200." | tee -a $LOGFILE
fi
done
fi
echo "Resolution set to: $set_res" | tee -a $LOGFILE
}
######################################
# Update the resolution in INI files #
######################################
update_ini_resolution() {
echo -ne "\nEditing Resolution in prefs files... " | tee -a "$LOGFILE"
# Find all SSEDisplayTweaks.ini files in the specified directory and its subdirectories
ini_files=$(find "$modlist_dir" -name "SSEDisplayTweaks.ini")
if [[ "$gamevar" == "Skyrim Special Edition" && -n "$ini_files" ]]; then
while IFS= read -r ini_file; do
# Use awk to replace the lines with the new values, handling spaces in paths
awk -v res="$set_res" '/^(#?)Resolution[[:space:]]*=/ { print "Resolution=" res; next } \
/^(#?)Fullscreen[[:space:]]*=/ { print "Fullscreen=false"; next } \
/^(#?)#Fullscreen[[:space:]]*=/ { print "#Fullscreen=false"; next } \
/^(#?)Borderless[[:space:]]*=/ { print "Borderless=true"; next } \
/^(#?)#Borderless[[:space:]]*=/ { print "#Borderless=true"; next }1' "$ini_file" >"$ini_file.new"
cp "$ini_file.new" "$ini_file"
echo "Updated $ini_file with Resolution=$res, Fullscreen=false, Borderless=true" >>"$LOGFILE" 2>&1
echo -e " Done." >>"$LOGFILE" 2>&1
done <<<"$ini_files"
elif [[ "$gamevar" == "Fallout 4" ]]; then
echo "Not Skyrim, skipping SSEDisplayTweaks" >>"$LOGFILE" 2>&1
fi
##########
# Split $set_res into two variables
isize_w=$(echo "$set_res" | cut -d'x' -f1)
isize_h=$(echo "$set_res" | cut -d'x' -f2)
# Find all instances of skyrimprefs.ini, Fallout4Prefs.ini, falloutprefs.ini, or Oblivion.ini in specified directories
if [[ "$gamevar" == "Skyrim Special Edition" ]]; then
ini_files=$(find "$modlist_dir/profiles" "$modlist_dir/Stock Game" "$modlist_dir/Game Root" "$modlist_dir/STOCK GAME" "$modlist_dir/Stock Game Folder" "$modlist_dir/Stock Folder" "$modlist_dir/Skyrim Stock" -iname "skyrimprefs.ini" 2>/dev/null)
elif [[ "$gamevar" == "Fallout 4" ]]; then
ini_files=$(find "$modlist_dir/profiles" "$modlist_dir/Stock Game" "$modlist_dir/Game Root" "$modlist_dir/STOCK GAME" "$modlist_dir/Stock Game Folder" "$modlist_dir/Stock Folder" -iname "Fallout4Prefs.ini" 2>/dev/null)
elif [[ "$gamevar" == "Fallout New Vegas" ]]; then
ini_files=$(find "$modlist_dir/profiles" "$modlist_dir/Stock Game" "$modlist_dir/Game Root" "$modlist_dir/STOCK GAME" "$modlist_dir/Stock Game Folder" "$modlist_dir/Stock Folder" -iname "falloutprefs.ini" 2>/dev/null)
elif [[ "$gamevar" == "Oblivion" ]]; then
ini_files=$(find "$modlist_dir/profiles" "$modlist_dir/Stock Game" "$modlist_dir/Game Root" "$modlist_dir/STOCK GAME" "$modlist_dir/Stock Game Folder" "$modlist_dir/Stock Folder" -iname "Oblivion.ini" 2>/dev/null)
fi
if [ -n "$ini_files" ]; then
while IFS= read -r ini_file; do
# Use awk to replace the lines with the new values in the appropriate ini file
if [[ "$gamevar" == "Skyrim Special Edition" ]] || [[ "$gamevar" == "Fallout 4" ]] || [[ "$gamevar" == "Fallout New Vegas" ]]; then
awk -v isize_w="$isize_w" -v isize_h="$isize_h" '/^iSize W/ { print "iSize W = " isize_w; next } \
/^iSize H/ { print "iSize H = " isize_h; next }1' "$ini_file" >"$HOME/temp_file" && mv "$HOME/temp_file" "$ini_file"
elif [[ "$gamevar" == "Oblivion" ]]; then
awk -v isize_w="$isize_w" -v isize_h="$isize_h" '/^iSize W=/ { print "iSize W=" isize_w; next } \
/^iSize H=/ { print "iSize H=" isize_h; next }1' "$ini_file" >"$HOME/temp_file" && mv "$HOME/temp_file" "$ini_file"
fi
echo "Updated $ini_file with iSize W=$isize_w, iSize H=$isize_h" >>"$LOGFILE" 2>&1
done <<<"$ini_files"
else
echo "No suitable prefs.ini files found in specified directories. Please set manually using the INI Editor in MO2." | tee -a "$LOGFILE"
fi
echo -e "Done." | tee -a "$LOGFILE"
}
###################
# Edit resolution #
###################
edit_resolution() {
if [[ -n "$selected_resolution" ]]; then
log_status "DEBUG" "Applying resolution: $selected_resolution"
set_res="$selected_resolution"
update_ini_resolution
else
log_status "DEBUG" "Resolution setup skipped"
fi
}
##########################
# Small additional tasks #
##########################
small_additional_tasks() {
# Delete MO2 plugins that don't work via Proton
file_to_delete="$modlist_dir/plugins/FixGameRegKey.py"
if [ -e "$file_to_delete" ]; then
rm "$file_to_delete"
echo "File deleted: $file_to_delete" >>$LOGFILE 2>&1
else
echo "File does not exist: $file_to_delete" >>"$LOGFILE" 2>&1
fi
# Download Font to support Bethini
wget https://github.com/mrbvrz/segoe-ui-linux/raw/refs/heads/master/font/seguisym.ttf -q -nc -O "$compat_data_path/pfx/drive_c/windows/Fonts/seguisym.ttf"
}
###############################
# Set Steam Artwork Function #
###############################
set_steam_artwork() {
# Only run for Tuxborn modlist
if [[ "$MODLIST" == *"Tuxborn"* ]]; then
log_status "DEBUG" "Setting up Steam artwork for Tuxborn..."
# Source directory with artwork
local source_dir="$modlist_dir/Steam Icons"
if [[ ! -d "$source_dir" ]]; then
log_status "WARN" "Steam Icons directory not found at $source_dir"
return 1
fi
# Find all Steam userdata directories
for userdata_dir in "$HOME/.local/share/Steam/userdata" "$HOME/.steam/steam/userdata"; do
if [[ ! -d "$userdata_dir" ]]; then
continue
fi
# Process each user ID directory
for user_id_dir in "$userdata_dir"/*; do
if [[ ! -d "$user_id_dir" || "$user_id_dir" == *"0"* ]]; then
continue # Skip non-directories and the anonymous user
fi
# Create grid directory if it doesn't exist
local grid_dir="$user_id_dir/config/grid"
mkdir -p "$grid_dir"
# Copy grid-tall.png to both APPID.png and APPIDp.png
if [[ -f "$source_dir/grid-tall.png" ]]; then
cp "$source_dir/grid-tall.png" "$grid_dir/${APPID}.png"
log_status "DEBUG" "Copied grid-tall.png to ${APPID}.png"
cp "$source_dir/grid-tall.png" "$grid_dir/${APPID}p.png"
log_status "DEBUG" "Copied grid-tall.png to ${APPID}p.png"
fi
# Copy grid-hero.png to APPID_hero.png
if [[ -f "$source_dir/grid-hero.png" ]]; then
cp "$source_dir/grid-hero.png" "$grid_dir/${APPID}_hero.png"
log_status "DEBUG" "Copied grid-hero.png to ${APPID}_hero.png"
fi
# Copy grid-logo.png to APPID_logo.png
if [[ -f "$source_dir/grid-logo.png" ]]; then
cp "$source_dir/grid-logo.png" "$grid_dir/${APPID}_logo.png"
log_status "DEBUG" "Copied grid-logo.png to ${APPID}_logo.png"
fi
log_status "DEBUG" "Tuxborn artwork copied for user ID $(basename "$user_id_dir")"
done
done
log_status "DEBUG" "Steam artwork setup complete for Tuxborn"
fi
}
##########################
# Modlist Specific Steps #
##########################
modlist_specific_steps() {
local modlist_lower=$(echo "${MODLIST// /}" | tr '[:upper:]' '[:lower:]')
# Call the Steam artwork function for all modlists
set_steam_artwork | tee -a "$LOGFILE"
# Handle Wildlander specially due to its custom spinner animation
if [[ "$MODLIST" == *"Wildlander"* ]]; then
log_status "INFO" "\nRunning steps specific to \e[32m$MODLIST\e[0m. This can take some time, be patient!"
# Install dotnet with spinner animation
spinner=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
run_protontricks --no-bwrap "$APPID" -q dotnet472 >/dev/null 2>&1 &
pid=$! # Store the PID of the background process
while kill -0 "$pid" >/dev/null 2>&1; do
for i in "${spinner[@]}"; do
echo -en "\r${i}\c"
sleep 0.1
done
done
wait "$pid" # Wait for the process to finish
# Clear the spinner and move to the next line
echo -en "\r\033[K" # Clear the spinner line
if [[ $? -ne 0 ]]; then
log_status "ERROR" "Component install failed with exit code $?"
else
log_status "SUCCESS" "Wine Component install completed successfully."
fi
new_output="$(run_protontricks --no-bwrap "$APPID" list-installed 2>/dev/null)"
log_status "DEBUG" "Components Found: $new_output"
return 0
fi
# Handle the rest of the modlists with the compact approach
for pattern in "${!modlist_configs[@]}"; do
if [[ "$pattern" != "wildlander" ]] && [[ "$modlist_lower" =~ ${pattern//|/|.*} ]]; then
log_status "INFO" "\nRunning steps specific to \e[32m$MODLIST\e[0m. This can take some time, be patient!"
IFS=' ' read -ra components <<< "${modlist_configs[$pattern]}"
for component in "${components[@]}"; do
if [[ "$component" == "dotnet8" ]]; then
log_status "INFO" "\nDownloading .NET 8 Runtime"
wget https://download.visualstudio.microsoft.com/download/pr/77284554-b8df-4697-9a9e-4c70a8b35f29/6763c16069d1ab8fa2bc506ef0767366/dotnet-runtime-8.0.5-win-x64.exe -q -nc --show-progress --progress=bar:force:noscroll -O "$HOME/Downloads/dotnet-runtime-8.0.5-win-x64.exe"
log_status "INFO" "Installing .NET 8 Runtime...."
if [[ "$DEBUG" == "1" ]]; then
run_protontricks --no-bwrap -c 'wine "$HOME/Downloads/dotnet-runtime-8.0.5-win-x64.exe" /Q' "$APPID"
else
WINEDEBUG=-all run_protontricks --no-bwrap -c 'wine "$HOME/Downloads/dotnet-runtime-8.0.5-win-x64.exe" /Q' "$APPID" >/dev/null 2>&1
fi
log_status "SUCCESS" "Done."
else
log_status "INFO" "Installing .NET ${component#dotnet}..."
if [[ "$DEBUG" == "1" ]]; then
run_protontricks --no-bwrap "$APPID" -q "$component"
else
WINEDEBUG=-all run_protontricks --no-bwrap "$APPID" -q "$component" >/dev/null 2>&1
fi
log_status "SUCCESS" "Done."
fi
done
set_win10_prefix
new_output="$(run_protontricks --no-bwrap "$APPID" list-installed 2>/dev/null)"
log_status "DEBUG" "Components Found: $new_output"
break
fi
done
}
######################################
# Create DXVK Graphics Pipeline file #
######################################
create_dxvk_file() {
echo "Use SDCard for DXVK File?: $basegame_sdcard" >>"$LOGFILE" 2>&1
echo -e "\nCreating dxvk.conf file - Checking if Modlist uses Game Root, Stock Game or Vanilla Game Directory.." >>"$LOGFILE" 2>&1
game_path_line=$(grep '^gamePath' "$modlist_ini")
echo "Game Path Line: $game_path_line" >>"$LOGFILE" 2>&1
if [[ "$game_path_line" == *Stock\ Game* || "$game_path_line" == *STOCK\ GAME* || "$game_path_line" == *Stock\ Game\ Folder* || "$game_path_line" == *Stock\ Folder* || "$game_path_line" == *Skyrim\ Stock* || "$game_path_line" == *Game\ Root* ]]; then
# Get the end of our path
if [[ $game_path_line =~ Stock\ Game\ Folder ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/Stock Game Folder/dxvk.conf"
elif [[ $game_path_line =~ Stock\ Folder ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/Stock Folder/dxvk.conf"
elif [[ $game_path_line =~ Skyrim\ Stock ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/Skyrim Stock/dxvk.conf"
elif [[ $game_path_line =~ Game\ Root ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/Game Root/dxvk.conf"
elif [[ $game_path_line =~ STOCK\ GAME ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/STOCK GAME/dxvk.conf"
elif [[ $game_path_line =~ Stock\ Game ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/Stock Game/dxvk.conf"
elif [[ $game_path_line =~ root\\Skyrim\ Special\ Edition ]]; then
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/root/Skyrim Special Edition/dxvk.conf"
fi
if [[ "$modlist_sdcard" -eq "1" ]]; then
echo "Using SDCard" >>"$LOGFILE" 2>&1
modlist_gamedir_sdcard="${modlist_gamedir#*mmcblk0p1}"
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_gamedir/dxvk.conf"
fi
elif [[ "$game_path_line" == *steamapps* ]]; then
echo -ne "Vanilla Game Directory required, editing Game Path.. " >>"$LOGFILE" 2>&1
modlist_gamedir="$steam_library"
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_gamedir/dxvk.conf"
if [[ "$basegame_sdcard" -eq "1" ]]; then
echo "Using SDCard" >>"$LOGFILE" 2>&1
modlist_gamedir_sdcard="${modlist_gamedir#*mmcblk0p1}"
echo "dxvk.enableGraphicsPipelineLibrary = False" >"$modlist_dir/$gamevar/dxvk.conf"
fi
fi
}
#############################
# Create protontricks alias #
#############################
protontricks_alias() {
if [[ "$which_protontricks" = "flatpak" ]]; then
local protontricks_alias_exists=$(grep "^alias protontricks=" ~/.bashrc)
local launch_alias_exists=$(grep "^alias protontricks-launch" ~/.bashrc)
if [[ -z "$protontricks_alias_exists" ]]; then
echo -e "\nAdding protontricks alias to ~/.bashrc"
echo "alias protontricks='flatpak run com.github.Matoking.protontricks'" >> ~/.bashrc
source ~/.bashrc
else
echo "protontricks alias already exists in ~/.bashrc" >> "$LOGFILE" 2>&1
fi
if [[ -z "$launch_alias_exists" ]]; then
echo -e "\nAdding protontricks-launch alias to ~/.bashrc"
echo "alias protontricks-launch='flatpak run --command=protontricks-launch com.github.Matoking.protontricks'" >> ~/.bashrc
source ~/.bashrc
else
echo "protontricks-launch alias already exists in ~/.bashrc" >> "$LOGFILE" 2>&1
fi
else
echo "Protontricks is not installed via flatpak, skipping alias creation." >> "$LOGFILE" 2>&1
fi
}
############################
# FNV Launch Option Notice #
############################
fnv_launch_options() {
log_status "DEBUG" "fnv_launch_options: gamevar='$gamevar', compat_data_path='$compat_data_path'"
if [[ "$gamevar" == "Fallout New Vegas" ]]; then
if [[ -n "$compat_data_path" && -d "$compat_data_path" ]]; then
log_status "WARN" "\n\e[41;97m======================= CRITICAL: STEAM LAUNCH OPTIONS =======================\e[0m"
log_status "SUCCESS" "\n\e[1;32mOpen the properties of your '$MODLIST' entry in Steam and add the following to the launch options:\e[0m"
log_status "SUCCESS" "\n\e[1;32mSTEAM_COMPAT_DATA_PATH=\"$compat_data_path\" %command%\e[0m"
log_status "WARN" "\e[41;97m=============================================================================\e[0m"
log_status "WARN" "\nThis is \e[1;4mESSENTIAL\e[0m for the modlist to load correctly. You \e[1;4mMUST\e[0m add it to your Steam launch options."
else
log_status "ERROR" "\nCould not determine the compatdata path for Fallout New Vegas. Please manually set the correct path in the Launch Options."
fi
fi
}
#####################
# Exit more cleanly #
#####################
cleaner_exit() {
# Clean up wine and winetricks processes
cleanup_wine_procs
log_status "DEBUG" "Cleanup complete"
exit 1
}
####################
# END OF FUNCTIONS #
####################
#######################
# Note Script Version #
#######################
echo -e "Script Version $script_ver" >>"$LOGFILE" 2>&1
######################
# Note Date and Time #
######################
echo -e "Script started at: $(date +'%Y-%m-%d %H:%M:%S')" >>"$LOGFILE" 2>&1
#############################
# Detect if running on deck #
#############################
detect_steamdeck
###########################################
# Detect Protontricks (flatpak or native) #
###########################################
detect_protontricks
###############################
# Detect Protontricks Version #
###############################
protontricks_version
##########################################
# Create protontricks alias in ~/.bashrc #
##########################################
protontricks_alias
##############################################################
# List Skyrim and Fallout Modlists from Steam (protontricks) #
##############################################################
IFS=$'\n' readarray -t output_array < <(run_protontricks -l | tr -d $'\r' | grep -i 'Non-Steam shortcut' | grep -i 'Skyrim\|Fallout\|FNV\|Oblivion' | cut -d ' ' -f 3-)
if [[ ${#output_array[@]} -eq 0 ]]; then
echo "" | tee -a "$LOGFILE"
log_status "ERROR" "No modlists detected for Skyrim, Oblivion or Fallout/FNV!"
log_status "INFO" "Please make sure your entry in Steam is something like 'Skyrim - ModlistName'"
log_status "INFO" "or 'Fallout - ModlistName' AND that you have pressed play in Steam at least once!"
cleaner_exit
fi
echo "" | tee -a "$LOGFILE"
echo -e "\e[33mDetected Modlists:\e[0m" | tee -a "$LOGFILE"
# Print numbered list with color
for i in "${!output_array[@]}"; do
echo -e "\e[32m$((i + 1)))\e[0m ${output_array[$i]}"
done
# Read user selection with proper prompt
echo "───────────────────────────────────────────────────────────────────"
while true; do
read -p $'\e[33mSelect a modlist (1-'"${#output_array[@]}"$'): \e[0m' choice_num
# Add a debug flag at the top for easy toggling
DEBUG_MODLIST_SELECTION=0 # Set to 1 to enable extra debug output
# After reading user input for choice_num:
if [[ $DEBUG_MODLIST_SELECTION -eq 1 ]]; then
echo "[DEBUG] Raw user input: '$choice_num'" | tee -a "$LOGFILE"
fi
choice_num=$(echo "$choice_num" | xargs) # Trim whitespace
if [[ $DEBUG_MODLIST_SELECTION -eq 1 ]]; then
echo "[DEBUG] Trimmed user input: '$choice_num'" | tee -a "$LOGFILE"
fi
# Before the selection validation if-statement:
if [[ $DEBUG_MODLIST_SELECTION -eq 1 ]]; then
echo "[DEBUG] Validating: '$choice_num' =~ ^[0-9]+$ && $choice_num -ge 1 && $choice_num -le ${#output_array[@]}" | tee -a "$LOGFILE"
fi
# Validate selection properly
if [[ "$choice_num" =~ ^[0-9]+$ ]] && [[ "$choice_num" -ge 1 ]] && [[ "$choice_num" -le "${#output_array[@]}" ]]; then
if [[ $DEBUG_MODLIST_SELECTION -eq 1 ]]; then
echo "[DEBUG] Selection valid. Index: $((choice_num - 1)), Value: '${output_array[$((choice_num - 1))]}'" | tee -a "$LOGFILE"
fi
choice="${output_array[$((choice_num - 1))]}"
MODLIST=$(echo "$choice" | cut -d ' ' -f 3- | rev | cut -d ' ' -f 2- | rev)
log_status "DEBUG" "MODLIST: $MODLIST"
break # Exit the loop if selection is valid
else
if [[ $DEBUG_MODLIST_SELECTION -eq 1 ]]; then
echo "[DEBUG] Invalid selection. choice_num: '$choice_num', output_array length: ${#output_array[@]}" | tee -a "$LOGFILE"
fi
log_status "ERROR" "Invalid selection. Please enter a number between 1 and ${#output_array[@]}."
# Removed exit 1, so the loop continues
fi
done
# Add a newline after the selection for cleaner output
echo ""
# Initial detection phase
cleanup_wine_procs
set_appid
detect_game
detect_steam_library
detect_modlist_dir_path
# Set modlist_sdcard if required
modlist_sdcard=0
if [[ "$modlist_dir" =~ ^/run/media ]]; then
modlist_sdcard=1
fi
# Detect compatdata path and Proton version
detect_compatdata_path
detect_proton_version
# Get resolution preference
if [ "$steamdeck" -eq 1 ]; then
selected_resolution="1280x800"
log_status "INFO" "Steam Deck detected - Resolution will be set to 1280x800"
else
echo -e "Do you wish to set the display resolution? (This can be changed manually later)"
read -p $'\e[33mSet resolution? (y/N): \e[0m' response
if [[ "$response" =~ ^[Yy]$ ]]; then
while true; do
read -p $'\e[33mEnter resolution (e.g., 1920x1080): \e[0m' user_res
if [[ "$user_res" =~ ^[0-9]+x[0-9]+$ ]]; then
selected_resolution="$user_res"
log_status "DEBUG" "Resolution will be set to: $selected_resolution"
break
else
log_status "ERROR" "Invalid format. Please use format: 1920x1080"
fi
done
else
log_status "INFO" "Resolution setup skipped"
fi
fi
# Then show the detection summary including the resolution if set
echo -e "\n\e[1mDetection Summary:\e[0m" | tee -a "$LOGFILE"
echo -e "===================" | tee -a "$LOGFILE"
echo -e "Selected Modlist: \e[32m$MODLIST\e[0m" | tee -a "$LOGFILE"
echo -e "Game Type: \e[32m$gamevar\e[0m" | tee -a "$LOGFILE"
echo -e "Steam App ID: \e[32m$APPID\e[0m" | tee -a "$LOGFILE"
echo -e "Modlist Directory: \e[32m$modlist_dir\e[0m" | tee -a "$LOGFILE"
echo -e "Proton Version: \e[32m$proton_ver\e[0m" | tee -a "$LOGFILE"
if [[ -n "$selected_resolution" ]]; then
echo -e "Resolution: \e[32m$selected_resolution\e[0m" | tee -a "$LOGFILE"
fi
# Show simple confirmation with minimal info
read -rp $'\e[32mDo you want to proceed with the installation? (y/N)\e[0m ' proceed
if [[ $proceed =~ ^[Yy]$ ]]; then
if [ "$DEBUG" = "1" ]; then
{
# Protontricks setup (10%)
set_protontricks_perms
# Dotfiles (20%)
enable_dotfiles
# Modlist-specific steps (30%)
modlist_specific_steps
# Wine components (40%)
install_wine_components
# Windows 10 prefix (50%)
set_win10_prefix
# ModOrganizer configuration (70%)
backup_modorganizer
blank_downloads_dir
replace_gamepath
edit_binary_working_paths
# Resolution and additional tasks (90%)
edit_resolution
small_additional_tasks
create_dxvk_file
# Final steps (100%)
chown_chmod_modlist_dir
fnv_launch_options
} 2>&1 | tee -a "$LOGFILE"
else
# Function to update progress
update_progress() {
local percent=$1
local bar_length=50
local filled_length=$((percent * bar_length / 100))
local bar=""
for ((i = 0; i < bar_length; i++)); do
if [ $i -lt $filled_length ]; then
bar+="="
else
bar+=" "
fi
done
printf "\r[%-${bar_length}s] %d%%" "$bar" "$percent"
}
{
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Setting up Protontricks..." " " "10"
fi
set_protontricks_perms >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Enabling dotfiles..." "========== " "20"
fi
enable_dotfiles >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
modlist_specific_steps_spinner
else
modlist_specific_steps
fi
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Installing Wine components..." "==================== " "40"
fi
install_wine_components >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Setting Windows 10 prefix..." "========================= " "50"
fi
set_win10_prefix >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Configuring Mod Organizer..." "=================================== " "70"
fi
backup_modorganizer >/dev/null 2>&1
blank_downloads_dir >/dev/null 2>&1
replace_gamepath >/dev/null 2>&1
edit_binary_working_paths >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Setting resolution and additional tasks..." "============================================ " "90"
fi
edit_resolution >/dev/null 2>&1
small_additional_tasks >/dev/null 2>&1
create_dxvk_file >/dev/null 2>&1
if [ "$DEBUG" != "1" ]; then
printf "\r\033[KProgress: [%-50s] %d%% - Completing installation..." "==================================================" "100"
fi
chown_chmod_modlist_dir
fnv_launch_options
} 2>>$LOGFILE
fi
# Show completion message
{
echo "" # Add blank line before success message
echo -e "\e[32m✓ Installation completed successfully!\e[0m"
echo -e "\n📝 Next Steps:"
echo " • Launch your modlist through Steam"
echo -e "\n💡 Detailed log available at: $LOGFILE\n"
} | tee -a "$LOGFILE"
# Show SD Card status if detected
if [[ "$steamdeck" -eq 1 ]]; then
if [[ "$modlist_dir" =~ ^/run/media/deck/[^/]+(/.*)?$ ]] || [[ "$modlist_dir" == "/run/media/mmcblk0p1"* ]]; then
echo -e "SD Card: \e[32mDetected\e[0m" | tee -a "$LOGFILE"
fi
else
if [[ "$modlist_dir" == "/run/media"* ]]; then
echo -e "Removable Media: \e[33mDetected at $modlist_dir\e[0m" | tee -a "$LOGFILE"
fi
fi
else
log_status "INFO" "Installation cancelled."
cleaner_exit
fi