diff --git a/xmcl-keystone-ui/src/composables/instanceVersionInstall.ts b/xmcl-keystone-ui/src/composables/instanceVersionInstall.ts index d7dff654b..52b596266 100644 --- a/xmcl-keystone-ui/src/composables/instanceVersionInstall.ts +++ b/xmcl-keystone-ui/src/composables/instanceVersionInstall.ts @@ -1,16 +1,16 @@ +import { appInsights } from '@/telemetry' +import { AnyError } from '@/util/error' import { getSWRV } from '@/util/swrvGet' import type { AssetIndexIssue, AssetIssue, JavaVersion, LibraryIssue, MinecraftJarIssue, ResolvedVersion } from '@xmcl/core' import type { InstallProfileIssueReport } from '@xmcl/installer' -import { Mutex } from 'async-mutex' import { DiagnoseServiceKey, InstallServiceKey, Instance, InstanceServiceKey, JavaRecord, JavaServiceKey, RuntimeVersions, ServerVersionHeader, VersionHeader, VersionServiceKey, parseOptifineVersion } from '@xmcl/runtime-api' +import { Mutex } from 'async-mutex' import { InjectionKey, Ref, ShallowRef } from 'vue' import { InstanceResolveVersion } from './instanceVersion' +import { useNotifier } from './notifier' import { useService } from './service' import { kSWRVConfig } from './swrvConfig' import { getForgeVersionsModel, getLabyModManifestModel, getMinecraftVersionsModel, getNeoForgedVersionModel } from './version' -import { useNotifier } from './notifier' -import { appInsights } from '@/telemetry' -import { AnyError } from '@/util/error' export interface InstanceInstallInstruction { instance: string @@ -37,7 +37,18 @@ export interface InstanceInstallInstruction { export const kInstanceVersionInstall = Symbol('InstanceVersionInstall') as InjectionKey> const kAbort = Symbol('Aborted') -function useInstanceVersionInstall(versions: Ref, servers: Ref, javas: Ref) { +function getJavaPathOrInstall(instances: Instance[], javas: JavaRecord[], resolved: ResolvedVersion, instance: string) { + const inst = instances.find(i => i.path === instance) + if (inst?.java) { + return inst.java + } + const validJava = javas.find(v => v.majorVersion === resolved.javaVersion.majorVersion && v.valid) + console.log('validJava', validJava) + return validJava ? validJava.path : resolved.javaVersion +} + + +function useInstanceVersionInstall(versions: Ref, servers: Ref, instances: Ref, javas: Ref) { const { installForge, installNeoForged, @@ -74,6 +85,7 @@ function useInstanceVersionInstall(versions: Ref, servers: Ref< await installMinecraftJar(localMinecraft.id, 'client') } + const resolvedMcVersion = await resolveLocalVersion(minecraft) let forgeVersion = undefined as undefined | string if (forge) { const localForge = local.find(v => v.forge === forge && v.minecraft === minecraft) @@ -82,13 +94,10 @@ function useInstanceVersionInstall(versions: Ref, servers: Ref< const found = forgeVersions.find(v => v.version === forge) const forgeVersionId = found?.version ?? forge - if (javas.value.length === 0 || javas.value.every(java => !java.valid)) { - // no valid java - const mcVersionResolved = await resolveLocalVersion(minecraft) - await installDefaultJava(mcVersionResolved.javaVersion) - } + const javaOrInstall = getJavaPathOrInstall(instances.value, javas.value, resolvedMcVersion, '') + const javaPath = typeof javaOrInstall === 'string' ? javaOrInstall : await installDefaultJava(javaOrInstall).then((r) => r.path) - forgeVersion = await installForge({ mcversion: minecraft, version: forgeVersionId, installer: found?.installer }) + forgeVersion = await installForge({ mcversion: minecraft, version: forgeVersionId, installer: found?.installer, java: javaPath }) } else { forgeVersion = localForge.id await refreshVersion(localForge.id) @@ -102,13 +111,10 @@ function useInstanceVersionInstall(versions: Ref, servers: Ref< const found = neoForgedVersion.find(v => v === neoForged) const id = found ?? neoForged - if (javas.value.length === 0 || javas.value.every(java => !java.valid)) { - // no valid java - const mcVersionResolved = await resolveLocalVersion(minecraft) - await installDefaultJava(mcVersionResolved.javaVersion) - } + const javaOrInstall = getJavaPathOrInstall(instances.value, javas.value, resolvedMcVersion, '') + const javaPath = typeof javaOrInstall === 'string' ? javaOrInstall : await installDefaultJava(javaOrInstall).then((r) => r.path) - forgeVersion = await installNeoForged({ version: id, minecraft }) + forgeVersion = await installNeoForged({ version: id, minecraft, java: javaPath }) } else { forgeVersion = localNeoForge.id await refreshVersion(localNeoForge.id) @@ -126,7 +132,11 @@ function useInstanceVersionInstall(versions: Ref, servers: Ref< return localOptifine.id } const { type, patch } = parseOptifineVersion(optifineVersion) - const [ver] = await installOptifine({ type, patch, mcversion: minecraft, inheritFrom: forgeVersion }) + + const javaOrInstall = getJavaPathOrInstall(instances.value, javas.value, resolvedMcVersion, '') + const javaPath = typeof javaOrInstall === 'string' ? javaOrInstall : await installDefaultJava(javaOrInstall).then((r) => r.path) + + const ver = await installOptifine({ type, patch, mcversion: minecraft, inheritFrom: forgeVersion, java: javaPath }) return ver } else if (forgeVersion) { return forgeVersion @@ -239,7 +249,7 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance const { installDefaultJava } = useService(JavaServiceKey) const { notify } = useNotifier() - const { install, installServer } = useInstanceVersionInstall(versions, servers, javas) + const { install, installServer } = useInstanceVersionInstall(versions, servers, instances, javas) let abortController = new AbortController() const instruction: ShallowRef = shallowRef(undefined) @@ -293,16 +303,6 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance return newLock } - function getJavaInstall(javas: JavaRecord[], resolved: ResolvedVersion, instance: string) { - const inst = instances.value.find(i => i.path === instance) - if (inst?.java) { - return undefined - } - const validJava = javas.find(v => v.majorVersion === resolved.javaVersion.majorVersion && v.valid) - console.log('validJava', validJava) - return validJava ? undefined : resolved.javaVersion - } - /** * @param instance The instance path * @param runtime The runtime version @@ -322,7 +322,10 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance result.resolvedVersion = resolved.id - result.java = getJavaInstall(javas, resolved, instance) + const javaInstallOrPath = getJavaPathOrInstall(instances.value, javas, resolved, instance) + if (typeof javaInstallOrPath === 'object') { + result.java = javaInstallOrPath + } const profileIssue = await diagnoseProfile(resolved.id, 'client', path.value) if (abortSignal?.aborted) { throw kAbort } @@ -414,8 +417,8 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance if (version) { await installDependencies(version, 'client') const resolved = await resolveLocalVersion(version) - const java = getJavaInstall(javas.value, resolved, instruction.instance) - if (java) { + const java = getJavaPathOrInstall(instances.value, javas.value, resolved, instruction.instance) + if (typeof java === 'object') { await installDefaultJava(java) } } @@ -427,14 +430,14 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance await installMinecraftJar(instruction.runtime.minecraft, 'client') } if (instruction.profile) { - await installByProfile(instruction.profile.installProfile) + const resolved = await resolveLocalVersion(instruction.version) + const java = getJavaPathOrInstall(instances.value, javas.value, resolved, instruction.instance) + const javaPath = typeof java === 'string' ? java : await installDefaultJava(java).then((r) => r.path) + + await installByProfile({ profile: instruction.profile.installProfile, side: 'client', java: javaPath }) + if (instruction.version) { await installDependencies(instruction.version, 'client') - const resolved = await resolveLocalVersion(instruction.version) - const java = getJavaInstall(javas.value, resolved, instruction.instance) - if (java) { - await installDefaultJava(java) - } } return } @@ -444,43 +447,34 @@ export function useInstanceVersionInstallInstruction(path: Ref, instance type: instruction.optifine.type, patch: instruction.optifine.patch, }) - if (version) { - await installDependencies(version, 'client') - const resolved = await resolveLocalVersion(version) - const java = getJavaInstall(javas.value, resolved, instruction.instance) - if (java) { - await installDefaultJava(java) - } + await installDependencies(version, 'client') + const resolved = await resolveLocalVersion(version) + const java = getJavaPathOrInstall(instances.value, javas.value, resolved, instruction.instance) + if (typeof java === 'object') { + await installDefaultJava(java) } await commit(version) return } if (instruction.forge) { - if (javas.value.length === 0 || javas.value.every(java => !java.valid)) { - // no valid java - const mcVersionResolved = await resolveLocalVersion(instruction.forge.minecraft) - await installDefaultJava(mcVersionResolved.javaVersion) - } + const resolved = await resolveLocalVersion(instruction.forge.minecraft) + const java = getJavaPathOrInstall(instances.value, javas.value, resolved, instruction.instance) + const javaPath = typeof java === 'string' ? java : await installDefaultJava(java).then((r) => r.path) const version = await installForge({ mcversion: instruction.forge.minecraft, version: instruction.forge.version, + java: javaPath, + side: 'client', }) - if (version) { - await installDependencies(version, 'client') - const resolved = await resolveLocalVersion(version) - const java = getJavaInstall(javas.value, resolved, instruction.instance) - if (java) { - await installDefaultJava(java) - } - } + await installDependencies(version, 'client') await commit(version) return } const resolved = await resolveLocalVersion(instruction.resolvedVersion) - const java = getJavaInstall(javas.value, resolved, instruction.instance) - if (java) { + const java = getJavaPathOrInstall(instances.value, javas.value, resolved, instruction.instance) + if (typeof java === 'object') { await installDefaultJava(java) } if (instruction.libriares) { diff --git a/xmcl-runtime-api/src/services/InstallService.ts b/xmcl-runtime-api/src/services/InstallService.ts index 69470fb8a..ff8b7c0e9 100644 --- a/xmcl-runtime-api/src/services/InstallService.ts +++ b/xmcl-runtime-api/src/services/InstallService.ts @@ -9,6 +9,8 @@ export interface InstallOptifineOptions extends OptifineVersion { */ forgeVersion?: string inheritFrom?: string + + java?: string } export interface InstallOptifineAsModOptions extends OptifineVersion { @@ -59,11 +61,26 @@ export interface InstallForgeOptions { */ version: string + /** + * The java path + */ + java?: string + side?: 'client' | 'server' root?: string } +export interface InstallProfileOptions { + profile: InstallProfile + + version?: string + + side?: 'client' | 'server' + + java?: string +} + export interface InstallNeoForgedOptions { /** * The minecraft version @@ -73,6 +90,10 @@ export interface InstallNeoForgedOptions { * The forge version (without minecraft version) */ version: string + /** + * The java path + */ + java?: string side?: 'client' | 'server' } @@ -178,7 +199,7 @@ export interface InstallService { installQuilt(meta: InstallQuiltOptions): Promise - installByProfile(profile: InstallProfile): Promise + installByProfile(profile: InstallProfileOptions): Promise } export const InstallServiceKey: ServiceKey = 'InstallService' diff --git a/xmcl-runtime-api/src/services/JavaService.ts b/xmcl-runtime-api/src/services/JavaService.ts index 30038e698..7fcced721 100644 --- a/xmcl-runtime-api/src/services/JavaService.ts +++ b/xmcl-runtime-api/src/services/JavaService.ts @@ -43,7 +43,7 @@ export interface JavaService { /** * Install a default jdk 8 or 16 to the a preserved location. It'll be installed under your launcher root location `jre` or `jre-next` folder */ - installDefaultJava(version?: JavaVersion): Promise + installDefaultJava(version?: JavaVersion): Promise /** * Resolve java info. If the java is not known by launcher. It will cache it into the launcher java list. */ diff --git a/xmcl-runtime/install/InstallService.ts b/xmcl-runtime/install/InstallService.ts index 225abc9dc..63f24661d 100644 --- a/xmcl-runtime/install/InstallService.ts +++ b/xmcl-runtime/install/InstallService.ts @@ -1,7 +1,7 @@ import { checksum, MinecraftFolder, ResolvedLibrary, Version } from '@xmcl/core' import { DownloadBaseOptions } from '@xmcl/file-transfer' -import { DEFAULT_FORGE_MAVEN, DEFAULT_RESOURCE_ROOT_URL, DownloadTask, installAssetsTask, installByProfileTask, installFabric, InstallForgeOptions, installForgeTask, InstallJarTask, InstallJsonTask, installLabyMod4Task, installLibrariesTask, installLiteloaderTask, installNeoForgedTask, installOptifineTask, InstallProfile, installQuiltVersion, installResolvedAssetsTask, installResolvedLibrariesTask, installVersionTask, LiteloaderVersion, MinecraftVersion, Options, PostProcessFailedError } from '@xmcl/installer' -import { InstallForgeOptions as _InstallForgeOptions, Asset, InstallService as IInstallService, InstallableLibrary, InstallFabricOptions, InstallLabyModOptions, InstallNeoForgedOptions, InstallOptifineAsModOptions, InstallOptifineOptions, InstallQuiltOptions, InstallServiceKey, isFabricLoaderLibrary, isForgeLibrary, LockKey, SharedState, OptifineVersion, Settings } from '@xmcl/runtime-api' +import { DEFAULT_FORGE_MAVEN, DEFAULT_RESOURCE_ROOT_URL, DownloadTask, installAssetsTask, installByProfileTask, installFabric, InstallForgeOptions, installForgeTask, InstallJarTask, InstallJsonTask, installLabyMod4Task, installLibrariesTask, installLiteloaderTask, installNeoForgedTask, installOptifineTask, installQuiltVersion, installResolvedAssetsTask, installResolvedLibrariesTask, installVersionTask, LiteloaderVersion, MinecraftVersion, Options, PostProcessFailedError } from '@xmcl/installer' +import { InstallForgeOptions as _InstallForgeOptions, Asset, InstallService as IInstallService, InstallableLibrary, InstallFabricOptions, InstallLabyModOptions, InstallNeoForgedOptions, InstallOptifineAsModOptions, InstallOptifineOptions, InstallProfileOptions, InstallQuiltOptions, InstallServiceKey, isFabricLoaderLibrary, isForgeLibrary, LockKey, OptifineVersion, Settings, SharedState } from '@xmcl/runtime-api' import { CancelledError, task } from '@xmcl/task' import { spawn } from 'child_process' import { existsSync } from 'fs' @@ -355,7 +355,13 @@ export class InstallService extends AbstractService implements IInstallService { const validJavaPaths = this.javaService.state.all.filter(v => v.valid) const installOptions = this.getForgeInstallOptions() - validJavaPaths.sort((a, b) => a.majorVersion === 8 ? -1 : b.majorVersion === 8 ? 1 : -1) + if (options.java) { + const java = validJavaPaths.find(v => v.path === options.java) + if (java) { + validJavaPaths.splice(validJavaPaths.indexOf(java), 1) + validJavaPaths.unshift(java) + } + } let version: string | undefined for (const java of validJavaPaths) { @@ -621,7 +627,7 @@ export class InstallService extends AbstractService implements IInstallService { } } - const java = this.javaService.getPreferredJava()?.path + const java = options.java ?? this.javaService.getPreferredJava()?.path const urls = [ await this.getOptifineDownloadUrl(options), @@ -652,7 +658,7 @@ export class InstallService extends AbstractService implements IInstallService { return id }, { id: optifineVersion })) - this.log(`Succeed to install optifine ${version} on ${options.inheritFrom ?? options.mcversion}. ${result[0]}`) + this.log(`Succeed to install optifine ${version} on ${options.inheritFrom ?? options.mcversion}. ${result}`) return result } @@ -666,10 +672,12 @@ export class InstallService extends AbstractService implements IInstallService { } } - async installByProfile(profile: InstallProfile, version?: string) { + async installByProfile({ profile, version, side, java }: InstallProfileOptions) { try { await this.submit(installByProfileTask(profile, this.getPath(), { ...this.getForgeInstallOptions(), + side, + java, }).setName('installForge', { id: version ?? profile.version })) } catch (err) { if (err instanceof CancelledError) { @@ -679,6 +687,8 @@ export class InstallService extends AbstractService implements IInstallService { await this.installNeoForged({ minecraft: profile.minecraft, version: profile.version.substring('neoforge-'.length), + side, + java, }) } else { const forgeVersion = profile.version.indexOf('-forge-') !== -1 @@ -689,6 +699,8 @@ export class InstallService extends AbstractService implements IInstallService { await this.installForge({ version: forgeVersion, mcversion: profile.minecraft, + side, + java, }) } this.warn(err) diff --git a/xmcl-runtime/java/JavaService.ts b/xmcl-runtime/java/JavaService.ts index 7eca6b5a2..7ec0a08c7 100644 --- a/xmcl-runtime/java/JavaService.ts +++ b/xmcl-runtime/java/JavaService.ts @@ -1,6 +1,6 @@ import { JavaVersion } from '@xmcl/core' import { DEFAULT_RUNTIME_ALL_URL, JavaRuntimeManifest, JavaRuntimeTargetType, JavaRuntimes, installJavaRuntimeTask, parseJavaVersion, resolveJava, scanLocalJava } from '@xmcl/installer' -import { JavaService as IJavaService, Java, JavaRecord, JavaSchema, JavaServiceKey, JavaState, SharedState, Settings } from '@xmcl/runtime-api' +import { JavaService as IJavaService, Java, JavaRecord, JavaSchema, JavaServiceKey, JavaState, Settings, SharedState } from '@xmcl/runtime-api' import { chmod, ensureFile, readFile, stat } from 'fs-extra' import { dirname, join } from 'path' import { Inject, LauncherAppKey, PathResolver, kGameDataPath } from '~/app' @@ -10,6 +10,7 @@ import { kDownloadOptions } from '~/network' import { ExposeServiceKey, ServiceStateManager, Singleton, StatefulService } from '~/service' import { getApiSets, shouldOverrideApiSet } from '~/settings' import { TaskFn, kTaskExecutor } from '~/task' +import { AnyError } from '~/util/error' import { LauncherApp } from '../app/LauncherApp' import { readdirIfPresent } from '../util/fs' import { requireString } from '../util/object' @@ -213,7 +214,11 @@ export class JavaService extends StatefulService implements IJavaServ await chmod(location, 0o765) } this.log(`Successfully install java internally ${location}`) - return await this.resolveJava(location) + const result = await this.resolveJava(location) + if (!result) { + throw new AnyError('InstallDefaultJavaError', 'Fail to install java') + } + return result } async validateJavaPath(javaPath: string): Promise {