Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 103 additions & 34 deletions apps/frontend/src/helpers/infer/loader-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { GameVersion, InferredVersionInfo, Project } from './infer'
import {
getGameVersionsMatchingMavenRange,
getGameVersionsMatchingSemverRange,
semverRangeIntersects,
} from './version-ranges'
import { versionType } from './version-utils'

Expand Down Expand Up @@ -132,38 +133,73 @@ export function createLoaderParsers(
),
}
},
// Fabric (or Babric for mc version beta 1.7.3)
'fabric.mod.json': (file: string): InferredVersionInfo => {
// Fabric (or its derivatives: Babric, Legacy Fabric, Ornithe)
'fabric.mod.json': async (file: string, zip: JSZip): Promise<InferredVersionInfo> => {
const metadata = JSON.parse(file) as any

const mcDependency = metadata.depends?.minecraft
const mcDependencies = Array.isArray(mcDependency) ? mcDependency : [mcDependency]

let detectedGameVersions = metadata.depends
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
? getGameVersionsMatchingSemverRange(mcDependency, simplifiedGameVersions)
: []
const loaders: string[] = []

// Detect Beta 1.7.3 -> Babric
const hasBabricVersion = mcDependencies.some(
(version: string | undefined) => version?.includes('1.0.0-beta.7.3'), // this is fabric's normalized mc version format
)

// Detect 1.3-1.13 -> legacy-fabric
const hasLegacyVersions = detectedGameVersions.some((version) => {
const match = version.match(/^1\.(\d+)/)
return match && parseInt(match[1]) >= 3 && parseInt(match[1]) <= 13
})

if (hasBabricVersion) {
loaders.push('babric')
detectedGameVersions = gameVersions
.filter((version) => version.version === 'b1.7.3')
.map((version) => version.version)
} else if (hasLegacyVersions) {
loaders.push('legacy-fabric')
} else {
loaders.push('fabric')
// Both Legacy Fabric and Ornithe add details about their intermediary to the jar manifest
const manifestFile = zip.file('META-INF/MANIFEST.MF')
if (manifestFile !== null) {
const manifestText = await manifestFile.async('text')
if (manifestText.match(/^Legacy-Fabric-Intermediary-Version: (.*)$/)) {
loaders.push('legacy-fabric')
}
if (manifestText.match(/^Calamus-Generation: (.*)$/)) {
loaders.push('ornithe')
}
}

// fall back to version comparison if nothing found in jar manifest
if (loaders.length == 0) {
// Fabric and its derivates all support different version ranges
// - Fabric supports 18w43b and later
// - Legacy Fabric supports (almost) only release versions 1.3-1.13.2
// - Ornithe supports versions up to 1.14.4
// - Babric supports only b1.7.3
// These ranges overlap but given the jar manifest logic above the only likely overlap scenarios are
// - a b1.7.3 mod that could be Ornithe but is most likely Babric
// - a Fabric mod that is dependent on "*" (i.e. all versions)
// In other cases, resolve as follows:
// 1. if the dependency falls entirely in >=1.3 <=1.13.2, assume Legacy Fabric
// 2. if the dependency falls entirely in <=18w43a, assume Ornithe

// dependency version strings are normalized to be Semver 2.0.0 compliant through
// https://github.com/FabricMC/fabric-loader/blob/master/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java

// assume Babric only if dependent on b1.7.3 exactly
const hasBabricVersion = mcDependencies.every(
(version: string | undefined) => version == '1.0.0-beta.7.3',
)

// Legacy Fabric supports (almost) only release versions which is easy to check
const hasLegacyFabricVersions = detectedGameVersions.every((version) => {
const match = version.match(/^1\.(\d+)/)
return match && parseInt(match[1]) >= 3 && parseInt(match[1]) <= 13
})

// Assume Ornithe only if the dependency range falls entirely into <18w43b
const hasOrnitheVersions = !semverRangeIntersects(mcDependencies, '>=1.14.0-alpha.18.43.b')

if (hasBabricVersion) {
loaders.push('babric')
detectedGameVersions = gameVersions
.filter((version) => version.version === 'b1.7.3')
.map((version) => version.version)
} else if (hasLegacyFabricVersions) {
loaders.push('legacy-fabric')
} else if (hasOrnitheVersions) {
loaders.push('ornithe')
} else {
loaders.push('fabric')
}
}

return {
Expand All @@ -174,23 +210,56 @@ export function createLoaderParsers(
game_versions: detectedGameVersions,
}
},
// Quilt
'quilt.mod.json': (file: string): InferredVersionInfo => {
// Quilt (or its derivatives: Ornithe)
'quilt.mod.json': async (file: string, zip: JSZip): Promise<InferredVersionInfo> => {
const metadata = JSON.parse(file) as any

const mcDependency = metadata.quilt_loader.depends?.find(
(x: any) => x.id === 'minecraft',
)?.versions
const mcDependencies = Array.isArray(mcDependency) ? mcDependency : [mcDependency]

const detectedGameVersions = metadata.quilt_loader.depends
? getGameVersionsMatchingSemverRange(mcDependency, simplifiedGameVersions)
: []
const loaders: string[] = []

// Ornithe add details about their intermediary to the jar manifest
const manifestFile = zip.file('META-INF/MANIFEST.MF')
if (manifestFile !== null) {
const manifestText = await manifestFile.async('text')
if (manifestText.match(/^Calamus-Generation: (.*)$/)) {
loaders.push('ornithe')
}
}

// fall back to version comparison if nothing found in jar manifest
if (loaders.length == 0) {
// Quilt and its derivates all support different version ranges
// - Quilt supports 1.14.4 and later
// - Ornithe supports versions up to 1.14.4
// These ranges overlap but given the jar manifest logic above
// we can simply prioritize Quilt for the small overlap range

// dependency version strings are normalized to be Semver 2.0.0 compliant through
// https://github.com/QuiltMC/quilt-loader/blob/develop/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/McVersionLookup.java

// Assume Ornithe only if the dependency range falls entirely into <1.14.4`
const hasOrnitheVersions = !semverRangeIntersects(mcDependencies, '>=1.14.4')

if (hasOrnitheVersions) {
loaders.push('ornithe')
} else {
loaders.push('quilt')
}
}

return {
name: `${project.title} ${metadata.quilt_loader.version}`,
version_number: metadata.quilt_loader.version,
loaders: ['quilt'],
loaders,
version_type: versionType(metadata.quilt_loader.version),
game_versions: metadata.quilt_loader.depends
? getGameVersionsMatchingSemverRange(
metadata.quilt_loader.depends.find((x: any) => x.id === 'minecraft')
? metadata.quilt_loader.depends.find((x: any) => x.id === 'minecraft').versions
: [],
simplifiedGameVersions,
)
: [],
game_versions: detectedGameVersions,
}
},
// Bukkit + Other Forks
Expand Down
17 changes: 16 additions & 1 deletion apps/frontend/src/helpers/infer/version-ranges.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { satisfies } from 'semver'
import { intersects, satisfies } from 'semver'

/**
* Returns game versions that match a semver range or array of ranges.
Expand All @@ -19,6 +19,21 @@ export function getGameVersionsMatchingSemverRange(
})
}

/**
* Returns whether a semver range intersects another semver range.
*/
export function semverRangeIntersects(
range: string | string[] | undefined,
other: string,
): boolean {
if (!range) {
return true
}

const ranges = Array.isArray(range) ? range : [range]
return ranges.some((r) => intersects(r, other))
}

/**
* Returns game versions that match a Maven-style version range.
*/
Expand Down
Loading