Browse Source

```

feat(scripts): 改进Cursor设备ID修改脚本的函数定位算法

- 在Linux和Mac的shell脚本中,将原有的简单正则匹配替换为更精确的JavaScript
  函数边界定位算法,通过花括号配对和模块标记限定搜索范围

- 在Windows的PowerShell脚本中新增Find-JsMatchingBraceEnd函数,实现JavaScript
  语法感知的花括号匹配,避免跨模块误替换导致的语法错误

- 优化PowerShell脚本中的路径处理逻辑,修复$PSScriptRoot为空时的兼容性问题

- 改进更新配置文件和更新器可执行文件的查找逻辑,解决PowerShell数组语法错误
```
煎饼果子卷鲨鱼辣椒 2 weeks ago
parent
commit
318a100435
  1. 142
      scripts/run/cursor_linux_id_modifier.sh
  2. 142
      scripts/run/cursor_mac_id_modifier.sh
  3. 191
      scripts/run/cursor_win_id_modifier.ps1

142
scripts/run/cursor_linux_id_modifier.sh

@ -1132,20 +1132,148 @@ EOF
if command -v python3 >/dev/null 2>&1; then if command -v python3 >/dev/null 2>&1; then
local b6_result local b6_result
b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY' b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY'
if True:
import re, sys import re, sys
path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3]
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
data = f.read() 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:
# ✅ 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: with open(path, "w", encoding="utf-8") as f:
f.write(new_data) f.write(new_data)
print("PATCHED") print("PATCHED")
break
else: else:
print("NOT_FOUND") print("NOT_FOUND")
PY PY

142
scripts/run/cursor_mac_id_modifier.sh

@ -1859,20 +1859,148 @@ EOF
if command -v python3 >/dev/null 2>&1; then if command -v python3 >/dev/null 2>&1; then
local b6_result local b6_result
b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY' b6_result=$(python3 - "$file" "$machine_guid" "$machine_id" <<'PY'
if True:
import re, sys import re, sys
path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3] path, machine_guid, machine_id = sys.argv[1], sys.argv[2], sys.argv[3]
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
data = f.read() 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:
# ✅ 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: with open(path, "w", encoding="utf-8") as f:
f.write(new_data) f.write(new_data)
print("PATCHED") print("PATCHED")
break
else: else:
print("NOT_FOUND") print("NOT_FOUND")
PY PY

191
scripts/run/cursor_win_id_modifier.ps1

@ -244,6 +244,81 @@ function Generate-RandomString {
return $result 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文件实现设备识别绕过(增强版三重方案) # 🔧 修改Cursor内核JS文件实现设备识别绕过(增强版三重方案)
# 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名 # 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名
# 方案B: b6 定点重写 - 机器码源函数直接返回固定值 # 方案B: b6 定点重写 - 机器码源函数直接返回固定值
@ -327,11 +402,22 @@ function Modify-CursorJSFiles {
# 部署外置 Hook 文件(供 Loader Stub 加载,支持多域名备用下载) # 部署外置 Hook 文件(供 Loader Stub 加载,支持多域名备用下载)
$hookTargetPath = "$env:USERPROFILE\.cursor_hook.js" $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 = @( $hookDownloadUrls = @(
"https://wget.la/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/hook/cursor_hook.js", "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", "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) ========== # ========== 方法B: b6 定点重写(机器码源函数,仅 main.js) ==========
# 说明:b6(t) 是 machineId 的核心生成函数,t=true 返回原始值,t=false 返回哈希 # 说明:b6(t) 是 machineId 的核心生成函数,t=true 返回原始值,t=false 返回哈希
if ((Split-Path $file -Leaf) -eq "main.js") { 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 $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) # 更新配置文件(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) { foreach ($file in $updateFiles) {
if (-not (Test-Path $file)) { continue } 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) { foreach ($updater in $updaterCandidates) {
if (-not (Test-Path $updater)) { continue } if (-not (Test-Path $updater)) { continue }

Loading…
Cancel
Save