mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
149 lines
5.1 KiB
TypeScript
149 lines
5.1 KiB
TypeScript
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||
|
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
|||
|
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
|||
|
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
|||
|
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
|||
|
|
//
|
|||
|
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
|||
|
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
|||
|
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
|||
|
|
|
|||
|
|
import type { LaunchOption } from '@xmcl/core';
|
|||
|
|
import { resolveJava, getPotentialJavaLocations } from '@xmcl/installer';
|
|||
|
|
import type { AppConfig } from '../config';
|
|||
|
|
import type { InstanceInfo } from './instance';
|
|||
|
|
|
|||
|
|
/** 游戏启动所需的账户档案 */
|
|||
|
|
export interface LaunchProfile {
|
|||
|
|
username: string;
|
|||
|
|
uuid: string;
|
|||
|
|
accessToken?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 快速联机目标服务器 */
|
|||
|
|
export interface LaunchServer {
|
|||
|
|
ip: string;
|
|||
|
|
port?: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 引号感知的命令行参数切分器。
|
|||
|
|
* 支持双引号 / 单引号包裹的含空格参数与反斜杠转义。
|
|||
|
|
* 用于 jvmArgs / gameArgs / preLaunchCmd 的解析。
|
|||
|
|
*/
|
|||
|
|
export function parseArgs(line: string): string[] {
|
|||
|
|
const args: string[] = [];
|
|||
|
|
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|||
|
|
let match: RegExpExecArray | null;
|
|||
|
|
while ((match = re.exec(line)) !== null) {
|
|||
|
|
if (match[1] !== undefined) {
|
|||
|
|
args.push(match[1].replace(/\\(["\\])/g, '$1'));
|
|||
|
|
} else if (match[2] !== undefined) {
|
|||
|
|
args.push(match[2].replace(/\\(['\\])/g, '$1'));
|
|||
|
|
} else {
|
|||
|
|
args.push(match[3]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return args;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将启动器配置(AppConfig.java / AppConfig.advanced)+ 实例信息 + 账户档案
|
|||
|
|
* 映射为 @xmcl/core 的 LaunchOption。
|
|||
|
|
*
|
|||
|
|
* 配置 → 启动参数 对应关系:
|
|||
|
|
* - java.memMode=auto → 实例 minMemory/maxMemory(未设置则 1024/4096)
|
|||
|
|
* - java.memMode=custom → min=min(2,memGB)G,max=memGB G
|
|||
|
|
* - java.gc=zgc/g1 → -XX:+UseZGC / -XX:+UseG1GC
|
|||
|
|
* - java.jvmArgs → 逐行解析并入 extraJVMArgs
|
|||
|
|
* - advanced.gameArgs → 解析并入 extraMCArgs
|
|||
|
|
* - advanced.winMode → resolution(fullscreen / custom 宽高)
|
|||
|
|
* - advanced.preLaunchCmd → prependCommand(Windows 批处理需 `cmd /c` 前缀)
|
|||
|
|
* - advanced.debugMode → -Dkoring.debugMode=true
|
|||
|
|
*/
|
|||
|
|
export function buildLaunchOptions(
|
|||
|
|
config: AppConfig,
|
|||
|
|
instance: InstanceInfo,
|
|||
|
|
profile: LaunchProfile,
|
|||
|
|
javaPath: string,
|
|||
|
|
server?: LaunchServer,
|
|||
|
|
): LaunchOption {
|
|||
|
|
const java = config.java;
|
|||
|
|
const adv = config.advanced;
|
|||
|
|
|
|||
|
|
// ---- 内存 ----
|
|||
|
|
let minMemory = instance.config.minMemory ?? 1024;
|
|||
|
|
let maxMemory = instance.config.maxMemory ?? 4096;
|
|||
|
|
if (java.memMode === 'custom') {
|
|||
|
|
const gb = Math.max(1, Math.min(16, java.memGB || 4));
|
|||
|
|
minMemory = Math.min(2, gb) * 1024;
|
|||
|
|
maxMemory = gb * 1024;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- JVM 参数 ----
|
|||
|
|
const extraJVMArgs: string[] = [];
|
|||
|
|
if (java.gc === 'zgc') extraJVMArgs.push('-XX:+UseZGC');
|
|||
|
|
else if (java.gc === 'g1') extraJVMArgs.push('-XX:+UseG1GC');
|
|||
|
|
if (java.jvmArgs?.trim()) {
|
|||
|
|
extraJVMArgs.push(...parseArgs(java.jvmArgs));
|
|||
|
|
}
|
|||
|
|
if (adv.debugMode) {
|
|||
|
|
extraJVMArgs.push('-Dkoring.debugMode=true');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- 游戏参数 ----
|
|||
|
|
const extraMCArgs: string[] = [];
|
|||
|
|
if (adv.gameArgs?.trim()) {
|
|||
|
|
extraMCArgs.push(...parseArgs(adv.gameArgs));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- 窗口 / 分辨率 ----
|
|||
|
|
let resolution: { width?: number; height?: number; fullscreen?: boolean } | undefined;
|
|||
|
|
if (adv.winMode === 'fullscreen') {
|
|||
|
|
resolution = { fullscreen: true };
|
|||
|
|
} else if (adv.winMode === 'custom') {
|
|||
|
|
resolution = { width: adv.customWidth || 854, height: adv.customHeight || 480 };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
gameProfile: { name: profile.username, id: profile.uuid },
|
|||
|
|
accessToken: profile.accessToken,
|
|||
|
|
javaPath,
|
|||
|
|
// version 由调用方在 Version.parse 后覆盖为 ResolvedVersion
|
|||
|
|
version: instance.config.runtime.minecraft,
|
|||
|
|
gamePath: instance.path,
|
|||
|
|
minMemory,
|
|||
|
|
maxMemory,
|
|||
|
|
resolution,
|
|||
|
|
extraJVMArgs,
|
|||
|
|
extraMCArgs,
|
|||
|
|
server,
|
|||
|
|
prependCommand: adv.preLaunchCmd?.trim() ? parseArgs(adv.preLaunchCmd) : undefined,
|
|||
|
|
launcherName: 'Koring Launcher',
|
|||
|
|
launcherBrand: 'Koring',
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 Java 可执行文件路径,优先级:
|
|||
|
|
* 1. 用户配置路径(resolveJava 校验,无效则继续向下)
|
|||
|
|
* 2. 系统扫描(`where java` / `which java` 结果逐个 resolve)
|
|||
|
|
* 3. 兜底 PATH 中的 `java`
|
|||
|
|
*/
|
|||
|
|
export async function resolveJavaPath(configuredPath: string): Promise<string> {
|
|||
|
|
if (configuredPath?.trim()) {
|
|||
|
|
const info = await resolveJava(configuredPath.trim()).catch(() => undefined);
|
|||
|
|
if (info) return info.path;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const locations = await getPotentialJavaLocations();
|
|||
|
|
for (const loc of locations) {
|
|||
|
|
const info = await resolveJava(loc).catch(() => undefined);
|
|||
|
|
if (info) return info.path;
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
// 扫描失败则继续回退
|
|||
|
|
}
|
|||
|
|
return 'java';
|
|||
|
|
}
|