mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
// 设备唯一标识(组合指纹 + 回退 MachineGuid)
|
||
//
|
||
// 用途:为“设备识别码”提供尽量唯一且稳定的标识(授权/激活/统计)。
|
||
// 来源优先级(均为硬件级,重装系统不变):
|
||
// 1. board — 主板 UUID(Win32_ComputerSystemProduct.UUID)
|
||
// 2. disk — 硬盘序列号(Win32_DiskDrive 首个物理盘)
|
||
// 3. bios — BIOS 序列号(Win32_BIOS;存在大量 OEM 默认值,故置后)
|
||
// 4. machine— 注册表 MachineGuid(系统安装级,作为最终回退)
|
||
// 取第一个有效值 → SHA-256 → 前 32 位十六进制 → UUID 样式分段展示。
|
||
import { execSync } from 'child_process';
|
||
import { createHash } from 'crypto';
|
||
|
||
export type DeviceIdSource = 'board' | 'disk' | 'bios' | 'machine' | 'none';
|
||
|
||
export interface DeviceIdentity {
|
||
deviceId: string | null;
|
||
source: DeviceIdSource;
|
||
}
|
||
|
||
/** OEM/虚拟机常见的占位序列号,视为无效 */
|
||
const INVALID_VALUES = [
|
||
'to be filled',
|
||
'o.e.m',
|
||
'default string',
|
||
'system serial number',
|
||
'not specified',
|
||
'not available',
|
||
'none',
|
||
'n/a',
|
||
'unknown',
|
||
'unspecified',
|
||
'innotek',
|
||
'bochs',
|
||
'vmware',
|
||
];
|
||
|
||
function isValid(value?: string | null): value is string {
|
||
if (!value) return false;
|
||
const v = value.trim();
|
||
if (!v) return false;
|
||
if (/^0+$/.test(v)) return false; // 全 0
|
||
if (/^0{8}-0{4}-0{4}-0{4}-0{12}$/i.test(v)) return false; // 00000000-... 全空 UUID
|
||
const low = v.toLowerCase();
|
||
return !INVALID_VALUES.some((bad) => low.includes(bad));
|
||
}
|
||
|
||
/** 读取四类候选标识(Windows 一条 PowerShell 完成,逐项容错) */
|
||
function probeSources(): { board: string; disk: string; bios: string; machine: string } {
|
||
const empty = { board: '', disk: '', bios: '', machine: '' };
|
||
if (process.platform !== 'win32') return empty;
|
||
const ps = [
|
||
'-NoProfile -Command',
|
||
"$ErrorActionPreference='SilentlyContinue';",
|
||
'$b=(Get-CimInstance Win32_ComputerSystemProduct).UUID;',
|
||
'$d=(Get-CimInstance Win32_DiskDrive|Select-Object -First 1).SerialNumber;',
|
||
'$i=(Get-CimInstance Win32_BIOS).SerialNumber;',
|
||
"$g=(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid;",
|
||
'[pscustomobject]@{board=[string]$b;disk=[string]$d;bios=[string]$i;machine=[string]$g}|ConvertTo-Json -Compress',
|
||
].join(' ');
|
||
try {
|
||
const output = execSync(`powershell ${ps}`, { encoding: 'utf-8', timeout: 8000, windowsHide: true });
|
||
const parsed = JSON.parse(output.trim() || '{}');
|
||
return {
|
||
board: String(parsed.board ?? ''),
|
||
disk: String(parsed.disk ?? ''),
|
||
bios: String(parsed.bios ?? ''),
|
||
machine: String(parsed.machine ?? ''),
|
||
};
|
||
} catch {
|
||
return empty;
|
||
}
|
||
}
|
||
|
||
/** 32 位十六进制 → UUID 样式(8-4-4-4-12,小写) */
|
||
function toUuidLike(hex32: string): string {
|
||
const h = hex32.toLowerCase();
|
||
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
||
}
|
||
|
||
function compute(seed: string): string {
|
||
const hex = createHash('sha256').update(`koring-device:${seed}`, 'utf-8').digest('hex');
|
||
return toUuidLike(hex.slice(0, 32));
|
||
}
|
||
|
||
let cached: DeviceIdentity | null = null;
|
||
|
||
/** 获取设备唯一标识(进程内缓存,只探测一次) */
|
||
export function getDeviceId(): DeviceIdentity {
|
||
if (cached) return cached;
|
||
|
||
const { board, disk, bios, machine } = probeSources();
|
||
|
||
let seed: string | undefined;
|
||
let source: DeviceIdSource = 'none';
|
||
if (isValid(board)) {
|
||
seed = board;
|
||
source = 'board';
|
||
} else if (isValid(disk)) {
|
||
seed = disk;
|
||
source = 'disk';
|
||
} else if (isValid(bios)) {
|
||
seed = bios;
|
||
source = 'bios';
|
||
} else if (isValid(machine)) {
|
||
seed = machine;
|
||
source = 'machine';
|
||
}
|
||
|
||
cached = seed
|
||
? { deviceId: compute(seed), source }
|
||
: { deviceId: null, source: 'none' };
|
||
|
||
return cached;
|
||
}
|
||
|
||
/** 调试用:清空缓存(下次调用重新探测) */
|
||
export function resetDeviceIdCache(): void {
|
||
cached = null;
|
||
}
|