Param( [string]$Java = 'java', [switch]$NoDeploy, [switch]$NoUpdate ) Add-Type -AssemblyName System.Windows.Forms Add-type -AssemblyName System.Drawing $ProgressPreference = 'SilentlyContinue' $ErrorActionPreference = 'Stop' function execJava { <# .Description execJava was created to additionally allow providing an array of strings to java arguments if the first argument is a string array, it will launch with arguments in that array otherwise it will launch with all provided arguments #> $SavedErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' if ($args[0].getType().Name -ne "String") { & $Java $args[0] 2>&1 | ForEach-Object{ "$_" } } else { & $Java $args 2>&1 | ForEach-Object{ "$_" } } $ErrorActionPreference = $SavedErrorActionPreference } class FormItem { $value [String] $displayText [Bool] $selected = $false FormItem ($_value, [String] $_displayText, [Bool] $_selected = $false) { $this.value = $_value $this.displayText = $_displayText $this.selected = $_selected } FormItem ($_value, [String] $_displayText) { $this.value = $_value $this.displayText = $_displayText } [String] ToString() { return $this.displayText } } class FormItems : System.Collections.ArrayList<#FormItem#> { [System.Collections.ArrayList] getSelectedBoolArray() { $rvalue = [System.Collections.ArrayList]::new() foreach ($i in $this) { [void] $rvalue.Add($i.selected) } return $rvalue } [System.Collections.ArrayList] getDisplay() { $rvalue = [System.Collections.ArrayList]::new() foreach ($i in $this) { [void] $rvalue.Add($i.displayText) } return $rvalue } } function renderForm([FormItems]$items, [String]$label, [String]$title, [System.Windows.Forms.SelectionMode]$mode) { # https://docs.microsoft.com/en-us/powershell/scripting/samples/multiple-selection-list-boxes?view=powershell-7.2 # https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.anchorstyles $form = New-Object System.Windows.Forms.Form $form.Text = $title $form.Size = New-Object System.Drawing.Size(300,200) $form.StartPosition = 'CenterScreen' $OKButton = New-Object System.Windows.Forms.Button $OKButton.Anchor = 2 $OKButton.Location = New-Object System.Drawing.Point(102,120) $OKButton.Size = New-Object System.Drawing.Size(75,23) $OKButton.Text = 'OK' $OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK $form.AcceptButton = $OKButton $form.Controls.Add($OKButton) $_label = New-Object System.Windows.Forms.Label $_label.Anchor = 1+4+8 $_label.Location = New-Object System.Drawing.Point(10,20) $_label.Size = New-Object System.Drawing.Size(280,20) $_label.Text = $label $form.Controls.Add($_label) $listBox = New-Object System.Windows.Forms.Listbox $listBox.Anchor = 15 $listBox.Location = New-Object System.Drawing.Point(10,40) $listBox.Size = New-Object System.Drawing.Size(260,20) $listBox.SelectionMode = $mode foreach ($i in $items.getDisplay()) { [void] $listBox.Items.Add($i) } for ($i = 0; $i -lt $items.Count; $i++) { [void] $listBox.SetSelected($i, $items[$i].selected) } $listBox.Height = 70 $form.Controls.Add($listBox) $form.Topmost = $true $dialogResult = $form.ShowDialog() if ($dialogResult -ne [System.Windows.Forms.DialogResult]::OK) { return $false } $selected = [System.Collections.ArrayList]::new() foreach ($i in $listbox.selectedIndices) { [void] $selected.Add($items[$i].value) } $selected.TrimToSize() return $selected } class config { [hashtable]$version = @{} [System.Collections.ArrayList]$included = [System.Collections.ArrayList]::new() } class configFile { [String]$path = ".\config.json" [config]$config = [config]::new() [void] load() { $fileContent = ConvertFrom-Json ((Get-Content $this.path) -Join ' ').ToString() $version = @{} foreach ($prop in $fileContent.version.PsObject.Properties) { $version.Add($prop.Name, $prop.Value) } $fileContent.version = $null $this.config = $fileContent $this.config.version = $version } [void] save() { Out-File $this.path -InputObject (ConvertTo-Json $this.config) } } function UpdateFromGithub { param ( [Parameter(Mandatory)][String]$Repository, [Parameter(Mandatory)][configFile]$Config, [scriptblock]$AssetsFilter = {$true}, [Parameter(Mandatory)][String]$OutFile ) [String]$InstalledTag = $Config.config.version[$Repository] $LatestRelease try { $LatestRelease = ConvertFrom-Json (Invoke-WebRequest "https://api.github.com/repos/$Repository/releases/latest").Content } catch [System.Net.WebException] { Write-Host "Couldn't check for update $Repository" return $false } [String]$UpdateTag = $LatestRelease.tag_name if ($InstalledTag -eq $UpdateTag) { Write-Host "$Repository up to date! ($InstalledTag)" return $false } $Assets = $LatestRelease.assets | Where-Object -FilterScript $AssetsFilter Write-Host "Downloading $Repository ($InstalledTag -> $UpdateTag)" Write-Host "Changelog: https://github.com/$Repository/compare/$InstalledTag...$UpdateTag" Invoke-WebRequest $Assets[0].browser_download_url -OutFile $OutFile $Config.config.version[$Repository] = $UpdateTag $Config.save() return $true } class CompatiblePackage { [String] $name [String[]] $versions CompatiblePackage([String] $_name, [String[]] $_versions) { $this.name = $_name $this.versions = $_versions } } class Integration { [String] $name [String] $description [String] $version [Bool] $excluded [Bool] $deprecated [String[]] $dependencies <#[compatiblePackage[]]#> $compatiblePackages = [System.Collections.ArrayList]::new() Integration([String] $_name, [String] $_description) { $this.name = $_name $this.description = $_description } Integration($integration) { $this.name = $integration.name $this.description = $integration.description $this.version = $integration.version $this.excluded = $integration.excluded $this.deprecated = $integration.deprecated $this.dependencies = $integration.dependencies foreach ($i in $integration.compatiblePackages) { [void] $this.compatiblePackages.Add([CompatiblePackage]::new($i.name, $i.versions)) } } [String] ToString() { return "$($this.name) - $($this.description)" } [Bool] Compatible([String] $name, [String] $version) { if ($this.compatiblePackages.Count -eq 0) { return $true } [compatiblePackage[]] $compatiblePackage = $this.compatiblePackages | Where-Object {$_.name -eq $name} if ($compatiblePackage.length -eq 0 ) { return $false } return $compatiblePackage.Contains($version) } } class IntegrationList : System.Collections.ArrayList<#Integration#> { IntegrationList($path) { $integration = ConvertFrom-Json(Get-Content $path) foreach ($i in $integration) { [void] $this.add([Integration]::new($i)) } } [String[]] getDependenciesNames([String] $name) { foreach ($i in $this) { if ($i.name -ne $name) { continue } $rvalue = [System.Collections.ArrayList]::new() foreach ($j in $i.dependencies) { if ($rvalue.Contains($j)) { continue } [void] $rvalue.Add($j) } return $rvalue } return $null } [System.Collections.ArrayList] getFullIntegrationList([String[]] $integrations) { <# .Description Returns Integration list with all dependencies included #> $rvalue = [System.Collections.ArrayList]::new($this.Count) foreach ($i in $integrations) { if ($rvalue.Contains($i)) { continue } [void] $rvalue.Add($i) $dependencies = $this.getDependenciesNames($i) foreach ($j in $this.getFullIntegrationList($dependencies)) { if ($rvalue.Contains($j)) { continue } [void] $rvalue.Add($j) } } return $rvalue } [FormItems] getForm() { $rvalue = [FormItems]::new() foreach ($i in $this) { [void] $rvalue.Add([FormItem]::new($i.name, "$($i.name) - for $($i.compatiblePackages[0].name) - $($i.description)")) } return $rvalue } } function main { $config = [configFile]::new() try { $config.load() } catch [System.IO.FileNotFoundException] { Write-Host ".\config.json not found" $config.save() } # Update if (-not $NoUpdate) { $updatedPatches = UpdateFromGithub -Repository "revanced/revanced-patches" -Config $config -AssetsFilter {$_.content_type -like "application/java-archive"} -OutFile ".\app\revanced-patches.jar" if ($updatedPatches) { Invoke-WebRequest "https://raw.githubusercontent.com/revanced/revanced-patches/main/patches.json" -OutFile ".\app\revanced-patches.json" } UpdateFromGithub -Repository "revanced/revanced-integrations" -Config $config -AssetsFilter {$_.content_type -like "application/*"} -OutFile ".\app\app-release-unsigned.apk" | Out-Null UpdateFromGithub -Repository "revanced/revanced-cli" -Config $config -AssetsFilter {$_.content_type -like "application/*"} -OutFile ".\app\revanced-cli.jar" | Out-Null } if ($NoDeploy) {return} # Select apk to patch $apks = (Get-ChildItem *.apk).Name [String]$selectedApk = '' if ($apks.Length -eq 0) {throw "No YouTube apk files found!"} elseif ($apks.getType().Name -eq 'String') {$selectedApk = $apks} else { $apkItems = [FormItems]::new() foreach ($i in $apks) { [void] $apkItems.Add([FormItem]::new($i, $i)) } $selectedApk = renderForm -items $apkItems -label "Select apk file to patch" -title "Installer for ReVanced - apk selector" -mode One } if (-not $selectedApk) {throw "No apk selected!"} # Get available integrations $integrations = [IntegrationList]::new(".\app\revanced-patches.json") # integrations selector $integrationsToInclude = [System.Collections.ArrayList]::new() $integrationsForm = $integrations.getForm() for ($i = 0; $i -lt $integrationsForm.Count; $i++) { if (-not $config.config.included.Contains($integrationsForm[$i].value)) { continue } $integrationsForm[$i].selected = $true } $integrationsToInclude = renderForm -items $integrationsForm -label "Select integrations to include" -title "Installer for ReVanced - integrations" -mode MultiExtended $config.config.included = $integrationsToInclude $config.save() # We save only what user selected, but we still need to include integrations that depends on selected $integrationsToInclude = $integrations.getFullIntegrationList($integrationsToInclude) # devices $devices = adb devices if ($devices.Length -eq 2) { throw "No devices found" } $devices = $devices[1..($devices.length-2)] [String]$selectedDevice = '' if ($devices.Length -eq 1) { $selectedDevice = ($devices | ForEach-Object {$_.split()[0]}) } else { $devicesForm = [FormItems]::new() foreach ($i in $devices) { [void] $devicesForm.Add([FormItem]::new($i.split()[0], $i)) } $selectedDevice = (renderForm -items $devicesForm -label "Select deployment target" -title "Installer for ReVanced - target" -mode One) } if (-not $selectedDevice) {throw "No device selected"} # install to device $patcherArgs = [System.Collections.ArrayList]::new(100) [void] $patcherArgs.AddRange(("-jar", ".\app\revanced-cli.jar", "-a", ".\$selectedApk", "-c", "-d", "$selectedDevice", "-o", "$env:tmp\revanced.apk", "-m", ".\app\app-release-unsigned.apk", "-b", ".\app\revanced-patches.jar", "--exclusive", "--experimental")) # is microg patch excluded? if (-not $integrationsToInclude.Contains("microg-support")) { [void] $patcherArgs.Add("--mount") } foreach ($i in $integrationsToInclude) { [void] $patcherArgs.AddRange(("-i", $i)) Write-Output "Included: $i" } Write-Output "Logs below are from the cli" execJava $patcherArgs } main