233 lines
10 KiB
PowerShell
233 lines
10 KiB
PowerShell
#Requires -Version 5.1
|
||
<#
|
||
Seep 工作台 —— 工具修复器(可选)
|
||
----------------------------------------------------------------
|
||
工具已随包内置在 Tool\mcp\Tool\safe\,正常无需运行本脚本。
|
||
仅当内置工具损坏/缺失时,用本脚本重新下载。
|
||
官方源优先,失败自动回退国内镜像。
|
||
#>
|
||
[CmdletBinding()]
|
||
param(
|
||
[string[]]$Only = @(), # 只装指定工具,如 -Only jadx,radare2
|
||
[switch]$Force # 已存在也重新下载
|
||
)
|
||
|
||
$ErrorActionPreference = 'Continue'
|
||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||
$Root = Split-Path -Parent $ScriptDir
|
||
# seep MCP 硬编码要求:TOOL_DIR = <脚本目录>/Tool → 工具必须落在 Tool\mcp\Tool\safe\
|
||
$ToolsDir = Join-Path $Root 'Tool\mcp\Tool\safe'
|
||
New-Item -ItemType Directory -Force -Path $ToolsDir | Out-Null
|
||
|
||
$Mirrors = @(
|
||
'', # 官方源
|
||
'https://ghproxy.net/',
|
||
'https://gh-proxy.com/'
|
||
)
|
||
|
||
function Write-Ok($m) { Write-Host " [OK] $m" -ForegroundColor Green }
|
||
function Write-Warn($m) { Write-Host " [!!] $m" -ForegroundColor Yellow }
|
||
function Write-Info($m) { Write-Host " [..] $m" -ForegroundColor Gray }
|
||
|
||
# 带镜像回退的下载
|
||
function Get-FileWithMirror {
|
||
param([string]$Url, [string]$OutFile)
|
||
foreach ($m in $Mirrors) {
|
||
$u = if ($m -eq '' -or $Url -notmatch '^https?://github') { $Url } else { "$m$Url" }
|
||
$tag = if ($m -eq '') { 'official' } else { $m.TrimEnd('/') }
|
||
try {
|
||
Write-Info "尝试 $tag ..."
|
||
$ProgressPreference = 'SilentlyContinue'
|
||
Invoke-WebRequest -Uri $u -OutFile $OutFile -TimeoutSec 300 -UseBasicParsing
|
||
if ((Test-Path $OutFile) -and (Get-Item $OutFile).Length -gt 1024) {
|
||
Write-Ok "下载成功 ($tag)"
|
||
return $true
|
||
}
|
||
} catch {
|
||
Write-Info "失败: $($_.Exception.Message.Split([Environment]::NewLine)[0])"
|
||
}
|
||
}
|
||
return $false
|
||
}
|
||
|
||
# 取 GitHub 最新 release 的 asset 下载地址
|
||
function Get-GitHubAsset {
|
||
param([string]$Repo, [string]$Pattern)
|
||
try {
|
||
$api = "https://api.github.com/repos/$Repo/releases/latest"
|
||
$rel = Invoke-RestMethod -Uri $api -TimeoutSec 60 -Headers @{ 'User-Agent' = 'seep-setup' }
|
||
$asset = $rel.assets | Where-Object { $_.name -match $Pattern } | Select-Object -First 1
|
||
if ($asset) { return @{ Url = $asset.browser_download_url; Name = $asset.name; Tag = $rel.tag_name } }
|
||
} catch {
|
||
Write-Warn "GitHub API 失败 ($Repo): $($_.Exception.Message.Split([Environment]::NewLine)[0])"
|
||
}
|
||
return $null
|
||
}
|
||
|
||
function Expand-ZipTo {
|
||
param([string]$Zip, [string]$Dest)
|
||
New-Item -ItemType Directory -Force -Path $Dest | Out-Null
|
||
Expand-Archive -Path $Zip -DestinationPath $Dest -Force
|
||
}
|
||
|
||
function Should-Run($name) {
|
||
if ($Only.Count -eq 0) { return $true }
|
||
return ($Only -contains $name)
|
||
}
|
||
|
||
$results = @{}
|
||
|
||
# ---------------------------------------------------------------- jadx
|
||
if (Should-Run 'jadx') {
|
||
Write-Host "`n [1/6] jadx (Java 反编译)" -ForegroundColor Cyan
|
||
$dst = Join-Path $ToolsDir 'jadx'
|
||
if ((Test-Path $dst) -and -not $Force) { Write-Ok '已存在,跳过'; $results['jadx'] = $true }
|
||
else {
|
||
$a = Get-GitHubAsset -Repo 'skylot/jadx' -Pattern '^jadx-[\d.]+\.zip$'
|
||
if ($a) {
|
||
$zip = Join-Path $env:TEMP $a.Name
|
||
if (Get-FileWithMirror -Url $a.Url -OutFile $zip) {
|
||
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
|
||
Expand-ZipTo -Zip $zip -Dest $dst
|
||
Remove-Item $zip -Force -ErrorAction SilentlyContinue
|
||
Write-Ok "jadx $($a.Tag) -> tools\jadx"
|
||
$results['jadx'] = $true
|
||
} else { Write-Warn 'jadx 下载失败'; $results['jadx'] = $false }
|
||
} else { Write-Warn 'jadx: 无法获取 release 信息'; $results['jadx'] = $false }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- radare2
|
||
if (Should-Run 'radare2') {
|
||
Write-Host "`n [2/6] radare2 (二进制逆向)" -ForegroundColor Cyan
|
||
$dst = Join-Path $ToolsDir 'radare2'
|
||
if ((Test-Path $dst) -and -not $Force) { Write-Ok '已存在,跳过'; $results['radare2'] = $true }
|
||
else {
|
||
$a = Get-GitHubAsset -Repo 'radareorg/radare2' -Pattern '^radare2-.*-w64\.zip$'
|
||
if ($a) {
|
||
$zip = Join-Path $env:TEMP $a.Name
|
||
if (Get-FileWithMirror -Url $a.Url -OutFile $zip) {
|
||
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
|
||
Expand-ZipTo -Zip $zip -Dest $dst
|
||
Remove-Item $zip -Force -ErrorAction SilentlyContinue
|
||
Write-Ok "radare2 $($a.Tag) -> tools\radare2"
|
||
$results['radare2'] = $true
|
||
} else { Write-Warn 'radare2 下载失败'; $results['radare2'] = $false }
|
||
} else { Write-Warn 'radare2: 无法获取 release 信息'; $results['radare2'] = $false }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- apktool
|
||
if (Should-Run 'apktool') {
|
||
Write-Host "`n [3/6] apktool (APK 解包/重打包)" -ForegroundColor Cyan
|
||
$dst = Join-Path $ToolsDir 'apktool'
|
||
if ((Test-Path $dst) -and -not $Force) { Write-Ok '已存在,跳过'; $results['apktool'] = $true }
|
||
else {
|
||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||
$a = Get-GitHubAsset -Repo 'iBotPeaches/Apktool' -Pattern '^apktool_[\d.]+\.jar$'
|
||
if ($a) {
|
||
$jar = Join-Path $dst 'apktool.jar'
|
||
if (Get-FileWithMirror -Url $a.Url -OutFile $jar) {
|
||
$bat = @'
|
||
@echo off
|
||
if "%JAVA_HOME%"=="" (set JAVA=java) else (set JAVA="%JAVA_HOME%\bin\java.exe")
|
||
%JAVA% -jar -Duser.language=en "%~dp0apktool.jar" %*
|
||
'@
|
||
Set-Content -Path (Join-Path $dst 'apktool.bat') -Value $bat -Encoding ASCII
|
||
Write-Ok "apktool $($a.Tag) -> tools\apktool"
|
||
$results['apktool'] = $true
|
||
} else { Write-Warn 'apktool 下载失败'; $results['apktool'] = $false }
|
||
} else { Write-Warn 'apktool: 无法获取 release 信息'; $results['apktool'] = $false }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- playwright-mcp
|
||
if (Should-Run 'playwright-mcp') {
|
||
Write-Host "`n [4/6] playwright-mcp (浏览器自动化)" -ForegroundColor Cyan
|
||
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
|
||
Write-Warn '未找到 npm,跳过'; $results['playwright-mcp'] = $false
|
||
} else {
|
||
$dst = Join-Path $ToolsDir 'playwright-mcp'
|
||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||
Push-Location $dst
|
||
try {
|
||
if (-not (Test-Path (Join-Path $dst 'package.json'))) {
|
||
'{ "name": "playwright-mcp-local", "private": true }' | Set-Content -Path 'package.json' -Encoding UTF8
|
||
}
|
||
Write-Info 'npm install @playwright/mcp ...'
|
||
& npm install '@playwright/mcp' --no-audit --no-fund 2>&1 | Out-Null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok 'playwright-mcp -> tools\playwright-mcp'; $results['playwright-mcp'] = $true }
|
||
else { Write-Warn 'npm install 失败(可改用 npx 方式)'; $results['playwright-mcp'] = $false }
|
||
} finally { Pop-Location }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- js-reverse-mcp
|
||
if (Should-Run 'js-reverse-mcp') {
|
||
Write-Host "`n [5/6] js-reverse-mcp (JS 逆向)" -ForegroundColor Cyan
|
||
# 官方推荐 npx 免安装方式:无需下载,仅在 mcp.json 注册即可
|
||
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
|
||
Write-Warn '未找到 npm,跳过(js-reverse-mcp 依赖 npx)'
|
||
$results['js-reverse-mcp'] = $false
|
||
} else {
|
||
Write-Info 'js-reverse-mcp 采用官方推荐的 npx 方式(无需本地下载)'
|
||
Write-Info '注册方式:mcp.json 中 command=npx, args=["js-reverse-mcp"]'
|
||
Write-Info '可选本地安装:git clone https://github.com/zhizhuodemao/js-reverse-mcp + npm i + npm run build'
|
||
Write-Ok 'js-reverse-mcp -> 走 npx(已在 mcp.json.template 中预置)'
|
||
$results['js-reverse-mcp'] = $true
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- ida-pro-mcp
|
||
if (Should-Run 'ida-pro-mcp') {
|
||
Write-Host "`n [6/6] ida-pro-mcp (IDA 桥接)" -ForegroundColor Cyan
|
||
$dst = Join-Path $ToolsDir 'ida-pro-mcp'
|
||
if ((Test-Path $dst) -and -not $Force) { Write-Ok '已存在,跳过'; $results['ida-pro-mcp'] = $true }
|
||
else {
|
||
Write-Info 'pip install ida-pro-mcp ...'
|
||
& python -m pip install --quiet ida-pro-mcp 2>&1 | Out-Null
|
||
if ($LASTEXITCODE -eq 0) {
|
||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||
Write-Ok 'ida-pro-mcp 已装入 Python 环境'
|
||
Write-Info '(IDA 本体需你自备,见 MANUAL\IDA-PRO.md)'
|
||
$results['ida-pro-mcp'] = $true
|
||
} else { Write-Warn 'ida-pro-mcp 安装失败'; $results['ida-pro-mcp'] = $false }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- PATH
|
||
Write-Host "`n [PATH] 加入工具目录" -ForegroundColor Cyan
|
||
$addPaths = @()
|
||
foreach ($p in @('radare2', 'jadx\bin', 'apktool')) {
|
||
$full = Join-Path $ToolsDir $p
|
||
if (Test-Path $full) { $addPaths += $full }
|
||
}
|
||
if ($addPaths.Count -gt 0) {
|
||
$old = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||
$parts = $old -split ';' | Where-Object { $_ -ne '' }
|
||
$added = 0
|
||
foreach ($p in $addPaths) {
|
||
if ($parts -notcontains $p) { $parts += $p; $added++ }
|
||
}
|
||
if ($added -gt 0) {
|
||
[Environment]::SetEnvironmentVariable('Path', ($parts -join ';'), 'User')
|
||
Write-Ok "已向用户 PATH 追加 $added 个目录(原 PATH 未备份,如需回滚请手动移除)"
|
||
} else { Write-Ok 'PATH 已包含这些目录' }
|
||
} else { Write-Warn '未发现可加入 PATH 的目录' }
|
||
|
||
# ---------------------------------------------------------------- 汇总
|
||
Write-Host "`n ---------- 工具下载汇总 ----------" -ForegroundColor White
|
||
foreach ($k in $results.Keys | Sort-Object) {
|
||
$v = $results[$k]
|
||
$mark = if ($v -eq $true) { '[OK] ' } elseif ($v -eq $false) { '[FAIL]' } else { '[SKIP]' }
|
||
Write-Host (" {0} {1}" -f $mark, $k)
|
||
}
|
||
|
||
$failed = @($results.Keys | Where-Object { $results[$_] -eq $false })
|
||
if ($failed.Count -gt 0) {
|
||
Write-Host "`n 失败项: $($failed -join ', ')" -ForegroundColor Yellow
|
||
Write-Host ' 可重跑本脚本,或按 MANUAL\PREREQUISITES.md 手动下载' -ForegroundColor Yellow
|
||
exit 1
|
||
}
|
||
exit 0
|