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;
|
|||
|
|
}
|