feat(cursor): 实现跨平台设备标识符Hook注入方案

- 新增 cursor_hook.js 核心Hook模块,拦截child_process、crypto、os等关键模块
- 实现统一ID配置管理,支持环境变量和JSON配置文件双重加载机制
- 开发Unix/macOS注入脚本(inject_hook_unix.sh),自动化Hook代码注入流程
- 开发Windows注入脚本(inject_hook_win.ps1),适配PowerShell环境
- 升级Linux修改器脚本,集成新的Hook方案并优化备份机制
- 添加完整的调试日志系统和防重复注入保护机制
- 支持动态import模块Hook,增强对ESM环境的兼容性
This commit is contained in:
煎饼果子卷鲨鱼辣椒
2025-12-21 17:04:02 +08:00
parent 0d7832621a
commit 7fd954532e
6 changed files with 1488 additions and 113 deletions

477
scripts/hook/cursor_hook.js Normal file
View File

@@ -0,0 +1,477 @@
/**
* Cursor 设备标识符 Hook 模块
*
* 🎯 功能:从底层拦截所有设备标识符的生成,实现一劳永逸的机器码修改
*
* 🔧 Hook 点:
* 1. child_process.execSync - 拦截 REG.exe 查询 MachineGuid
* 2. crypto.createHash - 拦截 SHA256 哈希计算
* 3. @vscode/deviceid - 拦截 devDeviceId 获取
* 4. @vscode/windows-registry - 拦截注册表读取
* 5. os.networkInterfaces - 拦截 MAC 地址获取
*
* 📦 使用方式:
* 将此代码注入到 main.js 文件顶部Sentry 初始化之后)
*
* ⚙️ 配置方式:
* 1. 环境变量CURSOR_MACHINE_ID, CURSOR_MAC_MACHINE_ID, CURSOR_DEV_DEVICE_ID, CURSOR_SQM_ID
* 2. 配置文件:~/.cursor_ids.json
* 3. 自动生成:如果没有配置,则自动生成并持久化
*/
// ==================== 配置区域 ====================
// 使用 var 确保在 ES Module 环境中也能正常工作
var __cursor_hook_config__ = {
// 是否启用 Hook设置为 false 可临时禁用)
enabled: true,
// 是否输出调试日志(设置为 true 可查看详细日志)
debug: false,
// 配置文件路径(相对于用户目录)
configFileName: '.cursor_ids.json',
// 标记:防止重复注入
injected: false
};
// ==================== Hook 实现 ====================
// 使用 IIFE 确保代码立即执行
(function() {
'use strict';
// 防止重复注入
if (globalThis.__cursor_patched__ || __cursor_hook_config__.injected) {
return;
}
globalThis.__cursor_patched__ = true;
__cursor_hook_config__.injected = true;
// 调试日志函数
const log = (...args) => {
if (__cursor_hook_config__.debug) {
console.log('[CursorHook]', ...args);
}
};
// ==================== ID 生成和管理 ====================
// 生成 UUID v4
const generateUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
// 生成 64 位十六进制字符串(用于 machineId
const generateHex64 = () => {
let hex = '';
for (let i = 0; i < 64; i++) {
hex += Math.floor(Math.random() * 16).toString(16);
}
return hex;
};
// 生成 MAC 地址格式的字符串
const generateMacAddress = () => {
const hex = '0123456789ABCDEF';
let mac = '';
for (let i = 0; i < 6; i++) {
if (i > 0) mac += ':';
mac += hex[Math.floor(Math.random() * 16)];
mac += hex[Math.floor(Math.random() * 16)];
}
return mac;
};
// 加载或生成 ID 配置
// 注意:使用 createRequire 来支持 ES Module 环境
const loadOrGenerateIds = () => {
// 在 ES Module 环境中,需要使用 createRequire 来加载 CommonJS 模块
let fs, path, os;
try {
// 尝试使用 Node.js 内置模块
const { createRequire } = require('module');
const require2 = createRequire(import.meta?.url || __filename);
fs = require2('fs');
path = require2('path');
os = require2('os');
} catch (e) {
// 回退到直接 require
fs = require('fs');
path = require('path');
os = require('os');
}
const configPath = path.join(os.homedir(), __cursor_hook_config__.configFileName);
let ids = null;
// 尝试从环境变量读取
if (process.env.CURSOR_MACHINE_ID) {
ids = {
machineId: process.env.CURSOR_MACHINE_ID,
macMachineId: process.env.CURSOR_MAC_MACHINE_ID || generateHex64(),
devDeviceId: process.env.CURSOR_DEV_DEVICE_ID || generateUUID(),
sqmId: process.env.CURSOR_SQM_ID || `{${generateUUID().toUpperCase()}}`
};
log('从环境变量加载 ID 配置');
return ids;
}
// 尝试从配置文件读取
try {
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
ids = JSON.parse(content);
log('从配置文件加载 ID 配置:', configPath);
return ids;
}
} catch (e) {
log('读取配置文件失败:', e.message);
}
// 生成新的 ID
ids = {
machineId: generateHex64(),
macMachineId: generateHex64(),
devDeviceId: generateUUID(),
sqmId: `{${generateUUID().toUpperCase()}}`,
macAddress: generateMacAddress(),
createdAt: new Date().toISOString()
};
// 保存到配置文件
try {
fs.writeFileSync(configPath, JSON.stringify(ids, null, 2), 'utf8');
log('已生成并保存新的 ID 配置:', configPath);
} catch (e) {
log('保存配置文件失败:', e.message);
}
return ids;
};
// 加载 ID 配置
const __cursor_ids__ = loadOrGenerateIds();
log('当前 ID 配置:', __cursor_ids__);
// ==================== Module Hook ====================
const Module = require('module');
const originalRequire = Module.prototype.require;
// 缓存已 Hook 的模块
const hookedModules = new Map();
Module.prototype.require = function(id) {
const result = originalRequire.apply(this, arguments);
// 如果已经 Hook 过,直接返回缓存
if (hookedModules.has(id)) {
return hookedModules.get(id);
}
let hooked = result;
// Hook child_process 模块
if (id === 'child_process') {
hooked = hookChildProcess(result);
}
// Hook os 模块
else if (id === 'os') {
hooked = hookOs(result);
}
// Hook crypto 模块
else if (id === 'crypto') {
hooked = hookCrypto(result);
}
// Hook @vscode/deviceid 模块
else if (id === '@vscode/deviceid') {
hooked = hookDeviceId(result);
}
// Hook @vscode/windows-registry 模块
else if (id === '@vscode/windows-registry') {
hooked = hookWindowsRegistry(result);
}
// 缓存 Hook 结果
if (hooked !== result) {
hookedModules.set(id, hooked);
log(`已 Hook 模块: ${id}`);
}
return hooked;
};
// ==================== child_process Hook ====================
function hookChildProcess(cp) {
const originalExecSync = cp.execSync;
cp.execSync = function(command, options) {
const cmdStr = String(command).toLowerCase();
// 拦截 MachineGuid 查询
if (cmdStr.includes('reg') && cmdStr.includes('machineguid')) {
log('拦截 MachineGuid 查询');
// 返回格式化的注册表输出
return Buffer.from(`\r\n MachineGuid REG_SZ ${__cursor_ids__.machineId.substring(0, 36)}\r\n`);
}
// 拦截 ioreg 命令 (macOS)
if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) {
log('拦截 IOPlatformUUID 查询');
return Buffer.from(`"IOPlatformUUID" = "${__cursor_ids__.machineId.substring(0, 36).toUpperCase()}"`);
}
// 拦截 machine-id 读取 (Linux)
if (cmdStr.includes('machine-id') || cmdStr.includes('hostname')) {
log('拦截 machine-id 查询');
return Buffer.from(__cursor_ids__.machineId.substring(0, 32));
}
return originalExecSync.apply(this, arguments);
};
return cp;
}
// ==================== os Hook ====================
function hookOs(os) {
const originalNetworkInterfaces = os.networkInterfaces;
os.networkInterfaces = function() {
log('拦截 networkInterfaces 调用');
// 返回虚拟的网络接口,使用固定的 MAC 地址
return {
'Ethernet': [{
address: '192.168.1.100',
netmask: '255.255.255.0',
family: 'IPv4',
mac: __cursor_ids__.macAddress || '00:00:00:00:00:00',
internal: false
}]
};
};
return os;
}
// ==================== crypto Hook ====================
function hookCrypto(crypto) {
const originalCreateHash = crypto.createHash;
const originalRandomUUID = crypto.randomUUID;
// Hook createHash - 用于拦截 machineId 的 SHA256 计算
crypto.createHash = function(algorithm) {
const hash = originalCreateHash.apply(this, arguments);
if (algorithm.toLowerCase() === 'sha256') {
const originalUpdate = hash.update.bind(hash);
const originalDigest = hash.digest.bind(hash);
let inputData = '';
hash.update = function(data, encoding) {
inputData += String(data);
return originalUpdate(data, encoding);
};
hash.digest = function(encoding) {
// 检查是否是 machineId 相关的哈希计算
if (inputData.includes('MachineGuid') ||
inputData.includes('IOPlatformUUID') ||
inputData.length === 32 ||
inputData.length === 36) {
log('拦截 SHA256 哈希计算,返回固定 machineId');
if (encoding === 'hex') {
return __cursor_ids__.machineId;
}
return Buffer.from(__cursor_ids__.machineId, 'hex');
}
return originalDigest(encoding);
};
}
return hash;
};
// Hook randomUUID - 用于拦截 devDeviceId 生成
if (originalRandomUUID) {
let uuidCallCount = 0;
crypto.randomUUID = function() {
uuidCallCount++;
// 第一次调用返回固定的 devDeviceId
if (uuidCallCount <= 2) {
log('拦截 randomUUID 调用,返回固定 devDeviceId');
return __cursor_ids__.devDeviceId;
}
return originalRandomUUID.apply(this, arguments);
};
}
return crypto;
}
// ==================== @vscode/deviceid Hook ====================
function hookDeviceId(deviceIdModule) {
log('Hook @vscode/deviceid 模块');
return {
...deviceIdModule,
getDeviceId: async function() {
log('拦截 getDeviceId 调用');
return __cursor_ids__.devDeviceId;
}
};
}
// ==================== @vscode/windows-registry Hook ====================
function hookWindowsRegistry(registryModule) {
log('Hook @vscode/windows-registry 模块');
const originalGetStringRegKey = registryModule.GetStringRegKey;
return {
...registryModule,
GetStringRegKey: function(hive, path, name) {
// 拦截 MachineId 读取
if (name === 'MachineId' || path.includes('SQMClient')) {
log('拦截注册表 MachineId/SQMClient 读取');
return __cursor_ids__.sqmId;
}
// 拦截 MachineGuid 读取
if (name === 'MachineGuid' || path.includes('Cryptography')) {
log('拦截注册表 MachineGuid 读取');
return __cursor_ids__.machineId.substring(0, 36);
}
return originalGetStringRegKey?.apply(this, arguments) || '';
}
};
}
// ==================== 动态 import Hook ====================
// Cursor 使用动态 import() 加载模块,我们需要 Hook 这些模块
// 由于 ES Module 的限制,我们通过 Hook 全局对象来实现
// 存储已 Hook 的动态导入模块
const hookedDynamicModules = new Map();
// Hook crypto 模块的动态导入
const hookDynamicCrypto = (cryptoModule) => {
if (hookedDynamicModules.has('crypto')) {
return hookedDynamicModules.get('crypto');
}
const hooked = { ...cryptoModule };
// Hook createHash
if (cryptoModule.createHash) {
const originalCreateHash = cryptoModule.createHash;
hooked.createHash = function(algorithm) {
const hash = originalCreateHash.apply(this, arguments);
if (algorithm.toLowerCase() === 'sha256') {
const originalDigest = hash.digest.bind(hash);
let inputData = '';
const originalUpdate = hash.update.bind(hash);
hash.update = function(data, encoding) {
inputData += String(data);
return originalUpdate(data, encoding);
};
hash.digest = function(encoding) {
// 检测 machineId 相关的哈希
if (inputData.includes('MachineGuid') ||
inputData.includes('IOPlatformUUID') ||
(inputData.length >= 32 && inputData.length <= 40)) {
log('动态导入: 拦截 SHA256 哈希');
return encoding === 'hex' ? __cursor_ids__.machineId : Buffer.from(__cursor_ids__.machineId, 'hex');
}
return originalDigest(encoding);
};
}
return hash;
};
}
hookedDynamicModules.set('crypto', hooked);
return hooked;
};
// Hook @vscode/deviceid 模块的动态导入
const hookDynamicDeviceId = (deviceIdModule) => {
if (hookedDynamicModules.has('@vscode/deviceid')) {
return hookedDynamicModules.get('@vscode/deviceid');
}
const hooked = {
...deviceIdModule,
getDeviceId: async () => {
log('动态导入: 拦截 getDeviceId');
return __cursor_ids__.devDeviceId;
}
};
hookedDynamicModules.set('@vscode/deviceid', hooked);
return hooked;
};
// Hook @vscode/windows-registry 模块的动态导入
const hookDynamicWindowsRegistry = (registryModule) => {
if (hookedDynamicModules.has('@vscode/windows-registry')) {
return hookedDynamicModules.get('@vscode/windows-registry');
}
const originalGetStringRegKey = registryModule.GetStringRegKey;
const hooked = {
...registryModule,
GetStringRegKey: function(hive, path, name) {
if (name === 'MachineId' || path?.includes('SQMClient')) {
log('动态导入: 拦截 SQMClient');
return __cursor_ids__.sqmId;
}
if (name === 'MachineGuid' || path?.includes('Cryptography')) {
log('动态导入: 拦截 MachineGuid');
return __cursor_ids__.machineId.substring(0, 36);
}
return originalGetStringRegKey?.apply(this, arguments) || '';
}
};
hookedDynamicModules.set('@vscode/windows-registry', hooked);
return hooked;
};
// 将 Hook 函数暴露到全局,供后续使用
globalThis.__cursor_hook_dynamic__ = {
crypto: hookDynamicCrypto,
deviceId: hookDynamicDeviceId,
windowsRegistry: hookDynamicWindowsRegistry,
ids: __cursor_ids__
};
log('Cursor Hook 初始化完成');
log('machineId:', __cursor_ids__.machineId.substring(0, 16) + '...');
log('devDeviceId:', __cursor_ids__.devDeviceId);
log('sqmId:', __cursor_ids__.sqmId);
})();
// ==================== 导出配置(供外部使用) ====================
if (typeof module !== 'undefined' && module.exports) {
module.exports = { __cursor_hook_config__ };
}
// ==================== ES Module 兼容性 ====================
// 如果在 ES Module 环境中,也暴露配置
if (typeof globalThis !== 'undefined') {
globalThis.__cursor_hook_config__ = __cursor_hook_config__;
}

View File

@@ -0,0 +1,255 @@
#!/bin/bash
# ========================================
# Cursor Hook 注入脚本 (macOS/Linux)
# ========================================
#
# 🎯 功能:将 cursor_hook.js 注入到 Cursor 的 main.js 文件顶部
#
# 📦 使用方式:
# chmod +x inject_hook_unix.sh
# ./inject_hook_unix.sh
#
# 参数:
# --rollback 回滚到原始版本
# --force 强制重新注入
# --debug 启用调试模式
#
# ========================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 参数解析
ROLLBACK=false
FORCE=false
DEBUG=false
for arg in "$@"; do
case $arg in
--rollback) ROLLBACK=true ;;
--force) FORCE=true ;;
--debug) DEBUG=true ;;
esac
done
# 日志函数
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_debug() { if $DEBUG; then echo -e "${BLUE}[DEBUG]${NC} $1"; fi; }
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOOK_SCRIPT="$SCRIPT_DIR/cursor_hook.js"
# 获取 Cursor main.js 路径
get_cursor_path() {
local paths=()
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
paths=(
"/Applications/Cursor.app/Contents/Resources/app/out/main.js"
"$HOME/Applications/Cursor.app/Contents/Resources/app/out/main.js"
)
else
# Linux
paths=(
"/opt/Cursor/resources/app/out/main.js"
"/usr/share/cursor/resources/app/out/main.js"
"$HOME/.local/share/cursor/resources/app/out/main.js"
"/snap/cursor/current/resources/app/out/main.js"
)
fi
for path in "${paths[@]}"; do
if [[ -f "$path" ]]; then
echo "$path"
return 0
fi
done
return 1
}
# 检查是否已注入
check_already_injected() {
local main_js="$1"
grep -q "__cursor_patched__" "$main_js" 2>/dev/null
}
# 备份原始文件
backup_main_js() {
local main_js="$1"
local backup_dir="$(dirname "$main_js")/backups"
mkdir -p "$backup_dir"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_path="$backup_dir/main.js.backup_$timestamp"
local original_backup="$backup_dir/main.js.original"
# 创建原始备份(如果不存在)
if [[ ! -f "$original_backup" ]]; then
cp "$main_js" "$original_backup"
log_info "已创建原始备份: $original_backup"
fi
cp "$main_js" "$backup_path"
log_info "已创建时间戳备份: $backup_path"
echo "$original_backup"
}
# 回滚到原始版本
restore_main_js() {
local main_js="$1"
local backup_dir="$(dirname "$main_js")/backups"
local original_backup="$backup_dir/main.js.original"
if [[ -f "$original_backup" ]]; then
cp "$original_backup" "$main_js"
log_info "已回滚到原始版本"
return 0
else
log_error "未找到原始备份文件"
return 1
fi
}
# 关闭 Cursor 进程
stop_cursor_process() {
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
pkill -x "Cursor" 2>/dev/null || true
pkill -x "Cursor Helper" 2>/dev/null || true
else
# Linux
pkill -f "cursor" 2>/dev/null || true
fi
sleep 2
log_info "Cursor 进程已关闭"
}
# 注入 Hook 代码
inject_hook() {
local main_js="$1"
local hook_script="$2"
# 读取 Hook 脚本内容
local hook_content=$(cat "$hook_script")
# 创建临时文件
local temp_file=$(mktemp)
# 读取 main.js 并注入 Hook
# 在版权声明之后注入
awk -v hook="$hook_content" '
/^\*\// && !injected {
print
print ""
print "// ========== Cursor Hook 注入开始 =========="
print hook
print "// ========== Cursor Hook 注入结束 =========="
print ""
injected = 1
next
}
{ print }
' "$main_js" > "$temp_file"
# 替换原文件
mv "$temp_file" "$main_js"
return 0
}
# 主函数
main() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Cursor Hook 注入工具 (Unix) ${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# 获取 Cursor main.js 路径
local main_js
main_js=$(get_cursor_path) || {
log_error "未找到 Cursor 安装路径"
log_error "请确保 Cursor 已正确安装"
exit 1
}
log_info "找到 Cursor main.js: $main_js"
# 回滚模式
if $ROLLBACK; then
log_info "执行回滚操作..."
stop_cursor_process
if restore_main_js "$main_js"; then
log_info "回滚成功!"
else
log_error "回滚失败!"
exit 1
fi
exit 0
fi
# 检查是否已注入
if check_already_injected "$main_js" && ! $FORCE; then
log_warn "Hook 已经注入,无需重复操作"
log_info "如需强制重新注入,请使用 --force 参数"
exit 0
fi
# 检查 Hook 脚本是否存在
if [[ ! -f "$HOOK_SCRIPT" ]]; then
log_error "未找到 cursor_hook.js 文件"
log_error "请确保 cursor_hook.js 与此脚本在同一目录"
exit 1
fi
log_info "找到 Hook 脚本: $HOOK_SCRIPT"
# 关闭 Cursor 进程
stop_cursor_process
# 备份原始文件
log_info "正在备份原始文件..."
backup_main_js "$main_js"
# 注入 Hook 代码
log_info "正在注入 Hook 代码..."
if inject_hook "$main_js" "$HOOK_SCRIPT"; then
log_info "Hook 注入成功!"
else
log_error "Hook 注入失败!"
log_warn "正在回滚..."
restore_main_js "$main_js"
exit 1
fi
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} ✅ Hook 注入完成! ${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
log_info "现在可以启动 Cursor 了"
log_info "ID 配置文件位置: ~/.cursor_ids.json"
echo ""
echo -e "${YELLOW}提示:${NC}"
echo " - 如需回滚,请运行: ./inject_hook_unix.sh --rollback"
echo " - 如需强制重新注入,请运行: ./inject_hook_unix.sh --force"
echo " - 如需启用调试日志,请运行: ./inject_hook_unix.sh --debug"
echo ""
}
# 执行主函数
main

View File

@@ -0,0 +1,266 @@
# ========================================
# Cursor Hook 注入脚本 (Windows)
# ========================================
#
# 🎯 功能:将 cursor_hook.js 注入到 Cursor 的 main.js 文件顶部
#
# 📦 使用方式:
# 1. 以管理员权限运行 PowerShell
# 2. 执行: .\inject_hook_win.ps1
#
# ⚠️ 注意事项:
# - 会自动备份原始 main.js 文件
# - 支持回滚到原始版本
# - Cursor 更新后需要重新注入
#
# ========================================
param(
[switch]$Rollback, # 回滚到原始版本
[switch]$Force, # 强制重新注入
[switch]$Debug # 启用调试模式
)
# 颜色定义
$RED = "`e[31m"
$GREEN = "`e[32m"
$YELLOW = "`e[33m"
$BLUE = "`e[34m"
$NC = "`e[0m"
# 日志函数
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
switch ($Level) {
"INFO" { Write-Host "$GREEN[INFO]$NC $Message" }
"WARN" { Write-Host "$YELLOW[WARN]$NC $Message" }
"ERROR" { Write-Host "$RED[ERROR]$NC $Message" }
"DEBUG" { if ($Debug) { Write-Host "$BLUE[DEBUG]$NC $Message" } }
}
}
# 获取 Cursor 安装路径
function Get-CursorPath {
$possiblePaths = @(
"$env:LOCALAPPDATA\Programs\cursor\resources\app\out\main.js",
"$env:LOCALAPPDATA\Programs\Cursor\resources\app\out\main.js",
"C:\Program Files\Cursor\resources\app\out\main.js",
"C:\Program Files (x86)\Cursor\resources\app\out\main.js"
)
foreach ($path in $possiblePaths) {
if (Test-Path $path) {
return $path
}
}
return $null
}
# 获取 Hook 脚本路径
function Get-HookScriptPath {
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$hookPath = Join-Path $scriptDir "cursor_hook.js"
if (Test-Path $hookPath) {
return $hookPath
}
# 尝试从当前目录查找
$currentDir = Get-Location
$hookPath = Join-Path $currentDir "cursor_hook.js"
if (Test-Path $hookPath) {
return $hookPath
}
return $null
}
# 检查是否已注入
function Test-AlreadyInjected {
param([string]$MainJsPath)
$content = Get-Content $MainJsPath -Raw -Encoding UTF8
return $content -match "__cursor_patched__"
}
# 备份原始文件
function Backup-MainJs {
param([string]$MainJsPath)
$backupDir = Join-Path (Split-Path -Parent $MainJsPath) "backups"
if (-not (Test-Path $backupDir)) {
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupPath = Join-Path $backupDir "main.js.backup_$timestamp"
# 检查是否有原始备份
$originalBackup = Join-Path $backupDir "main.js.original"
if (-not (Test-Path $originalBackup)) {
Copy-Item $MainJsPath $originalBackup -Force
Write-Log "已创建原始备份: $originalBackup"
}
Copy-Item $MainJsPath $backupPath -Force
Write-Log "已创建时间戳备份: $backupPath"
return $originalBackup
}
# 回滚到原始版本
function Restore-MainJs {
param([string]$MainJsPath)
$backupDir = Join-Path (Split-Path -Parent $MainJsPath) "backups"
$originalBackup = Join-Path $backupDir "main.js.original"
if (Test-Path $originalBackup) {
Copy-Item $originalBackup $MainJsPath -Force
Write-Log "已回滚到原始版本" "INFO"
return $true
} else {
Write-Log "未找到原始备份文件" "ERROR"
return $false
}
}
# 注入 Hook 代码
function Inject-Hook {
param(
[string]$MainJsPath,
[string]$HookScriptPath
)
# 读取 Hook 脚本内容
$hookContent = Get-Content $HookScriptPath -Raw -Encoding UTF8
# 读取 main.js 内容
$mainContent = Get-Content $MainJsPath -Raw -Encoding UTF8
# 查找注入点:在 Sentry 初始化代码之后
# Sentry 初始化代码特征: _sentryDebugIds
$sentryPattern = '(?<=\}\(\);)\s*(?=var\s+\w+\s*=\s*function)'
if ($mainContent -match $sentryPattern) {
# 在 Sentry 初始化之后注入
$injectionPoint = $mainContent.IndexOf('}();') + 4
$newContent = $mainContent.Substring(0, $injectionPoint) + "`n`n// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent.Substring($injectionPoint)
} else {
# 如果找不到 Sentry直接在文件开头注入在版权声明之后
$copyrightEnd = $mainContent.IndexOf('*/') + 2
if ($copyrightEnd -gt 2) {
$newContent = $mainContent.Substring(0, $copyrightEnd) + "`n`n// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent.Substring($copyrightEnd)
} else {
$newContent = "// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent
}
}
# 写入修改后的内容
Set-Content -Path $MainJsPath -Value $newContent -Encoding UTF8 -NoNewline
return $true
}
# 关闭 Cursor 进程
function Stop-CursorProcess {
$cursorProcesses = Get-Process -Name "Cursor*" -ErrorAction SilentlyContinue
if ($cursorProcesses) {
Write-Log "发现 Cursor 进程正在运行,正在关闭..."
$cursorProcesses | Stop-Process -Force
Start-Sleep -Seconds 2
Write-Log "Cursor 进程已关闭"
}
}
# 主函数
function Main {
Write-Host ""
Write-Host "$BLUE========================================$NC"
Write-Host "$BLUE Cursor Hook 注入工具 (Windows) $NC"
Write-Host "$BLUE========================================$NC"
Write-Host ""
# 获取 Cursor main.js 路径
$mainJsPath = Get-CursorPath
if (-not $mainJsPath) {
Write-Log "未找到 Cursor 安装路径" "ERROR"
Write-Log "请确保 Cursor 已正确安装" "ERROR"
exit 1
}
Write-Log "找到 Cursor main.js: $mainJsPath"
# 回滚模式
if ($Rollback) {
Write-Log "执行回滚操作..."
Stop-CursorProcess
if (Restore-MainJs -MainJsPath $mainJsPath) {
Write-Log "回滚成功!" "INFO"
} else {
Write-Log "回滚失败!" "ERROR"
exit 1
}
exit 0
}
# 检查是否已注入
if ((Test-AlreadyInjected -MainJsPath $mainJsPath) -and -not $Force) {
Write-Log "Hook 已经注入,无需重复操作" "WARN"
Write-Log "如需强制重新注入,请使用 -Force 参数" "INFO"
exit 0
}
# 获取 Hook 脚本路径
$hookScriptPath = Get-HookScriptPath
if (-not $hookScriptPath) {
Write-Log "未找到 cursor_hook.js 文件" "ERROR"
Write-Log "请确保 cursor_hook.js 与此脚本在同一目录" "ERROR"
exit 1
}
Write-Log "找到 Hook 脚本: $hookScriptPath"
# 关闭 Cursor 进程
Stop-CursorProcess
# 备份原始文件
Write-Log "正在备份原始文件..."
$backupPath = Backup-MainJs -MainJsPath $mainJsPath
# 注入 Hook 代码
Write-Log "正在注入 Hook 代码..."
try {
if (Inject-Hook -MainJsPath $mainJsPath -HookScriptPath $hookScriptPath) {
Write-Log "Hook 注入成功!" "INFO"
} else {
Write-Log "Hook 注入失败!" "ERROR"
exit 1
}
} catch {
Write-Log "注入过程中发生错误: $_" "ERROR"
Write-Log "正在回滚..." "WARN"
Restore-MainJs -MainJsPath $mainJsPath
exit 1
}
Write-Host ""
Write-Host "$GREEN========================================$NC"
Write-Host "$GREEN ✅ Hook 注入完成! $NC"
Write-Host "$GREEN========================================$NC"
Write-Host ""
Write-Log "现在可以启动 Cursor 了"
Write-Log "ID 配置文件位置: $env:USERPROFILE\.cursor_ids.json"
Write-Host ""
Write-Host "$YELLOW提示:$NC"
Write-Host " - 如需回滚,请运行: .\inject_hook_win.ps1 -Rollback"
Write-Host " - 如需强制重新注入,请运行: .\inject_hook_win.ps1 -Force"
Write-Host " - 如需启用调试日志,请运行: .\inject_hook_win.ps1 -Debug"
Write-Host ""
}
# 执行主函数
Main