#!/usr/bin/env bash SCRIPT_NAME="$(basename "${0}")" FALLOUT4_EXECUTABLE="Fallout4.exe" WINE_REGISTRY_KEY="HKEY_CURRENT_USER\\Software\\Wine" FALLOUT4_APPDEFAULTS_REGISTRY_KEY="HKEY_CURRENT_USER\\Software\\Wine\\AppDefaults\\${FALLOUT4_EXECUTABLE}" FALLOUT4_INSTALL_REGISTRY_KEY='HKEY_LOCAL_MACHINE\Software\Bethesda Softworks\Fallout4' USER_SHELL_FOLDERS_REGISTRY_KEY='HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders' function die() { printf "%s : %s() : %s\\n" "${SCRIPT_NAME}" "${FUNCNAME[1]}" "${1}" >&2 exit 1 } # get_registry_value() # Returns "data" from a specified Registry key and value, where "value"="data". # Note: the Wine reg query result will use \r\n CR linefeeds - strip these! function get_registry_value() { local registry_key="${1}" registry_value="${2}" declare -n data="${3}" data="$(wine reg query "${registry_key}" /v "${registry_value}" 2>/dev/null \ | awk -F 'REG_[A-Z]*' \ -vregistry_value="${registry_value}" \ '{ for (i=1;i<=NF;++i) gsub("(^[[:blank:]]*|[[:blank:]]*$)","",$i) if ($1 == registry_value) print $2 }' 2>/dev/null \ | sed 's/\r//g' )" [[ -z "${data}" ]] && die "Registry key data lookup [${registry_key}] \"${registry_value}\"=... failed" } # add_registry_value() # Adds a Wine Registry key or a key plus "value"="data". function add_registry_value() { local registry_key="${1}" registry_value="${2}" data="${3}" type="${4}" if [[ -z "${registry_value}" ]]; then wine reg add "${registry_key}" /f 2>/dev/null \ || die "Failed to add Registry key [${registry_key}]" else wine reg add "${registry_key}" /v "${registry_value}" /t "${type}" /d "${data}" /f 2>/dev/null \ || die "Failed to add Registry key+value: [${registry_key}] \"${registry_value}\"=\"${data}\" (${type})" fi } # delete_registry_value() # Deletes a Wine Registry key or key plus value pair. function delete_registry_value() { local registry_key="${1}" registry_value="${2}" if [[ -z "${registry_value}" ]]; then wine reg delete "${registry_key}" /va /f &>/dev/null else wine reg delete "${registry_key}" /v "${registry_value}" /f &>/dev/null fi } # convert_windows_to_unix_path() # Convert a Windows path to a canonical Unix path - follows all symbolic links. # Note: winepath outputs \r\n CR linefeeds - so we must strip these! function convert_windows_to_unix_path() { local windows_path="${1}" unix_path declare -n canonical_unix_path="${2}" unix_path="$(winepath -u ''"${windows_path}"'' 2>/dev/null | sed 's/\r//g')" canonical_unix_path="$(readlink -f "${unix_path}")" if [[ ! -d "${canonical_unix_path}" ]]; then die "Unable to convert Windows directory: \"${windows_path}\"; to a Unix directory." fi } # find_file() # Finds a specified file, using a specified base directory+relative path offset. # Note: must use a case-insensitive search! function find_file() { local search_path="${1}" relative_path="${2}" depth relative_path_regex declare -n target="${3}" [[ -d "${search_path}" ]] || die "File search directory does not exist \"${search_path}\"" depth="$(echo "${relative_path}" | awk -F'/' '{ print NF }')" relative_path_regex=".*$(echo "${relative_path}" | sed 's/[-.()\/]/\\&/g' 2>/dev/null)" # Without this last grep you will end up finding multiple directories target="$(find "${search_path}" -mindepth "$((depth-1))" -maxdepth "$((depth))" -iregex "${relative_path_regex}" 2>/dev/null | grep "${2}")" [[ -f "${target}" ]] || die "File search failed: \"${search_path}${relative_path}\"" } # ini_file_overwrite_value() # Add/Overwrite name=value pair in a specified .ini file section. # Note: Windows .ini files will use \r\n CR linefeeds - don't break this! function ini_file_overwrite_value() { local ini_file="${1}" name="${2}" value="${3}" section="${4}" [[ -f "${ini_file}" ]] || die ".ini file path not valid: \"${ini_file}\"" # shellcheck disable=SC1004 sed -i -e '\|^'"${name}"'=|d' -e '\|^\['"${section}"'\]|a\ '"${name}=${value}\\r" "${ini_file}" 2>/dev/null \ || die "sed operation failed on: \"${ini_file}\"" } # I don't like it when scripts are messy and dirty like they lack a main function. # Makes it so much harder to read them. function main { [[ -z "${WINEPREFIX}" ]] && die "WINEPREFIX env variable not set" [[ -d "${WINEPREFIX}" ]] || die "WINEPREFIX env variable is set to an invalid path: \"${WINEPREFIX}\"" ((EUID==0)) && die "Do not run this script as the root user!" # Get Wine user 'My Documents' folder from Wine Registry. get_registry_value "${USER_SHELL_FOLDERS_REGISTRY_KEY}" \ 'Personal' "WINDOWS_USER_DOCUMENTS_PATH" convert_windows_to_unix_path "${WINDOWS_USER_DOCUMENTS_PATH}" "UNIX_USER_DOCUMENTS_PATH" # Get install directory of Fallout 4 from Wine Registry. get_registry_value "${FALLOUT4_INSTALL_REGISTRY_KEY}" \ 'installed path' "WINDOWS_FALLOUT4_INSTALL_PATH" convert_windows_to_unix_path "${WINDOWS_FALLOUT4_INSTALL_PATH}" "UNIX_FALLOUT4_INSTALL_PATH" # Find 2 main Fallout 4 configuration files. find_file "${UNIX_USER_DOCUMENTS_PATH}" '/My Games/Fallout4/Fallout4.ini' "USER_FALLOUT4_INI_FILE" find_file "${UNIX_FALLOUT4_INSTALL_PATH}" 'Fallout4_Default.ini' "GAME_FALLOUT4DEFAULT_INI_FILE" # Set bBackgroundMouse=1 : Switches between the game controlling the mouse position (0) or the operating system (1). ini_file_overwrite_value "${USER_FALLOUT4_INI_FILE}" "bBackgroundMouse" "1" "Controls" ini_file_overwrite_value "${GAME_FALLOUT4DEFAULT_INI_FILE}" "bBackgroundMouse" "1" "Controls" # GrabFullscreen=Y add_registry_value "${WINE_REGISTRY_KEY}\\X11 Driver" 'GrabFullscreen' 'Y' 'REG_SZ' # Version=win7 add_registry_value "${FALLOUT4_APPDEFAULTS_REGISTRY_KEY}" 'Version' 'win7' 'REG_SZ' # Setup winetricks xact winetricks -q --force xact &>/dev/null || die "winetricks xact failed" # Move xact audio Dll Overrides to Fallout 4 AppDefaults. So this set of DLL Overrides # doesn't affect other games/applications in the same Wineprefix. for dll in "xaudio2_0" "xaudio2_1" "xaudio2_2" "xaudio2_3" "xaudio2_4" "xaudio2_5" "xaudio2_6" "xaudio2_7" "x3daudio1_0" "x3daudio1_1" "x3daudio1_2" "x3daudio1_3" "x3daudio1_4" "x3daudio1_5" "x3daudio1_6" "x3daudio1_7"; do delete_registry_value "${WINE_REGISTRY_KEY}\\DllOverrides" "*${dll}" delete_registry_value "${WINE_REGISTRY_KEY}\\DllOverrides" "*${dll}" delete_registry_value "${FALLOUT4_APPDEFAULTS_REGISTRY_KEY}\\DllOverrides" "*${dll}" add_registry_value "${FALLOUT4_APPDEFAULTS_REGISTRY_KEY}\\DllOverrides" "${dll}" 'native,builtin' 'REG_SZ' done # Delete all xact DLL Overrides we don't required (2D audio as well?) for dll in "xapofx1_1" "xapofx1_2" "xapofx1_3" "xapofx1_4" "xapofx1_5"; do delete_registry_value "${WINE_REGISTRY_KEY}\\DllOverrides" "${dll}" delete_registry_value "${WINE_REGISTRY_KEY}\\DllOverrides" "*${dll}" done } main $@