#!/bin/sh # purge-wrangler.sh # Author(s): Mayank Kumar (mayankk2308, github.com / mac_editor, egpu.io) # License: Specified in LICENSE.md. # Version: 6.3.4 # ----- ENVIRONMENT # Script and options (script="${0}" getopts ":ausld " option single_user_mode="$(sysctl -n kern.singleuser)" # Enable case-insensitive comparisons shopt -s nocasematch # Text management bold="$(tput bold)" normal="$(tput sgr0)" underline="$(tput smul)" # User interface artifacts gap=" " mark=">>" # Script binary local_bin="/usr/local/bin" script_bin="${local_bin}/purge-wrangler" tmp_script="${local_bin}/purge-wrangler-new" is_bin_call=0 call_script_file="" # Script version script_major_ver="6" && script_minor_ver="3" && script_patch_ver="4" script_ver="${script_major_ver}.${script_minor_ver}.${script_patch_ver}" latest_script_data="" latest_release_dwld="" # Script preference plist script_launchagent="io.egpu.purge-wrangler-agent" # User userinput userinput="" # System information macos_ver="$(sw_vers -productVersion)" macos_build="$(sw_vers -buildVersion)" system_thunderbolt_ver="" # Thunderbolt patch references hex_thunderboltswitchtype="494F5468756E646572626F6C74537769746368547970653" hex_thunderboltcheck="4883C3174889DF31F631D2E8000000004883F803" hex_thunderboltcheck_patch="4883C3174889DF31F631D2E8000000004883F800" hex_selected_thunderbolt="${hex_thunderboltcheck}" hex_selected_thunderbolt_patch="${hex_thunderboltcheck_patch}" # NVIDIA patch references hex_nvda_bypass="494F50434954756E6E656C6C6564" hex_nvda_bypass_patch="494F50434954756E6E656C6C6571" hex_nvda_clamshell="F0810D790A0300000200008B35730A0300" hex_nvda_clamshell_patch="F0810D790A0300000000008B35730A0300" # Ti82 patch references old_hex_skipenum="554889E54157415641554154534881EC2801" old_hex_skipenum_patch="554889E531C05DC341554154534881EC2801" hex_skipenum="554889E54157415641554154534881EC38010000" hex_skipenum_patch="554889E531C05DC341554154534881EC38010000" # Patch status indicators amdlegacy_enabled=2 tbswitch_enabled=2 nvidia_enabled=2 binpatch_enabled=0 ti82_enabled=2 nvdawebdrv_patched=2 using_nvdawebdrv=0 # Supported GPU architectures supported_amd_archs=("Polaris" "Ellesmere" "Vega" "Navi") supported_nv_archs=("GK" "GF") amdlegacy_downloadurl="http://raw.githubusercontent.com/mayankk2308/purge-wrangler/${script_ver}/resources/AMDLegacySupport.kext.zip" amdlegacy_integrity_hash="b64e399fa4d350b723170eb69780741c3f54af94570b995a201d70d540771500e67081b235c05deca95ad5e44cf1ba529766e47ff3b5f62eeb94161c80b0e29a" prompticon_downloadurl="http://raw.githubusercontent.com/mayankk2308/purge-wrangler/${script_ver}/resources/pw.png" prompticon_integrity_hash="28a86c463d184c19c666252a948148c24702990fc06d5b99e419c04efd6475324606263cf38c5a76be3f971c49aeecf89be61b1b8cbe68b73b33c69a903803c5" # Property Lists pb="/usr/libexec/PlistBuddy" set_iognvda_pcitunnelled=":IOKitPersonalities:3:IOPCITunnelCompatible bool" set_nvdastartup_pcitunnelled=":IOKitPersonalities:NVDAStartup:IOPCITunnelCompatible bool" set_nvdastartup_requiredos=":IOKitPersonalities:NVDAStartup:NVDARequiredOS" plist_defaultstring=" \n\n\n" # --- PATH INITIALIZER initialize_filepaths() { # General kext paths sysextensions_path="${root_vol}/System/Library/Extensions/" coreservices="${root_vol}/System/Library/CoreServices" libextensions_path="${root_vol}/Library/Extensions/" agc_kextpath="${sysextensions_path}AppleGraphicsControl.kext" agc_binsubpath="/Contents/PlugIns/AppleGPUWrangler.kext/Contents/MacOS/AppleGPUWrangler" agw_binpath="${agc_kextpath}${agc_binsubpath}" iondrv_kextpath="${sysextensions_path}IONDRVSupport.kext" iondrv_plistpath="${iondrv_kextpath}/Info.plist" iog_kextpath="${sysextensions_path}IOGraphicsFamily.kext" iog_subpath="/IOGraphicsFamily" iog_binpath="${iog_kextpath}${iog_subpath}" iotfam_kextpath="${sysextensions_path}IOThunderboltFamily.kext" iotfam_binsubpath="/Contents/MacOS/IOThunderboltFamily" iotfam_binpath="${iotfam_kextpath}${iotfam_binsubpath}" nvdastartup_kextpath="${sysextensions_path}NVDAStartup.kext" nvdastartup_plistpath="${nvdastartup_kextpath}/Contents/Info.plist" nvdastartupweb_kextpath="${libextensions_path}NVDAStartupWeb.kext" nvdastartupweb_plistpath="${nvdastartupweb_kextpath}/Contents/Info.plist" deprecated_nvsolution_kextpath="${libextensions_path}NVDAEGPUSupport.kext" deprecated_automate_egpu_kextpath="${libextensions_path}automate-eGPU.kext" amdlegacy_downloadpath="${libextensions_path}AMDLegacySupport.kext.zip" amdlegacy_kextpath="${libextensions_path}AMDLegacySupport.kext" support_dirpath="${root_vol}/Library/Application Support/Purge-Wrangler/" backupkext_dirpath="${support_dirpath}Kexts/" prompticon_filepath="${support_dirpath}pw.png" manifest="${support_dirpath}manifest.wglr" webdriver_plistpath="${root_vol}/usr/local/bin/webdriver.plist" scriptconfig_filepath="${support_dirpath}io.egpu.purge-wrangler.config.plist" } # --- SCRIPT HELPERs ## -- User Interface and Utilities ### Printf newline printfn() { printf '%b\n' "${@}" } ### Clear print printfc() { printf "\033[2K\r" printfn "${@}" } wait_for_kryptonite_info() { [ "$show_kry" = 1 ] && return printfn "\n${bold}Proceeding...${normal}" sleep 5 show_kry=1 } show_kryptonite_info() { ( [ "$show_kry" = 1 ] && [ "$1" != "-detail" ] ) && return printfn "${bold}Kryptonite${normal} is now the successor of ${bold}PurgeWrangler${normal}.\n" if [ "$1" = "-detail" ]; then printfn "With Kryptonite, eGPU support on Thunderbolt 1 and 2 macs is now" printfn "possible ${bold}without${normal} disabling ${bold}system integrity protection${normal}, ${bold}FileVault${normal}," printfn "and ${bold}authenticated-root${normal} - essentially true native-like support.\n" fi printfn "Please see this for more information: ${bold}https://rb.gy/rlvozf${normal}" } ### Prompt for a yes/no action yesno_action() { local prompt="${1}" local yesaction="${2}" local noaction="${3}" local no_newline="${4}" [[ -z "${no_newline}" ]] && printfn read -n1 -p "${prompt} [Y/N]: " userinput printf "\033[2K\r" [[ ${userinput} == "Y" ]] && eval "${yesaction}" && return [[ ${userinput} == "N" ]] && eval "${noaction}" && return printfn "Invalid choice. Please try again." yesno_action "${prompt}" "${yesaction}" "${noaction}" } ### Object downloader obj_download() { local url="${1}" local dst="${2}" local integrity_hash="${3}" curl -qLs -o "${dst}" "${url}" [[ "$(shasum -a 512 "${dst}" | awk '{ print $1 }')" != "${integrity_hash}" ]] && rm -rf "${dst}" 2>/dev/null 1>&2 || return 0 } ## -- Binary Patching Mechanism (P1 -> P2 -> P3) ### Optional: Check binary patchability check_bin_patchability() { local target_binary="${1}" local find="${2}" local dump="$(hexdump -ve '1/1 "%.2X"' "${target_binary}")" [[ "${dump}" == *"${find}"* ]] && return 0 || return 1 } ### P1: Create hex representation for target binary create_hexrepresentation() { local target_binary="${1}" local scratch_hex="${target_binary}.hex" hexdump -ve '1/1 "%.2X"' "${target_binary}" > "${scratch_hex}" } ### P2: Primary binary patching mechanism patch_binary() { local target_binary="${1}" local find="${2}" local replace="${3}" local scratch_hex="${target_binary}.hex" sed -i "" -e "s/${find}/${replace}/g" "${scratch_hex}" 2>/dev/null 1>&2 } ### P3: Generic binary generator for given hex file create_patched_binary() { local target_binary="${1}" local scratch_hex="${target_binary}.hex" local scratch_binary="${scratch_hex}.bin" xxd -r -p "${scratch_hex}" "${scratch_binary}" rm "${target_binary}" "${scratch_hex}" && mv "${scratch_binary}" "${target_binary}" } ## -- PLIST Patching Mechanism ### Patch specified plist modify_plist() { local target_plist="${1}" local command="${2}" local key="${3}" local value="${4}" $pb -c "${command} ${key} ${value}" "${target_plist}" 2>/dev/null 1>&2 } ## -- Configuration Handling ### Generates a configuration property list if needed generate_config() { mkdir -p "${support_dirpath}" [[ -f "${scriptconfig_filepath}" ]] && return > "${scriptconfig_filepath}" printfn "${plist_defaultstring}" >> "${scriptconfig_filepath}" modify_plist "${scriptconfig_filepath}" "Add" ":OSVersionAtPatch string" "${macos_ver}" modify_plist "${scriptconfig_filepath}" "Add" ":OSBuildAtPatch string" "${macos_build}" modify_plist "${scriptconfig_filepath}" "Add" ":DidApplyBinPatch bool" "false" modify_plist "${scriptconfig_filepath}" "Add" ":DidApplyPatchNVDAWebDrv bool" "false" } ### Updates the configuration as necessary update_config() { generate_config check_patch local status=("false" "true" "false") modify_plist "${scriptconfig_filepath}" "Set" ":OSVersionAtPatch" "${macos_ver}" modify_plist "${scriptconfig_filepath}" "Set" ":OSBuildAtPatch" "${macos_build}" modify_plist "${scriptconfig_filepath}" "Set" ":DidApplyBinPatch" "${status[${binpatch_enabled}]}" modify_plist "${scriptconfig_filepath}" "Set" ":DidApplyPatchNVDAWebDrv" "${status[${nvdawebdrv_patched}]}" } ### Deprecate manifest deprecate_manifest() { [[ ! -f "${manifest}" ]] && return macos_ver="$(sed "3q;d" "${manifest}")" macos_build="$(sed "4q;d" "${manifest}")" local manifest_patch="$(sed -n "9,11p" "${manifest}")" update_config [[ ${manifest_patch} =~ 1 ]] && $pb -c "Set :DidApplyBinPatch true" "${scriptconfig_filepath}" macos_ver="$(sw_vers -productVersion)" macos_build="$(sw_vers -buildVersion)" rm -f "${manifest}" } ### Create LaunchAgent create_launchagent() { local agent_dirpath="/Users/${SUDO_USER}/Library/LaunchAgents/" mkdir -p "${agent_dirpath}" local agent_plistpath="${agent_dirpath}${script_launchagent}.plist" > "${agent_plistpath}" printfn "${plist_defaultstring}" >> "${agent_plistpath}" $pb -c "Add :Label string ${script_launchagent}" "${agent_plistpath}" $pb -c "Add :OnDemand bool false" "${agent_plistpath}" $pb -c "Add :LaunchOnlyOnce bool true" "${agent_plistpath}" $pb -c "Add :RunAtLoad bool true" "${agent_plistpath}" $pb -c "Add :ProgramArguments array" "${agent_plistpath}" $pb -c "Add :ProgramArguments:0 string ${script_bin}" "${agent_plistpath}" $pb -c "Add :ProgramArguments:1 string -l" "${agent_plistpath}" chown "${SUDO_USER}" "${agent_plistpath}" obj_download "${prompticon_downloadurl}" "${prompticon_filepath}" "${prompticon_integrity_hash}" su "${SUDO_USER}" -c "launchctl load -w \"${agent_plistpath}\"" 2>/dev/null 1>&2 } # --- SCRIPT SOFTWARE UPDATE SYSTEM ### Perform software update perform_software_update() { printfn "${bold}Downloading...${normal}" curl -qLs -o "${tmp_script}" "${latest_release_dwld}" [[ "$(cat "${tmp_script}")" == "404: Not Found" ]] && printfn "Download failed.\n${bold}Continuing without updating...${normal}" && rm "${tmp_script}" && return printfn "Download complete.\n${bold}Updating...${normal}" chmod 700 "${tmp_script}" && chmod +x "${tmp_script}" rm "${script}" && mv "${tmp_script}" "${script}" chown "${SUDO_USER}" "${script}" printfn "Update complete." "${script}" "${option}" exit 0 } ### Check Github for newer version + prompt update fetch_latest_release() { mkdir -p -m 775 "${local_bin}" [[ "${is_bin_call}" == 0 ]] && return latest_script_data="$(curl -q -s "https://api.github.com/repos/mayankk2308/purge-wrangler/releases/latest")" latest_release_ver="$(printfn "${latest_script_data}" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')" latest_release_dwld="$(printfn "${latest_script_data}" | grep '"browser_download_url":' | sed -E 's/.*"([^"]+)".*/\1/')" latest_major_ver="$(printfn "${latest_release_ver}" | cut -d '.' -f1)" latest_minor_ver="$(printfn "${latest_release_ver}" | cut -d '.' -f2)" latest_patch_ver="$(printfn "${latest_release_ver}" | cut -d '.' -f3)" if [[ $latest_major_ver > $script_major_ver || ($latest_major_ver == $script_major_ver && $latest_minor_ver > $script_minor_ver)\ || ($latest_major_ver == $script_major_ver && $latest_minor_ver == $script_minor_ver && $latest_patch_ver > $script_patch_ver) && ! -z "${latest_release_dwld}" ]] then printfn "${mark}${gap}${bold}Software Update${normal}\n\nSoftware updates are available.\n\nOn Your System ${bold}${script_ver}${normal}\nLatest Available ${bold}${latest_release_ver}${normal}" yesno_action "${bold}Update now?${normal}" "perform_software_update" "echo \"${bold}Proceeding without updating...${normal}\" && sleep 0.4" fi } # --- SYSTEM CONFIGURATION MANAGER ### Trap CTRL+C exit trap terminate SIGINT ### Terminate script safely terminate() { [[ ${is_not_macOS11} == "0" ]] && diskutil unmount $(bless --getBoot) 2>/dev/null 1>&2 exit } ### Check caller validate_caller() { [[ -z "${script}" ]] && printfn "\n${bold}Cannot execute${normal}.\nPlease see the README for instructions.\n" && exit [[ "${script}" == "${script_bin}" ]] && is_bin_call=1 } ### Elevate privileges elevate_privileges() { if [[ $(id -u) != 0 ]] then sudo /bin/sh "${script}" "-${option}" exit fi } ### System integrity protection check check_sip() { if [[ ! -z "$(csrutil status | grep -i enabled)" ]] then printfn "\nPlease disable ${bold}System Integrity Protection${normal}.\n" exit fi if [[ "${is_not_macOS11}" == "0" && ! -z "$(csrutil authenticated-root status | grep -i enabled)" ]] then printfn "\nPlease disable ${bold}Authenticated Root${normal}.\n" exit fi } ### Check if system volume is writable check_sys_volume() { root_vol="" initialize_filepaths "${root_vol}" mount -uw / 2>/dev/null 1>&2 root_vol="/" [[ -w "${sysextensions_path}" ]] && return boot_vol_path="/Volumes/$(system_profiler SPSoftwareDataType | grep -i "Boot Volume" | cut -d':' -f2 | awk '{$1=$1};1')" boot_vol_id="$(diskutil info "${boot_vol_path}" | grep -i "device node" | cut -d':' -f2 | awk '{$1=$1};1')" boot_vol_id="${boot_vol_id%s1}" diskutil mount "${boot_vol_id}" 2>/dev/null 1>&2 root_vol="$(diskutil info "${boot_vol_id}" | grep -i "mount point" | cut -d':' -f2 | awk '{$1=$1};1')" mount -uw "${root_vol}" 2>/dev/null 1>&2 initialize_filepaths "${root_vol}" [[ -w "${sysextensions_path}" ]] && return printfn "\nYour system volume is ${bold}read-only${normal}. PurgeWrangler cannot proceed.\nPlease ${bold}restart${normal} your Mac and try again.\n" terminate } ### macOS compatibility check manage_macos_compat() { is_10151_or_newer=1 is_not_macOS11=0 macos_primary_ver="$(printfn "${macos_ver}" | cut -d '.' -f1)" macos_major_ver="$(printfn "${macos_ver}" | cut -d '.' -f2)" macos_minor_ver="$(printfn "${macos_ver}" | cut -d '.' -f3)" [[ -z "${macos_minor_ver}" ]] && macos_minor_ver=0 [[ (${macos_primary_ver} > 10) ]] && return hex_skipenum="${old_hex_skipenum}" hex_skipenum_patch="${old_hex_skipenum_patch}" is_not_macOS11=1 [[ ${macos_major_ver} == "15" && ${macos_minor_ver} > 0 ]] && return is_10151_or_newer=0 hex_selected_thunderbolt="${hex_thunderboltswitchtype}3" hex_selected_thunderbolt_patch="${system_thunderbolt_ver}" [[ (${macos_major_ver} < 13) || (${macos_major_ver} == "13" && ${macos_minor_ver} < 4) ]] && printfn "\n${bold}macOS 10.13.4 or later${normal} required.\n" && exit } ### Ensure presence of system extensions check_sys_extensions() { if [[ ! -s "${agc_kextpath}" || ! -s "${agw_binpath}" || ! -s "${iondrv_kextpath}" || ! -s "${iog_binpath}" || ! -s "${iotfam_kextpath}" || ! -s "${iotfam_binpath}" ]] then printfn "\nUnexpected system configuration or missing files." terminate fi } ### Retrieve thunderbolt version retrieve_tb_ver() { local tb_type="$(ioreg | grep AppleThunderboltNHIType)" tb_type="${tb_type##*+-o AppleThunderboltNHIType}" tb_type="${tb_type::1}" system_thunderbolt_ver="${hex_thunderboltswitchtype}${tb_type}" } ### Retrieve patch status check_patch() { binpatch_enabled=0 [[ ! -f "${agw_binpath}" || ! -f "${iog_binpath}" || ! -f "${iotfam_binpath}" ]] && return local hex_agwbin="$(hexdump -ve '1/1 "%.2X"' "${agw_binpath}")" local hex_iogbin="$(hexdump -ve '1/1 "%.2X"' "${iog_binpath}")" local hex_iotfambin="$(hexdump -ve '1/1 "%.2X"' "${iotfam_binpath}")" local nvdawebdrv_iopcitunnelcompat="$($pb -c "Print :IOKitPersonalities:NVDAStartup:IOPCITunnelCompatible" "${nvdastartupweb_plistpath}" 2>/dev/null)" [[ ! -e "${nvdastartupweb_kextpath}" ]] && nvdawebdrv_patched=2 || nvdawebdrv_patched=0 [[ "${nvdawebdrv_iopcitunnelcompat}" == "true" ]] && nvdawebdrv_patched=1 [[ -d "${amdlegacy_kextpath}" ]] && amdlegacy_enabled=1 || amdlegacy_enabled=0 [[ "${hex_agwbin}" =~ "${hex_selected_thunderbolt_patch}" && "${system_thunderbolt_ver}" != "${hex_thunderboltswitchtype}"3 ]] && tbswitch_enabled=1 || tbswitch_enabled=0 [[ "${hex_iogbin}" =~ "${hex_nvda_bypass_patch}" ]] && nvidia_enabled=1 || nvidia_enabled=0 [[ "${hex_iotfambin}" =~ "${hex_skipenum_patch}" ]] && ti82_enabled=1 || ti82_enabled=0 [[ ${tbswitch_enabled} == "1" || ${ti82_enabled} == "1" || ${nvidia_enabled} == "1" ]] && binpatch_enabled=1 } ### Display patch statuses check_patch_status() { printfn "${mark}${gap}${bold}System Status${normal}\n" local status=("Disabled" "Enabled" "Unknown") local drv_status=("Clean" "Patched" "Absent") printfn "${bold}Ti82 Devices${normal} ${status[${ti82_enabled}]}" printfn "${bold}TB1/2 AMD eGPUs${normal} ${status[$tbswitch_enabled]}" printfn "${bold}Legacy AMD eGPUs${normal} ${status[$amdlegacy_enabled]}" printfn "${bold}NVIDIA eGPUs${normal} ${status[$nvidia_enabled]}" printfn "${bold}Web Drivers${normal} ${drv_status[$nvdawebdrv_patched]}" } ### Cumulative system check perform_sys_check() { elevate_privileges show_kryptonite_info wait_for_kryptonite_info fetch_latest_release retrieve_tb_ver manage_macos_compat check_sip check_sys_volume check_sys_extensions check_patch deprecate_manifest using_nvdawebdrv=0 } # ----- OS MANAGEMENT ### Rebuild kexts as necessary rebuild_kexts() { if [[ "${is_not_macOS11}" == 1 ]]; then kextcache -i / 1>/dev/null 2>&1 return fi kmutil install --update-all --force --volume-root "${root_vol}" 1>/dev/null 2>&1 kcditto 1>/dev/null 2>&1 bless --folder "${coreservices}" --bootefi --create-snapshot } ### Sanitize system permissions and caches sanitize_system() { printfn "${bold}Sanitizing system...${normal}" chmod -R 755 "${agc_kextpath}" "${iog_kextpath}" "${iondrv_kextpath}" "${nvdastartupweb_kextpath}" "${nvdastartup_kextpath}" "${amdlegacy_kextpath}" "${iotfam_kextpath}" 1>/dev/null 2>&1 chown -R root:wheel "${agc_kextpath}" "${iog_kextpath}" "${iondrv_kextpath}" "${nvdastartupweb_kextpath}" "${nvdastartup_kextpath}" "${amdlegacy_kextpath}" "${iotfam_kextpath}" 1>/dev/null 2>&1 rebuild_kexts printfn "System sanitized." } # ----- BACKUP SYSTEM ### Primary procedure execute_backup() { mkdir -p "${backupkext_dirpath}" rsync -a "${agc_kextpath}" "${backupkext_dirpath}" rsync -a "${iog_kextpath}" "${backupkext_dirpath}" rsync -a "${iondrv_kextpath}" "${backupkext_dirpath}" rsync -a "${iotfam_kextpath}" "${backupkext_dirpath}" rsync -a "${nvdastartup_kextpath}" "${backupkext_dirpath}" tmutil snapshot 1>/dev/null 2>&1 } ### Backup procedure backup_system() { printfn "${bold}Backing up...${normal}" if [[ ! -z $(find "${backupkext_dirpath}" -mindepth 1 -print -quit 2>/dev/null) && -s "${scriptconfig_filepath}" ]] then local prev_macos_ver="$($pb -c "Print :OSVersionAtPatch" "${scriptconfig_filepath}")" local prev_macos_build="$($pb -c "Print :OSBuildAtPatch" "${scriptconfig_filepath}")" if [[ "${prev_macos_ver}" == "${macos_ver}" && "${prev_macos_build}" == "${macos_build}" ]] then if [[ ${binpatch_enabled} == 0 ]] then execute_backup printfn "Backup refreshed." update_config return fi printfn "Backup already exists." else printfn "\n${bold}Last Backup${normal} ${prev_macos_ver} ${bold}[${prev_macos_build}]${normal}" printfn "${bold}Current System${normal} ${macos_ver} ${bold}[${macos_build}]${normal}\n" printfn "${bold}Updating backup...${normal}" if [[ ${binpatch_enabled} == 1 ]] then printfn "${bold}Uninstalling patch(es) before updating backup...${normal}\n" uninstall fi execute_backup update_config printfn "Update complete." fi else execute_backup update_config printfn "Backup complete." fi } # --- CORE PATCHWORK ### Run clean reboot clean_reboot() { osascript -e 'tell application "Finder" to restart' & } # Reboot sequence initiate_reboot_seq() { printfn local timeout=5 while ((${timeout} > -1)); do local key="" read -r -s -n 1 -t 1 key [[ "${key}" == $'\e' ]] && printfc "Restart aborted." && return printf "\033[2K\rRestarting in ${bold}${timeout}s${normal}. Press ESC to abort..." timeout=$((${timeout} - 1)) done printf "\033[2K\rRestarting now." clean_reboot exit } ### Reboot prompt reboot_action() { [[ ${1} == -f ]] && printfn "${mark}${gap}${bold}Reboot${normal}" yesno_action "${bold}Reboot Now?${normal}" "printfn \"${bold}Rebooting... ${normal}\" && clean_reboot && exit" "printfn \"Reboot aborted.\"" } ### Conclude patching sequence end_binary_modifications() { update_config sanitize_system [[ "${2}" == -no-agent ]] && rm -rf "/Users/${SUDO_USER}/Library/LaunchAgents/${script_launchagent}.plist" || create_launchagent [[ ${single_user_mode} == 1 ]] && reboot 1>/dev/null 2>&1 && exit local message="${1}" initiate_reboot_seq } ### Install AMDLegacySupport.kext install_amd_legacy_kext() { if [[ "${1}" == -end ]]; then operation="AMD Legacy Only" printfn "${mark}${gap}${bold}AMD Legacy Support${normal}\n" fi [[ -d "${amdlegacy_kextpath}" ]] && printfn "${bold}AMDLegacySupport.kext${normal} already installed." && return printfn "${bold}Downloading AMDLegacySupport...${normal}" obj_download "${amdlegacy_downloadurl}" "${amdlegacy_downloadpath}" "${amdlegacy_integrity_hash}" [[ ! -e "${amdlegacy_downloadpath}" ]] && printfn "Could not download." && return 0 printfn "Download complete." [[ -d "${amdlegacy_kextpath}" ]] && rm -r "${amdlegacy_kextpath}" unzip -d "${libextensions_path}" "${amdlegacy_downloadpath}" 1>/dev/null 2>&1 rm -r "${amdlegacy_downloadpath}" "${libextensions_path}/__MACOSX" 1>/dev/null 2>&1 if [[ "${1}" == -end ]]; then end_binary_modifications "Installation complete."; fi } ### Enable Ti82 enable_ti82() { if [[ "${1}" == -end ]]; then operation="Ti82 Only" printfn "${mark}${gap}${bold}Enable Ti82 Support${normal}\n" && backup_system fi [[ ${ti82_enabled} == 1 ]] && printfn "Ti82 support is already enabled on this system." && return printfn "${bold}Enabling Ti82 support...${normal}" check_bin_patchability "${iotfam_binpath}" "${hex_skipenum}" [[ $? == 1 ]] && printfn "${bold}Unable to patch${normal} for Ti82 devices." && generate_sys_report && return 0 create_hexrepresentation "${iotfam_binpath}" patch_binary "${iotfam_binpath}" "${hex_skipenum}" "${hex_skipenum_patch}" create_patched_binary "${iotfam_binpath}" printfn "Ti82 support enabled." if [[ "${1}" == -end ]]; then end_binary_modifications "Patch complete."; fi } ### Patch TB1/2 block patch_tb() { printfn "${bold}Patching for AMD eGPUs...${normal}" [[ "${1}" == "-prompt" && ${amdlegacy_enabled} != 1 ]] && yesno_action "Enable ${bold}Legacy AMD Support${normal}?" "install_amd_legacy_kext && printfn" "printfn \"Skipping legacy kext.\n\"" [[ -e "${deprecated_automate_egpu_kextpath}" ]] && rm -r "${deprecated_automate_egpu_kextpath}" [[ ${nvidia_enabled} == 1 ]] && printfn "System has previously been patched for ${bold}NVIDIA eGPUs${normal}." && return [[ ${tbswitch_enabled} == 1 ]] && printfn "System has already been patched for ${bold}AMD eGPUs${normal}." && return [[ "${system_thunderbolt_ver}" == "${hex_thunderboltswitchtype}3" ]] && printfn "No patch required for this Mac." && return check_bin_patchability "${agw_binpath}" "${hex_selected_thunderbolt}" [[ $? == 1 ]] && printfn "${bold}Unable to patch${normal} for TB1/2 AMD eGPUs." && generate_sys_report && return 0 create_hexrepresentation "${agw_binpath}" patch_binary "${agw_binpath}" "${hex_selected_thunderbolt}" "${hex_selected_thunderbolt_patch}" create_patched_binary "${agw_binpath}" printfn "Patches applied." } ### Download and install NVIDIA Web Drivers install_web_drivers() { local installerpkg_path="/usr/local/NVDAInstall.pkg" local installerpkgexpanded_path="/usr/local/NVDAInstall" local nvdadrv_ver="${1}" local nvdadrv_downloadurl="${2}" rm -r "${installerpkgexpanded_path}" "${installerpkg_path}" 2>/dev/null 1>&2 printfn "${bold}Downloading drivers (${nvdadrv_ver})...${normal}" curl -q --connect-timeout 15 --progress-bar -o "${installerpkg_path}" "${nvdadrv_downloadurl}" if [[ ! -s "${installerpkg_path}" ]] then rm -r "${installerpkg_path}" 2>/dev/null 1>&2 printfn "Unable to download." return fi printfn "Download complete.\n${bold}Sanitizing package...${normal}" pkgutil --expand-full "${installerpkg_path}" "${installerpkgexpanded_path}" sed -i "" -e "/installation-check/d" "${installerpkgexpanded_path}/Distribution" local nvdastartup_pkgkextpath="$(find "${installerpkgexpanded_path}" -maxdepth 1 | grep -i NVWebDrivers)/Payload/Library/Extensions/NVDAStartupWeb.kext" if [[ ! -d "${nvdastartup_pkgkextpath}" ]] then rm -r "${installerpkg_path}" "${installerpkgexpanded_path}" 2>/dev/null 1>&2 printfn "Unable to patch driver." return fi $pb -c "Set ${set_nvdastartup_requiredos} \"${macos_build}\"" "${nvdastartup_pkgkextpath}/Contents/Info.plist" 2>/dev/null 1>&2 chown -R root:wheel "${nvdastartup_pkgkextpath}" rm -r "${installerpkg_path}" pkgutil --flatten-full "${installerpkgexpanded_path}" "${installerpkg_path}" 2>/dev/null 1>&2 printfn "Package sanitized.\n${bold}Installing...${normal}" local installer_err="$(installer -target "/" -pkg "${installerpkg_path}" 2>&1 1>/dev/null)" [[ -e "${nvdastartupweb_kextpath}" ]] && printfn "Installation complete." || printfn "Installation failed." rm -r "${installerpkg_path}" "${installerpkgexpanded_path}" rm "${webdriver_plistpath}" } ### Reset webdriver data reset_nvdawebdrv_stats() { nvdawebdrv_lastcompatible_ver="" nvdawebdrv_lastcompatible_downloadurl="" nvdawebdrv_latest_ver="" nvdawebdrv_latest_macosbuild="" nvdawebdrv_latest_downloadurl="" nvdawebdrv_alreadypresentos="" nvdawebdrv_target_ver="" nvdawebdrv_target_downloadurl="" nvdawebdrv_canpatchlatest=3 } ### Populate webdriver data get_nvdawebdrv_stats() { reset_nvdawebdrv_stats nvdawebdrv_target_ver="${1}" printfn "${bold}Fetching driver information...${normal}" [[ -f "${nvdastartupweb_plistpath}" ]] && nvdawebdrv_alreadypresentos="$(${pb} -c "Print ${set_nvdastartup_requiredos}" "${nvdastartupweb_plistpath}" 2>/dev/null)" local webdriver_data="$(curl -q -s "https://gfe.nvidia.com/mac-update")" [[ -z "${webdriver_data}" ]] && return printfn "${webdriver_data}" > "${webdriver_plistpath}" local index=0 local current_macos_build="${macos_build}" local currentdriver_downloadurl="" local currentdriver_ver="" while [[ ! -z "${current_macos_build}" ]] do currentdriver_downloadurl="$($pb -c "Print :updates:${index}:downloadURL" "${webdriver_plistpath}" 2>/dev/null)" currentdriver_ver="$($pb -c "Print :updates:${index}:version" "${webdriver_plistpath}" 2>/dev/null)" current_macos_build="$($pb -c "Print :updates:${index}:OS" "${webdriver_plistpath}" 2>/dev/null)" if [[ ! -z "${currentdriver_ver}" && "${nvdawebdrv_target_ver}" == "${currentdriver_ver}" ]] then nvdawebdrv_target_ver="${currentdriver_ver}" nvdawebdrv_target_downloadurl="${currentdriver_downloadurl}" return fi if [[ ${index} == 0 ]] then nvdawebdrv_latest_downloadurl="${currentdriver_downloadurl}" nvdawebdrv_latest_ver="${currentdriver_ver}" nvdawebdrv_latest_macosbuild="${current_macos_build}" fi [[ "${current_macos_build}" == "${macos_build}" ]] && break (( index++ )) done if [[ -z "${currentdriver_downloadurl}" || -z "${currentdriver_ver}" ]] then local currentdriver_majormacosbuild="${nvdawebdrv_latest_macosbuild:0:2}" local macos_major_build="${macos_build:0:2}" (( ${currentdriver_majormacosbuild} - ${macos_major_build} != 0 )) && nvdawebdrv_canpatchlatest=2 || nvdawebdrv_canpatchlatest=1 else nvdawebdrv_canpatchlatest=0 nvdawebdrv_lastcompatible_ver="${currentdriver_ver}" nvdawebdrv_lastcompatible_downloadurl="${currentdriver_downloadurl}" fi printfn "Information fetched." } ### Patch NVIDIA Web Driver version patch_nvdawebdrv_version() { printfn "${bold}Patching drivers...${normal}" $pb -c "Set ${set_nvdastartup_requiredos} \"${macos_build}\"" "${nvdastartupweb_plistpath}" 2>/dev/null 1>&2 printfn "Drivers patched." } ### Current webdriver possibilities webdriver_possibilities() { if [[ "${3}" == -already-present ]] then [[ "${nvdawebdrv_alreadypresentos}" == "${macos_build}" ]] && printfn "Appropriate NVIDIA Web Drivers are ${bold}already installed${normal}." && return printfn "Installed ${bold}NVIDIA Web Drivers${normal} are specifying incorrect macOS build." [[ "${4}" != "-prompt" ]] && printfn "${bold}Resolving...${normal}" && patch_nvdawebdrv_version 1>/dev/null && printfn "Resolved." && return yesno_action "${bold}Attempt to Rectify${normal}?" "patch_nvdawebdrv_version" "printfn \"Drivers unchanged.\n\"" else local recommendation=("Not Required" "Suggested" "Not Advised" "Cannot Determine") printfn "\nWeb drivers will require patching.\n${bold}Patch Recommendation${normal}: ${recommendation[${nvdawebdrv_canpatchlatest}]}" yesno_action "${bold}Patch?${normal}" "install_web_drivers \"${1}\" \"${2}\"" "printfn \"Installation aborted.\" && return" fi } ### Run Webdriver installation procedure run_webdriver_installer() { get_nvdawebdrv_stats [[ ! -z "${nvdawebdrv_alreadypresentos}" ]] && webdriver_possibilities "" "" "-already-present" "${1}" && return case ${nvdawebdrv_canpatchlatest} in 0) install_web_drivers "${nvdawebdrv_lastcompatible_ver}" "${nvdawebdrv_lastcompatible_downloadurl}";; 1) [[ "${1}" == -prompt ]] && webdriver_possibilities "${nvdawebdrv_latest_ver}" "${nvdawebdrv_latest_downloadurl}" && return install_web_drivers "${nvdawebdrv_latest_ver}" "${nvdawebdrv_latest_downloadurl}";; 2|3) [[ "${1}" == -prompt ]] && webdriver_possibilities "${nvdawebdrv_latest_ver}" "${nvdawebdrv_latest_downloadurl}" && return printfn "No NVIDIA driver available for your system.";; esac } ### Install specified version of Web Drivers install_ver_spec_webdrv() { operation="NVIDIA WDrv Only" printfn "${mark}${gap}${bold}Install NVIDIA Web Drivers${normal}\n" printfn "Specify a ${bold}Webdriver version${normal} to install (${bold}L = Latest${normal}).\nExisting drivers will be overwritten.\n${bold}Example${normal}: 387.10.10.10.25.161\n" read -p "${bold}Version${normal} [L|Q]: " userinput printfn [[ -z "${userinput}" || "${userinput}" == Q ]] && printfn "No changes made." && return get_nvdawebdrv_stats "${userinput}" [[ "${userinput}" != "L" && -z "${nvdawebdrv_target_downloadurl}" ]] && printfn "No driver found for specified version. Cannot proceed." && return [[ "${userinput}" != "L" ]] && install_web_drivers "${nvdawebdrv_target_ver}" "${nvdawebdrv_target_downloadurl}" || install_web_drivers "${nvdawebdrv_latest_ver}" "${nvdawebdrv_latest_downloadurl}" using_nvdawebdrv=1 } ### Run NVIDIA eGPU patcher run_patch_nv() { if [[ "${1}" == -prompt ]] then [[ -f "${nvdastartupweb_plistpath}" ]] && nvdawebdrv_alreadypresentos="$(${pb} -c "Print ${set_nvdastartup_requiredos}" "${nvdastartupweb_plistpath}" 2>/dev/null)" if [[ ! -z "${nvdawebdrv_alreadypresentos}" ]] then webdriver_possibilities "" "" "-already-present" "-prompt" using_nvdawebdrv=1 else yesno_action "Install ${bold}NVIDIA Web Drivers${normal}?" "using_nvdawebdrv=1 && run_webdriver_installer -prompt" "printfn \"Skipping web drivers.\n\"" fi fi local nvdastartupplist_topatch="${nvdastartupweb_plistpath}" if (( ${using_nvdawebdrv} == 1 )) then [[ ! -f "${nvdastartupweb_plistpath}" ]] && return nvram nvda_drv=1 else nvram -d nvda_drv 2>/dev/null nvdastartupplist_topatch="${nvdastartup_plistpath}" fi check_bin_patchability "${agw_binpath}" "${hex_nvda_bypass}" local pass=$? check_bin_patchability "${iog_binpath}" "${hex_nvda_bypass}" pass=$(( ${pass} + $? )) (( ${pass} != 0 )) && printfn "${bold}Unable to patch${normal} for NVIDIA eGPUs." && generate_sys_report && return 0 create_hexrepresentation "${agw_binpath}" create_hexrepresentation "${iog_binpath}" patch_binary "${agw_binpath}" "${hex_nvda_bypass}" "${hex_nvda_bypass_patch}" patch_binary "${iog_binpath}" "${hex_nvda_clamshell}" "${hex_nvda_clamshell_patch}" patch_binary "${iog_binpath}" "${hex_nvda_bypass}" "${hex_nvda_bypass_patch}" create_patched_binary "${agw_binpath}" create_patched_binary "${iog_binpath}" modify_plist "${iondrv_plistpath}" "Add" "${set_iognvda_pcitunnelled}" "true" modify_plist "${nvdastartupplist_topatch}" "Add" "${set_nvdastartup_pcitunnelled}" "true" rm -r "${deprecated_automate_egpu_kextpath}" 2>/dev/null 1>&2 rm -r "${deprecated_nvsolution_kextpath}" 2>/dev/null 1>&2 printfn "Patches applied." } ### Patch for NVIDIA eGPUs patch_nv() { printfn "${bold}Patching for NVIDIA eGPUs...${normal}" [[ -e "${nvdastartupweb_kextpath}" && ${nvdawebdrv_patched} == 0 ]] && run_patch_nv "${1}" && return [[ ${nvidia_enabled} == 1 ]] && printfn "System has already been patched for ${bold}NVIDIA eGPUs${normal}." && return [[ ${tbswitch_enabled} == 1 ]] && printfn "System has previously been patched for ${bold}AMD eGPUs${normal}." && return run_patch_nv "${1}" } # Run webdriver uninstallation process run_webdriver_uninstaller() { nvram -d nvda_drv local webdriver_uninstaller="/Library/PreferencePanes/NVIDIA Driver Manager.prefPane/Contents/MacOS/NVIDIA Web Driver Uninstaller.app/Contents/Resources/NVUninstall.pkg" [[ ! -s "${webdriver_uninstaller}" ]] && return printfn "${bold}Uninstalling NVIDIA drivers...${normal}" installer -target "/" -pkg "${webdriver_uninstaller}" 2>&1 1>/dev/null [[ ${single_user_mode} == 0 ]] && printfn "Drivers Uninstalled." || printfn "Drivers deactivated." } ### Retrieve GPU name get_gpu_name() { local id="${1}" local vendor="${2}" local device_names="$(curl -s "http://pci-ids.ucw.cz/read/PC/${vendor}/${id}" | grep -i "itemname" | sed -E "s/.*Name\: (.*)$/\1/")" local device_name="$(printfn "${device_names}" | tail -1 | cut -d '[' -f2)" local egpu_arch="$(printfn "${device_names}" | tail -1 | cut -d '[' -f1)" if [[ ! -z "${device_name}" ]] then printfn "${device_name%?}:${egpu_arch}" else [[ ${vendor} == "10de" ]] && printfn "NVIDIA" [[ ${vendor} == "1002" ]] && printfn "AMD" fi } ### Retrieve eGPU data retrieve_egpu_data() { egpu_vendor=$(printfn "${ioreg_info}" | grep \"vendor-id\" | cut -d "=" -f2 | sed 's/ //' | cut -c1-4 | sed -E 's/^(.{2})(.{2}).*$/\2\1/') egpu_dev_id=$(printfn "${ioreg_info}" | grep \"device-id\" | cut -d "=" -f2 | sed 's/ //' | cut -c1-4 | sed -E 's/^(.{2})(.{2}).*$/\2\1/') } ### Retrieve Ti82 need get_ti82_need() { local ti82_data="$(system_profiler SPThunderboltDataType 2>/dev/null | grep -i unsupported)" [[ -z ${ti82_data} ]] && printfn "No" || printfn "Yes" } ### Detect eGPU detect_egpu() { printfn "${bold}Plug-in eGPU${normal}. Press ESC if you are not plugging in eGPU.\n" egpu_vendor="" egpu_dev_id="" IFS='' for (( i = 20; i > 0; i-- )) do printf "\033[2K\r" printf "${bold}Detecting eGPU (${i})...${normal}" needs_ti82="$(get_ti82_need)" [[ "${needs_ti82}" == "Yes" ]] && printfc "Detection not possible. Ti82 override needed first." && return ioreg_info="$(ioreg -n display@0)" retrieve_egpu_data local key="" read -r -s -n 1 -t 1 key if [[ ! -z "${egpu_vendor}" ]] then legacy_amd_needed=1 webdrv_needed=1 local name_data="$(get_gpu_name "${egpu_dev_id}" "${egpu_vendor}")" local egpu_name="$(printfn "${name_data}" | cut -d ":" -f1)" egpu_arch="$(printfn "${name_data}" | cut -d ":" -f2)" for arch in "${supported_amd_archs[@]}"; do if [[ "${egpu_arch}" =~ "${arch}" ]]; then legacy_amd_needed=0 break fi done for arch in "${supported_nv_archs[@]}"; do if [[ "${egpu_arch}" =~ "${arch}" ]]; then webdrv_needed=0 break fi done printfc "${bold}External GPU${normal}\t${egpu_name}" printfn "${bold}GPU Arch${normal}\t${egpu_arch}" printfn "${bold}Thunderbolt${normal}\t${system_thunderbolt_ver: -1}" return fi [[ "${key}" == $'\e' ]] && printfc "eGPU detection skipped. Please provide more information." && return done printfc "Detection failed. Please provide more information." } ### Manual eGPU setup manual_setup_egpu() { [[ "${needs_ti82}" == "No" ]] && yesno_action "${bold}Enable Ti82${normal}?" "enable_ti82 && printfn" "printfn \"Skipping Ti82 support.\n\"" -n local menu_items=("AMD" "NVIDIA" "Cancel") local menu_actions=("patch_tb -prompt" "patch_nv -prompt" "printfn \"Further patching aborted.\"") generate_menu "eGPU Vendor" "0" "-1" "0" "${menu_items[@]}" autoprocess_input "Choice" "" "" "false" "${menu_actions[@]}" } ### Automatic eGPU setup auto_setup_egpu() { operation="Setup" printfn "${mark}${gap}${bold}Setup eGPU${normal}\n" [[ ${binpatch_enabled} == "1" ]] && printfn "System has previously been modified. Uninstall first." && return detect_egpu printfn backup_system printfn [[ ${needs_ti82} == "Yes" ]] && enable_ti82 && printfn if [[ "${egpu_vendor}" == "1002" ]] then if [[ "${egpu_arch}" =~ "Navi" && ${is_10151_or_newer} != 1 ]]; then printfn "${bold}Navi${normal} eGPUs require ${bold}macOS 10.15.1${normal} or later." else [[ ${legacy_amd_needed} == 1 ]] && install_amd_legacy_kext && printfn patch_tb fi elif [[ "${egpu_vendor}" == "10de" ]] then if [[ "${egpu_arch}" =~ "TU" ]]; then printfn "${bold}Turing/RTX${normal} eGPUs are not supported on macOS." else [[ ${webdrv_needed} == 1 ]] && run_webdriver_installer && using_nvdawebdrv=1 && printfn patch_nv fi else manual_setup_egpu fi check_patch [[ ${binpatch_enabled} != "1" && ${amdlegacy_enabled} != "1" ]] && return printfn print_anomalies printfn end_binary_modifications "Modifications complete." } ### Auto-patch daemon executor execute_daemon_auto_patch() { printfn "[$(date +%Y-%m-%d::%H:%M:%S)]" printfn "My ID is $(id -u)" printfn "Executing autopatch!" sleep 10 printfn "Done!" } ### In-place re-patcher uninstall() { operation="Uninstall" printfn "${mark}${gap}${bold}Uninstall${normal}\n" [[ ${amdlegacy_enabled} == "0" && ${binpatch_enabled} == "0" && ! -e "${nvdastartupweb_kextpath}" ]] && printfn "No patches detected.\n${bold}System already clean.${normal}" && return printfn "${bold}Uninstalling...${normal}" [[ -d "${amdlegacy_kextpath}" ]] && rm -r "${amdlegacy_kextpath}" [[ -e "${nvdastartupweb_kextpath}" ]] && yesno_action "Remove ${bold}NVIDIA Web Drivers${normal}?" "run_webdriver_uninstaller" "printfn \"Skipping web drivers.\n\"" printfn "${bold}Reverting binaries...${normal}" if [[ ${ti82_enabled} == 1 ]] then create_hexrepresentation "${iotfam_binpath}" patch_binary "${iotfam_binpath}" "${hex_skipenum_patch}" "${hex_skipenum}" create_patched_binary "${iotfam_binpath}" fi create_hexrepresentation "${agw_binpath}" [[ ${tbswitch_enabled} == 1 ]] && patch_binary "${agw_binpath}" "${hex_selected_thunderbolt_patch}" "${hex_selected_thunderbolt}" if [[ ${nvidia_enabled} == 1 ]] then create_hexrepresentation "${iog_binpath}" patch_binary "${iog_binpath}" "${hex_nvda_bypass_patch}" "${hex_nvda_bypass}" patch_binary "${iog_binpath}" "${hex_nvda_clamshell_patch}" "${hex_nvda_clamshell}" patch_binary "${agw_binpath}" "${hex_nvda_bypass_patch}" "${hex_nvda_bypass}" create_patched_binary "${iog_binpath}" modify_plist "${nvdastartupweb_plistpath}" "Delete" "${set_nvdastartup_pcitunnelled}" modify_plist "${nvdastartup_plistpath}" "Delete" "${set_nvdastartup_pcitunnelled}" modify_plist "${iondrv_plistpath}" "Delete" "${set_iognvda_pcitunnelled}" fi create_patched_binary "${agw_binpath}" printfn "Binaries reverted." end_binary_modifications "Uninstallation Complete." -no-agent } # ----- BINARY MANAGER ### Binary first-time setup first_time_setup() { [[ $is_bin_call == 1 ]] && return script_sha="$(shasum -a 512 -b "${script}" | awk '{ print $1 }')" bin_sha="" [[ -s "${script_bin}" ]] && bin_sha="$(shasum -a 512 -b "${script_bin}" | awk '{ print $1 }')" [[ "${bin_sha}" == "${script_sha}" ]] && return rsync -a "${script}" "${script_bin}" chown "${SUDO_USER}" "${script_bin}" chmod 755 "${script_bin}" } # ----- ANOMALY MANAGER ### Detect discrete GPU vendor detect_discrete_gpu_vendor() { local dgpu_ioreg="$(ioreg -n GFX0@0)" dgpu_vendor="$(printfn "${dgpu_ioreg}" | grep \"vendor-id\" | cut -d "=" -f2 | sed 's/ //' | cut -c1-4 | sed -E 's/^(.{2})(.{2}).*$/\2\1/')" dgpu_dev_id="$(printfn "${dgpu_ioreg}" | grep \"device-id\" | cut -d "=" -f2 | sed 's/ //' | cut -c1-4 | sed -E 's/^(.{2})(.{2}).*$/\2\1/')" } ### Detect Mac Model detect_mac_model() { local model_id="$(system_profiler SPHardwareDataType 2>/dev/null | awk '/Model Identifier/ {print $3}')" [[ "${model_id}" == *"MacBook"* ]] && is_desktop_mac=0 || is_desktop_mac=1 } ### Compute anomaly states anomaly_states() { detect_mac_model detect_discrete_gpu_vendor resolution_needed=0 [[ ${is_desktop_mac} == 1 ]] && resolution_needed="-1" && return if [[ "${dgpu_vendor}" == "10de" ]] then [[ -f "${nvdastartupweb_plistpath}" && ${nvidia_enabled} == 1 ]] && resolution_needed=1 && return [[ ${tbswitch_enabled} == 1 ]] && resolution_needed=2 && return elif [[ "${dgpu_vendor}" == "1002" ]] then [[ ${nvidia_enabled} == 1 ]] && resolution_needed=3 && return fi } ### Print anomalies, if any print_anomalies() { printfn "${bold}Analyzing system...${normal}" anomaly_states case "${resolution_needed}" in 1) printfn "\n${bold}Problem${normal} Loss of OpenCL/GL on all NVIDIA GPUs.\n${bold}Resolution${normal} Use ${bold}purge-nvda.sh${normal} NVIDIA optimizations.";; 2) printfn "\n${bold}Problem${normal} Black screens on monitors connected to eGPU.\n${bold}Resolution${normal} Use ${bold}purge-nvda.sh${normal} AMD optimizations.";; 3) printfn "\n${bold}Problem${normal} Black screens/slow performance with eGPU.\n${bold}Resolution${normal} Use \`${bold}pmset -a gpuswitch 0${normal}\` to force iGPU.";; *) [[ ${is_desktop_mac} == 1 ]] && printfn "No resolutions to any anomalies if present. See README." || printfn "No anomalies expected.";; esac } ### Anomaly detection detect_anomalies() { printfn "${mark}${gap}${bold}System Diagnosis${normal}\n" print_anomalies } # --- USER INTERFACE ### Request donation donate() { open "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mayankk2308@gmail.com&lc=US&item_name=Development%20of%20PurgeWrangler&no_note=0¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHostedGuest" printfn "See your ${bold}web browser${normal}." } ### Generate system report generate_sys_report() { [[ ${1} == -standalone ]] && printfn "${mark}${gap}${bold}System Log${normal}\n" printfn "${bold}Generating system log...${normal}" logfiles="pwlog-$(date +%Y-%m-%d-%H-%M-%S)" report_dirpath="/Users/${SUDO_USER}/Desktop/${logfiles}" mkdir -p "${report_dirpath}" rsync -a "${scriptconfig_filepath}" "${report_dirpath}/PatchState.plist" rsync -a "${backupkext_dirpath}" "${report_dirpath}/Backup Kexts" system_profiler SPSoftwareDataType 2>/dev/null | grep -Eiv 'Boot Volume|Name' > "${report_dirpath}/Mac.log" system_profiler SPHardwareDataType 2>/dev/null | grep -Eiv 'Serial|UUID' >> "${report_dirpath}/Mac.log" system_profiler SPThunderboltDataType 2>/dev/null > "${report_dirpath}/ThunderboltDevices.log" system_profiler SPDisplaysDataType 2>/dev/null > "${report_dirpath}/GPUs.log" system_profiler SPPCIDataType 2>/dev/null > "${report_dirpath}/PCI.log" system_profiler SPExtensionsDataType 2>/dev/null > "${report_dirpath}/Extensions.log" ioreg_info="$(ioreg -n display@0)" retrieve_egpu_data printfn "${egpu_dev_id} ${egpu_vendor}" > "${report_dirpath}/eGPU.log" get_gpu_name "${egpu_dev_id}" "${egpu_vendor}" >> "${report_dirpath}/eGPU.log" kextstat > "${report_dirpath}/Kextstat.log" current_dir="$(pwd)" cd "/Users/${SUDO_USER}/Desktop" zip -r -X "${logfiles}.zip" "${logfiles}" 2>/dev/null 1>&2 cd "${current_dir}" rm -r "${report_dirpath}" chown "${SUDO_USER}" "${report_dirpath}.zip" printfn "Log generated on the Desktop.\n\nOpen an ${bold}issue${normal} on Github and share this file." } ### Notify notify() { osascript -e " set promptIcon to \"nil\" set outcome to \"nil\" set theDialogText to \"${1}\" try set promptIcon to (POSIX file \"${prompticon_filepath}\") as alias end try if promptIcon is \"nil\" then set outcome to (display dialog theDialogText buttons {\"Never\", \"No\", \"Yes\"} default button \"Yes\" cancel button \"No\") else set outcome to (display dialog theDialogText buttons {\"Never\", \"No\", \"Yes\"} default button \"Yes\" cancel button \"No\" with icon promptIcon) end if if (outcome = {button returned:\"Yes\"}) then tell application \"Terminal\" activate do script \"purge-wrangler -a\" end tell else if (outcome = {button returned:\"Never\"}) then do shell script \"rm ~/Library/LaunchAgents/io.egpu.purge-wrangler-agent.plist\" with administrator privileges end if" 2>/dev/null 1>&2 sleep 10 } ### Show update prompt show_update_prompt() { initialize_filepaths manage_macos_compat check_patch [[ ! -e "${scriptconfig_filepath}" ]] && sleep 10 && return local prev_macos_ver="$($pb -c "Print :OSVersionAtPatch" "${scriptconfig_filepath}")" local prev_macos_build="$($pb -c "Print :OSBuildAtPatch" "${scriptconfig_filepath}")" local did_patch="$($pb -c "Print :DidApplyBinPatch" "${scriptconfig_filepath}")" local did_patch_nvdawebdrv="$($pb -c "Print :DidApplyPatchNVDAWebDrv" "${scriptconfig_filepath}")" [[ "${did_patch_nvdawebdrv}" == "true" && ${nvdawebdrv_patched} == 0 ]] && notify "There has been a change with previously patched NVIDIA Web Drivers. Re-apply PurgeWrangler patches to enable driver for eGPUs?" && return [[ ${binpatch_enabled} == "1" || ("${prev_macos_ver}" == "${macos_ver}" && "${prev_macos_build}" == "${macos_build}") || ${did_patch} == false ]] && sleep 10 && return notify "PurgeWrangler eGPU patches have been reset because macOS was updated. Would you like to re-enable patches?" } ### Generalized args processor autoprocess_args() { local choice="${1}" && shift local caller="${2}" && shift local actions=("${@}") printf "\033[2K\r" if [[ ${choice} =~ ^[0-9]+$ && (( ${choice} > 0 && ${choice} -le ${#actions[@]} )) ]] then eval "${actions[(( ${choice} - 1 ))]}" return fi printfn "Invalid choice." return } ### Generalized input request autoprocess_input() { local message="${1}" && shift local caller="${1}" && shift local exit_action="${1}" && shift local prompt_back="${1}" && shift local actions=("${@}") local readonce="" (( ${#actions[@]} < 10 )) && readonce="-n1" read ${readonce} -p "${bold}${message}${normal} [1-${#actions[@]}]: " userinput autoprocess_args "${userinput}" "${caller}" "${actions[@]}" [[ "${prompt_back}" == true ]] && yesno_action "${bold}Back to menu?${normal}" "${caller}" "${exit_action}" } ### Generalized menu generator generate_menu() { local header="${1}" && shift local indent_level="${1}" && shift local gap_after="${1}" && shift local should_clear="${1}" && shift local items=("${@}") local indent="" for (( i = 0; i < ${indent_level}; i++ )) do indent="${indent} " done [[ ${should_clear} == 1 ]] && clear printfn "${indent}${mark}${gap}${bold}${header}${normal}\n" for (( i = 0; i < ${#items[@]}; i++ )) do num=$(( i + 1 )) printfn "${indent}${gap}${bold}${num}${normal}. ${items[${i}]}" (( ${num} == ${gap_after} )) && printfn done printfn } ### Args processor process_cli_args() { case "${option}" in a) auto_setup_egpu && printfn;; u) uninstall && printfn;; s) check_patch_status && printfn;; d) execute_daemon_auto_patch && printfn;; *) first_time_setup && present_menu;; esac } ### Present more options present_more_options_menu() { local menu_items=("Add AMD Legacy Support" "Enable Ti82 Support" "Install NVIDIA Web Drivers" "System Diagnosis & Logging" "Reboot" "Back") local menu_actions=("install_amd_legacy_kext -end" "enable_ti82 -end" "install_ver_spec_webdrv" "detect_anomalies && printfn && generate_sys_report -standalone" "reboot_action -f" "present_menu") generate_menu "More Options" "0" "3" "1" "${menu_items[@]}" autoprocess_input "What next?" "perform_sys_check && present_more_options_menu" "present_menu" "true" "${menu_actions[@]}" } ### Script menu present_menu() { local menu_items=("About Kryptonite" "Setup eGPU" "System Status" "Uninstall" "More Options" "Donate" "Quit") local menu_actions=("show_kryptonite_info -detail" "auto_setup_egpu" "check_patch_status" "uninstall" "present_more_options_menu" "donate" "exit") generate_menu "PurgeWrangler (${script_ver})" "0" "3" "1" "${menu_items[@]}" autoprocess_input "What next?" "perform_sys_check && present_menu" "terminate" "true" "${menu_actions[@]}" } # --- SCRIPT DRIVER ### Primary execution routine begin() { [[ ${single_user_mode} == 1 ]] && printfn "\nExecution not supported in single user mode.\n" && exit [[ "${option}" == "l" ]] && show_update_prompt && return validate_caller perform_sys_check process_cli_args } begin)