import JSZip from 'jszip' import TOML from 'toml' export const createDataPackVersion = async function ( project, version, primaryFile, members, allGameVersions, loaders ) { // force version to start with number, as required by FML const newVersionNumber = version.version_number.match(/^\d/) ? version.version_number : `1-${version.version_number}` const targetStartingDigitsRegex = /^(\d+)(\D+)$/g const newSlug = `${project.slug .replace('-', '_') .replace(/\W/g, '') .replace(targetStartingDigitsRegex, '$2') .replace(/^(\d+)$/g, project.id.replace(targetStartingDigitsRegex, '$2')) .substring(0, 63)}_mr` const iconPath = `${project.slug}_pack.png` const fabricModJson = { schemaVersion: 1, id: newSlug, version: newVersionNumber, name: project.title, description: project.description, authors: members.map((x) => x.name), contact: { homepage: `${process.env.domain}/${project.project_type}/${project.slug ?? project.id}`, }, license: project.license.id, icon: iconPath, environment: '*', depends: { 'fabric-resource-loader-v0': '*', }, } const quiltModJson = { schema_version: 1, quilt_loader: { group: 'com.modrinth', id: newSlug, version: newVersionNumber, metadata: { name: project.title, description: project.description, contributors: members.reduce( (acc, x) => ({ ...acc, [x.name]: x.role, }), {} ), contact: { homepage: `${process.env.domain}/${project.project_type}/${project.slug ?? project.id}`, }, icon: iconPath, }, intermediate_mappings: 'net.fabricmc:intermediary', depends: [ { id: 'quilt_resource_loader', versions: '*', unless: 'fabric-resource-loader-v0', }, ], }, } const cutoffIndex = allGameVersions.findIndex((x) => x.version === '1.18.2') let maximumIndex = Number.MIN_VALUE for (const val of version.game_versions) { const index = allGameVersions.findIndex((x) => x.version === val) if (index > maximumIndex) { maximumIndex = index } } const newForge = maximumIndex < cutoffIndex const forgeModsToml = { modLoader: newForge ? 'lowcodefml' : 'javafml', loaderVersion: newForge ? '[40,)' : '[25,)', license: project.license.id, showAsResourcePack: false, mods: [ { modId: newSlug, version: newVersionNumber, displayName: project.title, description: project.description, logoFile: iconPath, updateJSONURL: `${getAuthUrl().replace('/v2/', '')}/updates/${ project.id }/forge_updates.json`, credits: 'Generated by Modrinth', authors: members.map((x) => x.name).join(', '), displayURL: `${process.env.domain}/${project.project_type}/${project.slug ?? project.id}`, }, ], } if (project.source_url) { quiltModJson.quilt_loader.metadata.contact.sources = project.source_url fabricModJson.contact.sources = project.source_url } if (project.issues_url) { quiltModJson.quilt_loader.metadata.contact.issues = project.issues_url fabricModJson.contact.issues = project.issues_url forgeModsToml.issueTrackerURL = project.issues_url } const primaryFileData = await (await fetch(primaryFile.url)).blob() const primaryZipReader = new JSZip() await primaryZipReader.loadAsync(primaryFileData) if (loaders.includes('fabric')) { primaryZipReader.file('fabric.mod.json', JSON.stringify(fabricModJson)) } if (loaders.includes('quilt')) { primaryZipReader.file('quilt.mod.json', JSON.stringify(quiltModJson)) } if (loaders.includes('forge')) { primaryZipReader.file('META-INF/mods.toml', TOML.stringify(forgeModsToml)) } if (!newForge && loaders.includes('forge')) { const classFile = new Uint8Array( await ( await fetch('https://cdn.modrinth.com/wrapper/ModrinthWrapperRestiched.class') ).arrayBuffer() ) let binary = '' for (let i = 0; i < classFile.byteLength; i++) { binary += String.fromCharCode(classFile[i]) } let sanitizedId = project.id if (project.id.match(/^(\d+)/g)) { sanitizedId = '_' + sanitizedId } sanitizedId = sanitizedId.substring(0, 8) binary = binary .replace( String.fromCharCode(32) + 'needs1to1be1changed1modrinth1mod', String.fromCharCode(newSlug.length) + newSlug ) .replace('/wrappera/', `/${sanitizedId}/`) const newArr = [] for (let i = 0; i < binary.length; i++) { newArr.push(binary.charCodeAt(i)) } primaryZipReader.file( `com/modrinth/${sanitizedId}/ModrinthWrapper.class`, new Uint8Array(newArr) ) } const resourcePack = version.files.find((x) => x.file_type === 'required-resource-pack') const resourcePackData = resourcePack ? await (await fetch(resourcePack.url)).blob() : null if (resourcePackData) { const resourcePackReader = new JSZip() await resourcePackReader.loadAsync(resourcePackData) for (const [path, file] of Object.entries(resourcePackReader.files)) { if (!primaryZipReader.file(path) && !path.includes('.mcassetsroot')) { primaryZipReader.file(path, await file.async('uint8array')) } } } if (primaryZipReader.file('pack.png')) { primaryZipReader.file(iconPath, await primaryZipReader.file('pack.png').async('uint8array')) } return await primaryZipReader.generateAsync({ type: 'blob', mimeType: 'application/java-archive', }) }