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

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
//! 流式摘要:边下边算,不额外读一遍盘。
//!
//! 承 analyse/03 §5.6 的「先校验后落盘」:`MessageDigest` 在写流的同时喂数据,
//! 流结束时比对,不符就丢掉中间文件。
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use sha1::Digest;
use super::types::HashAlgo;
/// 小写十六进制编码(不引 `hex` 依赖)。
#[must_use]
pub fn to_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(char::from(HEX[usize::from(byte >> 4)]));
out.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
out
}
/// 增量摘要器。
#[derive(Debug)]
pub enum Hasher {
/// SHA-1。
Sha1(sha1::Sha1),
/// SHA-256。
Sha256(sha2::Sha256),
}
impl Hasher {
/// 按算法新建。
#[must_use]
pub fn new(algo: HashAlgo) -> Self {
match algo {
HashAlgo::Sha1 => Self::Sha1(sha1::Sha1::new()),
HashAlgo::Sha256 => Self::Sha256(sha2::Sha256::new()),
}
}
/// 算法。
#[must_use]
pub const fn algo(&self) -> HashAlgo {
match self {
Self::Sha1(_) => HashAlgo::Sha1,
Self::Sha256(_) => HashAlgo::Sha256,
}
}
/// 喂一段数据。
pub fn update(&mut self, bytes: &[u8]) {
match self {
Self::Sha1(hasher) => Digest::update(hasher, bytes),
Self::Sha256(hasher) => Digest::update(hasher, bytes),
}
}
/// 收尾成小写十六进制。
#[must_use]
pub fn finish(self) -> String {
match self {
Self::Sha1(hasher) => to_hex(&hasher.finalize()),
Self::Sha256(hasher) => to_hex(&hasher.finalize()),
}
}
}
/// 一次性算内存里的摘要。
#[must_use]
pub fn digest_bytes(algo: HashAlgo, bytes: &[u8]) -> String {
let mut hasher = Hasher::new(algo);
hasher.update(bytes);
hasher.finish()
}
/// 算整个文件的摘要(`cache` 的抽样校验与 `register` 路径用)。
pub fn digest_file(path: &Path, algo: HashAlgo) -> io::Result<String> {
let mut hasher = Hasher::new(algo);
let mut buffer = vec![0_u8; 64 * 1024];
let mut file = File::open(path)?;
loop {
let read = file.read(&mut buffer)?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(hasher.finish())
}
/// 把文件前 `limit` 字节喂进已有摘要器(续传时让「边下边算」接得上)。
pub fn update_hasher_from_file(hasher: &mut Hasher, path: &Path, limit: u64) -> io::Result<()> {
if limit == 0 {
return Ok(());
}
let mut buffer = vec![0_u8; 64 * 1024];
let mut reader = File::open(path)?.take(limit);
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(())
}
/// 对已有文件的前 `prefix_len` 字节补算摘要(诊断/抽样校验用)。
pub fn digest_prefix(path: &Path, algo: HashAlgo, prefix_len: u64) -> io::Result<String> {
let mut hasher = Hasher::new(algo);
update_hasher_from_file(&mut hasher, path, prefix_len)?;
Ok(hasher.finish())
}
/// 摘要比较(大小写不敏感,避免上游给了大写 hex 就白跑一趟)。
#[must_use]
pub fn matches(expected_hex: &str, actual_hex: &str) -> bool {
expected_hex.len() == actual_hex.len() && expected_hex.eq_ignore_ascii_case(actual_hex)
}