dsh-maoying 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +87 -17
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
* 限额 JSON 读体、client.css 现读下发。
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
54
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, writeFileSync } from 'node:fs'
|
|
55
|
+
import { createHash } from 'node:crypto'
|
|
55
56
|
import { readFile } from 'node:fs/promises'
|
|
56
57
|
import { createRequire } from 'node:module'
|
|
57
58
|
import os from 'node:os'
|
|
@@ -197,6 +198,53 @@ export function hostRejectsInlineDelegation(version) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
/** 宿主包版本:从 <home>/node_modules require('@deepseek-ai/dsh/package.json')。 */
|
|
201
|
+
/**
|
|
202
|
+
* 裸读 asar 内文件(零依赖,不依赖 Electron 的 asar fs 补丁)。asar 格式:
|
|
203
|
+
* 头部 [0..4]=pickle 定长、[12..16]=头 JSON 字节数,头 JSON 是文件树(每个
|
|
204
|
+
* 文件带 offset/size),文件区从 16+jsonSize 起按 offset 裸存。桌面应用的
|
|
205
|
+
* 运行时进程里 existsSync(asar 内路径) 行为不可靠(实测探针轨迹:existsSync
|
|
206
|
+
* 为真但 readFileSync 拿不到内容),按字节切是唯一确定性读法。
|
|
207
|
+
* @returns {object|null} 解析后的 JSON;任何一步失败返回 null
|
|
208
|
+
*/
|
|
209
|
+
function asarReadJson(asarPath, innerPath) {
|
|
210
|
+
let fd = null
|
|
211
|
+
try {
|
|
212
|
+
fd = openSync(asarPath, 'r')
|
|
213
|
+
const head = Buffer.alloc(16)
|
|
214
|
+
if (readSync(fd, head, 0, 16, 0) !== 16) return null
|
|
215
|
+
const jsonSize = head.readUInt32LE(12)
|
|
216
|
+
const header = Buffer.alloc(jsonSize)
|
|
217
|
+
if (readSync(fd, header, 0, jsonSize, 16) !== jsonSize) return null
|
|
218
|
+
let node = JSON.parse(header.toString('utf8'))
|
|
219
|
+
for (const seg of innerPath.split('/')) {
|
|
220
|
+
node = node?.files?.[seg]
|
|
221
|
+
if (!node) return null
|
|
222
|
+
}
|
|
223
|
+
if (node.files || typeof node.offset !== 'string' || typeof node.size !== 'number') return null
|
|
224
|
+
// 桌面壳的 asar 文件区带块对齐位移(实测 +2 字节),头里的 integrity.hash
|
|
225
|
+
// (文件 SHA256)就是权威标尺:读一带余量窗口,滑窗找到哈希命中的精确切片。
|
|
226
|
+
const size = node.size
|
|
227
|
+
const want = typeof node.integrity?.hash === 'string' ? node.integrity.hash : null
|
|
228
|
+
const slack = 64
|
|
229
|
+
const win = Buffer.alloc(size + 2 * slack)
|
|
230
|
+
const base = 16 + jsonSize
|
|
231
|
+
const at = base + Number(node.offset) - slack
|
|
232
|
+
if (readSync(fd, win, 0, win.length, at) !== win.length) return null
|
|
233
|
+
for (let off = 0; off + size <= win.length; off++) {
|
|
234
|
+
if (win[off] !== 0x7b) continue // JSON 必以 { 开头,先粗筛再哈希
|
|
235
|
+
if (want !== null && createHash('sha256').update(win.subarray(off, off + size)).digest('hex') !== want) continue
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(win.toString('utf8', off, off + size))
|
|
238
|
+
} catch { /* 哈希没配或解析失败继续滑 */ }
|
|
239
|
+
}
|
|
240
|
+
return null
|
|
241
|
+
} catch {
|
|
242
|
+
return null
|
|
243
|
+
} finally {
|
|
244
|
+
if (fd !== null) { try { closeSync(fd) } catch { /* 关闭失败无所谓 */ } }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
200
248
|
/** 从 start 向上最多 8 层找最近的 @deepseek-ai/dsh package.json,命中记轨迹 */
|
|
201
249
|
function anchorVersion(start, probe, trace) {
|
|
202
250
|
let dir = path.dirname(path.resolve(start))
|
|
@@ -245,31 +293,53 @@ function collectHostVersion(dshHome, trace) {
|
|
|
245
293
|
if (hit) return hit
|
|
246
294
|
}
|
|
247
295
|
if (scanned === 0) trace.push({ probe: 'argv-scan', hit: null, note: '无可锚定的 argv 项' })
|
|
248
|
-
// ③ app.asar
|
|
296
|
+
// ③ app.asar 裸读锚定(桌面应用的确定性读法,见 asarReadJson 说明)
|
|
249
297
|
if (process.versions?.electron) {
|
|
250
298
|
const execDir = path.dirname(process.execPath)
|
|
251
|
-
const
|
|
252
|
-
path.resolve(execDir, '..', 'Resources', 'app.asar'
|
|
253
|
-
path.resolve(execDir, 'resources', 'app.asar'
|
|
254
|
-
path.resolve(execDir, '..', 'Resources', 'app.asar', 'package.json'),
|
|
255
|
-
path.resolve(execDir, 'resources', 'app.asar', 'package.json'),
|
|
299
|
+
const asarPaths = [
|
|
300
|
+
path.resolve(execDir, '..', 'Resources', 'app.asar'),
|
|
301
|
+
path.resolve(execDir, 'resources', 'app.asar'),
|
|
256
302
|
]
|
|
257
|
-
let
|
|
258
|
-
for (const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const pkg =
|
|
263
|
-
|
|
264
|
-
|
|
303
|
+
let sawAsar = false
|
|
304
|
+
for (const asarPath of asarPaths) {
|
|
305
|
+
if (!existsSync(asarPath)) continue
|
|
306
|
+
sawAsar = true
|
|
307
|
+
for (const inner of ['dsh/package.json', 'package.json']) {
|
|
308
|
+
const pkg = asarReadJson(asarPath, inner)
|
|
309
|
+
// 桌面壳内置 dsh 的包名是 @deepseek-ai/dsh-desktop-runtime(实测),CLI
|
|
310
|
+
// 源码包叫 @deepseek-ai/dsh——两者都算命中。
|
|
311
|
+
const okName = pkg?.name === '@deepseek-ai/dsh' || pkg?.name === '@deepseek-ai/dsh-desktop-runtime'
|
|
312
|
+
if (okName && typeof pkg.version === 'string' && pkg.version !== '') {
|
|
313
|
+
trace.push({ probe: 'asar', hit: pkg.version, via: `${asarPath}!${inner}` })
|
|
265
314
|
return pkg.version
|
|
266
315
|
}
|
|
267
|
-
}
|
|
316
|
+
}
|
|
268
317
|
}
|
|
269
|
-
|
|
318
|
+
trace.push({ probe: 'asar', hit: null, note: sawAsar ? 'asar 存在但未读到 @deepseek-ai/dsh' : 'asar 路径不存在' })
|
|
270
319
|
} else {
|
|
271
320
|
trace.push({ probe: 'asar', hit: null, note: '非 Electron 进程' })
|
|
272
321
|
}
|
|
322
|
+
// ③′ 桌面运行时 payload 版本:primary-runtime/runtime.json 的 desktopVersion
|
|
323
|
+
// (真实文件系统,任何上下文可读;比 asar 略弱——payload 可能落后应用壳)
|
|
324
|
+
{
|
|
325
|
+
const execDir = path.dirname(process.execPath)
|
|
326
|
+
const runtimeJsons = (process.argv ?? [])
|
|
327
|
+
.filter((a) => typeof a === 'string' && a !== '' && !a.startsWith('-'))
|
|
328
|
+
.map((a) => path.join(a, 'runtime.json'))
|
|
329
|
+
runtimeJsons.push(path.resolve(execDir, '..', 'Resources', 'runtime', 'primary-runtime', 'runtime.json'))
|
|
330
|
+
for (const rj of runtimeJsons) {
|
|
331
|
+
try {
|
|
332
|
+
if (!existsSync(rj)) continue
|
|
333
|
+
const meta = JSON.parse(readFileSync(rj, 'utf8'))
|
|
334
|
+
const v = meta?.desktopVersion
|
|
335
|
+
if (typeof v === 'string' && v !== '') {
|
|
336
|
+
trace.push({ probe: 'desktop-runtime', hit: v, via: rj })
|
|
337
|
+
return v
|
|
338
|
+
}
|
|
339
|
+
} catch { /* 单个坏文件换下一个 */ }
|
|
340
|
+
}
|
|
341
|
+
trace.push({ probe: 'desktop-runtime', hit: null })
|
|
342
|
+
}
|
|
273
343
|
// ④ Electron app.getVersion()。纯 Node 下 require('electron') 得到路径字符串
|
|
274
344
|
// 或直接找不到,类型守卫天然挡掉。
|
|
275
345
|
try {
|