diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index adcbe936..25a08a82 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -325,41 +325,8 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'oneclient':'{"extractBundleOverrides":["bundle_path","cluster_id"],"isBundleSyncing":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"installUpdate":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"],"updateBundlePackages":["cluster_id"],"checkForUpdate":[],"cacheArt":["path"],"copyClusterContent":["source_id","target_id"],"refreshArt":["path"],"getVersions":[]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'debug':'{"getOsVersion":[],"getBuildTimestamp":[],"getArch":[],"hasDevSettings":[],"isInDev":[],"getPackageVersion":[],"getType":[],"getLocale":[],"getFullDebugInfo":[],"getPlatform":[],"getFullDebugInfoParsedString":[],"getFamily":[],"getFullDebugInfoParsed":[],"getOsDistro":[],"openDevTools":[],"getGitCommitHash":[]}', 'core':'{"isClusterRunning":["cluster_id"],"getGameVersions":[],"searchPackages":["provider","query"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"openMsaLogin":[],"removePackage":["cluster_id","package_hash"],"setDefaultUser":["uuid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"killProcess":["pid"],"getPackage":["provider","slug"],"convertUsernameUUID":["username_uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getClusters":[],"fetchMinecraftProfile":["uuid"],"getLogs":["id"],"getProfileOrDefault":["name"],"getRunningProcessesByClusterId":["cluster_id"],"getLinkedPackages":["cluster_id"],"getGlobalProfile":[],"refreshAccounts":[],"getPackageBody":["provider","body"],"togglePackage":["cluster_id","package_hash"],"createSettingsProfile":["name"],"getClusterById":["id"],"getWorlds":["id"],"createCluster":["options"],"installModpack":["modpack","cluster_id"],"open":["input"],"getLoadersForVersion":["mc_version"],"refreshAccount":["uuid"],"getScreenshots":["id"],"launchCluster":["id","uuid","search_for_java"],"setClusterStage":["id","stage"],"getLogByName":["id","name"],"getProcessLogTail":["id","max_lines"],"getUser":["uuid"],"writeSettings":["setting"],"fetchLoggedInProfile":["access_token"],"updateClusterProfile":["name","profile"],"removeCape":["access_token"],"readSettings":[],"updateClusterById":["id","request"],"getDefaultUser":["fallback"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getRunningProcesses":[],"getMultiplePackages":["provider","slugs"],"changeSkin":["access_token","skin_url","skin_variant"],"getUsers":[],"syncCluster":["cluster_id"],"getUsersFromAuthor":["provider","author"],"changeCape":["access_token","cape_uuid"],"removeUser":["uuid"],"setDiscordRPCMessage":["message"],"removeCluster":["id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } -export type Router = { 'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getOsDistro: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise, -getFullDebugInfo: () => Promise, -getFullDebugInfoParsed: () => Promise, -getFullDebugInfoParsedString: () => Promise, -hasDevSettings: () => Promise }, -'oneclient': { getClustersGroupedByMajor: () => Promise>, -getBundlesFor: (clusterId: number) => Promise, -getVersions: () => Promise, -extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, -checkForUpdate: () => Promise, -installUpdate: () => Promise, -downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, -updateBundlePackages: (clusterId: number) => Promise, -isBundleSyncing: () => Promise, -cacheArt: (path: string) => Promise, -refreshArt: (path: string) => Promise, -copyClusterContent: (sourceId: number, targetId: number) => Promise }, -'events': { ingress: (event: IngressPayload) => Promise, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'core': { getClusters: () => Promise, +const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"installUpdate":[],"cacheArt":["path"],"extractBundleOverrides":["bundle_path","cluster_id"],"getVersions":[],"updateBundlePackages":["cluster_id"],"isBundleSyncing":[],"checkForUpdate":[],"refreshArt":["path"],"copyClusterContent":["source_id","target_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getClustersGroupedByMajor":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'debug':'{"getFullDebugInfo":[],"getArch":[],"getFullDebugInfoParsed":[],"openDevTools":[],"getOsDistro":[],"getLocale":[],"getGitCommitHash":[],"isInDev":[],"getOsVersion":[],"getBuildTimestamp":[],"getFamily":[],"getFullDebugInfoParsedString":[],"hasDevSettings":[],"getPlatform":[],"getPackageVersion":[],"getType":[]}', 'core':'{"isClusterRunning":["cluster_id"],"readSettings":[],"launchCluster":["id","uuid","search_for_java"],"writeSettings":["setting"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getLinkedPackages":["cluster_id"],"getLogs":["id"],"setClusterStage":["id","stage"],"killProcess":["pid"],"updateClusterById":["id","request"],"refreshAccounts":[],"getProcessLogTail":["id","max_lines"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"getGameVersions":[],"getWorlds":["id"],"removePackage":["cluster_id","package_hash"],"togglePackage":["cluster_id","package_hash"],"openMsaLogin":[],"getLogByName":["id","name"],"installModpack":["modpack","cluster_id"],"createCluster":["options"],"getProfileOrDefault":["name"],"getPackageBody":["provider","body"],"removeUser":["uuid"],"getUsersFromAuthor":["provider","author"],"fetchMinecraftProfile":["uuid"],"fetchLoggedInProfile":["access_token"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getPackage":["provider","slug"],"searchPackages":["provider","query"],"setDefaultUser":["uuid"],"getLoadersForVersion":["mc_version"],"createSettingsProfile":["name"],"getMultiplePackages":["provider","slugs"],"syncCluster":["cluster_id"],"changeSkin":["access_token","skin_url","skin_variant"],"getUser":["uuid"],"getDefaultUser":["fallback"],"getRunningProcesses":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeCape":["access_token"],"setDiscordRPCMessage":["message"],"getRunningProcessesByClusterId":["cluster_id"],"open":["input"],"getClusterById":["id"],"updateClusterProfile":["name","profile"],"changeCape":["access_token","cape_uuid"],"convertUsernameUUID":["username_uuid"],"removeCluster":["id"],"getScreenshots":["id"],"getClusters":[],"getUsers":[],"getGlobalProfile":[],"refreshAccount":["uuid"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}' } +export type Router = { 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, @@ -412,7 +379,40 @@ changeCape: (accessToken: string, capeUuid: string) => Promise Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, -open: (input: string) => Promise } }; +open: (input: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +'debug': { openDevTools: () => Promise, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getOsDistro: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise, +getFullDebugInfo: () => Promise, +getFullDebugInfoParsed: () => Promise, +getFullDebugInfoParsedString: () => Promise, +hasDevSettings: () => Promise }, +'oneclient': { getClustersGroupedByMajor: () => Promise>, +getBundlesFor: (clusterId: number) => Promise, +getVersions: () => Promise, +extractBundleOverrides: (bundlePath: string, clusterId: number) => Promise, +checkForUpdate: () => Promise, +installUpdate: () => Promise, +downloadPackageFromBundle: (package: ModpackFileKind, clusterId: number, bundleName: string, skipCompatibility: boolean | null) => Promise, +updateBundlePackages: (clusterId: number) => Promise, +isBundleSyncing: () => Promise, +cacheArt: (path: string) => Promise, +refreshArt: (path: string) => Promise, +copyClusterContent: (sourceId: number, targetId: number) => Promise } }; export type { InferCommandOutput } diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index e6d16670..0078e409 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -13,8 +13,8 @@ use crate::api::game::metadata::{self, download_minecraft}; use crate::api::ingress::IngressSendExt; use crate::api::{java, setting_profiles}; use crate::error::LauncherResult; +use crate::store::Dirs; use crate::store::ingress::{IngressType, SubIngress}; -use crate::store::{Dirs, State}; use crate::utils::DatabaseModelExt; use crate::utils::io::{self, IOError}; @@ -176,20 +176,10 @@ async fn install_cluster( setting_profiles::dao::get_profile_or_default(cluster.setting_profile_name.as_ref()) .await?; - let state = State::get().await?; - let mut metadata = state.metadata.write().await; - let versions_list = &metadata.get_vanilla_or_fetch().await?.versions; - - let version_index = versions_list - .iter() - .position(|v| v.id == cluster.mc_version) - .ok_or_else(|| ClusterError::InvalidVersion(cluster.mc_version.clone()))?; - - let version = versions_list[version_index].clone(); - - let minecraft_updated = metadata::is_version_updated(version_index, versions_list); - - drop(metadata); + let (version, _version_index, minecraft_updated) = + metadata::resolve_minecraft_version(&cluster.mc_version) + .await + .map_err(|_| ClusterError::InvalidVersion(cluster.mc_version.clone()))?; let loader_version = metadata::get_loader_version( &cluster.mc_version, diff --git a/packages/core/src/api/game/launch.rs b/packages/core/src/api/game/launch.rs index bbcaae6b..ceb7ff01 100644 --- a/packages/core/src/api/game/launch.rs +++ b/packages/core/src/api/game/launch.rs @@ -133,20 +133,10 @@ pub async fn launch_minecraft( let cwd = &io::canonicalize(dirs.clusters_dir().join(cluster.folder_name.clone()))?; - let (version, updated) = { - let metadata_store = state.metadata.read().await; - let versions = &metadata_store.get_vanilla()?.versions; - - let version_index = versions - .iter() - .position(|it| it.id == cluster.mc_version) - .ok_or_else(|| anyhow::anyhow!("invalid game version {}", cluster.mc_version))?; - - ( - versions[version_index].clone(), - metadata::is_version_updated(version_index, versions), - ) - }; + let (version, _version_index, updated) = + metadata::resolve_minecraft_version(&cluster.mc_version) + .await + .map_err(|_| anyhow::anyhow!("invalid game version {}", cluster.mc_version))?; let loader_version = metadata::get_loader_version( &cluster.mc_version, diff --git a/packages/core/src/api/game/metadata.rs b/packages/core/src/api/game/metadata.rs index f3d60809..f1ddc0e7 100644 --- a/packages/core/src/api/game/metadata.rs +++ b/packages/core/src/api/game/metadata.rs @@ -474,12 +474,12 @@ pub async fn get_loader_version( return Ok(Some(resolved)); } - if loader_version.is_some() { + if !saw_matching_game_version || loader_version.is_some() { tracing::warn!( mc_version = %mc_version, loader = ?loader, requested_loader_version = ?loader_version, - "Requested loader version not found in cached metadata; refetching manifests" + "No matching loader metadata found in cache; refetching manifests" ); metadata.fetch_all().await; @@ -488,13 +488,15 @@ pub async fn get_loader_version( if let Some(resolved) = resolved_loader { return Ok(Some(resolved)); } + } + if let Some(requested_loader_version) = loader_version { if !saw_matching_game_version { return Err(MetadataError::NoMatchingVersion.into()); } return Err(MetadataError::RequestedLoaderVersionNotFound { - requested: loader_version.expect("checked as some").to_string(), + requested: requested_loader_version.to_string(), } .into()); } @@ -506,6 +508,35 @@ pub async fn get_loader_version( } } +#[tracing::instrument] +pub async fn resolve_minecraft_version(mc_version: &str) -> LauncherResult<(Version, usize, bool)> { + let state = State::get().await?; + let mut metadata = state.metadata.write().await; + + let mut manifest = metadata.get_vanilla_or_fetch().await?; + let mut version_index = manifest.versions.iter().position(|it| it.id == mc_version); + + if version_index.is_none() { + tracing::warn!( + mc_version = %mc_version, + "requested Minecraft version missing from cached manifest; refetching metadata" + ); + + metadata.fetch_all().await; + manifest = metadata.get_vanilla()?; + version_index = manifest.versions.iter().position(|it| it.id == mc_version); + } + + let version_index = version_index.ok_or(MetadataError::NoMatchingVersion)?; + let versions = &manifest.versions; + + Ok(( + versions[version_index].clone(), + version_index, + is_version_updated(version_index, versions), + )) +} + // MARK: Get Versions /// Gets the list of Minecraft versions from the metadata store #[tracing::instrument] diff --git a/packages/core/src/store/metadata.rs b/packages/core/src/store/metadata.rs index 56939327..192b908b 100644 --- a/packages/core/src/store/metadata.rs +++ b/packages/core/src/store/metadata.rs @@ -265,7 +265,23 @@ impl Metadata { } async fn fetch_vanilla_manifest(loader: GameLoader) -> LauncherResult { - fetch_manifest(loader).await + match fetch_manifest(loader).await { + Ok(manifest) => Ok(manifest), + Err(err) => { + tracing::warn!( + "failed to fetch vanilla manifest from metadata mirror: {}; falling back to Mojang", + err + ); + + fetch_json::( + Method::GET, + interfrost::api::minecraft::VERSION_MANIFEST_URL, + None, + None, + ) + .await + } + } } async fn fetch_modded_manifest(loader: GameLoader) -> LauncherResult {