// parse_dsstore.js - 从 macOS .DS_Store 里抽出目录项名字 // 用法: node parse_dsstore.js [--json out.json] // 原理: DS_Store 是 B 树,文件/目录名记录以 [type=0x00000005][offset u32][len u32][len×UTF-16BE] 内联存放, // 因此用滑动扫描定位 type 标记再读长度即可,无需完整解析 B 树。 const fs = require('fs'); const path = require('path'); const Argv = process.argv.slice(2); const File = Argv[0]; const JsonOut = (() => { const I = Argv.indexOf('--json'); return I >= 0 && Argv[I + 1] ? Argv[I + 1] : ''; })(); const Buf = fs.readFileSync(File); const Names = new Set(); // 主路径:定位 type=5 记录 for (let I = 0; I + 12 <= Buf.length; I++) { if (Buf[I] !== 0 || Buf[I + 1] !== 0 || Buf[I + 2] !== 0 || Buf[I + 3] !== 5) continue; const Len = Buf.readUInt32BE(I + 8); if (Len === 0 || Len > 1024) continue; const Start = I + 12; if (Start + Len * 2 > Buf.length) continue; const Name = Buf.slice(Start, Start + Len * 2).toString('utf16le'); // 用 utf16le 读 BE 数据会字节交换;这里改回手工 BE 解码 let BeName = ''; for (let K = 0; K < Len; K++) BeName += String.fromCharCode(Buf.readUInt16BE(Start + K * 2)); if (!BeName || /[\x00-\x08\x0b-\x1f]/.test(BeName)) continue; Names.add(BeName); void Name; } // 兜底:任意可打印 UTF-16BE 串(长度 >= 3) if (Names.size === 0) { for (let I = 0; I + 6 <= Buf.length; I += 2) { let S = ''; let J = I; while (J + 2 <= Buf.length) { const C = Buf.readUInt16BE(J); if (C < 0x20 || C > 0x7e) break; S += String.fromCharCode(C); J += 2; } if (S.length >= 3) { Names.add(S); I = J - 2; } } } const List = [...Names].sort(); const Stat = fs.statSync(File); console.log('文件: ' + path.basename(File) + ' ' + Stat.size + ' 字节'); console.log('条目: ' + List.length); for (const N of List) console.log(' ' + N); if (JsonOut) fs.writeFileSync(path.resolve(JsonOut), JSON.stringify({ File, Size: Stat.size, Entries: List }, null, 2), 'utf8');