_widevine_setup/wvd_console.ps1
Rama main · réplica de lectura del repositorio original.
# ============================================================================
# Widevine L3 Console - one place for the whole device.wvd pipeline.
# Launch: wvd.bat (or: powershell -ExecutionPolicy Bypass -File wvd_console.ps1)
#
# Method that WORKS: Android Studio AVD (google_apis x86_64 ships real Widevine
# L3) + KeyDive. MEmu/LDPlayer ship ClearKey-only - dead end, removed.
# ============================================================================
param([switch]$Auto) # -Auto: run the whole pipeline headless (no menu, no prompts)
$ErrorActionPreference = "SilentlyContinue"
$HERE = Split-Path -Parent $MyInvocation.MyCommand.Path
$ROOT = Split-Path -Parent $HERE # C:\dev\apple_music
$SDK = "C:\Android\Sdk"
# JRE: pick the jdk-* dir dynamically (don't hardcode 17.0.x - it changes).
$JRE = (Get-ChildItem "C:\Android\jre17\jdk-*" -Directory -EA SilentlyContinue | Select-Object -First 1).FullName
if(-not $JRE){ $JRE = "C:\Android\jre17" }
$ADB = "$SDK\platform-tools\adb.exe"
$EMU = "$SDK\emulator\emulator.exe"
$SERIAL = "emulator-5554"
$AVD = "wvd"
$IMAGE = "system-images;android-30;google_apis;x86_64"
$SDKM = "$SDK\cmdline-tools\latest\bin\sdkmanager.bat"
$AVDM = "$SDK\cmdline-tools\latest\bin\avdmanager.bat"
$FRIDA = "$HERE\frida-server-x86_64"
# Interpreter: dev .venv OR the bundled embeddable python\ that ships in the
# installer. The old code only knew .venv -> on a real install keydive.exe was
# never found and Extract silently produced no .wvd.
if(Test-Path "$ROOT\.venv\Scripts\python.exe"){ $VENVPY = "$ROOT\.venv\Scripts\python.exe" } elseif(Test-Path "$ROOT\python\python.exe"){ $VENVPY = "$ROOT\python\python.exe" } else { $VENVPY = "python" }
$KEYDIVE= Join-Path (Split-Path -Parent $VENVPY) "Scripts\keydive.exe"
$WVDDST = "$ROOT\tools\widevine\device.wvd"
$env:JAVA_HOME = $JRE
$env:ANDROID_SDK_ROOT = $SDK
$env:PATH = "$JRE\bin;$SDK\platform-tools;$SDK\emulator;$env:PATH"
function Mark($ok){ if($ok){"[OK]"}else{"[ -- ]"} }
function Line($label,$ok,$detail){ "{0,-6} {1,-22} {2}" -f (Mark $ok),$label,$detail }
function Show-Status {
Write-Host "`n==== Widevine L3 pipeline status ====" -ForegroundColor Cyan
Line "JRE 17" (Test-Path "$JRE\bin\java.exe") $JRE
Line "platform-tools"(Test-Path $ADB) "adb"
Line "emulator" (Test-Path $EMU) "emulator.exe"
Line "system-image" (Test-Path "$SDK\system-images\android-30\google_apis\x86_64\system.img") "android-30 google_apis x86_64"
$aehd = (sc.exe query aehd 2>$null | Select-String "RUNNING") -ne $null
Line "AEHD driver" $aehd "hypervisor (needs admin to install)"
Line "AVD '$AVD'" (Test-Path "$env:USERPROFILE\.android\avd\$AVD.avd") ""
$emuUp = (Get-Process qemu-system-x86_64 -EA SilentlyContinue) -ne $null
Line "emulator up" $emuUp $SERIAL
Line "device.wvd" (Test-Path $WVDDST) $WVDDST
Write-Host ""
}
function Connect-Wifi {
# THE fix that unblocked everything (Jun 2026): on a cold boot the guest has
# NO saved Wi-Fi network, so wlan0 never associates with the emulator's
# simulated AP "AndroidWifi". Result: ConnectivityService reports
# "Active default network: none" -> Chrome has no internet -> KeyDive's DRM
# playback can't do a license exchange -> no .wvd. One command associates it.
# (eth0 NAT is alive at the qemu layer but the image ships no EthernetService,
# so Wi-Fi via netsim is the only path the framework will adopt.)
Write-Host "Connecting guest Wi-Fi to AndroidWifi..." -ForegroundColor Cyan
& $ADB -s $SERIAL shell "cmd wifi connect-network AndroidWifi open" 2>$null | Out-Null
for($i=0;$i -lt 15;$i++){
$net = (& $ADB -s $SERIAL shell "dumpsys connectivity | grep 'Active default network'" 2>$null)
if($net -and ($net -notmatch "none")){ Write-Host " Network up: $($net.Trim())" -ForegroundColor Green; return $true }
Start-Sleep 2
}
Write-Host " WARN: emulator network still 'none' after Wi-Fi connect." -ForegroundColor Yellow
# A bare fact gets scrolled past. The consequence is what matters here:
# with no guest network KeyDive cannot do a license exchange, so minting
# WILL fail - and downstream that looks like "it froze for no reason".
Write-Host " This is fatal for minting: KeyDive needs the guest online to do a" -ForegroundColor Yellow
Write-Host " license exchange. Expect the extraction step to fail." -ForegroundColor Yellow
Write-Host " Usual causes: AEHD/hypervisor not running, or the emulator booted" -ForegroundColor Yellow
Write-Host " before netsim was ready - stop the emulator (5) and retry." -ForegroundColor Yellow
return $false
}
function Boot-Emulator {
if(-not (Get-Process qemu-system-x86_64 -EA SilentlyContinue)){
if(-not ((sc.exe query aehd 2>$null | Select-String "RUNNING"))){
Write-Host "!! AEHD hypervisor not running - x86_64 emulator can't boot." -ForegroundColor Red
Write-Host " Install it (one-time, needs admin/UAC):" -ForegroundColor Red
Write-Host " $SDK\extras\google\Android_Emulator_Hypervisor_Driver\silent_install.bat" -ForegroundColor Red
return $false
}
# -no-window: NEVER pop the phone UI on a tester's desktop (mints headless).
Write-Host "Launching emulator (headless, no window)..." -ForegroundColor Cyan
Start-Process -FilePath $EMU -WindowStyle Hidden -ArgumentList `
"-avd",$AVD,"-no-window","-no-snapshot","-no-boot-anim","-gpu","swiftshader_indirect","-no-audio",`
"-dns-server","8.8.8.8,8.8.4.4","-netdelay","none","-netspeed","full"
& $ADB start-server | Out-Null
Write-Host "Waiting for boot..." -NoNewline
for($i=0;$i -lt 30;$i++){
Start-Sleep 5
if((& $ADB -s $SERIAL shell getprop sys.boot_completed 2>$null).Trim() -eq "1"){ Write-Host " booted." -ForegroundColor Green; break }
Write-Host "." -NoNewline
}
} else { Write-Host "Emulator already running." -ForegroundColor Yellow }
& $ADB -s $SERIAL root | Out-Null
Start-Sleep 2
Connect-Wifi | Out-Null # <-- network fix; must run BEFORE KeyDive (every boot)
Ensure-Frida # <-- now runs even if the emulator was already up
return $true
}
function Ensure-Frida {
# CRITICAL: the frida-server on the device MUST match KeyDive's frida CLIENT
# version, else KeyDive reports "Frida server is not running" even though it is.
# Derive the version from the installed frida and fetch the matching android
# x86_64 server (cached). The bundled frida-server-x86_64 is only a last resort.
$ver = (& $VENVPY -c "import frida; print(frida.__version__)" 2>$null)
if($ver){ $ver = $ver.Trim() }
$srv = $FRIDA
if($ver){
$cached = "$HERE\frida-server-$ver-x86_64"
if(-not (Test-Path $cached)){
$url = "https://github.com/frida/frida/releases/download/$ver/frida-server-$ver-android-x86_64.xz"
$xz = "$cached.xz"
Write-Host "Downloading frida-server $ver (matches KeyDive)..." -ForegroundColor Cyan
# -TimeoutSec bounds the REQUEST, not the body transfer. On Windows
# PowerShell 5.1 a stalled GitHub download can sit for hours with one
# motionless "Downloading..." line on screen. That is exactly where
# "one-click install does nothing" came from (22.08.2026): it is
# indistinguishable from a hang, and the bundled fallback lives in
# the catch block, which a stall NEVER reaches.
#
# So: download in a background job and bound the WHOLE step by wall
# clock. If it does not finish, kill the job and fall back honestly
# instead of waiting forever.
$dlOk = $false
$deadline = 150
try {
$job = Start-Job -ScriptBlock {
param($u, $o)
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -UseBasicParsing -Uri $u -OutFile $o -TimeoutSec 60
} -ArgumentList $url, $xz
# A heartbeat every 10s. A silent screen reads as "frozen" and
# people kill the window even when the work is going fine.
$waited = 0
while($waited -lt $deadline -and $job.State -eq 'Running'){
Start-Sleep -Seconds 5; $waited += 5
if($waited % 10 -eq 0){
$sz = 0
if(Test-Path $xz){ $sz = [math]::Round((Get-Item $xz).Length / 1MB, 1) }
Write-Host (" ...{0}s, {1} MB" -f $waited, $sz) -ForegroundColor DarkGray
}
}
if($job.State -eq 'Running'){
Stop-Job $job -EA SilentlyContinue
Write-Host " frida-server: no progress in ${deadline}s - giving up on the download." -ForegroundColor Yellow
} else {
Receive-Job $job -EA SilentlyContinue | Out-Null
if((Test-Path $xz) -and (Get-Item $xz).Length -gt 0){ $dlOk = $true }
}
Remove-Job $job -Force -EA SilentlyContinue
if($dlOk){
& $VENVPY -c "import lzma,sys; open(sys.argv[2],'wb').write(lzma.open(sys.argv[1]).read())" $xz $cached
}
Remove-Item $xz -Force -EA SilentlyContinue
} catch { Write-Host "frida-server $ver download failed ($_) - using bundled." -ForegroundColor Yellow }
if(-not $dlOk){
# Name the CONSEQUENCE, not just the fact. A frida version
# mismatch surfaces in KeyDive as "Frida server is not running",
# a message that leads nobody to the real cause.
Write-Host " Falling back to the bundled frida-server." -ForegroundColor Yellow
Write-Host " If KeyDive later says 'Frida server is not running', the cause is a" -ForegroundColor Yellow
Write-Host " version mismatch with client $ver, not a dead server." -ForegroundColor Yellow
}
}
if(Test-Path $cached){ $srv = $cached }
}
# Kill any stale (possibly wrong-version) frida-server already on the device.
& $ADB -s $SERIAL shell "pkill -f frida-server" 2>$null | Out-Null
Write-Host "Pushing frida-server to device..." -ForegroundColor Cyan
& $ADB -s $SERIAL push "$srv" /data/local/tmp/frida-server 2>$null | Out-Null
& $ADB -s $SERIAL shell "chmod 755 /data/local/tmp/frida-server" 2>$null | Out-Null
& $ADB -s $SERIAL shell "nohup /data/local/tmp/frida-server >/data/local/tmp/fs.log 2>&1 &" | Out-Null
Start-Sleep 3
$running = & $ADB -s $SERIAL shell "ps -A 2>/dev/null | grep frida-server" 2>$null
if($running){ Write-Host "frida-server $ver started." -ForegroundColor Green }
else {
$log = & $ADB -s $SERIAL shell "cat /data/local/tmp/fs.log 2>/dev/null"
Write-Host "frida-server did NOT start. Log: $log" -ForegroundColor Red
}
}
function Skip-ChromeFRE {
# KeyDive's -a web drives Chrome; a fresh profile stops on the welcome/First-Run
# screen and KeyDive hangs (the classic "Chrome stuck on welcome" snag). With adb
# root we pre-seed the chrome-command-line flag file so Chrome skips the FRE - best
# effort, never fatal.
& $ADB -s $SERIAL shell "echo 'chrome --no-first-run --disable-fre --no-default-browser-check' > /data/local/tmp/chrome-command-line" 2>$null | Out-Null
& $ADB -s $SERIAL shell "chmod 644 /data/local/tmp/chrome-command-line" 2>$null | Out-Null
# Best-effort: mark Play-services/Chrome first-run done so no setup wizard steals focus.
& $ADB -s $SERIAL shell "settings put global device_provisioned 1" 2>$null | Out-Null
& $ADB -s $SERIAL shell "settings put secure user_setup_complete 1" 2>$null | Out-Null
}
function Ensure-KeyDive {
# KeyDive is a pip package; a bundled install has python\ but not keydive yet.
# Install it into whichever interpreter we resolved so Extract never fails with
# a silent "no .wvd" just because the tool was missing.
& $VENVPY -c "import keydive" 2>$null
if($LASTEXITCODE -eq 0){ return $true }
Write-Host "Installing KeyDive (pip)..." -ForegroundColor Cyan
& $VENVPY -m pip install --upgrade --disable-pip-version-check keydive
& $VENVPY -c "import keydive" 2>$null
return ($LASTEXITCODE -eq 0)
}
function Ensure-Sdk {
# platform-tools + emulator + system-image + the AEHD driver package. sdkmanager
# resumes partial downloads; runs elevated when Ripster itself is admin.
if((Test-Path $ADB) -and (Test-Path "$SDK\system-images\android-30\google_apis\x86_64\system.img")){ return $true }
if(-not (Test-Path $SDKM)){ Write-Host "!! cmdline-tools missing - run the in-app Setup (JRE + SDK tools) first." -ForegroundColor Red; return $false }
Write-Host "Installing SDK components (platform-tools, emulator, system-image, AEHD)..." -ForegroundColor Cyan
cmd /c "echo y| `"$SDKM`" `"platform-tools`" `"emulator`" `"$IMAGE`" `"extras;google;Android_Emulator_Hypervisor_Driver`""
return ((Test-Path $ADB) -and (Test-Path "$SDK\system-images\android-30\google_apis\x86_64\system.img"))
}
function Ensure-Aehd {
# Hypervisor driver — the one admin-gated step. With Ripster running elevated
# (PrivilegesRequired=admin / uac_admin) this installs without a separate UAC.
if((sc.exe query aehd 2>$null | Select-String "RUNNING")){ return $true }
$inst = "$SDK\extras\google\Android_Emulator_Hypervisor_Driver\silent_install.bat"
if(-not (Test-Path $inst)){ Write-Host "AEHD installer missing - run Ensure-Sdk first." -ForegroundColor Red; return $false }
Write-Host "Installing AEHD hypervisor driver..." -ForegroundColor Cyan
Push-Location (Split-Path $inst); cmd /c "`"$inst`""; Pop-Location
return ((sc.exe query aehd 2>$null | Select-String "RUNNING") -ne $null)
}
function Ensure-Avd {
if(Test-Path "$env:USERPROFILE\.android\avd\$AVD.avd"){ return $true }
if(-not (Test-Path $AVDM)){ Write-Host "avdmanager missing - run the in-app Setup first." -ForegroundColor Red; return $false }
Write-Host "Creating AVD '$AVD'..." -ForegroundColor Cyan
cmd /c "echo no| `"$AVDM`" create avd -n $AVD -k `"$IMAGE`" --force"
return (Test-Path "$env:USERPROFILE\.android\avd\$AVD.avd")
}
function One-Click {
# The whole pipeline in one go: provision everything that can be auto-installed,
# then boot -> extract -> verify. Assumes admin (Ripster runs elevated).
if(Test-Path $WVDDST){ Write-Host "device.wvd already present: $WVDDST" -ForegroundColor Green; return }
if(-not (Ensure-Sdk)){ Write-Host "ONE_CLICK: FAIL sdk" -ForegroundColor Red; return }
if(-not (Ensure-Aehd)){ Write-Host "ONE_CLICK: FAIL aehd (needs admin)" -ForegroundColor Red; return }
if(-not (Ensure-Avd)){ Write-Host "ONE_CLICK: FAIL avd" -ForegroundColor Red; return }
Extract-Wvd # boot -> KeyDive -> auto Install-Wvd
if(Test-Path $WVDDST){ Verify-Wvd; Write-Host "ONE_CLICK: OK $WVDDST" -ForegroundColor Green }
else { Write-Host "ONE_CLICK: FAIL extract (KeyDive produced no .wvd)" -ForegroundColor Red }
}
function Extract-Wvd {
if(-not (Boot-Emulator)){ return }
if(-not (Ensure-KeyDive)){ Write-Host "KeyDive install failed (pip) - check your internet." -ForegroundColor Red; return }
Skip-ChromeFRE
Write-Host "Running KeyDive (-a web -> Chrome plays DRM -> captures CDM)..." -ForegroundColor Cyan
if(Test-Path "$HERE\_keydive_out"){ Remove-Item "$HERE\_keydive_out" -Recurse -Force }
Push-Location $HERE
# Invoke via -m (NOT the .exe shim - console-script .exe shims don't run under
# the embeddable python; -m keydive works on every layout).
& $VENVPY -m keydive -s $SERIAL --output "_keydive_out" -w -a web
Pop-Location
$wvd = Get-ChildItem "$HERE\_keydive_out" -Recurse -Filter *.wvd | Select-Object -First 1
if($wvd){ Write-Host "Extracted: $($wvd.FullName)" -ForegroundColor Green; Install-Wvd }
else { Write-Host "No .wvd produced. If Chrome stuck on welcome screen, accept it and retry." -ForegroundColor Red }
}
function Install-Wvd {
$wvd = Get-ChildItem "$HERE\_keydive_out" -Recurse -Filter *.wvd | Select-Object -First 1
if(-not $wvd){ Write-Host "No extracted .wvd to install - run Extract first." -ForegroundColor Red; return }
New-Item -ItemType Directory -Force "$ROOT\tools\widevine" | Out-Null
Copy-Item $wvd.FullName $WVDDST -Force
Write-Host "Installed -> $WVDDST" -ForegroundColor Green
}
function Verify-Wvd {
if(-not (Test-Path $WVDDST)){ Write-Host "No device.wvd installed." -ForegroundColor Red; return }
Write-Host "Verifying against SoundCloud Widevine license server..." -ForegroundColor Cyan
& $VENVPY "$HERE\verify_wvd.py"
}
function Stop-Emulator {
& $ADB -s $SERIAL emu kill 2>$null | Out-Null
Get-Process qemu-system-x86_64 -EA SilentlyContinue | Stop-Process -Force
Write-Host "Emulator stopped." -ForegroundColor Green
}
# ---- AUTO mode: full pipeline headless, no prompts (driven by the Setup tab) ----
if($Auto){
try { Start-Transcript -Path "$HERE\wvd_mint.log" -Append -EA SilentlyContinue | Out-Null } catch {}
Write-Host ("=== AUTO MINT run: {0} ===" -f (Get-Date)) -ForegroundColor DarkCyan
Show-Status
if(Test-Path $WVDDST){ Write-Host "AUTO_RESULT: OK (device.wvd already present) $WVDDST" -ForegroundColor Green; exit 0 }
Write-Host "=== AUTO MINT: provision -> boot -> extract -> install -> verify -> stop ===" -ForegroundColor Cyan
if(-not (Ensure-Sdk)){ Write-Host "AUTO_RESULT: FAIL sdk" -ForegroundColor Red; exit 2 }
if(-not (Ensure-Aehd)){ Write-Host "AUTO_RESULT: FAIL aehd (needs admin)" -ForegroundColor Red; exit 2 }
if(-not (Ensure-Avd)){ Write-Host "AUTO_RESULT: FAIL avd" -ForegroundColor Red; exit 2 }
if(-not (Boot-Emulator)){ Write-Host "AUTO_RESULT: FAIL boot (AEHD/emulator)" -ForegroundColor Red; exit 2 }
Extract-Wvd # KeyDive -> auto Install-Wvd
if(-not (Test-Path $WVDDST)){
Write-Host "AUTO_RESULT: FAIL extract (KeyDive produced no .wvd - Chrome FRE/DRM?)" -ForegroundColor Red
Stop-Emulator; exit 3
}
Verify-Wvd
Stop-Emulator
Write-Host "AUTO_RESULT: OK $WVDDST" -ForegroundColor Green
exit 0
}
# ---- menu loop ----
while($true){
Show-Status
Write-Host " [9] ONE-CLICK: install everything + extract device.wvd" -ForegroundColor Green
Write-Host " [1] Boot emulator + frida-server"
Write-Host " [2] Extract device.wvd (KeyDive) -> auto-installs"
Write-Host " [3] Install last extracted .wvd into Ripster"
Write-Host " [4] Verify .wvd against SoundCloud"
Write-Host " [5] Stop emulator"
Write-Host " [0] Exit"
$c = Read-Host "`nChoose"
switch($c){
"9"{ One-Click }
"1"{ Boot-Emulator }
"2"{ Extract-Wvd }
"3"{ Install-Wvd }
"4"{ Verify-Wvd }
"5"{ Stop-Emulator }
"0"{ break }
default{ Write-Host "?" -ForegroundColor Yellow }
}
}