Skip to content

Commit

Permalink
fix: Should also identify the modpack source during the importing
Browse files Browse the repository at this point in the history
  • Loading branch information
ci010 committed May 22, 2024
1 parent 9e35c60 commit 81c0cc4
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 72 deletions.
79 changes: 10 additions & 69 deletions xmcl-runtime/modpack/ModpackService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CurseforgeModpackManifest, EditInstanceOptions, ExportModpackOptions, ModpackService as IModpackService, InstanceFile, McbbsModpackManifest, ModpackException, ModpackInstallProfile, ModpackServiceKey, ModrinthModpackManifest, ResourceMetadata, RuntimeVersions, getCurseforgeModpackFromInstance, getMcbbsModpackFromInstance, getModrinthModpackFromInstance, isAllowInModrinthModpack } from '@xmcl/runtime-api'
import { open, openEntryReadStream, readAllEntries } from '@xmcl/unzip'
import { CurseforgeModpackManifest, ExportModpackOptions, ModpackService as IModpackService, InstanceFile, McbbsModpackManifest, ModpackException, ModpackInstallProfile, ModpackServiceKey, ModrinthModpackManifest, ResourceMetadata, getCurseforgeModpackFromInstance, getMcbbsModpackFromInstance, getModrinthModpackFromInstance, isAllowInModrinthModpack } from '@xmcl/runtime-api'
import { open, readAllEntries } from '@xmcl/unzip'
import { stat } from 'fs-extra'
import { join } from 'path'
import { Entry, ZipFile } from 'yauzl'
Expand All @@ -9,7 +9,6 @@ import { ResourceService, ResourceWorker, kResourceWorker } from '~/resource'
import { AbstractService, ExposeServiceKey } from '~/service'
import { TaskFn, kTaskExecutor } from '~/task'
import { AnyError } from '../util/error'
import { checksumFromStream } from '../util/fs'
import { requireObject } from '../util/object'
import { ZipTask } from '../util/zip'

Expand All @@ -36,9 +35,11 @@ export interface ModpackHandler<M = any> {
*/
resolveUnpackPath(manifest: M, e: Entry): string | void

readMetadata(zipFile: ZipFile, entries: Entry[]): Promise<M | undefined>
readManifest(zipFile: ZipFile, entries: Entry[]): Promise<M | undefined>
resolveInstanceOptions(manifest: M): ModpackInstallProfile['instance']
resolveInstanceFiles(manifest: M): Promise<InstanceFile[]>

resolveModpackMetadata?(path: string, sha1: string): Promise<ResourceMetadata | undefined>
}

/**
Expand Down Expand Up @@ -206,6 +207,8 @@ export class ModpackService extends AbstractService implements IModpackService {

if (!manifest || !handler) throw new ModpackException({ type: 'invalidModpack', path: modpackFile })

const metadata = await handler.resolveModpackMetadata?.(modpackFile, cacheOrHash) || {}

const instance = handler.resolveInstanceOptions(manifest)

const instanceFiles = await handler.resolveInstanceFiles(manifest)
Expand All @@ -222,6 +225,7 @@ export class ModpackService extends AbstractService implements IModpackService {
},
downloads: [
`zip:///${modpackFile}?entry=${encodeURIComponent(e.fileName)}`,
`zip://${cacheOrHash}/${e.fileName}`,
],
}
return file
Expand All @@ -239,6 +243,7 @@ export class ModpackService extends AbstractService implements IModpackService {
await this.resourceService.updateResources([{
hash: cacheOrHash,
metadata: {
...metadata,
instance: {
instance,
files,
Expand All @@ -252,73 +257,9 @@ export class ModpackService extends AbstractService implements IModpackService {
return files
}

async getModpackInstallProfile(path: string): Promise<ModpackInstallProfile> {
const cacheOrHash = await this.getCachedInstallProfile(path)

if (typeof cacheOrHash === 'object') return cacheOrHash

this.log(`Parse modpack profile ${path}`)

const zip = await open(path)
const entries = await readAllEntries(zip)

const [manifest, handler] = await this.getManifestAndHandler(zip, entries)

if (!manifest || !handler) throw new ModpackException({ type: 'invalidModpack', path })

const instance = handler.resolveInstanceOptions(manifest)

const instanceFiles = await handler.resolveInstanceFiles(manifest)

const files = (await Promise.all(entries
.filter((e) => !!handler.resolveUnpackPath(manifest, e) && !e.fileName.endsWith('/'))
.map(async (e) => {
const relativePath = handler.resolveUnpackPath(manifest, e)!
const file: InstanceFile = {
path: relativePath,
size: e.uncompressedSize,
hashes: {
crc32: e.crc32.toString(),
},
downloads: [
`zip:///${path}?entry=${encodeURIComponent(e.fileName)}`,
`zip://${cacheOrHash}/${e.fileName}`,
],
}
return file
})))
.concat(instanceFiles)
.filter(f => !f.path.endsWith('/'))

for (const file of files) {
transformFile(file)
}

try {
// Update the resource
this.log(`Update instance resource modpack profile ${path}`)
await this.resourceService.updateResources([{
hash: cacheOrHash,
metadata: {
instance: {
instance,
files,
},
},
}])
} catch (e) {
this.error(new AnyError('ModpackInstallProfileError', 'Fail to update resource', { cause: e }))
}

return {
instance,
files,
}
}

private async getManifestAndHandler(zip: ZipFile, entries: Entry[]) {
for (const handler of Object.values(this.handlers)) {
const manifest = await handler.readMetadata(zip, entries).catch(e => undefined)
const manifest = await handler.readManifest(zip, entries).catch(e => undefined)
if (manifest) {
return [manifest, handler] as const
}
Expand Down
13 changes: 12 additions & 1 deletion xmcl-runtime/modpack/pluginCurseforgeModpackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@ import { guessCurseforgeFileUrl } from '../util/curseforge'
import { ModpackService } from './ModpackService'
import { getCurseforgeFiles, getCurseforgeProjects } from './getCurseforgeFiles'
import { resolveHashes } from './resolveHashes'
import { kResourceWorker } from '~/resource'

export const pluginCurseforgeModpackHandler: LauncherAppPlugin = async (app) => {
const modpackService = await app.registry.get(ModpackService)
modpackService.registerHandler<CurseforgeModpackManifest>('curseforge', {
async resolveModpackMetadata(path, sha1) {
const client = await app.registry.getOrCreate(CurseforgeV1Client)
const worker = await app.registry.getOrCreate(kResourceWorker)

const print = await worker.fingerprint(path)
const result = await client.getFingerprintsMatchesByGameId(432, [print])
const f = result.exactMatches[0]
if (!f) return undefined
return { curseforge: { projectId: f.file.modId, fileId: f.file.id } }
},
resolveUnpackPath: function (manifest: CurseforgeModpackManifest, e: Entry) {
let overridePrefix = manifest.overrides ?? 'overrides/'
if (!overridePrefix.endsWith('/')) overridePrefix += '/'
if (e.fileName.startsWith(overridePrefix)) {
return e.fileName.substring(overridePrefix.length)
}
},
readMetadata: async (zipFile: ZipFile, entries: Entry[]): Promise<CurseforgeModpackManifest | undefined> => {
readManifest: async (zipFile: ZipFile, entries: Entry[]): Promise<CurseforgeModpackManifest | undefined> => {
const curseforgeManifest = entries.find(e => e.fileName === 'manifest.json')
if (curseforgeManifest) {
const b = await readEntry(zipFile, curseforgeManifest)
Expand Down
2 changes: 1 addition & 1 deletion xmcl-runtime/modpack/pluginMcbbsModpackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { resolveHashes } from './resolveHashes'
export const pluginMcbbsModpackHandler: LauncherAppPlugin = async (app) => {
const modpackService = await app.registry.get(ModpackService)
modpackService.registerHandler<McbbsModpackManifest>('mcbbs', {
readMetadata: async (zip, entries) => {
readManifest: async (zip, entries) => {
const mcbbsManifest = entries.find(e => e.fileName === 'mcbbs.packmeta')
if (mcbbsManifest) {
return readEntry(zip, mcbbsManifest).then(b => JSON.parse(b.toString()) as McbbsModpackManifest)
Expand Down
18 changes: 17 additions & 1 deletion xmcl-runtime/modpack/pluginModrinthModpackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@ import { LauncherAppPlugin } from '~/app'
import { ModpackService } from './ModpackService'
import { readEntry } from '@xmcl/unzip'
import { Entry } from 'yauzl'
import { ModrinthV2Client } from '@xmcl/modrinth'

export const pluginModrinthModpackHandler: LauncherAppPlugin = async (app) => {
const modpackService = await app.registry.get(ModpackService)
modpackService.registerHandler<ModrinthModpackManifest>('modrinth', {
async readMetadata(zip, entries) {
async resolveModpackMetadata(path, sha1) {
const client = await app.registry.getOrCreate(ModrinthV2Client)
const hashes = await client.getProjectVersionsByHash([sha1], 'sha1')
const content = hashes[sha1]
if (!content) return undefined
const file = content.files.find(f => f.hashes.sha1 === sha1)
return {
modrinth: {
projectId: content.project_id,
versionId: content.id,
filename: file?.filename,
url: file?.url,
},
}
},
async readManifest(zip, entries) {
const modrinthManifest = entries.find(e => e.fileName === 'modrinth.index.json')
if (modrinthManifest) {
const b = await readEntry(zip, modrinthManifest)
Expand Down

0 comments on commit 81c0cc4

Please sign in to comment.