mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 01:15:14 +08:00
@@ -1,7 +1,7 @@
|
||||
use bat_adapters::official::yostar_jp::PatchPlatform;
|
||||
use bat_infrastructure::{
|
||||
OfficialServerInfoSource, OfficialUpdateConfig, OfficialUpdateProgress, OfficialUpdateReport,
|
||||
OfficialUpdateService, OfficialUpdateStatus,
|
||||
OfficialUpdateService, OfficialUpdateStatus, OfficialVerificationSummary,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
@@ -1523,6 +1523,32 @@ fn print_list(label: &str, values: &[String], limit: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_verification_summary(summary: &OfficialVerificationSummary) {
|
||||
println!(" 校验摘要:");
|
||||
for line in verification_summary_lines(summary) {
|
||||
println!(" - {line}");
|
||||
}
|
||||
}
|
||||
|
||||
fn verification_summary_lines(summary: &OfficialVerificationSummary) -> Vec<String> {
|
||||
vec![
|
||||
format!(
|
||||
"官方 .hash 强校验: {} 对 ({})",
|
||||
summary.official_hash_verified_count, summary.official_hash_scope
|
||||
),
|
||||
format!(
|
||||
"本地 BLAKE3 复用校验: {} 项通过, {} 项需修复 ({})",
|
||||
summary.local_manifest_blake3_verified_count,
|
||||
summary.local_manifest_repair_needed_count,
|
||||
summary.local_manifest_blake3_scope
|
||||
),
|
||||
format!(
|
||||
"ZIP 结构校验: {} 个 ZIP 通过 ({})",
|
||||
summary.zip_structure_verified_count, summary.zip_structure_scope
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn format_bool(value: bool) -> &'static str {
|
||||
if value {
|
||||
"yes"
|
||||
@@ -1590,6 +1616,7 @@ impl HumanReport for OfficialUpdateReport {
|
||||
print_field("本地校验通过", self.local_manifest_verified_count);
|
||||
print_field("需修复", self.local_manifest_repair_needed_count);
|
||||
print_field("官方 hash 校验", self.official_seed_hash_verified_count);
|
||||
print_verification_summary(&self.verification_summary);
|
||||
print_field("catalog marker", self.addressables_marker_checked_count);
|
||||
print_list("变更 endpoint", &self.changed_endpoint_urls, 8);
|
||||
print_list("计划 URL", &self.download_urls, 8);
|
||||
@@ -1684,6 +1711,7 @@ impl HumanReport for VerifyCommandReport {
|
||||
print_field("本地失败数", self.local_manifest_failure_count);
|
||||
print_field("官方 hash 对", self.official_hash_pair_count);
|
||||
print_field("官方 hash 通过", self.official_hash_verified_count);
|
||||
print_verification_summary(&self.verification_summary);
|
||||
if !self.official_hash_errors.is_empty() {
|
||||
print_list("官方 hash 错误", &self.official_hash_errors, 8);
|
||||
}
|
||||
@@ -1798,6 +1826,7 @@ struct VerifyCommandReport {
|
||||
local_manifest_failure_count: usize,
|
||||
official_hash_pair_count: usize,
|
||||
official_hash_verified_count: usize,
|
||||
verification_summary: OfficialVerificationSummary,
|
||||
official_hash_errors: Vec<String>,
|
||||
failures: Vec<VerificationItemReport>,
|
||||
}
|
||||
@@ -1837,6 +1866,12 @@ fn run_verify_command(options: &CliOptions) -> anyhow::Result<bool> {
|
||||
let healthy = update_report.update_status == OfficialUpdateStatus::UpToDate
|
||||
&& update_report.local_manifest_repair_needed_count == 0
|
||||
&& verification.is_clean();
|
||||
let verification_summary = OfficialVerificationSummary::new(
|
||||
verification.manifest_blake3_verified_count(),
|
||||
verification.failure_count(),
|
||||
verification.official_hash_verified_count,
|
||||
verification.zip_structure_verified_count(),
|
||||
);
|
||||
let report = VerifyCommandReport {
|
||||
command: "verify",
|
||||
status: if healthy { "verified" } else { "failed" },
|
||||
@@ -1854,6 +1889,7 @@ fn run_verify_command(options: &CliOptions) -> anyhow::Result<bool> {
|
||||
local_manifest_failure_count: verification.failure_count(),
|
||||
official_hash_pair_count: verification.official_hash_pair_count,
|
||||
official_hash_verified_count: verification.official_hash_verified_count,
|
||||
verification_summary,
|
||||
official_hash_errors: verification.official_hash_errors,
|
||||
failures,
|
||||
};
|
||||
@@ -3384,6 +3420,26 @@ mod tests {
|
||||
assert!(STARTUP_BANNER.contains("BlueArchiveToolkit"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_summary_output_names_all_validation_types() {
|
||||
let summary = OfficialVerificationSummary::new(7, 1, 2, 3);
|
||||
let lines = verification_summary_lines(&summary);
|
||||
|
||||
assert_eq!(lines.len(), 3);
|
||||
assert!(lines[0].contains("官方 .hash 强校验"));
|
||||
assert!(lines[0].contains("xxHash32"));
|
||||
assert!(lines[1].contains("本地 BLAKE3 复用校验"));
|
||||
assert!(lines[1].contains("需修复"));
|
||||
assert!(lines[2].contains("ZIP 结构校验"));
|
||||
assert!(lines[2].contains("central directory"));
|
||||
|
||||
let json = serde_json::to_value(&summary).unwrap();
|
||||
assert_eq!(json["official_hash_verified_count"], 2);
|
||||
assert_eq!(json["local_manifest_blake3_verified_count"], 7);
|
||||
assert_eq!(json["local_manifest_repair_needed_count"], 1);
|
||||
assert_eq!(json["zip_structure_verified_count"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn daemon_socket_path_uses_state_dir() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -50,7 +50,7 @@ pub use official_update::{
|
||||
GameMainConfigSnapshot, LauncherMetadataSnapshot, OfficialBootstrapCache,
|
||||
OfficialEndpointMarkerRole, OfficialEndpointMarkerSnapshot, OfficialServerInfoSource,
|
||||
OfficialUpdateConfig, OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService,
|
||||
OfficialUpdateSnapshot, OfficialUpdateStatus, ResolvedBootstrap,
|
||||
OfficialUpdateSnapshot, OfficialUpdateStatus, OfficialVerificationSummary, ResolvedBootstrap,
|
||||
};
|
||||
pub use resources::{InMemoryResourceRepository, SqliteResourceRepository};
|
||||
|
||||
|
||||
@@ -270,6 +270,8 @@ pub struct OfficialLocalManifestAuditItem {
|
||||
/// ZIP structure validation error, when the URL or destination is a ZIP
|
||||
/// and the local file did not pass ZIP structure validation.
|
||||
pub zip_error: Option<String>,
|
||||
/// Whether this item required and passed ZIP structure validation.
|
||||
pub zip_structure_verified: bool,
|
||||
}
|
||||
|
||||
/// Local download-manifest audit result for a pull plan.
|
||||
@@ -297,6 +299,23 @@ impl OfficialLocalManifestAuditReport {
|
||||
pub fn repair_needed_count(&self) -> usize {
|
||||
self.items.len().saturating_sub(self.verified_count())
|
||||
}
|
||||
|
||||
/// Returns the number of files that passed local manifest path, size, and
|
||||
/// BLAKE3 validation.
|
||||
pub fn manifest_blake3_verified_count(&self) -> usize {
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|item| item.status.is_verified())
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Returns the number of ZIP files that passed structural validation.
|
||||
pub fn zip_structure_verified_count(&self) -> usize {
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|item| item.zip_structure_verified)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Full local verification report for all entries currently recorded in the
|
||||
@@ -334,6 +353,23 @@ impl OfficialLocalVerificationReport {
|
||||
pub fn failure_count(&self) -> usize {
|
||||
self.items.len().saturating_sub(self.verified_count())
|
||||
}
|
||||
|
||||
/// Returns the number of local manifest entries that passed path, size,
|
||||
/// and BLAKE3 validation.
|
||||
pub fn manifest_blake3_verified_count(&self) -> usize {
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|item| item.status.is_verified())
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Returns the number of ZIP files that passed structural validation.
|
||||
pub fn zip_structure_verified_count(&self) -> usize {
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|item| item.zip_structure_verified)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Existing local state for an official pull plan.
|
||||
@@ -1018,6 +1054,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3: None,
|
||||
actual_blake3: None,
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1036,6 +1073,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: None,
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1051,6 +1089,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: None,
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1065,6 +1104,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: None,
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1079,6 +1119,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: None,
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1094,9 +1135,12 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: Some(actual_blake3),
|
||||
zip_error: None,
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
}
|
||||
|
||||
let zip_structure_required =
|
||||
url_or_path_has_zip_extension(url) || path_has_zip_extension(&destination);
|
||||
if let Err(error) = self.validate_zip_if_needed(url, &destination) {
|
||||
return Ok(OfficialLocalManifestAuditItem {
|
||||
url: url.to_string(),
|
||||
@@ -1108,6 +1152,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: Some(actual_blake3),
|
||||
zip_error: Some(error),
|
||||
zip_structure_verified: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1121,6 +1166,7 @@ impl OfficialResourcePullService {
|
||||
expected_blake3,
|
||||
actual_blake3: Some(actual_blake3),
|
||||
zip_error: None,
|
||||
zip_structure_verified: zip_structure_required,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1841,6 +1887,8 @@ fi
|
||||
let clean_audit = service.audit_local_manifest(&plan).unwrap();
|
||||
assert!(clean_audit.is_clean());
|
||||
assert_eq!(clean_audit.verified_count(), first_report.items.len());
|
||||
assert_eq!(clean_audit.manifest_blake3_verified_count(), 7);
|
||||
assert_eq!(clean_audit.zip_structure_verified_count(), 4);
|
||||
|
||||
let corrupted = first_report
|
||||
.items
|
||||
@@ -1879,6 +1927,8 @@ fi
|
||||
assert!(verification.is_clean());
|
||||
assert_eq!(verification.items.len(), 7);
|
||||
assert_eq!(verification.verified_count(), 7);
|
||||
assert_eq!(verification.manifest_blake3_verified_count(), 7);
|
||||
assert_eq!(verification.zip_structure_verified_count(), 4);
|
||||
assert_eq!(verification.official_hash_pair_count, 1);
|
||||
assert_eq!(verification.official_hash_verified_count, 1);
|
||||
|
||||
|
||||
@@ -314,6 +314,50 @@ impl ExtendedSnapshotDelta {
|
||||
}
|
||||
}
|
||||
|
||||
/// Structured verification summary for CLI and JSON reports.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OfficialVerificationSummary {
|
||||
/// Number of resources verified by the local download manifest's path,
|
||||
/// byte size, and BLAKE3 digest.
|
||||
pub local_manifest_blake3_verified_count: usize,
|
||||
/// Number of resources that still need local repair after manifest audit.
|
||||
pub local_manifest_repair_needed_count: usize,
|
||||
/// Human-facing description of the local manifest validation boundary.
|
||||
pub local_manifest_blake3_scope: String,
|
||||
/// Number of official seed `.bytes`/`.hash` pairs verified by the official
|
||||
/// decimal xxHash32 rule.
|
||||
pub official_hash_verified_count: usize,
|
||||
/// Human-facing description of the official hash validation boundary.
|
||||
pub official_hash_scope: String,
|
||||
/// Number of ZIP files that passed structural validation.
|
||||
pub zip_structure_verified_count: usize,
|
||||
/// Human-facing description of the ZIP structure validation boundary.
|
||||
pub zip_structure_scope: String,
|
||||
}
|
||||
|
||||
impl OfficialVerificationSummary {
|
||||
/// Creates a verification summary from validation counters.
|
||||
pub fn new(
|
||||
local_manifest_blake3_verified_count: usize,
|
||||
local_manifest_repair_needed_count: usize,
|
||||
official_hash_verified_count: usize,
|
||||
zip_structure_verified_count: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
local_manifest_blake3_verified_count,
|
||||
local_manifest_repair_needed_count,
|
||||
local_manifest_blake3_scope:
|
||||
"本地 download manifest 的 URL/path、size 和 BLAKE3 复用校验".to_string(),
|
||||
official_hash_verified_count,
|
||||
official_hash_scope: "官方 seed .bytes/.hash 对,按官方十进制 xxHash32(seed=0) 强校验"
|
||||
.to_string(),
|
||||
zip_structure_verified_count,
|
||||
zip_structure_scope:
|
||||
"所有 .zip 文件的 EOCD、central directory、local header 和边界结构校验".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structured report returned by an official update run.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OfficialUpdateReport {
|
||||
@@ -381,6 +425,8 @@ pub struct OfficialUpdateReport {
|
||||
pub transferred_bytes: u64,
|
||||
/// Number of official seed hashes verified.
|
||||
pub official_seed_hash_verified_count: usize,
|
||||
/// Verification summary with explicit boundaries for each validation type.
|
||||
pub verification_summary: OfficialVerificationSummary,
|
||||
/// Download manifest path.
|
||||
pub download_manifest: PathBuf,
|
||||
/// Snapshot path written after success.
|
||||
@@ -683,6 +729,14 @@ impl OfficialUpdateService {
|
||||
.as_ref()
|
||||
.map(|audit| audit.repair_needed_count())
|
||||
.unwrap_or(0);
|
||||
let local_manifest_blake3_verified_count = local_audit
|
||||
.as_ref()
|
||||
.map(|audit| audit.manifest_blake3_verified_count())
|
||||
.unwrap_or(0);
|
||||
let local_zip_structure_verified_count = local_audit
|
||||
.as_ref()
|
||||
.map(|audit| audit.zip_structure_verified_count())
|
||||
.unwrap_or(0);
|
||||
let repair_needed = config.repair
|
||||
&& local_audit
|
||||
.as_ref()
|
||||
@@ -743,6 +797,12 @@ impl OfficialUpdateService {
|
||||
final_bytes: 0,
|
||||
transferred_bytes: 0,
|
||||
official_seed_hash_verified_count: 0,
|
||||
verification_summary: OfficialVerificationSummary::new(
|
||||
local_manifest_blake3_verified_count,
|
||||
local_manifest_repair_needed_count,
|
||||
0,
|
||||
local_zip_structure_verified_count,
|
||||
),
|
||||
download_manifest: fetcher.download_manifest_path(),
|
||||
snapshot_written: None,
|
||||
};
|
||||
@@ -830,6 +890,12 @@ impl OfficialUpdateService {
|
||||
report.official_seed_hash_verified_count = pull_report.official_hash_verified_count();
|
||||
report.local_manifest_verified_count = final_audit.verified_count();
|
||||
report.local_manifest_repair_needed_count = final_audit.repair_needed_count();
|
||||
report.verification_summary = OfficialVerificationSummary::new(
|
||||
final_audit.manifest_blake3_verified_count(),
|
||||
final_audit.repair_needed_count(),
|
||||
pull_report.official_hash_verified_count(),
|
||||
final_audit.zip_structure_verified_count(),
|
||||
);
|
||||
report.snapshot_written = Some(snapshot_path);
|
||||
|
||||
progress(OfficialUpdateProgress::new(
|
||||
|
||||
@@ -200,6 +200,26 @@ fn official_update_first_run_pulls_without_pre_audit() {
|
||||
assert_eq!(report.downloaded_count, 19);
|
||||
assert_eq!(report.skipped_count, 0);
|
||||
assert_eq!(report.local_manifest_repair_needed_count, 0);
|
||||
assert_eq!(
|
||||
report
|
||||
.verification_summary
|
||||
.local_manifest_blake3_verified_count,
|
||||
19
|
||||
);
|
||||
assert_eq!(report.verification_summary.official_hash_verified_count, 5);
|
||||
assert_eq!(report.verification_summary.zip_structure_verified_count, 6);
|
||||
assert!(report
|
||||
.verification_summary
|
||||
.official_hash_scope
|
||||
.contains("xxHash32"));
|
||||
assert!(report
|
||||
.verification_summary
|
||||
.local_manifest_blake3_scope
|
||||
.contains("BLAKE3"));
|
||||
assert!(report
|
||||
.verification_summary
|
||||
.zip_structure_scope
|
||||
.contains("central directory"));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "local-state" && event.message.contains("是否已有本地资源=false")
|
||||
}));
|
||||
@@ -226,6 +246,13 @@ fn official_update_second_run_audits_existing_resources_before_reuse() {
|
||||
assert_eq!(report.downloaded_count, 0);
|
||||
assert_eq!(report.local_manifest_verified_count, 19);
|
||||
assert_eq!(report.local_manifest_repair_needed_count, 0);
|
||||
assert_eq!(
|
||||
report
|
||||
.verification_summary
|
||||
.local_manifest_blake3_verified_count,
|
||||
19
|
||||
);
|
||||
assert_eq!(report.verification_summary.zip_structure_verified_count, 6);
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "local-state" && event.message.contains("是否已有本地资源=true")
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user