diff --git a/scripts/run/cursor_linux_id_modifier.sh b/scripts/run/cursor_linux_id_modifier.sh index 37ef2d0..e460c4a 100755 --- a/scripts/run/cursor_linux_id_modifier.sh +++ b/scripts/run/cursor_linux_id_modifier.sh @@ -1131,25 +1131,153 @@ EOF if [ "$(basename "$file")" = "main.js" ]; then if command -v python3 >/dev/null 2>&1; then local b6_result - b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY' -import re, sys -path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] -with open(path, "r", encoding="utf-8") as f: - data = f.read() -pattern = re.compile(r'async function (\\w+)\\((\\w+)\\)\\{.*?createHash\\(\"sha256\"\\).*?return \\w+\\?\\w+:\\w+\\}', re.S) -def repl(m): - name = m.group(1) - param = m.group(2) - return f"async function {name}({param}){{return {param}?\\\"{machine_guid}\\\":\\\"{machine_id}\\\";}}" -new_data, count = pattern.subn(repl, data, count=1) -if count: - with open(path, "w", encoding="utf-8") as f: - f.write(new_data) - print("PATCHED") -else: - print("NOT_FOUND") + b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY' +if True: + import re, sys + + path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] + + with open(path, "r", encoding="utf-8") as f: + data = f.read() + + # ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界 + marker = "out-build/vs/base/node/id.js" + marker_index = data.find(marker) + if marker_index < 0: + print("NOT_FOUND") + raise SystemExit(0) + + window_end = min(len(data), marker_index + 200000) + window = data[marker_index:window_end] + + def find_matching_brace(text, open_index, max_scan=20000): + limit = min(len(text), open_index + max_scan) + depth = 1 + in_single = in_double = in_template = False + in_line_comment = in_block_comment = False + escape = False + i = open_index + 1 + while i < limit: + ch = text[i] + nxt = text[i + 1] if i + 1 < limit else "" + + if in_line_comment: + if ch == "\n": + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + continue + i += 1 + continue + + if in_single: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == "'": + in_single = False + i += 1 + continue + if in_double: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == '"': + in_double = False + i += 1 + continue + if in_template: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == "`": + in_template = False + i += 1 + continue + + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + + if ch == "'": + in_single = True + i += 1 + continue + if ch == '"': + in_double = True + i += 1 + continue + if ch == "`": + in_template = True + i += 1 + continue + + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + return i + + i += 1 + return None + + hash_re = re.compile(r'createHash\\([\"\\']sha256[\"\\']\\)') + sig_re = re.compile(r'^async function (\\w+)\\((\\w+)\\)') + + for hm in hash_re.finditer(window): + hash_pos = hm.start() + func_start = window.rfind("async function", 0, hash_pos) + if func_start < 0: + continue + + open_brace = window.find("{", func_start) + if open_brace < 0: + continue + + end_brace = find_matching_brace(window, open_brace, max_scan=20000) + if end_brace is None: + continue + + func_text = window[func_start:end_brace + 1] + if len(func_text) > 8000: + continue + + sm = sig_re.match(func_text) + if not sm: + continue + name, param = sm.group(1), sm.group(2) + + # 特征校验:sha256 + hex digest + return param ? raw : hash + if not re.search(r'\\.digest\\([\"\\']hex[\"\\']\\)', func_text): + continue + if not re.search(r'return\\s+' + re.escape(param) + r'\\?\\w+:\\w+\\}', func_text): + continue + + replacement = f'async function {name}({param}){{return {param}?"{machine_guid}":"{machine_id}";}}' + abs_start = marker_index + func_start + abs_end = marker_index + end_brace + new_data = data[:abs_start] + replacement + data[abs_end + 1:] + with open(path, "w", encoding="utf-8") as f: + f.write(new_data) + print("PATCHED") + break + else: + print("NOT_FOUND") PY -) + ) if [ "$b6_result" = "PATCHED" ]; then log_info " ✓ [方案B] 已重写 b6 特征函数" b6_patched=true diff --git a/scripts/run/cursor_mac_id_modifier.sh b/scripts/run/cursor_mac_id_modifier.sh index ff0a4d0..cb83899 100644 --- a/scripts/run/cursor_mac_id_modifier.sh +++ b/scripts/run/cursor_mac_id_modifier.sh @@ -1859,24 +1859,152 @@ EOF if command -v python3 >/dev/null 2>&1; then local b6_result b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY' -import re, sys -path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] -with open(path, "r", encoding="utf-8") as f: - data = f.read() -pattern = re.compile(r'async function (\\w+)\\((\\w+)\\)\\{.*?createHash\\(\"sha256\"\\).*?return \\w+\\?\\w+:\\w+\\}', re.S) -def repl(m): - name = m.group(1) - param = m.group(2) - return f"async function {name}({param}){{return {param}?\\\"{machine_guid}\\\":\\\"{machine_id}\\\";}}" -new_data, count = pattern.subn(repl, data, count=1) -if count: - with open(path, "w", encoding="utf-8") as f: - f.write(new_data) - print("PATCHED") -else: - print("NOT_FOUND") +if True: + import re, sys + + path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] + + with open(path, "r", encoding="utf-8") as f: + data = f.read() + + # ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界 + marker = "out-build/vs/base/node/id.js" + marker_index = data.find(marker) + if marker_index < 0: + print("NOT_FOUND") + raise SystemExit(0) + + window_end = min(len(data), marker_index + 200000) + window = data[marker_index:window_end] + + def find_matching_brace(text, open_index, max_scan=20000): + limit = min(len(text), open_index + max_scan) + depth = 1 + in_single = in_double = in_template = False + in_line_comment = in_block_comment = False + escape = False + i = open_index + 1 + while i < limit: + ch = text[i] + nxt = text[i + 1] if i + 1 < limit else "" + + if in_line_comment: + if ch == "\n": + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + continue + i += 1 + continue + + if in_single: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == "'": + in_single = False + i += 1 + continue + if in_double: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == '"': + in_double = False + i += 1 + continue + if in_template: + if escape: + escape = False + elif ch == "\\\\": + escape = True + elif ch == "`": + in_template = False + i += 1 + continue + + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + + if ch == "'": + in_single = True + i += 1 + continue + if ch == '"': + in_double = True + i += 1 + continue + if ch == "`": + in_template = True + i += 1 + continue + + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + return i + + i += 1 + return None + + hash_re = re.compile(r'createHash\\([\"\\']sha256[\"\\']\\)') + sig_re = re.compile(r'^async function (\\w+)\\((\\w+)\\)') + + for hm in hash_re.finditer(window): + hash_pos = hm.start() + func_start = window.rfind("async function", 0, hash_pos) + if func_start < 0: + continue + + open_brace = window.find("{", func_start) + if open_brace < 0: + continue + + end_brace = find_matching_brace(window, open_brace, max_scan=20000) + if end_brace is None: + continue + + func_text = window[func_start:end_brace + 1] + if len(func_text) > 8000: + continue + + sm = sig_re.match(func_text) + if not sm: + continue + name, param = sm.group(1), sm.group(2) + + # 特征校验:sha256 + hex digest + return param ? raw : hash + if not re.search(r'\\.digest\\([\"\\']hex[\"\\']\\)', func_text): + continue + if not re.search(r'return\\s+' + re.escape(param) + r'\\?\\w+:\\w+\\}', func_text): + continue + + replacement = f'async function {name}({param}){{return {param}?"{machine_guid}":"{machine_id}";}}' + abs_start = marker_index + func_start + abs_end = marker_index + end_brace + new_data = data[:abs_start] + replacement + data[abs_end + 1:] + with open(path, "w", encoding="utf-8") as f: + f.write(new_data) + print("PATCHED") + break + else: + print("NOT_FOUND") PY -) + ) if [ "$b6_result" = "PATCHED" ]; then log_info " ✓ [方案B] 已重写 b6 特征函数" b6_patched=true diff --git a/scripts/run/cursor_win_id_modifier.ps1 b/scripts/run/cursor_win_id_modifier.ps1 index 7e0251d..945a6c1 100644 --- a/scripts/run/cursor_win_id_modifier.ps1 +++ b/scripts/run/cursor_win_id_modifier.ps1 @@ -244,6 +244,81 @@ function Generate-RandomString { return $result } +# 🔍 简易 JavaScript 花括号匹配(用于在限定片段内定位函数边界,避免正则跨段误替换) +# 说明:这是一个轻量解析器,足以应对 main.js 中的压缩函数体(含 try/catch、字符串、注释)。 +function Find-JsMatchingBraceEnd { + param( + [Parameter(Mandatory = $true)][string]$Text, + [Parameter(Mandatory = $true)][int]$OpenBraceIndex, + [int]$MaxScan = 20000 + ) + + if ($OpenBraceIndex -lt 0 -or $OpenBraceIndex -ge $Text.Length) { + return -1 + } + + $limit = [Math]::Min($Text.Length, $OpenBraceIndex + $MaxScan) + + $depth = 1 + $inSingle = $false + $inDouble = $false + $inTemplate = $false + $inLineComment = $false + $inBlockComment = $false + $escape = $false + + for ($i = $OpenBraceIndex + 1; $i -lt $limit; $i++) { + $ch = $Text[$i] + $next = if ($i + 1 -lt $limit) { $Text[$i + 1] } else { [char]0 } + + if ($inLineComment) { + if ($ch -eq "`n") { $inLineComment = $false } + continue + } + if ($inBlockComment) { + if ($ch -eq '*' -and $next -eq '/') { $inBlockComment = $false; $i++; continue } + continue + } + + if ($inSingle) { + if ($escape) { $escape = $false; continue } + if ($ch -eq '\') { $escape = $true; continue } + if ($ch -eq "'") { $inSingle = $false } + continue + } + if ($inDouble) { + if ($escape) { $escape = $false; continue } + if ($ch -eq '\') { $escape = $true; continue } + if ($ch -eq '"') { $inDouble = $false } + continue + } + if ($inTemplate) { + if ($escape) { $escape = $false; continue } + if ($ch -eq '\') { $escape = $true; continue } + if ($ch -eq '`') { $inTemplate = $false } + continue + } + + # 注释检测(仅在非字符串状态下) + if ($ch -eq '/' -and $next -eq '/') { $inLineComment = $true; $i++; continue } + if ($ch -eq '/' -and $next -eq '*') { $inBlockComment = $true; $i++; continue } + + # 字符串/模板字符串 + if ($ch -eq "'") { $inSingle = $true; continue } + if ($ch -eq '"') { $inDouble = $true; continue } + if ($ch -eq '`') { $inTemplate = $true; continue } + + # 花括号深度 + if ($ch -eq '{') { $depth++; continue } + if ($ch -eq '}') { + $depth-- + if ($depth -eq 0) { return $i } + } + } + + return -1 +} + # 🔧 修改Cursor内核JS文件实现设备识别绕过(增强版三重方案) # 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名 # 方案B: b6 定点重写 - 机器码源函数直接返回固定值 @@ -327,11 +402,22 @@ function Modify-CursorJSFiles { # 部署外置 Hook 文件(供 Loader Stub 加载,支持多域名备用下载) $hookTargetPath = "$env:USERPROFILE\.cursor_hook.js" - $hookSourceCandidates = @( - (Join-Path $PSScriptRoot "..\hook\cursor_hook.js"), - (Join-Path (Get-Location) "scripts\hook\cursor_hook.js") - ) - $hookSourcePath = $hookSourceCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + # 兼容:通过 `irm ... | iex` 执行时 $PSScriptRoot 可能为空,Join-Path 会直接报错 + $hookSourceCandidates = @() + if (-not [string]::IsNullOrWhiteSpace($PSScriptRoot)) { + $hookSourceCandidates += (Join-Path $PSScriptRoot "..\hook\cursor_hook.js") + } elseif ($MyInvocation.MyCommand.Path) { + $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + if (-not [string]::IsNullOrWhiteSpace($scriptDir)) { + $hookSourceCandidates += (Join-Path $scriptDir "..\hook\cursor_hook.js") + } + } + $cwdPath = $null + try { $cwdPath = (Get-Location).Path } catch { $cwdPath = $null } + if (-not [string]::IsNullOrWhiteSpace($cwdPath)) { + $hookSourceCandidates += (Join-Path $cwdPath "scripts\hook\cursor_hook.js") + } + $hookSourcePath = $hookSourceCandidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1 $hookDownloadUrls = @( "https://wget.la/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js", "https://down.npee.cn/?https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js", @@ -518,22 +604,62 @@ function Modify-CursorJSFiles { # ========== 方法B: b6 定点重写(机器码源函数,仅 main.js) ========== # 说明:b6(t) 是 machineId 的核心生成函数,t=true 返回原始值,t=false 返回哈希 if ((Split-Path $file -Leaf) -eq "main.js") { - # ⚠️ 安全修复:旧版“泛匹配 async function + .*?”会在部分版本中跨越大段代码误替换,直接破坏 main.js 语法。 - # 这里改为仅锚定 b6(...) 的函数定义;若版本不存在 b6,则跳过方案B,避免误伤。 - $b6Pattern = '(?s)async function b6\((\w+)\)\{.*?createHash\(["'']sha256["'']\).*?return \1\?\w+:\w+\}' - $b6Replacement = "async function b6(`$1){return `$1?'$machineGuid':'$machineId';}" - $b6Regex = [regex]::new($b6Pattern) - $b6Match = $b6Regex.Match($content) - if ($b6Match.Success) { - if ($b6Match.Length -gt 5000) { - Write-Host " $YELLOW⚠️ $NC [方案B] b6 匹配长度异常($($b6Match.Length)),为避免破坏文件已跳过" - } else { - $content = $b6Regex.Replace($content, $b6Replacement, 1) - Write-Host " $GREEN✓$NC [方案B] 已重写 b6(t) 返回值" + # ✅ 1+3 融合:限定 out-build/vs/base/node/id.js 模块内做特征匹配 + 花括号配对定位函数边界 + # 目的:提升跨版本覆盖率,同时避免正则跨模块误吞导致 main.js 语法损坏。 + try { + $moduleMarker = "out-build/vs/base/node/id.js" + $markerIndex = $content.IndexOf($moduleMarker) + if ($markerIndex -lt 0) { + throw "未找到 id.js 模块标记" + } + + $windowLen = [Math]::Min($content.Length - $markerIndex, 200000) + $windowText = $content.Substring($markerIndex, $windowLen) + + $hashRegex = [regex]::new('createHash\(["'']sha256["'']\)') + $hashMatches = $hashRegex.Matches($windowText) + $patched = $false + + foreach ($hm in $hashMatches) { + $hashPos = $hm.Index + $funcStart = $windowText.LastIndexOf("async function", $hashPos) + if ($funcStart -lt 0) { continue } + + $openBrace = $windowText.IndexOf("{", $funcStart) + if ($openBrace -lt 0) { continue } + + $endBrace = Find-JsMatchingBraceEnd -Text $windowText -OpenBraceIndex $openBrace -MaxScan 20000 + if ($endBrace -lt 0) { continue } + + $funcText = $windowText.Substring($funcStart, $endBrace - $funcStart + 1) + if ($funcText.Length -gt 8000) { continue } + + $sig = [regex]::Match($funcText, '^async function (\w+)\((\w+)\)') + if (-not $sig.Success) { continue } + $fn = $sig.Groups[1].Value + $param = $sig.Groups[2].Value + + # 特征校验:sha256 + hex digest + return param ? raw : hash + if (-not ($funcText -match 'createHash\(["'']sha256["'']\)')) { continue } + if (-not ($funcText -match '\.digest\(["'']hex["'']\)')) { continue } + if (-not ($funcText -match ('return\s+' + [regex]::Escape($param) + '\?\w+:\w+\}'))) { continue } + + $replacement = "async function $fn($param){return $param?'$machineGuid':'$machineId';}" + $absStart = $markerIndex + $funcStart + $absEnd = $markerIndex + $endBrace + $content = $content.Substring(0, $absStart) + $replacement + $content.Substring($absEnd + 1) + + Write-Host " $GREEN✓$NC [方案B] 已重写 $fn($param) 机器码源函数(融合版特征匹配)" $replacedB6 = $true + $patched = $true + break } - } else { - Write-Host " $YELLOW⚠️ $NC [方案B] 未找到 b6(t) 目标函数,已跳过" + + if (-not $patched) { + Write-Host " $YELLOW⚠️ $NC [方案B] 未定位到机器码源函数特征,已跳过" + } + } catch { + Write-Host " $YELLOW⚠️ $NC [方案B] 定位失败,已跳过:$($_.Exception.Message)" } } @@ -1114,12 +1240,15 @@ function Disable-CursorAutoUpdate { } # 更新配置文件(JSON/YAML) - $updateFiles = @( - "$cursorAppPath\resources\app-update.yml", - "$cursorAppPath\resources\app\update-config.json", - (if ($global:CursorAppDataDir) { Join-Path $global:CursorAppDataDir "update-config.json" } else { $null }), - (if ($global:CursorAppDataDir) { Join-Path $global:CursorAppDataDir "settings.json" } else { $null }) - ) | Where-Object { $_ } + # 兼容修复:PowerShell 不支持把 (if ... ) 当作表达式写进数组里,会报 “if 不是 cmdlet” + $updateFiles = @() + $updateFiles += "$cursorAppPath\resources\app-update.yml" + $updateFiles += "$cursorAppPath\resources\app\update-config.json" + if ($global:CursorAppDataDir) { + $updateFiles += (Join-Path $global:CursorAppDataDir "update-config.json") + $updateFiles += (Join-Path $global:CursorAppDataDir "settings.json") + } + $updateFiles = $updateFiles | Where-Object { $_ } foreach ($file in $updateFiles) { if (-not (Test-Path $file)) { continue } @@ -1161,11 +1290,13 @@ function Disable-CursorAutoUpdate { } # 尝试禁用更新器可执行文件 - $updaterCandidates = @( - "$cursorAppPath\Update.exe", - (if ($global:CursorLocalAppDataDir) { Join-Path $global:CursorLocalAppDataDir "Update.exe" } else { $null }), - "$cursorAppPath\CursorUpdater.exe" - ) | Where-Object { $_ } + $updaterCandidates = @() + $updaterCandidates += "$cursorAppPath\Update.exe" + if ($global:CursorLocalAppDataDir) { + $updaterCandidates += (Join-Path $global:CursorLocalAppDataDir "Update.exe") + } + $updaterCandidates += "$cursorAppPath\CursorUpdater.exe" + $updaterCandidates = $updaterCandidates | Where-Object { $_ } foreach ($updater in $updaterCandidates) { if (-not (Test-Path $updater)) { continue }