Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
commit
bc41dfcf7d
517 files changed
+92844
No files matched your search
@@ -0,0 +1,160 @@
|
||||
//! `ull install <version>`(无加载器):M27 原版安装流程的宿主。
|
||||
//!
|
||||
//! ```text
|
||||
//! RemoteSource::resolve(版本清单缓存 → 版本 JSON → 继承链)
|
||||
//! → RemoteSource::asset_index(资源索引,顺便拿字节给下面预取)
|
||||
//! → InstallPlan::build(纯计算:文件清单 / 总大小 / 角色)
|
||||
//! → [--dry-run 到此为止]
|
||||
//! → execute(跳过已就绪 → 引擎并发下载 → 原子写版本 JSON)
|
||||
//! → register(profiles.json + config/instances/<id>.json)
|
||||
//! ```
|
||||
//!
|
||||
//! 异步整段包在一次 `block_on` 里:CLI 不依赖 tokio(D18 依赖树纪律),
|
||||
//! reqwest 的连接池也不会跨运行时而失效。
|
||||
|
||||
use unlucky_core::download::blocking::block_on;
|
||||
use unlucky_core::download::cache::now_ms;
|
||||
use unlucky_core::download::DownloadEngine;
|
||||
use unlucky_core::game::arguments::Environment;
|
||||
use unlucky_core::game::install::{
|
||||
execute, InstallError, InstallErrorKind, InstallPlan, InstallReport, PlanInput, RemoteSource,
|
||||
};
|
||||
|
||||
use super::{
|
||||
build_engine, ensure_name_free, failure_error, map_install_error, report, CONCURRENCY,
|
||||
};
|
||||
use crate::cli::InstallArgs;
|
||||
use crate::context::Ctx;
|
||||
use crate::error::CliError;
|
||||
use crate::output::Report;
|
||||
|
||||
/// 执行 `install`(原版)。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 用法错误、版本不存在、网络失败、校验不符、写盘失败、实例清单损坏。
|
||||
pub(super) fn run(ctx: &Ctx, args: &InstallArgs) -> Result<Report, CliError> {
|
||||
let version = args.version.trim().to_string();
|
||||
if version.is_empty() {
|
||||
return Err(CliError::usage(
|
||||
"install 需要版本 id:ull install <version>",
|
||||
));
|
||||
}
|
||||
let name = args
|
||||
.name
|
||||
.clone()
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
.unwrap_or_else(|| version.clone());
|
||||
ensure_name_free(ctx, &name, &version)?;
|
||||
|
||||
let engine = build_engine(ctx);
|
||||
let now = now_ms();
|
||||
let (plan, report) = plan_and_install(
|
||||
ctx,
|
||||
&engine,
|
||||
&version,
|
||||
args.dry_run,
|
||||
args.progress,
|
||||
args.json_progress,
|
||||
)?;
|
||||
|
||||
if let Some(report) = &report {
|
||||
if !report.is_complete() {
|
||||
return Err(failure_error(report, ctx));
|
||||
}
|
||||
}
|
||||
|
||||
let registration = if args.dry_run {
|
||||
None
|
||||
} else {
|
||||
Some(super::register_instance(ctx, &name, &version)?)
|
||||
};
|
||||
|
||||
Ok(report::build(
|
||||
ctx,
|
||||
&engine,
|
||||
now,
|
||||
args.dry_run,
|
||||
&plan,
|
||||
report.as_ref(),
|
||||
registration.as_ref(),
|
||||
&name,
|
||||
&version,
|
||||
))
|
||||
}
|
||||
|
||||
/// 规划 + 执行原版安装(`--dry-run` 只规划),供加载器安装当"前置步骤"复用。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 版本不存在、网络失败、清单装不全、写盘失败。
|
||||
pub(super) fn plan_and_install(
|
||||
ctx: &Ctx,
|
||||
engine: &DownloadEngine,
|
||||
version: &str,
|
||||
dry_run: bool,
|
||||
progress: bool,
|
||||
json_progress: bool,
|
||||
) -> Result<(InstallPlan, Option<InstallReport>), CliError> {
|
||||
let game = ctx.game();
|
||||
let meta_cache = ctx.layout().meta_cache();
|
||||
let now = now_ms();
|
||||
|
||||
block_on(async {
|
||||
let source = RemoteSource::new(engine, &game, &meta_cache, now);
|
||||
let resolved = source.resolve(version).await?;
|
||||
let fetched = match resolved
|
||||
.asset_index
|
||||
.as_ref()
|
||||
.filter(|index| index.url.is_some())
|
||||
{
|
||||
Some(reference) => {
|
||||
let (index, bytes) = source.asset_index(reference).await?;
|
||||
Some((index, game.asset_index(&reference.id), bytes))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let mut plan = InstallPlan::build(PlanInput {
|
||||
layout: &game,
|
||||
version_id: version,
|
||||
resolved: &resolved,
|
||||
asset_index: fetched.as_ref().map(|(index, _, _)| index),
|
||||
environment: &Environment::host(),
|
||||
})?;
|
||||
if let Some((_, dest, bytes)) = fetched {
|
||||
plan.prefetch(dest, bytes);
|
||||
}
|
||||
if dry_run {
|
||||
return Ok::<Attempt, InstallError>(Attempt { plan, report: None });
|
||||
}
|
||||
// 计划里没有客户端 jar(旧版清单缺 downloads.client)→ 拒绝"装出个空壳"。
|
||||
if !plan.is_installable() {
|
||||
let reasons = if plan.warnings.is_empty() {
|
||||
"清单缺少客户端 jar 的下载信息".to_string()
|
||||
} else {
|
||||
plan.warnings.join(";")
|
||||
};
|
||||
return Err(InstallError::new(
|
||||
InstallErrorKind::Metadata,
|
||||
format!("版本 {version} 装不全:{reasons}"),
|
||||
)
|
||||
.with_hint(
|
||||
"旧版(pre-1.6)清单兜底待补;可先用官方启动器装好该版本再用 `ull launch`",
|
||||
));
|
||||
}
|
||||
let sink = report::progress_sink(progress, json_progress, plan.file_count());
|
||||
let report = execute(engine, &plan, CONCURRENCY, sink).await?;
|
||||
Ok(Attempt {
|
||||
plan,
|
||||
report: Some(report),
|
||||
})
|
||||
})
|
||||
.map(|attempt| (attempt.plan, attempt.report))
|
||||
.map_err(|error: InstallError| map_install_error(&error, ctx))
|
||||
}
|
||||
|
||||
/// 一次安装尝试的产物(`--dry-run` 只有计划)。
|
||||
struct Attempt {
|
||||
plan: InstallPlan,
|
||||
report: Option<InstallReport>,
|
||||
}
|
||||
Reference in new issue
Block a user