5 Commits
Author SHA1 Message Date
amd64fox 2a179d3cf0 version bump to 1.2.96
enable the redesigned context menu, canvas toggle, and video shelf previews
2026-08-08 14:03:04 +03:00
amd64fox c2cc808ef6 fix(block_slots): prepare semantic patching for Spotify 1.2.96
- resolve slot enums from mapper control flow instead of field offsets
- allow short or exact versions in the validation workflow
2026-08-08 00:09:42 +03:00
amd64fox 50bc5a6526 version bump to 1.2.95 2026-07-25 02:13:21 +03:00
amd64foxandthynome 3d1ddd6850 fix(binary): handle BinaryScanner failures
#878

Co-authored-by: thynome <125388433+thynomex@users.noreply.github.com>
2026-07-14 22:58:38 +03:00
amd64fox afb4c3fcd1 Add exclusions to Microsoft Defender
- Add exclusions to Microsoft Defender to prevent false positives
2026-07-11 20:50:11 +03:00
36 changed files with 761 additions and 50 deletions
+29 -14
View File
@@ -1,38 +1,53 @@
name: Check Spotx for the latest Spotify name: Check Spotx for the latest Spotify
run-name: Check SpotX for Spotify ${{ inputs.version || 'latest' }}
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
version:
description: Spotify version like 1.2.95 or 1.2.95.453, empty means latest
required: false
default: ''
type: string
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Spotx - name: Run Spotx
shell: powershell shell: powershell
env:
SPOTIFY_VERSION: ${{ inputs.version }}
run: | run: |
$jsonUrl = "https://raw.githubusercontent.com/LoaderSpot/table/refs/heads/main/table/versions.json" $spotifyVersion = ([string]$env:SPOTIFY_VERSION).Trim()
$resp = Invoke-RestMethod -Uri $jsonUrl
# Use fullversion because installer names include architecture suffixes if ([string]::IsNullOrWhiteSpace($spotifyVersion)) {
$latestEntry = $resp.PSObject.Properties | Select-Object -First 1 $jsonUrl = "https://raw.githubusercontent.com/LoaderSpot/table/refs/heads/main/table/versions.json"
if (-not $latestEntry) { $resp = Invoke-RestMethod -Uri $jsonUrl
throw "No Spotify versions found in $jsonUrl"
# Use fullversion for the installer name
$latestEntry = $resp.PSObject.Properties | Select-Object -First 1
if (-not $latestEntry) {
throw "No Spotify versions found in $jsonUrl"
}
$spotifyVersion = [string]$latestEntry.Value.fullversion
if ([string]::IsNullOrWhiteSpace($spotifyVersion) -or $spotifyVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
throw "Invalid Spotify fullversion for $($latestEntry.Name)"
}
}
elseif ($spotifyVersion -notmatch '^\d+\.\d+\.\d+(?:\.\d+)?$') {
throw "Invalid Spotify version: $spotifyVersion"
} }
$spotifyFullVersion = $latestEntry.Value.fullversion Write-Host "Selected Spotify version: $spotifyVersion"
if ([string]::IsNullOrWhiteSpace($spotifyFullVersion) -or $spotifyFullVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
throw "Invalid Spotify fullversion for $($latestEntry.Name)"
}
# Incoming parameters # Incoming parameters
$parameters = @{ $parameters = @{
Verbose = $true Verbose = $true
new_theme = $true new_theme = $true
version = $spotifyFullVersion version = $spotifyVersion
confirm_spoti_recomended_over = $true confirm_spoti_recomended_over = $true
cache_limit = 1000 cache_limit = 1000
block_update_on = $true block_update_on = $true
+115 -11
View File
@@ -111,7 +111,7 @@
"native_description": "Enable music leavebehinds on eligible playlists for desktop", "native_description": "Enable music leavebehinds on eligible playlists for desktop",
"version": { "version": {
"fr": "1.2.10", "fr": "1.2.10",
"to": "" "to": "1.2.93"
} }
}, },
"HptoLocationRefactor": { "HptoLocationRefactor": {
@@ -417,7 +417,7 @@
"native_description": "Allow switching between the new Home Ads format and legacy HPTO format (as opposed to only showing the legacy HPTO format)", "native_description": "Allow switching between the new Home Ads format and legacy HPTO format (as opposed to only showing the legacy HPTO format)",
"version": { "version": {
"fr": "1.2.34", "fr": "1.2.34",
"to": "" "to": "1.2.94"
} }
}, },
"LimitedAdsLabelsOnPlaylistCards": { "LimitedAdsLabelsOnPlaylistCards": {
@@ -512,7 +512,7 @@
"native_description": "Enables the Employee Flag to send to Sentry", "native_description": "Enables the Employee Flag to send to Sentry",
"version": { "version": {
"fr": "1.2.44", "fr": "1.2.44",
"to": "" "to": "1.2.93"
} }
}, },
"HomeImpressions": { "HomeImpressions": {
@@ -656,7 +656,7 @@
"native_description": "Enable Hpto announcements on Home", "native_description": "Enable Hpto announcements on Home",
"version": { "version": {
"fr": "1.2.65", "fr": "1.2.65",
"to": "" "to": "1.2.94"
} }
}, },
"SponsoredPlaylistV2": { "SponsoredPlaylistV2": {
@@ -778,7 +778,7 @@
"name": "bypassApplyUpdateCheck", "name": "bypassApplyUpdateCheck",
"version": { "version": {
"fr": "1.2.84", "fr": "1.2.84",
"to": "" "to": "1.2.93"
} }
}, },
"ad_feedback_in_stream": { "ad_feedback_in_stream": {
@@ -836,6 +836,62 @@
"fr": "1.2.92", "fr": "1.2.92",
"to": "" "to": ""
} }
},
"EmbeddedAdHtmlDisplay": {
"name": "enableEmbeddedAdHtmlDisplay",
"description": "Enable HTML display ads in the embedded NPV",
"version": {
"fr": "1.2.94",
"to": ""
}
},
"ImageOptimizationSentrySpanMeasurement": {
"name": "enableImageOptimizationSentrySpanMeasurement",
"description": "Enable Sentry image resource span attributes for image optimization rollout measurement",
"version": {
"fr": "1.2.94",
"to": ""
}
},
"SentryReactRouterV6Routing": {
"name": "enableSentryReactRouterV6Routing",
"description": "Enable Sentry React Router v6 route instrumentation for Web Player SPA transactions",
"version": {
"fr": "1.2.94",
"to": ""
}
},
"SponsoredPlaylistHorizontalVideo": {
"name": "enableSponsoredPlaylistHorizontalVideo",
"description": "Enables horizontal video layout for sponsored playlist headers on desktop",
"version": {
"fr": "1.2.95",
"to": ""
}
},
"AdsSurfaceStateForAdOrchestration": {
"name": "useAdsSurfaceStateForAdOrchestration",
"description": "Use ads-owned NPV and cinema surface state for ad orchestration",
"version": {
"fr": "1.2.96",
"to": ""
}
},
"HptoHarmonyVideoPlayer": {
"name": "enableHptoHarmonyVideoPlayer",
"description": "Enable the Harmony-based video player for HPTO home video ads, replacing the Betamax player",
"version": {
"fr": "1.2.96",
"to": ""
}
},
"SidekickFeedbackBanner": {
"name": "enableSidekickFeedbackBanner",
"description": "Show the feedback banner in the Sidekick chat",
"version": {
"fr": "1.2.96",
"to": ""
}
} }
}, },
"EnableExp": { "EnableExp": {
@@ -962,7 +1018,7 @@
"native_description": "Enable silence trimming in podcasts", "native_description": "Enable silence trimming in podcasts",
"version": { "version": {
"fr": "1.1.99", "fr": "1.1.99",
"to": "" "to": "1.2.93"
} }
}, },
"PlaybackSpeedIncrements": { "PlaybackSpeedIncrements": {
@@ -1448,7 +1504,7 @@
"native_description": "Disable the Premium Only Modal", "native_description": "Disable the Premium Only Modal",
"version": { "version": {
"fr": "1.2.39", "fr": "1.2.39",
"to": "" "to": "1.2.95"
} }
}, },
"CenteredLayout": { "CenteredLayout": {
@@ -1664,7 +1720,7 @@
"native_description": "Enable reverse sort direction in Your Library", "native_description": "Enable reverse sort direction in Your Library",
"version": { "version": {
"fr": "1.2.60", "fr": "1.2.60",
"to": "" "to": "1.2.94"
} }
}, },
"BackendSearchHistory": { "BackendSearchHistory": {
@@ -1910,7 +1966,7 @@
"native_description": "Enable BPM and key columns in playlist recommendations", "native_description": "Enable BPM and key columns in playlist recommendations",
"version": { "version": {
"fr": "1.2.82", "fr": "1.2.82",
"to": "" "to": "1.2.94"
} }
}, },
"ShareActionBarButton": { "ShareActionBarButton": {
@@ -1934,7 +1990,7 @@
"native_description": "Show video preference settings for users to control video playback types", "native_description": "Show video preference settings for users to control video playback types",
"version": { "version": {
"fr": "1.2.86", "fr": "1.2.86",
"to": "" "to": "1.2.95"
} }
}, },
"VideoQualityEnabled": { "VideoQualityEnabled": {
@@ -1984,6 +2040,54 @@
"fr": "1.2.89", "fr": "1.2.89",
"to": "" "to": ""
} }
},
"LeaderboardEmptySlotHandling": {
"name": "enableLeaderboardEmptySlotHandling",
"native_description": "Config for clearing the current leaderboard ad and hiding the leaderboard container when the ad slot returns an empty response",
"version": {
"fr": "1.2.94",
"to": ""
}
},
"EntityHeaderNew": {
"name": "enableEntityHeaderNew",
"native_description": "Enable the new entity header design",
"version": {
"fr": "1.2.95",
"to": ""
}
},
"ContextMenuLayoutV2": {
"name": "enableContextMenuLayoutV2",
"native_description": "Enables the Context Menu 2.0 layout",
"version": {
"fr": "1.2.96",
"to": ""
}
},
"CanvasContextMenuToggle": {
"name": "enableCanvasContextMenuToggle",
"native_description": "Enables the Canvas context menu toggle",
"version": {
"fr": "1.2.96",
"to": ""
}
},
"MusicVideos": {
"name": "enableMusicVideos",
"native_description": "Whether music videos are available for the current user. Override to true for supported products and markets",
"version": {
"fr": "1.2.96",
"to": ""
}
},
"VideoShelfPreviews": {
"name": "enableVideoShelfPreviews",
"native_description": "Enable video previews on hover in video shelf cards",
"version": {
"fr": "1.2.96",
"to": ""
}
} }
}, },
"CustomExp": { "CustomExp": {
@@ -2084,7 +2188,7 @@
"value": "CONTROL", "value": "CONTROL",
"version": { "version": {
"fr": "1.2.31", "fr": "1.2.31",
"to": "" "to": "1.2.93"
} }
}, },
"CanvasNpv": { "CanvasNpv": {
+518 -25
View File
@@ -2,7 +2,7 @@
param param
( (
[Parameter(HelpMessage = 'Latest recommended Spotify version for Windows 10+.')] [Parameter(HelpMessage = 'Latest recommended Spotify version for Windows 10+.')]
[string]$latest_full = "1.2.94", [string]$latest_full = "1.2.96",
[Parameter(HelpMessage = 'Latest supported Spotify version for Windows 7-8.1')] [Parameter(HelpMessage = 'Latest supported Spotify version for Windows 7-8.1')]
[string]$last_win7_full = "1.2.5.1006.g22820f93", [string]$last_win7_full = "1.2.5.1006.g22820f93",
@@ -30,6 +30,9 @@ param
[Parameter(HelpMessage = 'Skip pause before exit')] [Parameter(HelpMessage = 'Skip pause before exit')]
[switch]$no_pause, [switch]$no_pause,
[Parameter(HelpMessage = 'Skip Microsoft Defender exclusions')]
[switch]$defender_exclusions_off,
[Parameter(HelpMessage = "Use github.io mirror instead of raw.githubusercontent.")] [Parameter(HelpMessage = "Use github.io mirror instead of raw.githubusercontent.")]
[Alias("m")] [Alias("m")]
[switch]$mirror, [switch]$mirror,
@@ -325,7 +328,8 @@ function Format-LanguageCode {
return $returnCode return $returnCode
} }
$spotifyDirectory = Join-Path $env:APPDATA 'Spotify' $spotifyRoamingDirectory = Join-Path $env:APPDATA 'Spotify'
$spotifyDirectory = $spotifyRoamingDirectory
$spotifyDirectory2 = Join-Path $env:LOCALAPPDATA 'Spotify' $spotifyDirectory2 = Join-Path $env:LOCALAPPDATA 'Spotify'
# Использовать кастомный путь если указан параметр -SpotifyPath # Использовать кастомный путь если указан параметр -SpotifyPath
@@ -341,6 +345,7 @@ $dll_bak = Join-Path $spotifyDirectory 'Spotify.dll.bak'
$chrome_elf_bak = Join-Path $spotifyDirectory 'chrome_elf.dll.bak' $chrome_elf_bak = Join-Path $spotifyDirectory 'chrome_elf.dll.bak'
$spotifyUninstall = Join-Path ([System.IO.Path]::GetTempPath()) 'SpotifyUninstall.exe' $spotifyUninstall = Join-Path ([System.IO.Path]::GetTempPath()) 'SpotifyUninstall.exe'
$start_menu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Spotify.lnk' $start_menu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Spotify.lnk'
$xpui_spa_patch = Join-Path (Join-Path $spotifyDirectory 'Apps') 'xpui.spa'
$upgrade_client = $false $upgrade_client = $false
$downgrading = $false $downgrading = $false
@@ -1514,6 +1519,226 @@ function DesktopFolder {
return $desktop_folder return $desktop_folder
} }
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
try {
$principal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $identity
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
finally {
$identity.Dispose()
}
}
function ConvertTo-DefenderExclusionPath {
param (
[string[]]$Path
)
$normalizedPaths = foreach ($item in $Path) {
if ([string]::IsNullOrWhiteSpace($item)) { continue }
try {
[IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($item))
}
catch {
Write-Verbose "Invalid Microsoft Defender exclusion path: $item"
}
}
return @($normalizedPaths | Sort-Object -Unique)
}
function ConvertTo-PowerShellStringLiteral {
param (
[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[string]$Value
)
if ($Value.IndexOf([char]34) -ge 0) {
throw 'Double quotes are not valid in Windows paths'
}
$escapedValue = [Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Value)
return "'$escapedValue'"
}
function Add-SpotifyDefenderExclusions {
param (
[Parameter(Mandatory = $true)]
[string[]]$Path,
[Parameter(Mandatory = $true)]
[string[]]$ProcessPath
)
[string[]]$paths = @(ConvertTo-DefenderExclusionPath -Path $Path)
[string[]]$processPaths = @(ConvertTo-DefenderExclusionPath -Path $ProcessPath)
if ($paths.Count -eq 0 -and $processPaths.Count -eq 0) { return }
$defenderPrompt = $lang.DefenderPrompt
$defenderAdded = $lang.DefenderAdded
try {
$isAdministrator = Test-IsAdministrator
if (-not $isAdministrator) {
do {
$defenderChoice = Read-Host -Prompt $defenderPrompt
Write-Host
if ($defenderChoice -notmatch '^y$|^n$') { incorrectValue }
}
while ($defenderChoice -notmatch '^y$|^n$')
if ($defenderChoice -eq 'n') { return }
}
if ($isAdministrator) {
$modulePath = [IO.Path]::Combine(
[Environment]::SystemDirectory,
'WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1'
)
$null = Import-Module -Name $modulePath -Force -ErrorAction Stop
$preferences = Defender\Get-MpPreference -ErrorAction Stop
$currentPaths = @($preferences.ExclusionPath)
$currentProcessPaths = @($preferences.ExclusionProcess)
$missingPaths = @($paths | Where-Object { $_ -notin $currentPaths })
$missingProcessPaths = @($processPaths | Where-Object { $_ -notin $currentProcessPaths })
if ($missingPaths.Count -gt 0) {
$null = Defender\Add-MpPreference -ExclusionPath $missingPaths -Force -ErrorAction Stop
}
if ($missingProcessPaths.Count -gt 0) {
$null = Defender\Add-MpPreference -ExclusionProcess $missingProcessPaths -Force -ErrorAction Stop
}
$verified = $false
for ($attempt = 0; $attempt -lt 5; $attempt++) {
$updatedPreferences = Defender\Get-MpPreference -ErrorAction Stop
$updatedPaths = @($updatedPreferences.ExclusionPath)
$updatedProcessPaths = @($updatedPreferences.ExclusionProcess)
$remainingPaths = @($paths | Where-Object { $_ -notin $updatedPaths })
$remainingProcessPaths = @($processPaths | Where-Object { $_ -notin $updatedProcessPaths })
if ($remainingPaths.Count -eq 0 -and $remainingProcessPaths.Count -eq 0) {
$verified = $true
break
}
if ($attempt -lt 4) { Start-Sleep -Milliseconds 250 }
}
if (-not $verified) {
throw 'Microsoft Defender exclusion verification failed'
}
Write-Host $defenderAdded
Write-Host
return
}
$pathLiterals = @($paths | ForEach-Object {
ConvertTo-PowerShellStringLiteral -Value $_
}) -join ', '
$processPathLiterals = @($processPaths | ForEach-Object {
ConvertTo-PowerShellStringLiteral -Value $_
}) -join ', '
$command = @(
"`$ErrorActionPreference = 'Stop';"
'try {'
"[string[]]`$paths = @($pathLiterals);"
"[string[]]`$processPaths = @($processPathLiterals);"
"`$modulePath = [IO.Path]::Combine([Environment]::SystemDirectory, 'WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1');"
'$null = Import-Module -Name $modulePath -Force -ErrorAction Stop;'
'$preferences = Defender\Get-MpPreference -ErrorAction Stop;'
'$currentPaths = @($preferences.ExclusionPath);'
'$currentProcessPaths = @($preferences.ExclusionProcess);'
'$missingPaths = @($paths | Where-Object { $_ -notin $currentPaths });'
'$missingProcessPaths = @($processPaths | Where-Object { $_ -notin $currentProcessPaths });'
'if ($missingPaths.Count -gt 0) { $null = Defender\Add-MpPreference -ExclusionPath $missingPaths -Force -ErrorAction Stop };'
'if ($missingProcessPaths.Count -gt 0) { $null = Defender\Add-MpPreference -ExclusionProcess $missingProcessPaths -Force -ErrorAction Stop };'
'$verified = $false;'
'for ($attempt = 0; $attempt -lt 5; $attempt++) {'
'$updatedPreferences = Defender\Get-MpPreference -ErrorAction Stop;'
'$updatedPaths = @($updatedPreferences.ExclusionPath);'
'$updatedProcessPaths = @($updatedPreferences.ExclusionProcess);'
'$remainingPaths = @($paths | Where-Object { $_ -notin $updatedPaths });'
'$remainingProcessPaths = @($processPaths | Where-Object { $_ -notin $updatedProcessPaths });'
'if ($remainingPaths.Count -eq 0 -and $remainingProcessPaths.Count -eq 0) { $verified = $true; break };'
'if ($attempt -lt 4) { Start-Sleep -Milliseconds 250 };'
'}'
"if (-not `$verified) { throw 'Microsoft Defender exclusion verification failed' };"
'exit 0'
'}'
'catch {'
'exit 1'
'}'
) -join ' '
$systemDirectoryName = if (
[Environment]::Is64BitOperatingSystem -and
-not [Environment]::Is64BitProcess
) {
'Sysnative'
}
else {
'System32'
}
$powerShellPath = Join-Path $env:SystemRoot "$systemDirectoryName\WindowsPowerShell\v1.0\powershell.exe"
$startProcessParams = @{
FilePath = $powerShellPath
ArgumentList = "-NoLogo -NoProfile -Command `"$command`""
Verb = 'RunAs'
WindowStyle = 'Normal'
Wait = $true
PassThru = $true
ErrorAction = 'Stop'
}
$process = Start-Process @startProcessParams
if ($process.ExitCode -ne 0) {
throw "Elevated Microsoft Defender command failed with exit code $($process.ExitCode)"
}
Write-Host $defenderAdded
Write-Host
}
catch {
Write-Warning $lang.DefenderFailed
Write-Verbose $_.Exception.Message
Write-Host
}
}
if (-not $defender_exclusions_off) {
try {
$desktopShortcut = Join-Path (DesktopFolder) 'Spotify.lnk'
$currentProcess = [Diagnostics.Process]::GetCurrentProcess()
try {
$defenderPowerShellProcess = $currentProcess.MainModule.FileName
}
finally {
$currentProcess.Dispose()
}
$defenderExclusionPaths = @(
$spotifyRoamingDirectory
$spotifyDirectory2
$spotifyExecutable
$spotifyDll
$chrome_elf
$xpui_spa_patch
$desktopShortcut
$start_menu
)
$null = Add-SpotifyDefenderExclusions `
-Path $defenderExclusionPaths `
-ProcessPath $defenderPowerShellProcess
}
catch {
Write-Warning $lang.DefenderFailed
Write-Verbose $_.Exception.Message
Write-Host
}
}
function Kill-Spotify { function Kill-Spotify {
param ( param (
[int]$maxAttempts = 5 [int]$maxAttempts = 5
@@ -2841,7 +3066,17 @@ public static class BinaryScanner {
} }
"@ "@
Add-Type -TypeDefinition $csharpCode try {
Add-Type -TypeDefinition $csharpCode -ErrorAction Stop
}
catch {
$compilerError = $_.Exception.Message -split '\r?\n' | Select-Object -First 1
throw "BinaryScanner initialization failed: $compilerError"
}
if (-not ([System.Management.Automation.PSTypeName]'BinaryScanner').Type) {
throw "BinaryScanner initialization failed: Type was not loaded"
}
} }
function Convert-HexStringToBytes { function Convert-HexStringToBytes {
@@ -2974,7 +3209,13 @@ function Reset-Dll-Sign {
$TargetStringText = "Check failed: sep_pos != std::wstring::npos." $TargetStringText = "Check failed: sep_pos != std::wstring::npos."
$Patch_x64 = Convert-HexStringToBytes "B8 01 00 00 00 C3" $Patch_x64 = Convert-HexStringToBytes "B8 01 00 00 00 C3"
$Patch_ARM64 = Convert-HexStringToBytes "20 00 80 52 C0 03 5F D6" $Patch_ARM64 = Convert-HexStringToBytes "20 00 80 52 C0 03 5F D6"
Initialize-BinaryScanner try {
Initialize-BinaryScanner
}
catch {
Write-Warning $_.Exception.Message
Stop-Script
}
Write-Verbose "Loading file: $FilePath" Write-Verbose "Loading file: $FilePath"
if (-not (Test-Path $FilePath)) { if (-not (Test-Path $FilePath)) {
@@ -3006,8 +3247,21 @@ function Reset-Dll-Sign {
Write-Verbose "Searching for function..." Write-Verbose "Searching for function..."
$StringBytes = [System.Text.Encoding]::ASCII.GetBytes($TargetStringText) $StringBytes = [System.Text.Encoding]::ASCII.GetBytes($TargetStringText)
$StringOffset = [BinaryScanner]::FindBytes($bytes, $StringBytes, 0) try {
if ($StringOffset -eq -1) { $StringOffset = & {
$ErrorActionPreference = "Stop"
[BinaryScanner]::FindBytes($bytes, $StringBytes, 0)
}
}
catch {
Write-Warning ("BinaryScanner scan failed for Spotify.dll: {0}" -f $_.Exception.Message)
Stop-Script
}
if ($null -eq $StringOffset) {
Write-Warning "BinaryScanner returned no result for Spotify.dll"
Stop-Script
}
if ($StringOffset -lt 0) {
Write-Warning "String not found in Spotify.dll" Write-Warning "String not found in Spotify.dll"
Stop-Script Stop-Script
} }
@@ -3155,6 +3409,250 @@ function Read-X64BlockSlotsField {
throw "Unexpected block_slots field instruction" throw "Unexpected block_slots field instruction"
} }
function Get-X64BlockSlotsMapperSequenceEnum {
param(
[byte[]]$Bytes,
[int]$SequenceOffset,
[int]$BranchOffset,
[bool]$MatchTaken = $true
)
$movEcxR8d = Convert-HexStringToBytes '41 8B C8'
$subEcx8 = Convert-HexStringToBytes '83 E9'
$subEcx32 = Convert-HexStringToBytes '81 E9'
$cmpEcx8 = Convert-HexStringToBytes '83 F9'
$cmpEcx32 = Convert-HexStringToBytes '81 F9'
if (-not [BinaryScanner]::MatchBytes($Bytes, $SequenceOffset, $movEcxR8d)) {
return @()
}
$matches = @()
for ($enumValue = 1; $enumValue -le 0x3FF; $enumValue++) {
$ecx = [int64]$enumValue
$zeroFlag = $false
$hasFlags = $false
$cursor = $SequenceOffset + $movEcxR8d.Length
$valid = $true
$matched = $false
while ($cursor -le $BranchOffset) {
if ([BinaryScanner]::MatchBytes($Bytes, $cursor, $subEcx8)) {
$ecx -= [int64](Read-X64SignedByte -Bytes $Bytes -Offset ($cursor + 2))
$zeroFlag = ($ecx -eq 0)
$hasFlags = $true
$cursor += 3
continue
}
if ([BinaryScanner]::MatchBytes($Bytes, $cursor, $subEcx32)) {
$ecx -= [int64][BitConverter]::ToInt32($Bytes, $cursor + 2)
$zeroFlag = ($ecx -eq 0)
$hasFlags = $true
$cursor += 6
continue
}
if ([BinaryScanner]::MatchBytes($Bytes, $cursor, $cmpEcx8)) {
$compareValue = [int64](Read-X64SignedByte -Bytes $Bytes -Offset ($cursor + 2))
$zeroFlag = ($ecx -eq $compareValue)
$hasFlags = $true
$cursor += 3
continue
}
if ([BinaryScanner]::MatchBytes($Bytes, $cursor, $cmpEcx32)) {
$compareValue = [int64][BitConverter]::ToInt32($Bytes, $cursor + 2)
$zeroFlag = ($ecx -eq $compareValue)
$hasFlags = $true
$cursor += 6
continue
}
$branchLength = 0
$branchOnEqual = $false
if (($cursor + 2) -le $Bytes.Length -and ($Bytes[$cursor] -eq 0x74 -or $Bytes[$cursor] -eq 0x75)) {
$branchLength = 2
$branchOnEqual = ($Bytes[$cursor] -eq 0x74)
}
elseif (($cursor + 6) -le $Bytes.Length -and $Bytes[$cursor] -eq 0x0F -and
($Bytes[$cursor + 1] -eq 0x84 -or $Bytes[$cursor + 1] -eq 0x85)) {
$branchLength = 6
$branchOnEqual = ($Bytes[$cursor + 1] -eq 0x84)
}
else {
$valid = $false
break
}
if (-not $hasFlags) {
$valid = $false
break
}
$branchTaken = if ($branchOnEqual) { $zeroFlag } else { -not $zeroFlag }
if ($cursor -eq $BranchOffset) {
$matched = if ($MatchTaken) { $branchTaken } else { -not $branchTaken }
break
}
if ($branchTaken) {
$valid = $false
break
}
$cursor += $branchLength
}
if ($valid -and $matched) {
$matches += [uint32]$enumValue
}
}
return $matches
}
function Get-BlockSlotsMapperEnumValue {
param(
[byte[]]$Bytes,
[object]$PeInfo,
[int64]$MapperStartRva,
[int64]$MapperEndRva,
[int[]]$AnchorRefs
)
$mapperOffset = Get-PEOffsetFromRva -Sections $PeInfo.Sections -Rva $MapperStartRva
if ($null -eq $mapperOffset) {
throw 'slot_is_disabled mapper offset was not found'
}
$mapperOffset = [int]$mapperOffset
$mapperEndOffset = [int64]$mapperOffset + ($MapperEndRva - $MapperStartRva)
if ($mapperEndOffset -le $mapperOffset -or $mapperEndOffset -gt $Bytes.Length) {
throw 'slot_is_disabled mapper range is invalid'
}
$stringCases = @(
[PSCustomObject]@{
PrefixLength = 20
Pattern = Convert-HexStringToBytes '0F 57 C0 0F 11 02 48 89 7A 10 48 89 7A 18 41 B8 10 00 00 00 48 8D 15 00 00 00 00'
Mask = Convert-HexStringToBytes 'FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00 00 00 00'
DispatchMode = 'Target'
},
[PSCustomObject]@{
PrefixLength = 18
Pattern = Convert-HexStringToBytes '0F 57 C0 0F 11 02 48 89 7A 10 48 89 7A 18 44 8D 41 0F 48 8D 15 00 00 00 00'
Mask = Convert-HexStringToBytes 'FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00 00 00 00'
DispatchMode = 'Fallthrough'
}
)
$movEcxR8d = Convert-HexStringToBytes '41 8B C8'
$cmpR8d8 = Convert-HexStringToBytes '41 83 F8'
$cmpR8d32 = Convert-HexStringToBytes '41 81 F8'
$enumValues = @{}
foreach ($anchorRef in $AnchorRefs) {
if (([int64]$anchorRef + 7) -gt $mapperEndOffset) {
continue
}
foreach ($stringCase in $stringCases) {
$caseOffset = [int]$anchorRef - $stringCase.PrefixLength
if ($caseOffset -lt $mapperOffset -or
-not [BinaryScanner]::MatchMaskedBytes($Bytes, $caseOffset, $stringCase.Pattern, $stringCase.Mask)) {
continue
}
$dispatches = @()
if ($stringCase.DispatchMode -eq 'Target') {
for ($branchOffset = $mapperOffset; $branchOffset -lt $caseOffset; $branchOffset++) {
if (($Bytes[$branchOffset] -eq 0x74 -or $Bytes[$branchOffset] -eq 0x75) -and
($branchOffset + 2) -le $mapperEndOffset) {
$branchOnEqual = ($Bytes[$branchOffset] -eq 0x74)
$branchTarget = [int64]$branchOffset + 2 +
(Read-X64SignedByte -Bytes $Bytes -Offset ($branchOffset + 1))
}
elseif ($Bytes[$branchOffset] -eq 0x0F -and ($branchOffset + 6) -le $mapperEndOffset -and
($Bytes[$branchOffset + 1] -eq 0x84 -or $Bytes[$branchOffset + 1] -eq 0x85)) {
$branchOnEqual = ($Bytes[$branchOffset + 1] -eq 0x84)
$branchTarget = [int64]$branchOffset + 6 + [BitConverter]::ToInt32($Bytes, $branchOffset + 2)
}
else {
continue
}
if ($branchTarget -eq $caseOffset) {
$dispatches += [PSCustomObject]@{
Offset = $branchOffset
BranchOnEqual = $branchOnEqual
MatchTaken = $true
}
}
}
}
else {
if ($caseOffset -ge ($mapperOffset + 6) -and $Bytes[$caseOffset - 6] -eq 0x0F -and
($Bytes[$caseOffset - 5] -eq 0x84 -or $Bytes[$caseOffset - 5] -eq 0x85)) {
$branchOffset = $caseOffset - 6
$branchOnEqual = ($Bytes[$caseOffset - 5] -eq 0x84)
$branchTarget = [int64]$caseOffset + [BitConverter]::ToInt32($Bytes, $caseOffset - 4)
}
elseif ($caseOffset -ge ($mapperOffset + 2) -and
($Bytes[$caseOffset - 2] -eq 0x74 -or $Bytes[$caseOffset - 2] -eq 0x75)) {
$branchOffset = $caseOffset - 2
$branchOnEqual = ($Bytes[$caseOffset - 2] -eq 0x74)
$branchTarget = [int64]$caseOffset +
(Read-X64SignedByte -Bytes $Bytes -Offset ($caseOffset - 1))
}
else {
continue
}
if ($branchTarget -lt $mapperOffset -or $branchTarget -ge $mapperEndOffset -or $branchTarget -eq $caseOffset) {
continue
}
$dispatches += [PSCustomObject]@{
Offset = $branchOffset
BranchOnEqual = $branchOnEqual
MatchTaken = $false
}
}
foreach ($dispatch in $dispatches) {
$branchOffset = [int]$dispatch.Offset
$selectsEquality = ($dispatch.BranchOnEqual -eq $dispatch.MatchTaken)
if ($selectsEquality -and $branchOffset -ge ($mapperOffset + 4) -and
[BinaryScanner]::MatchBytes($Bytes, $branchOffset - 4, $cmpR8d8)) {
$enumValue = [int64](Read-X64SignedByte -Bytes $Bytes -Offset ($branchOffset - 1))
if ($enumValue -gt 0 -and $enumValue -le 0x3FF) {
$enumValues['{0:X}' -f $enumValue] = [uint32]$enumValue
}
}
if ($selectsEquality -and $branchOffset -ge ($mapperOffset + 7) -and
[BinaryScanner]::MatchBytes($Bytes, $branchOffset - 7, $cmpR8d32)) {
$enumValue = [BitConverter]::ToUInt32($Bytes, $branchOffset - 4)
if ($enumValue -gt 0 -and $enumValue -le 0x3FF) {
$enumValues['{0:X}' -f $enumValue] = $enumValue
}
}
$sequenceStart = [Math]::Max($mapperOffset, $branchOffset - 0x100)
for ($candidateOffset = $sequenceStart; $candidateOffset -lt $branchOffset; $candidateOffset++) {
if (-not [BinaryScanner]::MatchBytes($Bytes, $candidateOffset, $movEcxR8d)) {
continue
}
$sequenceEnums = @(Get-X64BlockSlotsMapperSequenceEnum `
-Bytes $Bytes `
-SequenceOffset $candidateOffset `
-BranchOffset $branchOffset `
-MatchTaken $dispatch.MatchTaken)
foreach ($enumValue in $sequenceEnums) {
$enumValues['{0:X}' -f [uint32]$enumValue] = [uint32]$enumValue
}
}
}
}
}
if ($enumValues.Count -ne 1) {
throw "Expected one slot_is_disabled enum value, found $($enumValues.Count)"
}
return [uint32](@($enumValues.Values)[0])
}
function Get-BlockSlotsPredicateInfo { function Get-BlockSlotsPredicateInfo {
param( param(
[byte[]]$Bytes, [byte[]]$Bytes,
@@ -3311,12 +3809,6 @@ function Get-BlockSlotsPredicateInfo {
} }
} }
if ($flagField.Value -ne ($valueField.Value + 1) -or
$valueField.Value -ne ($gateField.Value + 0x18) -or
$fallbackField.Value -ne ($gateField.Value - 8)) {
return $null
}
$jumpDisplacement = [int64]$returnFalseOffset - ([int64]$patchOffset + 2) $jumpDisplacement = [int64]$returnFalseOffset - ([int64]$patchOffset + 2)
if ($jumpDisplacement -lt 0 -or $jumpDisplacement -gt 0x7F) { if ($jumpDisplacement -lt 0 -or $jumpDisplacement -gt 0x7F) {
return $null return $null
@@ -3401,12 +3893,23 @@ function Find-BlockSlotsBinaryPatchLocation {
if ($anchorFunctionSize -lt 0x100 -or $anchorFunctionSize -gt 0x4000) { if ($anchorFunctionSize -lt 0x100 -or $anchorFunctionSize -gt 0x4000) {
throw 'Unexpected slot_is_disabled mapper function size' throw 'Unexpected slot_is_disabled mapper function size'
} }
$anchorFunctions['{0:X}' -f [int64]$anchorFunction[0]] = $true $anchorFunctions['{0:X}' -f [int64]$anchorFunction[0]] = [PSCustomObject]@{
StartRva = [int64]$anchorFunction[0]
EndRva = [int64]$anchorFunction[1]
}
} }
if ($anchorFunctions.Count -ne 1) { if ($anchorFunctions.Count -ne 1) {
throw "Expected one slot_is_disabled mapper function, found $($anchorFunctions.Count)" throw "Expected one slot_is_disabled mapper function, found $($anchorFunctions.Count)"
} }
$mapperFunction = @($anchorFunctions.Values)[0]
$slotDisabledEnum = Get-BlockSlotsMapperEnumValue `
-Bytes $Bytes `
-PeInfo $PeInfo `
-MapperStartRva $mapperFunction.StartRva `
-MapperEndRva $mapperFunction.EndRva `
-AnchorRefs $anchorRefs
$callPatterns = @( $callPatterns = @(
[PSCustomObject]@{ [PSCustomObject]@{
Pattern = Convert-HexStringToBytes 'E8 00 00 00 00 84 C0 75 00 BA 00 00 00 00' Pattern = Convert-HexStringToBytes 'E8 00 00 00 00 84 C0 75 00 BA 00 00 00 00'
@@ -3425,7 +3928,6 @@ function Find-BlockSlotsBinaryPatchLocation {
$textStart = [int]$text.RawPtr $textStart = [int]$text.RawPtr
$textEnd = [int64]$text.RawPtr + [int64]$text.RawSize $textEnd = [int64]$text.RawPtr + [int64]$text.RawSize
$validatedTargets = @{} $validatedTargets = @{}
$ambiguousEnums = @{}
foreach ($callPattern in $callPatterns) { foreach ($callPattern in $callPatterns) {
$searchOffset = $textStart $searchOffset = $textStart
@@ -3444,7 +3946,7 @@ function Find-BlockSlotsBinaryPatchLocation {
try { try {
$enumValue = [BitConverter]::ToUInt32($Bytes, $callOffset + $callPattern.EnumOffset) $enumValue = [BitConverter]::ToUInt32($Bytes, $callOffset + $callPattern.EnumOffset)
if ($enumValue -eq 0 -or $enumValue -gt 0x3FF) { if ($enumValue -ne $slotDisabledEnum) {
continue continue
} }
@@ -3496,12 +3998,7 @@ function Find-BlockSlotsBinaryPatchLocation {
} }
} }
else { else {
if ([uint32]$validatedTargets[$key].EnumValue -ne [uint32]$enumValue) { $validatedTargets[$key].CallerCount++
$ambiguousEnums[$key] = $true
}
else {
$validatedTargets[$key].CallerCount++
}
} }
} }
catch { catch {
@@ -3513,9 +4010,6 @@ function Find-BlockSlotsBinaryPatchLocation {
if ($validatedTargets.Count -eq 0) { if ($validatedTargets.Count -eq 0) {
throw 'block_slots semantic function was not found' throw 'block_slots semantic function was not found'
} }
if ($ambiguousEnums.Count -gt 0) {
throw 'block_slots semantic callers use different enum values'
}
if ($validatedTargets.Count -ne 1) { if ($validatedTargets.Count -ne 1) {
throw "Expected one block_slots semantic function, found $($validatedTargets.Count)" throw "Expected one block_slots semantic function, found $($validatedTargets.Count)"
} }
@@ -3814,7 +4308,6 @@ Write-Host ($lang).ModSpoti`n
Remove-TempDirectory -Directory $tempDirectory Remove-TempDirectory -Directory $tempDirectory
$xpui_spa_patch = Join-Path (Join-Path $spotifyDirectory 'Apps') 'xpui.spa'
$xpui_js_patch = Join-Path (Join-Path (Join-Path $spotifyDirectory 'Apps') 'xpui') 'xpui.js' $xpui_js_patch = Join-Path (Join-Path (Join-Path $spotifyDirectory 'Apps') 'xpui') 'xpui.js'
$test_spa = Test-Path -Path $xpui_spa_patch $test_spa = Test-Path -Path $xpui_spa_patch
$test_js = Test-Path -Path $xpui_js_patch $test_js = Test-Path -Path $xpui_js_patch
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════════════╗ ╔════════════════════════════════════════╗
║ Сардэчна запрашаем у SpotX для Windows ║ ║ Сардэчна запрашаем у SpotX для Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════════════╗ ╔═══════════════════════════════════╗
║ উইন্ডোজ স্পটএক্স এ আপনাকে স্বাগতম ║ ║ উইন্ডোজ স্পটএক্স এ আপনাকে স্বাগতম ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═════════════════════════════╗ ╔═════════════════════════════╗
║ Vítejte v SpotX pro Windows ║ ║ Vítejte v SpotX pro Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════════╗ ╔══════════════════════════════════╗
║ Willkommen bei SpotX für Windows ║ ║ Willkommen bei SpotX für Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════════╗ ╔════════════════════════════════════╗
║ Καλώς ήρθατε στο SpotX για Windows ║ ║ Καλώς ήρθατε στο SpotX για Windows ║
+3
View File
@@ -36,6 +36,9 @@
Error = "Error" Error = "Error"
FileLocBroken = "Location of Spotify files is broken, uninstall Spotify client and run the script again" FileLocBroken = "Location of Spotify files is broken, uninstall Spotify client and run the script again"
Spicetify = "Spicetify detected, it must be installed after SpotX, open recommended actions in FAQ ? [Y/N]" Spicetify = "Spicetify detected, it must be installed after SpotX, open recommended actions in FAQ ? [Y/N]"
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
NoRestore = "SpotX has already been installed, xpui.bak not found. `nPlease uninstall Spotify client and run Install.bat again" NoRestore = "SpotX has already been installed, xpui.bak not found. `nPlease uninstall Spotify client and run Install.bat again"
InstallComplete = "installation completed" InstallComplete = "installation completed"
HostInfo = "Unwanted URLs found in hosts file" HostInfo = "Unwanted URLs found in hosts file"
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═════════════════════════════════════════════╗ ╔═════════════════════════════════════════════╗
║ Le damos la bienvenida a SpotX para Windows ║ ║ Le damos la bienvenida a SpotX para Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════════╗ ╔═══════════════════════════════╗
║ Tervetuloa SpotX:n Windowsiin ║ ║ Tervetuloa SpotX:n Windowsiin ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════╗
║ Maligayang pagdating sa SpotX para sa Windows ║ ║ Maligayang pagdating sa SpotX para sa Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════════╗ ╔══════════════════════════════════╗
║ Bienvenue sur SpotX pour Windows ║ ║ Bienvenue sur SpotX pour Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════════╗ ╔════════════════════════════════════╗
║ SpotX for Windows में आपका स्वागत है ║ ║ SpotX for Windows में आपका स्वागत है ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════════════════╗ ╔════════════════════════════════════════════╗
║ Üdvözöllek a SpotX Windowsos telepítőjében ║ ║ Üdvözöllek a SpotX Windowsos telepítőjében ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════════════════╗ ╔═══════════════════════════════════════╗
║ Selamat datang di SpotX untuk Windows ║ ║ Selamat datang di SpotX untuk Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════════╗ ╔═══════════════════════════════╗
║ Benvenuti a SpotX per Windows ║ ║ Benvenuti a SpotX per Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═══════════════════════════╗ ╔═══════════════════════════╗
║ SpotX Windows用にようこそ ║ ║ SpotX Windows用にようこそ ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════╗ ╔════════════════════════════╗
║ Witamy w SpotX dla Windows ║ ║ Witamy w SpotX dla Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═════════════════════════════════╗ ╔═════════════════════════════════╗
║ Bem-vindo ao SpotX para Windows ║ ║ Bem-vindo ao SpotX para Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════════════╗ ╔════════════════════════════════════════╗
║ Bine ați venit la SpotX pentru Windows ║ ║ Bine ați venit la SpotX pentru Windows ║
+3
View File
@@ -36,6 +36,9 @@
Error = "Ошибка" Error = "Ошибка"
FileLocBroken = "Расположение файлов Spotify нарушено, удалите клиент Spotify и снова запустите скрипт" FileLocBroken = "Расположение файлов Spotify нарушено, удалите клиент Spotify и снова запустите скрипт"
Spicetify = "Обнаружен Spicetify, он должен быть установлен после SpotX, открыть рекомендуемые действия в FAQ ? [Y/N]" Spicetify = "Обнаружен Spicetify, он должен быть установлен после SpotX, открыть рекомендуемые действия в FAQ ? [Y/N]"
DefenderPrompt = "Добавить исключения в Microsoft Defender для предотвращения ложных срабатываний? [Y/N]"
DefenderAdded = "Исключения добавлены в Microsoft Defender"
DefenderFailed = "Исключения не добавлены в Microsoft Defender"
NoRestore = "SpotX уже был установлен, но файл для восстановления xpui.bak не найден. `nУдалите клиент Spotify и снова запустите Install.bat" NoRestore = "SpotX уже был установлен, но файл для восстановления xpui.bak не найден. `nУдалите клиент Spotify и снова запустите Install.bat"
InstallComplete = "Установка завершена" InstallComplete = "Установка завершена"
HostInfo = "В файле hosts найдены нежелательные Url-адреса" HostInfo = "В файле hosts найдены нежелательные Url-адреса"
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ Welcome to SpotX for Windows ║ ║ Welcome to SpotX for Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════════╗ ╔══════════════════════════════════╗
║ Välkommen till SpotX för Windows ║ ║ Välkommen till SpotX för Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════╗ ╔════════════════════════════════╗
║ Windows SpotXக்கு வரவேற்கிறோம் ║ ║ Windows SpotXக்கு வரவேற்கிறோம் ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════════╗ ╔══════════════════════════════════╗
║ Windows için SpotX'e Hoşgeldiniz ║ ║ Windows için SpotX'e Hoşgeldiniz ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔══════════════════════════════════════╗ ╔══════════════════════════════════════╗
║ Ласкаво просимо до SpotX для Windows ║ ║ Ласкаво просимо до SpotX для Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═════════════════════════════════════╗ ╔═════════════════════════════════════╗
║ Chào mừng đến với SpotX cho Windows ║ ║ Chào mừng đến với SpotX cho Windows ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔═════════════════════════════╗ ╔═════════════════════════════╗
║ 歡迎使用 SpotX Windows 版 ║ ║ 歡迎使用 SpotX Windows 版 ║
+3
View File
@@ -1,4 +1,7 @@
[PSCustomObject]@{ [PSCustomObject]@{
DefenderPrompt = "Add exclusions to Microsoft Defender to prevent false positives? [Y/N]"
DefenderAdded = "Exclusions added to Microsoft Defender"
DefenderFailed = "Microsoft Defender exclusions were not added"
Welcome = " Welcome = "
╔════════════════════════════════╗ ╔════════════════════════════════╗
║ 欢迎使用 SpotX 的 Windows 版本 ║ ║ 欢迎使用 SpotX 的 Windows 版本 ║