Skip to content

Instantly share code, notes, and snippets.

@grepwood
Created September 20, 2018 01:07
Show Gist options
  • Select an option

  • Save grepwood/ddc9e41779fa5414b9946fe4b212c84d to your computer and use it in GitHub Desktop.

Select an option

Save grepwood/ddc9e41779fa5414b9946fe4b212c84d to your computer and use it in GitHub Desktop.

Revisions

  1. grepwood created this gist Sep 20, 2018.
    154 changes: 154 additions & 0 deletions fallout4-setup.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    #!/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 $@