Files
koring-launcher/electron/core/launcher.ts
T
dream_pep 3653e26a09 feat: 发布 v1.2.0 版本,新增多项核心功能
- 引入 electron-updater 实现自动更新功能
- 新增 Java 环境扫描与校验的 IPC 处理逻辑
- 实现离线账号登录功能
- 新增配置变更跨进程广播机制
- 重构游戏启动逻辑,使用主进程内存配置作为唯一权威来源
- 新增界面显示与语言设置的配置页面
- 添加 Windows 平台自动发布 CI 流水线
- 迁移旧版配置/认证文件到用户数据目录
- 修复崩溃日志路径、表单控件等多项 bug
- 重构设置页组件系统统一界面样式
2026-08-28 02:08:12 +08:00

147 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
//
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
// 未经允许的情况下删除此版权头可能会受到民事指控
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
import * as fs from 'fs';
import * as path from 'path';
import { Version, launch, createMinecraftProcessWatcher } from '@xmcl/core';
import type { AppConfig } from '../config';
import { getInstanceInfo, updateInstance } from './instance';
import {
buildLaunchOptions,
resolveJavaPath,
type LaunchProfile,
type LaunchServer,
} from './launch-options';
export interface GameLaunchResult {
pid: number;
version: string;
username: string;
}
export interface LaunchEvent {
event: string;
[key: string]: unknown;
}
export interface LaunchGameOptions {
/** 主进程权威配置(内存中最新值) */
config: AppConfig;
/** 实例名 */
instanceName: string;
/** 实例父目录(游戏根目录) */
gamePath: string;
/** 账户档案 */
profile: LaunchProfile;
/** 快速联机目标服务器 */
server?: LaunchServer;
/** 事件回调(stdout / stderr / window-ready / exit */
onEvent?: (event: LaunchEvent) => void;
}
/**
* 统一游戏启动入口:
* 实例信息 → 版本解析 → Java 解析 → 配置映射(buildLaunchOptions)→ @xmcl/core launch
* 启动后监听 window-ready / exit,并在退出时累计实例 playtime。
*/
export async function launchGame(options: LaunchGameOptions): Promise<GameLaunchResult> {
const { config, instanceName, gamePath, profile, server, onEvent } = options;
// 1. 读取实例信息并做健康检查
const instance = await getInstanceInfo(instanceName, gamePath);
if (!instance.healthy) {
const detail = instance.issues.join('') || '未知问题';
throw new Error(`实例「${instanceName}」未安装完整:${detail}。请先在资源中心安装或重新安装该实例。`);
}
// 2. 解析版本
const resolved = await Version.parse(instance.path, instance.config.runtime.minecraft);
// 3. 解析 Java 路径(配置路径 → 系统扫描 → PATH)
const javaPath = await resolveJavaPath(config.java.javaPath);
// 4. 配置 → LaunchOptionversion 覆盖为已解析版本)
const launchOption = buildLaunchOptions(config, instance, profile, javaPath, server);
launchOption.version = resolved;
// 5. 启动(detached:启动器关闭后游戏继续运行;pipe:转发 stdout/stderr
const mcProcess = await launch({
...launchOption,
extraExecOption: { detached: true, stdio: 'pipe' },
});
// 6. 事件监听
const watcher = createMinecraftProcessWatcher(mcProcess);
watcher.on('minecraft-window-ready', () => {
onEvent?.({ event: 'window-ready' });
});
watcher.on('minecraft-exit', ({ code }) => {
onEvent?.({ event: 'exit', code });
});
mcProcess.stdout?.on('data', (chunk: Buffer) => {
const message = chunk.toString();
if (message.trim()) onEvent?.({ event: 'stdout', message });
});
mcProcess.stderr?.on('data', (chunk: Buffer) => {
const message = chunk.toString();
if (message.trim()) onEvent?.({ event: 'stderr', message });
});
// 7. playtime 累计(游戏进程退出时)
const startTime = Date.now();
mcProcess.on('exit', async () => {
const elapsed = Date.now() - startTime;
try {
const info = await getInstanceInfo(instanceName, gamePath);
await updateInstance(instanceName, gamePath, {
lastPlayedDate: Date.now(),
playtime: (info.config.playtime || 0) + elapsed,
});
} catch {
// 忽略 playtime 更新失败
}
});
// 8. 更新最近访问时间
await updateInstance(instanceName, gamePath, { lastAccessDate: Date.now() }).catch(() => {});
return {
pid: mcProcess.pid || 0,
version: instance.config.runtime.minecraft,
username: profile.username,
};
}
/**
* 诊断指定游戏目录下某个版本的健康状态(JSON + JAR 是否存在)。
* 供 launch:diagnose 使用。
*/
export async function diagnoseVersion(gamePath: string, version: string): Promise<Record<string, unknown>> {
const issues: string[] = [];
const versionDir = path.join(gamePath, 'versions', version);
const versionJsonPath = path.join(versionDir, `${version}.json`);
const jarPath = path.join(versionDir, `${version}.jar`);
if (!fs.existsSync(versionJsonPath)) {
issues.push(`Version JSON not found: ${versionJsonPath}`);
}
if (!fs.existsSync(jarPath)) {
issues.push(`Client JAR not found: ${jarPath}`);
}
return {
version,
gamePath,
healthy: issues.length === 0,
issues,
};
}