Skip to content

Commit

Permalink
feat: Support install optifine as a mod
Browse files Browse the repository at this point in the history
  • Loading branch information
ci010 committed Oct 16, 2023
1 parent 42c0760 commit 28cadc8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 46 deletions.
2 changes: 1 addition & 1 deletion xmcl-keystone-ui/src/composables/instanceMods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function useInstanceMods(instancePath: Ref<string>, instanceRuntime: Ref<

const enabledModCounts = computed(() => mods.value.filter(v => v.enabled).length)

watch([computed(() => state.value?.mods)], () => {
watch([computed(() => state.value?.mods), java], () => {
if (!state.value?.mods) return
updateItems(state.value?.mods, instanceRuntime.value)
})
Expand Down
30 changes: 19 additions & 11 deletions xmcl-keystone-ui/src/composables/modSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,13 @@ export function useLocalModsSearch(keyword: Ref<string>, modLoaderFilters: Ref<M
const _all: Mod[] = []
const _installed: Mod[] = []

if (runtime.value.optifine) {
_installed.push(getOptifineAsMod())
} else {
const hasOptifine = keyword.value.toLowerCase().includes('optifine')
if (hasOptifine) {
_all.push(getOptifineAsMod())
}
}
let hasOptifine = false

const processModFile = (m: ModFile, instanceFile: boolean) => {
if (m.modId === 'OptiFine') {
hasOptifine = true
return getOptifineAsMod(m)
}
const curseforgeId = m.resource.metadata.curseforge?.projectId
const modrinthId = m.resource.metadata.modrinth?.projectId
const name = m.name
Expand Down Expand Up @@ -279,6 +276,17 @@ export function useLocalModsSearch(keyword: Ref<string>, modLoaderFilters: Ref<M
}
}

if (!hasOptifine) {
if (runtime.value.optifine) {
_installed.push(getOptifineAsMod())
} else {
const hasOptifine = keyword.value.toLowerCase().includes('optifine')
if (hasOptifine) {
_all.push(getOptifineAsMod())
}
}
}

return markRaw([_all, _installed] as const)
})

Expand Down Expand Up @@ -309,14 +317,14 @@ export function useLocalModsSearch(keyword: Ref<string>, modLoaderFilters: Ref<M
}
}

const getOptifineAsMod = () => {
const getOptifineAsMod = (f?: ModFile) => {
const result: Mod = {
id: 'optifine',
id: 'OptiFine',
icon: 'image://builtin/optifine',
title: 'Optifine',
author: 'sp614x',
description: 'Optifine is a Minecraft optimization mod. It allows Minecraft to run faster and look better with full support for HD textures and many configuration options.',
installed: [],
installed: f ? [f] : [],
downloadCount: 0,
followerCount: 0,
}
Expand Down
89 changes: 57 additions & 32 deletions xmcl-keystone-ui/src/views/ModDetailOptifine.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@

<script setup lang="ts">
import { useService } from '@/composables'
import { kInstance } from '@/composables/instance'
import { useMarkdown } from '@/composables/markdown'
import { useModDetailEnable } from '@/composables/modDetail'
import { kSWRVConfig } from '@/composables/swrvConfig'
import { useOptifineVersions } from '@/composables/version'
import { injection } from '@/util/inject'
import { Mod } from '@/util/mod'
import { InstallServiceKey, InstanceServiceKey, ResourceDomain, ResourceServiceKey, RuntimeVersions } from '@xmcl/runtime-api'
import { ModVersion } from './ModDetailVersion.vue'
import ModDetail, { ModDetailData } from './ModDetail.vue'
import { InstallServiceKey, InstanceModsServiceKey, InstanceServiceKey, RuntimeVersions } from '@xmcl/runtime-api'
import useSWRV from 'swrv'
import { kSWRVConfig } from '@/composables/swrvConfig'
import { useMarkdown } from '@/composables/markdown'
import { useService } from '@/composables'
import { injection } from '@/util/inject'
import { kInstance } from '@/composables/instance'
import { kLocalVersions } from '@/composables/versionLocal'
import ModDetail, { ModDetailData } from './ModDetail.vue'
import { ModVersion } from './ModDetailVersion.vue'
const props = defineProps<{
mod: Mod
runtime: RuntimeVersions
}>()
const { versions: localVersions } = injection(kLocalVersions)
const { versions: optVersions, refreshing, installed } = useOptifineVersions(computed(() => props.runtime.minecraft), computed(() => props.runtime.forge || ''), localVersions)
const { versions: optVersions, refreshing } = useOptifineVersions(computed(() => props.runtime.minecraft), computed(() => props.runtime.forge || ''), computed(() => []))
const selectedVersion = ref(undefined as ModVersion | undefined)
provide('selectedVersion', selectedVersion)
Expand All @@ -28,7 +27,8 @@ const { render: renderMd } = useMarkdown()
const { data: changelog, isValidating: loadingChangelog } = useSWRV(computed(() => selectedVersion.value && `https://www.optifine.net/changelog?f=OptiFine_${selectedVersion.value.id}.jar`), async (url) => {
const res = await fetch(url)
const text = await res.text()
return renderMd(text)
const [content] = text.split('\r\n\r\n')
return renderMd(content)
}, inject(kSWRVConfig))
const versions = computed(() => {
Expand All @@ -37,11 +37,11 @@ const versions = computed(() => {
const version = `${f.type}_${f.patch}`
const id = `${f.mcversion}_${version}`
const modVersion: ModVersion = reactive({
id,
id: props.mod.installed[0].version === version ? props.mod.installed[0].path : id,
name: version,
version,
downloadCount: 0,
installed: props.runtime.optifine === version,
installed: computed(() => props.runtime.optifine === version || props.mod.installed[0].version === version),
loaders: ['vanilla', 'forge'],
minecraftVersion: f.mcversion,
type: 'release',
Expand All @@ -56,6 +56,15 @@ const versions = computed(() => {
selectedVersion.value = versions.value[0]
const { data: optifineHome, isValidating: loadingDescription } = useSWRV('/optifine-home', async () => {
const response = await fetch('https://www.optifine.net/home')
const content = await response.text()
const parsed = new DOMParser().parseFromString(content, 'text/html')
const contentHTML = parsed.querySelector('.content')
return contentHTML?.innerHTML || ''
}, inject(kSWRVConfig))
const _optifineHome = computed(() => optifineHome.value || props.mod.description)
const model = computed(() => {
const result: ModDetailData = reactive({
id: props.mod.id,
Expand All @@ -70,21 +79,27 @@ const model = computed(() => {
downloadCount: 0,
follows: 0,
url: 'https://www.optifine.net/home',
htmlContent: props.mod.description,
htmlContent: _optifineHome,
installed: !!props.mod.installed,
enabled: false,
})
return result
})
const { editInstance } = useService(InstanceServiceKey)
const { installOptifine } = useService(InstallServiceKey)
const { getResourcesByUris } = useService(ResourceServiceKey)
const { installOptifineAsResource } = useService(InstallServiceKey)
const { path, runtime } = injection(kInstance)
const updating = ref(false)
const { install: installMod, uninstall: uninstallMod } = useService(InstanceModsServiceKey)
const onInstall = async (m: ModVersion) => {
try {
updating.value = true
const [mc, ...rest] = m.id.split('_')
const restStr = rest.join('_')
const index = restStr.lastIndexOf('_')
const type = restStr.substring(0, index)
const patch = restStr.substring(index + 1)
if (!runtime.value.forge) {
await editInstance({
instancePath: path.value,
Expand All @@ -93,12 +108,15 @@ const onInstall = async (m: ModVersion) => {
optifine: m.version,
},
})
const [mc, type, patch] = m.id.split('_')
if (installed.value[m.version]) {
const installedVersion = installed.value[m.version]
const filePath = localVersions.value.find(v => v.id === installedVersion)?.path
} else {
if (hasInstalledVersion.value) {
const oldFiles = props.mod.installed.map(i => i.resource)
const resource = await installOptifineAsResource({ mcversion: mc, type, patch })
await installMod({ path: path.value, mods: [resource] })
await uninstallMod({ path: path.value, mods: oldFiles })
} else {
const [_, resource] = await installOptifine({ mcversion: mc, type, patch })
const resource = await installOptifineAsResource({ mcversion: mc, type, patch })
await installMod({ path: path.value, mods: [resource] })
}
}
} finally {
Expand All @@ -107,24 +125,31 @@ const onInstall = async (m: ModVersion) => {
}
const onDelete = () => {
const newRuntime = { ...runtime.value, optifine: '' }
editInstance({
instancePath: path.value,
runtime: newRuntime,
})
if (runtime.value.optifine) {
const newRuntime = { ...runtime.value, optifine: '' }
editInstance({
instancePath: path.value,
runtime: newRuntime,
})
}
if (props.mod.installed.length > 0) {
uninstallMod({ path: path.value, mods: props.mod.installed.map(i => i.resource) })
}
}
const { enabled, installed, hasInstalledVersion } = useModDetailEnable(selectedVersion, computed(() => props.mod.installed), updating)
</script>
<template>
<ModDetail
:detail="model"
:dependencies="[]"
:enabled="false"
:has-installed-version="!!runtime.optifine"
:selected-installed="runtime.optifine === selectedVersion?.version"
:loading="false"
:enabled="enabled"
:has-installed-version="!!runtime.optifine || hasInstalledVersion"
:selected-installed="runtime.optifine === selectedVersion?.version || installed"
:loading="loadingDescription"
:versions="versions"
:updating="false"
:updating="updating"
no-enabled
:has-more="false"
:loading-versions="refreshing"
Expand Down
3 changes: 2 additions & 1 deletion xmcl-keystone-ui/src/views/ModDetailResource.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useModDetailUpdate, useModDetailEnable } from '@/composables/modDetail'
import { clientModrinthV2 } from '@/util/clients'
import { useInstanceModLoaderDefault } from '@/util/instanceModLoaderDefault'
import { isNoModLoader } from '@/util/isNoModloader'
import { getExpectedSize } from '@/util/size'
const props = defineProps<{
mod: Mod
Expand Down Expand Up @@ -100,7 +101,7 @@ const model = computed(() => {
result.push({
icon: '123',
name: t('fileDetail.fileSize'),
value: resource.size.toString(),
value: getExpectedSize(resource.size),
}, {
icon: 'tag',
name: t('fileDetail.hash'),
Expand Down
2 changes: 1 addition & 1 deletion xmcl-keystone-ui/src/views/ModDetailVersion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
>
<div
v-if="!version.changelogLoading"
class="select-text text-gray-500 transition-colors hover:text-black dark:hover:text-gray-300"
class="markdown-body select-text text-gray-500 transition-colors hover:text-black dark:hover:text-gray-300"
v-html="version.changelog"
/>
<v-skeleton-loader
Expand Down
4 changes: 4 additions & 0 deletions xmcl-runtime-api/src/services/InstallService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ export interface InstallService {
* Install the optifine to the minecraft
*/
installOptifine(options: InstallOptifineOptions): Promise<[string, Resource]>
/**
* Install the optifine uniersal jar as a resource
*/
installOptifineAsResource(options: InstallOptifineOptions): Promise<Resource>
/**
* Install a specific liteloader version
*/
Expand Down
32 changes: 32 additions & 0 deletions xmcl-runtime/lib/services/InstallService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { JavaService } from './JavaService'
import { ResourceService } from './ResourceService'
import { AbstractService, ExposeServiceKey, Lock, Singleton } from './Service'
import { VersionService } from './VersionService'
import { missing } from '../util/fs'

/**
* Version install service provide some functions to install Minecraft/Forge/Liteloader, etc. version
Expand Down Expand Up @@ -720,6 +721,37 @@ export class InstallService extends AbstractService implements IInstallService {
return version
}

async installOptifineAsResource(options: InstallOptifineOptions) {
const optifineVersion = `${options.type}_${options.patch}`
const version = `${options.mcversion}_${optifineVersion}`
const path = new MinecraftFolder(this.getPath()).getLibraryByPath(`/optifine/OptiFine/${version}/OptiFine-${version}-universal.jar`)
const resourceService = this.resourceService
if (await missing(path)) {
const urls = [] as string[]
if (getApiSets(this.settings)[0].name === 'mcbbs') {
urls.push(
`https://download.mcbbs.net/optifine/${options.mcversion}/${options.type}/${options.patch}`,
`https://bmclapi2.bangbang93.com/optifine/${options.mcversion}/${options.type}/${options.patch}`,
)
} else {
urls.push(
`https://bmclapi2.bangbang93.com/optifine/${options.mcversion}/${options.type}/${options.patch}`,
`https://download.mcbbs.net/optifine/${options.mcversion}/${options.type}/${options.patch}`,
)
}
const downloadOptions = await this.app.registry.get(kDownloadOptions)
await this.submit(task('installOptifine', async function () {
await this.yield(new DownloadTask({
...downloadOptions,
url: urls,
destination: path,
}).setName('download'))
}))
}
const [resource] = await resourceService.importResources([{ path, domain: ResourceDomain.Mods }])
return resource
}

@Lock((v: InstallOptifineOptions) => LockKey.version(`optifine-${v.mcversion}-${v.type}_${v.patch}`))
async installOptifine(options: InstallOptifineOptions) {
const minecraft = new MinecraftFolder(this.getPath())
Expand Down

0 comments on commit 28cadc8

Please sign in to comment.