4 Commits
79817c9b44
...
f35d9ede3b
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
f35d9ede3b |
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环境的兼容性 |
1 month ago |
|
|
7fd954532e |
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环境的兼容性 |
1 month ago |
|
|
0d7832621a |
```
fix(cursor): 修复 macOS 下 Cursor 权限与路径问题以支持多用户环境 - 使用 TARGET_USER 和 TARGET_HOME 替代 $(whoami) 与 $HOME,确保在 sudo 环境中操作正确用户目录 - 修正权限命令为 u+rwX,防止错误赋予文件可执行权限 - 统一所有 Cursor 配置、缓存及数据路径引用至目标用户目录 - 增加 get_user_home_dir 函数以准确获取指定用户的主目录,提升兼容性 - 更新注释说明关键变更原因,避免误用 $(whoami) 导致的 EACCES 错误 ``` |
1 month ago |
|
|
7dcbf8410c |
```
docs(README): 更新价格表中的货币单位与表述 调整 README.md 和 README_CN.md 中的价格表格, 将套餐价格的货币单位统一为更清晰的格式, 并优化描述以提升可读性与准确性。 ``` |
1 month ago |
8 changed files with 1610 additions and 238 deletions
-
6README.md
-
6README_CN.md
-
477scripts/hook/cursor_hook.js
-
255scripts/hook/inject_hook_unix.sh
-
266scripts/hook/inject_hook_win.ps1
-
241scripts/run/cursor_linux_id_modifier.sh
-
340scripts/run/cursor_mac_id_modifier.sh
-
257scripts/run/cursor_win_id_modifier.ps1
@ -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__; |
||||
|
} |
||||
|
|
||||
@ -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 |
||||
|
|
||||
@ -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 |
||||
|
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue