diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 8852cd5123..fc536765e9 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -53,6 +53,7 @@ "@vue-email/components": "^0.0.21", "@vue-email/render": "^0.0.9", "@vueuse/core": "^11.1.0", + "@zip.js/zip.js": "^2.8.34", "ace-builds": "^1.36.2", "ansi-to-html": "^0.7.2", "chart.js": "^4.5.1", diff --git a/apps/frontend/src/helpers/infer/infer.ts b/apps/frontend/src/helpers/infer/infer.ts index 9bda8a8049..de5956abf1 100644 --- a/apps/frontend/src/helpers/infer/infer.ts +++ b/apps/frontend/src/helpers/infer/infer.ts @@ -19,6 +19,26 @@ export interface InferredVersionInfo { game_versions?: string[] } +async function readMrpackManifest(rawFile: RawFile): Promise { + const { BlobReader, TextWriter, ZipReader } = await import('@zip.js/zip.js') + const reader = new ZipReader(new BlobReader(rawFile)) + + try { + const entries = await reader.getEntries() + const manifest = entries.find( + (entry) => !entry.directory && entry.filename === 'modrinth.index.json', + ) + + if (!manifest || manifest.directory) { + throw new Error('Missing modrinth.index.json') + } + + return await manifest.getData(new TextWriter()) + } finally { + await reader.close() + } +} + /** * Fills in missing version information from the filename if not already present. */ @@ -56,11 +76,18 @@ export const inferVersionInfo = async function ( const simplifiedGameVersions = gameVersions .filter((it) => it.version_type === 'release') .map((it) => it.version) + const loaderParsers = createLoaderParsers(project, gameVersions, simplifiedGameVersions) + const fileName = rawFile.name.toLowerCase() + + if (fileName.endsWith('.mrpack') || fileName.endsWith('.mrpack-primary')) { + const manifest = await readMrpackManifest(rawFile) + const result = loaderParsers['modrinth.index.json'](manifest) + return fillMissingFromFilename(result, rawFile.name, project.title) + } const zipReader = new JSZip() const zip = await zipReader.loadAsync(rawFile) - const loaderParsers = createLoaderParsers(project, gameVersions, simplifiedGameVersions) const packParser = createPackParser(project, gameVersions, rawFile) const multiFileDetectors = createMultiFileDetectors(project, gameVersions, rawFile) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 614d6852ae..e0dcfd0625 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -308,6 +308,9 @@ importers: '@vueuse/core': specifier: ^11.1.0 version: 11.3.0(vue@3.5.27(typescript@5.9.3)) + '@zip.js/zip.js': + specifier: ^2.8.34 + version: 2.8.34 ace-builds: specifier: ^1.36.2 version: 1.43.6 @@ -5228,6 +5231,10 @@ packages: '@yr/monotone-cubic-spline@1.0.3': resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==} + '@zip.js/zip.js@2.8.34': + resolution: {integrity: sha512-+6a3lyqq69rpseLbvDPiVIWsZ/HdTGAAD6afFtug6ECPDGttb2dHnPC6cJgdPofYkzL9OvXizegq+DQVfL2rnA==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -14910,6 +14917,8 @@ snapshots: '@yr/monotone-cubic-spline@1.0.3': {} + '@zip.js/zip.js@2.8.34': {} + abbrev@2.0.0: {} abbrev@3.0.1: {}