wip: app-side logic

This commit is contained in:
aecsocket
2026-07-07 18:52:02 +01:00
parent 3ed75a146a
commit 450eb67f6a
10 changed files with 131 additions and 46 deletions
@@ -269,16 +269,31 @@ provideInstallationSettings({
debug('resolveLoaderVersions: no manifest', { loader, gameVersion }) debug('resolveLoaderVersions: no manifest', { loader, gameVersion })
return [] return []
} }
if (loader === 'fabric' || loader === 'quilt') { const entry = manifest.gameVersions?.find((item) => item.id === gameVersion)
const result = manifest.gameVersions[0]?.loaders ?? [] if (entry?.versionGroup) {
debug('resolveLoaderVersions: fabric/quilt result', { const result =
manifest.versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? []
debug('resolveLoaderVersions: version group result', {
loader,
gameVersion,
versionGroup: entry.versionGroup,
count: result.length,
})
return result
}
const placeholder = manifest.gameVersions?.find((item) => item.id === '${modrinth.gameVersion}')
if (placeholder) {
const result = manifest.gameVersions?.some((item) => item.id === gameVersion)
? placeholder.loaders
: []
debug('resolveLoaderVersions: placeholder result', {
loader, loader,
gameVersion, gameVersion,
count: result.length, count: result.length,
}) })
return result return result
} }
const result = manifest.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? [] const result = entry?.loaders ?? []
debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length }) debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length })
return result return result
}, },
+7
View File
@@ -133,11 +133,18 @@ type Hooks = {
type Manifest = { type Manifest = {
gameVersions: ManifestGameVersion[] gameVersions: ManifestGameVersion[]
versionGroups?: ManifestVersionGroup[]
} }
type ManifestGameVersion = { type ManifestGameVersion = {
id: string id: string
stable: boolean stable: boolean
versionGroup?: string
loaders: ManifestLoaderVersion[]
}
type ManifestVersionGroup = {
id: string
loaders: ManifestLoaderVersion[] loaders: ManifestLoaderVersion[]
} }
@@ -3,16 +3,25 @@ export namespace LauncherMeta {
export namespace v0 { export namespace v0 {
export type LoaderVersion = { export type LoaderVersion = {
id: string id: string
url: string
stable: boolean stable: boolean
} }
export type GameVersionEntry = { export type GameVersionEntry = {
id: string
stable: boolean
versionGroup?: string
loaders: LoaderVersion[]
}
export type VersionGroup = {
id: string id: string
loaders: LoaderVersion[] loaders: LoaderVersion[]
} }
export type Manifest = { export type Manifest = {
gameVersions: GameVersionEntry[] gameVersions: GameVersionEntry[]
versionGroups?: VersionGroup[]
} }
} }
} }
@@ -20,10 +20,13 @@ export class LauncherMetaManifestV0Module extends AbstractModule {
* *
* @param loader - Loader platform (fabric, forge, quilt, neo) * @param loader - Loader platform (fabric, forge, quilt, neo)
*/ */
public async getManifest(loader: string): Promise<LauncherMeta.Manifest.v0.Manifest> { public async getManifest(
loader: string,
formatVersion = 0,
): Promise<LauncherMeta.Manifest.v0.Manifest> {
return this.client.request<LauncherMeta.Manifest.v0.Manifest>('/manifest.json', { return this.client.request<LauncherMeta.Manifest.v0.Manifest>('/manifest.json', {
api: LAUNCHER_META_BASE_URL, api: LAUNCHER_META_BASE_URL,
version: `${loader}/v0`, version: `${loader}/v${formatVersion}`,
method: 'GET', method: 'GET',
skipAuth: true, skipAuth: true,
headers: { 'Content-Type': '' }, headers: { 'Content-Type': '' },
+3 -1
View File
@@ -22,8 +22,10 @@ pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
// #[tracing::instrument] // #[tracing::instrument]
pub async fn get_loader_versions(loader: &str) -> crate::Result<Manifest> { pub async fn get_loader_versions(loader: &str) -> crate::Result<Manifest> {
let state = State::get().await?; let state = State::get().await?;
let cache_key =
daedalus::modded::loader_manifest_metadata(loader).cache_key;
let loaders = CachedEntry::get_loader_manifest( let loaders = CachedEntry::get_loader_manifest(
loader, &cache_key,
None, None,
&state.pool, &state.pool,
&state.api_semaphore, &state.api_semaphore,
+33 -14
View File
@@ -24,7 +24,7 @@ use crate::{State, get_resource_file, process};
use chrono::Utc; use chrono::Utc;
use daedalus as d; use daedalus as d;
use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo}; use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo};
use daedalus::modded::LoaderVersion; use daedalus::modded::{LoaderVersion, Manifest};
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use std::fmt::Write; use std::fmt::Write;
@@ -175,19 +175,18 @@ pub async fn get_loader_version_from_profile(
let versions = let versions =
crate::api::metadata::get_loader_versions(loader.as_meta_str()).await?; crate::api::metadata::get_loader_versions(loader.as_meta_str()).await?;
let loaders = versions.game_versions.into_iter().find(|x| { if let Some(loaders) =
x.id.replace(daedalus::modded::DUMMY_REPLACE_STRING, game_version) loader_versions_for_game_version(&versions, game_version)
== game_version {
}); let loader_version =
loaders
if let Some(loaders) = loaders { .iter()
let loader_version = loaders.loaders.iter().find(|x| filter(x)).or( .find(|x| filter(x))
if version == "stable" { .or(if version == "stable" {
loaders.loaders.first() loaders.first()
} else { } else {
None None
}, });
);
Ok(loader_version.cloned()) Ok(loader_version.cloned())
} else { } else {
@@ -195,6 +194,26 @@ pub async fn get_loader_version_from_profile(
} }
} }
fn loader_versions_for_game_version<'a>(
manifest: &'a Manifest,
game_version: &str,
) -> Option<&'a [LoaderVersion]> {
let version = manifest.game_versions.iter().find(|x| {
x.id.replace(daedalus::modded::DUMMY_REPLACE_STRING, game_version)
== game_version
})?;
if let Some(version_group) = &version.version_group {
manifest
.version_groups
.iter()
.find(|group| group.id == *version_group)
.map(|group| group.loaders.as_slice())
} else {
Some(version.loaders.as_slice())
}
}
/// Resolves the Minecraft version manifest and finds the index for the given /// Resolves the Minecraft version manifest and finds the index for the given
/// game version. If the version isn't found in the cache, forces a manifest /// game version. If the version isn't found in the cache, forces a manifest
/// refresh to pick up newly-released versions. /// refresh to pick up newly-released versions.
+16 -9
View File
@@ -1396,19 +1396,25 @@ impl CachedEntry {
let fetch_urls = keys let fetch_urls = keys
.iter() .iter()
.map(|x| { .map(|x| {
let metadata =
daedalus::modded::loader_manifest_metadata_from_cache_key(
&x.key().to_string(),
);
( (
x.key().to_string(), metadata.cache_key,
metadata.loader,
format!( format!(
"{}{}/v0/manifest.json", "{}{}",
env!("MODRINTH_LAUNCHER_META_URL"), env!("MODRINTH_LAUNCHER_META_URL"),
x.key() metadata.path,
), ),
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
futures::future::try_join_all(fetch_urls.iter().map( futures::future::try_join_all(fetch_urls.iter().map(
|(_, url)| { |(_, _, url)| {
fetch_json( fetch_json(
Method::GET, Method::GET,
url, url,
@@ -1424,14 +1430,15 @@ impl CachedEntry {
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, metadata)| { .map(|(index, metadata)| {
( let mut entry =
CacheValue::LoaderManifest(CachedLoaderManifest { CacheValue::LoaderManifest(CachedLoaderManifest {
loader: fetch_urls[index].0.to_string(), loader: fetch_urls[index].1.to_string(),
manifest: metadata, manifest: metadata,
}) })
.get_entry(), .get_entry();
true, entry.id.clone_from(&fetch_urls[index].0);
)
(entry, true)
}) })
.collect() .collect()
} }
@@ -370,11 +370,13 @@ const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
const manifest = ctx.loaderVersionsCache.value[apiLoader] const manifest = ctx.loaderVersionsCache.value[apiLoader]
if (!manifest) return [] if (!manifest) return []
const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}') const hasPlaceholder = manifest.gameVersions.some((x) => x.id === '${modrinth.gameVersion}')
const supportedVersions = new Set( const supportedVersions = new Set(
manifest manifest.gameVersions
.filter( .filter(
(x) => x.id !== '${modrinth.gameVersion}' && (hasPlaceholder || x.loaders.length > 0), (x) =>
x.id !== '${modrinth.gameVersion}' &&
(hasPlaceholder || x.loaders.length > 0 || !!x.versionGroup),
) )
.map((x) => x.id), .map((x) => x.id),
) )
@@ -466,14 +468,14 @@ function getLoaderVersionsForGameVersion(
apiLoader, apiLoader,
gameVersion, gameVersion,
hasManifest: !!manifest, hasManifest: !!manifest,
manifestLength: manifest?.length, manifestLength: manifest?.gameVersions.length,
}) })
if (!manifest) return [] if (!manifest) return []
// Some loaders (e.g. Fabric) list all versions under a placeholder entry // Some loaders (e.g. Fabric) list all versions under a placeholder entry
const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}') const placeholder = manifest.gameVersions.find((x) => x.id === '${modrinth.gameVersion}')
if (placeholder) { if (placeholder) {
if (!manifest.some((x) => x.id === gameVersion)) return [] if (!manifest.gameVersions.some((x) => x.id === gameVersion)) return []
debug( debug(
'getLoaderVersionsForGameVersion: using placeholder, loaders:', 'getLoaderVersionsForGameVersion: using placeholder, loaders:',
placeholder.loaders.length, placeholder.loaders.length,
@@ -481,7 +483,20 @@ function getLoaderVersionsForGameVersion(
return placeholder.loaders return placeholder.loaders
} }
const entry = manifest.find((x) => x.id === gameVersion) const entry = manifest.gameVersions.find((x) => x.id === gameVersion)
if (entry?.versionGroup) {
const loaders =
manifest.versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? []
debug(
'getLoaderVersionsForGameVersion: version group for',
gameVersion,
':',
entry.versionGroup,
loaders.length + ' loaders',
)
return loaders
}
debug( debug(
'getLoaderVersionsForGameVersion: entry for', 'getLoaderVersionsForGameVersion: entry for',
gameVersion, gameVersion,
@@ -24,7 +24,8 @@ export type Gamemode = 'survival' | 'creative' | 'hardcore'
export type Difficulty = 'peaceful' | 'easy' | 'normal' | 'hard' export type Difficulty = 'peaceful' | 'easy' | 'normal' | 'hard'
export type LoaderVersionType = 'stable' | 'latest' | 'other' export type LoaderVersionType = 'stable' | 'latest' | 'other'
export type GeneratorSettingsMode = 'default' | 'flat' | 'custom' export type GeneratorSettingsMode = 'default' | 'flat' | 'custom'
export type LoaderManifestResolver = (loader: string) => Promise<LauncherMeta.Manifest.v0.Manifest> export type LoaderManifest = LauncherMeta.Manifest.v0.Manifest
export type LoaderManifestResolver = (loader: string) => Promise<LoaderManifest>
export interface LoaderVersionEntry { export interface LoaderVersionEntry {
id: string id: string
stable: boolean stable: boolean
@@ -160,7 +161,7 @@ export interface CreationFlowContextValue {
hideLoaderChips: ComputedRef<boolean> hideLoaderChips: ComputedRef<boolean>
hideLoaderVersion: ComputedRef<boolean> hideLoaderVersion: ComputedRef<boolean>
showSnapshots: Ref<boolean> showSnapshots: Ref<boolean>
loaderVersionsCache: Ref<Record<string, { id: string; loaders: LoaderVersionEntry[] }[]>> loaderVersionsCache: Ref<Record<string, LoaderManifest>>
paperSupportedVersions: Ref<Set<string> | null> paperSupportedVersions: Ref<Set<string> | null>
purpurSupportedVersions: Ref<Set<string> | null> purpurSupportedVersions: Ref<Set<string> | null>
@@ -295,9 +296,7 @@ export function createCreationFlowContext(
const loaderVersionType = ref<LoaderVersionType>('stable') const loaderVersionType = ref<LoaderVersionType>('stable')
const selectedLoaderVersion = ref<string | null>(null) const selectedLoaderVersion = ref<string | null>(null)
const showSnapshots = ref(false) const showSnapshots = ref(false)
const loaderVersionsCache = ref<Record<string, { id: string; loaders: LoaderVersionEntry[] }[]>>( const loaderVersionsCache = ref<Record<string, LoaderManifest>>({})
{},
)
const paperSupportedVersions = ref<Set<string> | null>(null) const paperSupportedVersions = ref<Set<string> | null>(null)
const purpurSupportedVersions = ref<Set<string> | null>(null) const purpurSupportedVersions = ref<Set<string> | null>(null)
@@ -364,11 +363,11 @@ export function createCreationFlowContext(
(await client.launchermeta.manifest_v0.getManifest(apiLoader)), (await client.launchermeta.manifest_v0.getManifest(apiLoader)),
staleTime: Infinity, staleTime: Infinity,
}) })
loaderVersionsCache.value[apiLoader] = data.gameVersions loaderVersionsCache.value[apiLoader] = data
debug('fetchLoaderManifest: loaded', apiLoader, 'gameVersions:', data.gameVersions.length) debug('fetchLoaderManifest: loaded', apiLoader, 'gameVersions:', data.gameVersions.length)
} catch (error) { } catch (error) {
debug('fetchLoaderManifest: failed', apiLoader, error) debug('fetchLoaderManifest: failed', apiLoader, error)
loaderVersionsCache.value[apiLoader] = [] loaderVersionsCache.value[apiLoader] = { gameVersions: [] }
} }
} }
@@ -376,12 +376,17 @@ function getLoaderVersionsForGameVersion(
} }
const manifest = manifestQuery.data.value?.gameVersions const manifest = manifestQuery.data.value?.gameVersions
const versionGroups = manifestQuery.data.value?.versionGroups
if (!manifest) return [] if (!manifest) return []
const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}') const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}')
if (placeholder) return placeholder.loaders if (placeholder) return placeholder.loaders
const entry = manifest.find((x) => x.id === gameVersion) const entry = manifest.find((x) => x.id === gameVersion)
if (entry?.versionGroup) {
return versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? []
}
return entry?.loaders ?? [] return entry?.loaders ?? []
} }
@@ -505,7 +510,9 @@ provideInstallationSettings({
const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}') const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}')
if (!hasPlaceholder) { if (!hasPlaceholder) {
const supportedVersions = new Set( const supportedVersions = new Set(
manifest.filter((x) => x.loaders.length > 0).map((x) => x.id), manifest
.filter((x) => x.loaders.length > 0 || !!x.versionGroup)
.map((x) => x.id),
) )
return versions return versions
.filter((v) => supportedVersions.has(v.version)) .filter((v) => supportedVersions.has(v.version))
@@ -547,7 +554,9 @@ provideInstallationSettings({
if (hasPlaceholder) { if (hasPlaceholder) {
return tags.gameVersions.value.some((v) => v.version_type !== 'release') return tags.gameVersions.value.some((v) => v.version_type !== 'release')
} }
const supportedVersions = new Set(manifest.filter((x) => x.loaders.length > 0).map((x) => x.id)) const supportedVersions = new Set(
manifest.filter((x) => x.loaders.length > 0 || !!x.versionGroup).map((x) => x.id),
)
const supported = tags.gameVersions.value.filter((v) => supportedVersions.has(v.version)) const supported = tags.gameVersions.value.filter((v) => supportedVersions.has(v.version))
return supported.some((v) => v.version_type !== 'release') return supported.some((v) => v.version_type !== 'release')
}, },