mirror of
https://github.com/SpotX-Official/SpotX.git
synced 2026-08-10 01:51:09 +10:00
Compare commits
5
Commits
e22aba4ed4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a179d3cf0 | ||
|
|
c2cc808ef6 | ||
|
|
50bc5a6526 | ||
|
|
3d1ddd6850 | ||
|
|
afb4c3fcd1 |
@@ -1,38 +1,53 @@
|
||||
name: Check Spotx for the latest Spotify
|
||||
run-name: Check SpotX for Spotify ${{ inputs.version || 'latest' }}
|
||||
|
||||
on:
|
||||
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:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Spotx
|
||||
shell: powershell
|
||||
env:
|
||||
SPOTIFY_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
$jsonUrl = "https://raw.githubusercontent.com/LoaderSpot/table/refs/heads/main/table/versions.json"
|
||||
$resp = Invoke-RestMethod -Uri $jsonUrl
|
||||
$spotifyVersion = ([string]$env:SPOTIFY_VERSION).Trim()
|
||||
|
||||
# Use fullversion because installer names include architecture suffixes
|
||||
$latestEntry = $resp.PSObject.Properties | Select-Object -First 1
|
||||
if (-not $latestEntry) {
|
||||
throw "No Spotify versions found in $jsonUrl"
|
||||
if ([string]::IsNullOrWhiteSpace($spotifyVersion)) {
|
||||
$jsonUrl = "https://raw.githubusercontent.com/LoaderSpot/table/refs/heads/main/table/versions.json"
|
||||
$resp = Invoke-RestMethod -Uri $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
|
||||
if ([string]::IsNullOrWhiteSpace($spotifyFullVersion) -or $spotifyFullVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
|
||||
throw "Invalid Spotify fullversion for $($latestEntry.Name)"
|
||||
}
|
||||
Write-Host "Selected Spotify version: $spotifyVersion"
|
||||
|
||||
# Incoming parameters
|
||||
$parameters = @{
|
||||
Verbose = $true
|
||||
new_theme = $true
|
||||
version = $spotifyFullVersion
|
||||
version = $spotifyVersion
|
||||
confirm_spoti_recomended_over = $true
|
||||
cache_limit = 1000
|
||||
block_update_on = $true
|
||||
|
||||
+115
-11
@@ -111,7 +111,7 @@
|
||||
"native_description": "Enable music leavebehinds on eligible playlists for desktop",
|
||||
"version": {
|
||||
"fr": "1.2.10",
|
||||
"to": ""
|
||||
"to": "1.2.93"
|
||||
}
|
||||
},
|
||||
"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)",
|
||||
"version": {
|
||||
"fr": "1.2.34",
|
||||
"to": ""
|
||||
"to": "1.2.94"
|
||||
}
|
||||
},
|
||||
"LimitedAdsLabelsOnPlaylistCards": {
|
||||
@@ -512,7 +512,7 @@
|
||||
"native_description": "Enables the Employee Flag to send to Sentry",
|
||||
"version": {
|
||||
"fr": "1.2.44",
|
||||
"to": ""
|
||||
"to": "1.2.93"
|
||||
}
|
||||
},
|
||||
"HomeImpressions": {
|
||||
@@ -656,7 +656,7 @@
|
||||
"native_description": "Enable Hpto announcements on Home",
|
||||
"version": {
|
||||
"fr": "1.2.65",
|
||||
"to": ""
|
||||
"to": "1.2.94"
|
||||
}
|
||||
},
|
||||
"SponsoredPlaylistV2": {
|
||||
@@ -778,7 +778,7 @@
|
||||
"name": "bypassApplyUpdateCheck",
|
||||
"version": {
|
||||
"fr": "1.2.84",
|
||||
"to": ""
|
||||
"to": "1.2.93"
|
||||
}
|
||||
},
|
||||
"ad_feedback_in_stream": {
|
||||
@@ -836,6 +836,62 @@
|
||||
"fr": "1.2.92",
|
||||
"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": {
|
||||
@@ -962,7 +1018,7 @@
|
||||
"native_description": "Enable silence trimming in podcasts",
|
||||
"version": {
|
||||
"fr": "1.1.99",
|
||||
"to": ""
|
||||
"to": "1.2.93"
|
||||
}
|
||||
},
|
||||
"PlaybackSpeedIncrements": {
|
||||
@@ -1448,7 +1504,7 @@
|
||||
"native_description": "Disable the Premium Only Modal",
|
||||
"version": {
|
||||
"fr": "1.2.39",
|
||||
"to": ""
|
||||
"to": "1.2.95"
|
||||
}
|
||||
},
|
||||
"CenteredLayout": {
|
||||
@@ -1664,7 +1720,7 @@
|
||||
"native_description": "Enable reverse sort direction in Your Library",
|
||||
"version": {
|
||||
"fr": "1.2.60",
|
||||
"to": ""
|
||||
"to": "1.2.94"
|
||||
}
|
||||
},
|
||||
"BackendSearchHistory": {
|
||||
@@ -1910,7 +1966,7 @@
|
||||
"native_description": "Enable BPM and key columns in playlist recommendations",
|
||||
"version": {
|
||||
"fr": "1.2.82",
|
||||
"to": ""
|
||||
"to": "1.2.94"
|
||||
}
|
||||
},
|
||||
"ShareActionBarButton": {
|
||||
@@ -1934,7 +1990,7 @@
|
||||
"native_description": "Show video preference settings for users to control video playback types",
|
||||
"version": {
|
||||
"fr": "1.2.86",
|
||||
"to": ""
|
||||
"to": "1.2.95"
|
||||
}
|
||||
},
|
||||
"VideoQualityEnabled": {
|
||||
@@ -1984,6 +2040,54 @@
|
||||
"fr": "1.2.89",
|
||||
"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": {
|
||||
@@ -2084,7 +2188,7 @@
|
||||
"value": "CONTROL",
|
||||
"version": {
|
||||
"fr": "1.2.31",
|
||||
"to": ""
|
||||
"to": "1.2.93"
|
||||
}
|
||||
},
|
||||
"CanvasNpv": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
param
|
||||
(
|
||||
[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')]
|
||||
[string]$last_win7_full = "1.2.5.1006.g22820f93",
|
||||
@@ -30,6 +30,9 @@ param
|
||||
[Parameter(HelpMessage = 'Skip pause before exit')]
|
||||
[switch]$no_pause,
|
||||
|
||||
[Parameter(HelpMessage = 'Skip Microsoft Defender exclusions')]
|
||||
[switch]$defender_exclusions_off,
|
||||
|
||||
[Parameter(HelpMessage = "Use github.io mirror instead of raw.githubusercontent.")]
|
||||
[Alias("m")]
|
||||
[switch]$mirror,
|
||||
@@ -325,7 +328,8 @@ function Format-LanguageCode {
|
||||
return $returnCode
|
||||
}
|
||||
|
||||
$spotifyDirectory = Join-Path $env:APPDATA 'Spotify'
|
||||
$spotifyRoamingDirectory = Join-Path $env:APPDATA 'Spotify'
|
||||
$spotifyDirectory = $spotifyRoamingDirectory
|
||||
$spotifyDirectory2 = Join-Path $env:LOCALAPPDATA 'Spotify'
|
||||
|
||||
# Использовать кастомный путь если указан параметр -SpotifyPath
|
||||
@@ -341,6 +345,7 @@ $dll_bak = Join-Path $spotifyDirectory 'Spotify.dll.bak'
|
||||
$chrome_elf_bak = Join-Path $spotifyDirectory 'chrome_elf.dll.bak'
|
||||
$spotifyUninstall = Join-Path ([System.IO.Path]::GetTempPath()) 'SpotifyUninstall.exe'
|
||||
$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
|
||||
$downgrading = $false
|
||||
@@ -1514,6 +1519,226 @@ function DesktopFolder {
|
||||
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 {
|
||||
param (
|
||||
[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 {
|
||||
@@ -2974,7 +3209,13 @@ function Reset-Dll-Sign {
|
||||
$TargetStringText = "Check failed: sep_pos != std::wstring::npos."
|
||||
$Patch_x64 = Convert-HexStringToBytes "B8 01 00 00 00 C3"
|
||||
$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"
|
||||
if (-not (Test-Path $FilePath)) {
|
||||
@@ -3006,8 +3247,21 @@ function Reset-Dll-Sign {
|
||||
|
||||
Write-Verbose "Searching for function..."
|
||||
$StringBytes = [System.Text.Encoding]::ASCII.GetBytes($TargetStringText)
|
||||
$StringOffset = [BinaryScanner]::FindBytes($bytes, $StringBytes, 0)
|
||||
if ($StringOffset -eq -1) {
|
||||
try {
|
||||
$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"
|
||||
Stop-Script
|
||||
}
|
||||
@@ -3155,6 +3409,250 @@ function Read-X64BlockSlotsField {
|
||||
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 {
|
||||
param(
|
||||
[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)
|
||||
if ($jumpDisplacement -lt 0 -or $jumpDisplacement -gt 0x7F) {
|
||||
return $null
|
||||
@@ -3401,12 +3893,23 @@ function Find-BlockSlotsBinaryPatchLocation {
|
||||
if ($anchorFunctionSize -lt 0x100 -or $anchorFunctionSize -gt 0x4000) {
|
||||
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) {
|
||||
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 = @(
|
||||
[PSCustomObject]@{
|
||||
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
|
||||
$textEnd = [int64]$text.RawPtr + [int64]$text.RawSize
|
||||
$validatedTargets = @{}
|
||||
$ambiguousEnums = @{}
|
||||
|
||||
foreach ($callPattern in $callPatterns) {
|
||||
$searchOffset = $textStart
|
||||
@@ -3444,7 +3946,7 @@ function Find-BlockSlotsBinaryPatchLocation {
|
||||
|
||||
try {
|
||||
$enumValue = [BitConverter]::ToUInt32($Bytes, $callOffset + $callPattern.EnumOffset)
|
||||
if ($enumValue -eq 0 -or $enumValue -gt 0x3FF) {
|
||||
if ($enumValue -ne $slotDisabledEnum) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3496,12 +3998,7 @@ function Find-BlockSlotsBinaryPatchLocation {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ([uint32]$validatedTargets[$key].EnumValue -ne [uint32]$enumValue) {
|
||||
$ambiguousEnums[$key] = $true
|
||||
}
|
||||
else {
|
||||
$validatedTargets[$key].CallerCount++
|
||||
}
|
||||
$validatedTargets[$key].CallerCount++
|
||||
}
|
||||
}
|
||||
catch {
|
||||
@@ -3513,9 +4010,6 @@ function Find-BlockSlotsBinaryPatchLocation {
|
||||
if ($validatedTargets.Count -eq 0) {
|
||||
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) {
|
||||
throw "Expected one block_slots semantic function, found $($validatedTargets.Count)"
|
||||
}
|
||||
@@ -3814,7 +4308,6 @@ Write-Host ($lang).ModSpoti`n
|
||||
|
||||
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'
|
||||
$test_spa = Test-Path -Path $xpui_spa_patch
|
||||
$test_js = Test-Path -Path $xpui_js_patch
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════════════╗
|
||||
║ Сардэчна запрашаем у SpotX для Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════════════╗
|
||||
║ উইন্ডোজ স্পটএক্স এ আপনাকে স্বাগতম ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═════════════════════════════╗
|
||||
║ Vítejte v SpotX pro Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔══════════════════════════════════╗
|
||||
║ Willkommen bei SpotX für Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════════╗
|
||||
║ Καλώς ήρθατε στο SpotX για Windows ║
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
Error = "Error"
|
||||
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]"
|
||||
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"
|
||||
InstallComplete = "installation completed"
|
||||
HostInfo = "Unwanted URLs found in hosts file"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═════════════════════════════════════════════╗
|
||||
║ Le damos la bienvenida a SpotX para Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════════╗
|
||||
║ Tervetuloa SpotX:n Windowsiin ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════════════════════════╗
|
||||
║ Maligayang pagdating sa SpotX para sa Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔══════════════════════════════════╗
|
||||
║ Bienvenue sur SpotX pour Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════════╗
|
||||
║ SpotX for Windows में आपका स्वागत है ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Üdvözöllek a SpotX Windowsos telepítőjében ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════════════════╗
|
||||
║ Selamat datang di SpotX untuk Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════════╗
|
||||
║ Benvenuti a SpotX per Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═══════════════════════════╗
|
||||
║ SpotX Windows用にようこそ ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════╗
|
||||
║ Witamy w SpotX dla Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═════════════════════════════════╗
|
||||
║ Bem-vindo ao SpotX para Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════════════╗
|
||||
║ Bine ați venit la SpotX pentru Windows ║
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
Error = "Ошибка"
|
||||
FileLocBroken = "Расположение файлов Spotify нарушено, удалите клиент Spotify и снова запустите скрипт"
|
||||
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"
|
||||
InstallComplete = "Установка завершена"
|
||||
HostInfo = "В файле hosts найдены нежелательные Url-адреса"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 to SpotX for Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔══════════════════════════════════╗
|
||||
║ Välkommen till SpotX för Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════╗
|
||||
║ Windows SpotXக்கு வரவேற்கிறோம் ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔══════════════════════════════════╗
|
||||
║ Windows için SpotX'e Hoşgeldiniz ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔══════════════════════════════════════╗
|
||||
║ Ласкаво просимо до SpotX для Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═════════════════════════════════════╗
|
||||
║ Chào mừng đến với SpotX cho Windows ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔═════════════════════════════╗
|
||||
║ 歡迎使用 SpotX Windows 版 ║
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[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 = "
|
||||
╔════════════════════════════════╗
|
||||
║ 欢迎使用 SpotX 的 Windows 版本 ║
|
||||
|
||||
Reference in New Issue
Block a user