mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
fix: 抽象官方库存与 sidecar hash 策略
This commit is contained in:
@@ -15,7 +15,8 @@ use crate::zip_validation::{
|
||||
};
|
||||
use bat_adapters::official::yostar_jp::YostarJpResourceEndpointKind;
|
||||
use bat_adapters::official::{
|
||||
destination_under_root, DownloadUrlMapper, OfficialResourceBackend, YostarJpBackend,
|
||||
destination_under_root, DownloadUrlMapper, OfficialResourceBackend, SidecarHashStrategy,
|
||||
XxHash32DecimalSeedZero, YostarJpBackend,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
@@ -1835,12 +1836,15 @@ pub fn verify_official_seed_catalog_hash(
|
||||
hash_url: &str,
|
||||
hash_bytes: &[u8],
|
||||
) -> Result<OfficialResourceHashVerification, String> {
|
||||
let expected = parse_official_xxhash32_decimal(hash_url, hash_bytes)?;
|
||||
let actual = xxhash32(data);
|
||||
let strategy = XxHash32DecimalSeedZero;
|
||||
let verification = strategy
|
||||
.verify(data, hash_bytes)
|
||||
.map_err(|error| format!("{error}:{hash_url}"))?;
|
||||
|
||||
if actual != expected {
|
||||
if verification.expected != verification.actual {
|
||||
return Err(format!(
|
||||
"官方 hash 校验失败 {data_url}:期望 {expected}(来自 {hash_url}),实际 {actual}"
|
||||
"官方 hash 校验失败 {data_url}:期望 {}(来自 {hash_url}),实际 {}",
|
||||
verification.expected, verification.actual
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1848,8 +1852,8 @@ pub fn verify_official_seed_catalog_hash(
|
||||
data_url: data_url.to_string(),
|
||||
hash_url: hash_url.to_string(),
|
||||
algorithm: OfficialResourceHashAlgorithm::XxHash32Decimal,
|
||||
expected: expected.to_string(),
|
||||
actual: actual.to_string(),
|
||||
expected: verification.expected,
|
||||
actual: verification.actual,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2209,92 +2213,6 @@ fn blake3_file_hex(path: &Path) -> Result<String, String> {
|
||||
Ok(hasher.finalize().to_hex().to_string())
|
||||
}
|
||||
|
||||
fn parse_official_xxhash32_decimal(hash_url: &str, bytes: &[u8]) -> Result<u32, String> {
|
||||
let text = std::str::from_utf8(bytes)
|
||||
.map_err(|error| format!("官方 hash sidecar 不是 UTF-8:{hash_url}:{error}"))?
|
||||
.trim();
|
||||
text.parse::<u32>()
|
||||
.map_err(|error| format!("官方 hash sidecar 不是十进制 xxHash32:{hash_url}:{error}"))
|
||||
}
|
||||
|
||||
fn xxhash32(bytes: &[u8]) -> u32 {
|
||||
const PRIME1: u32 = 0x9E37_79B1;
|
||||
const PRIME2: u32 = 0x85EB_CA77;
|
||||
const PRIME3: u32 = 0xC2B2_AE3D;
|
||||
const PRIME4: u32 = 0x27D4_EB2F;
|
||||
const PRIME5: u32 = 0x1656_67B1;
|
||||
|
||||
let len = bytes.len();
|
||||
let mut index = 0usize;
|
||||
let mut hash = if len >= 16 {
|
||||
let mut v1 = PRIME1.wrapping_add(PRIME2);
|
||||
let mut v2 = PRIME2;
|
||||
let mut v3 = 0;
|
||||
let mut v4 = 0u32.wrapping_sub(PRIME1);
|
||||
|
||||
while index + 16 <= len {
|
||||
v1 = xxhash32_round(v1, read_u32_le(bytes, index));
|
||||
v2 = xxhash32_round(v2, read_u32_le(bytes, index + 4));
|
||||
v3 = xxhash32_round(v3, read_u32_le(bytes, index + 8));
|
||||
v4 = xxhash32_round(v4, read_u32_le(bytes, index + 12));
|
||||
index += 16;
|
||||
}
|
||||
|
||||
v1.rotate_left(1)
|
||||
.wrapping_add(v2.rotate_left(7))
|
||||
.wrapping_add(v3.rotate_left(12))
|
||||
.wrapping_add(v4.rotate_left(18))
|
||||
} else {
|
||||
PRIME5
|
||||
}
|
||||
.wrapping_add(len as u32);
|
||||
|
||||
while index + 4 <= len {
|
||||
hash = hash
|
||||
.wrapping_add(read_u32_le(bytes, index).wrapping_mul(PRIME3))
|
||||
.rotate_left(17)
|
||||
.wrapping_mul(PRIME4);
|
||||
index += 4;
|
||||
}
|
||||
|
||||
while index < len {
|
||||
hash = hash
|
||||
.wrapping_add((bytes[index] as u32).wrapping_mul(PRIME5))
|
||||
.rotate_left(11)
|
||||
.wrapping_mul(PRIME1);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
xxhash32_avalanche(hash)
|
||||
}
|
||||
|
||||
fn xxhash32_round(acc: u32, input: u32) -> u32 {
|
||||
const PRIME2: u32 = 0x85EB_CA77;
|
||||
const PRIME1: u32 = 0x9E37_79B1;
|
||||
|
||||
acc.wrapping_add(input.wrapping_mul(PRIME2))
|
||||
.rotate_left(13)
|
||||
.wrapping_mul(PRIME1)
|
||||
}
|
||||
|
||||
fn xxhash32_avalanche(mut hash: u32) -> u32 {
|
||||
hash ^= hash >> 15;
|
||||
hash = hash.wrapping_mul(0x85EB_CA77);
|
||||
hash ^= hash >> 13;
|
||||
hash = hash.wrapping_mul(0xC2B2_AE3D);
|
||||
hash ^= hash >> 16;
|
||||
hash
|
||||
}
|
||||
|
||||
fn read_u32_le(bytes: &[u8], offset: usize) -> u32 {
|
||||
u32::from_le_bytes([
|
||||
bytes[offset],
|
||||
bytes[offset + 1],
|
||||
bytes[offset + 2],
|
||||
bytes[offset + 3],
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2311,6 +2229,13 @@ mod tests {
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn xxhash32(bytes: &[u8]) -> u32 {
|
||||
XxHash32DecimalSeedZero
|
||||
.digest(bytes)
|
||||
.parse()
|
||||
.expect("xxHash32 decimal digest")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_bytes_maps_rejections_to_error_codes() {
|
||||
// 非官方 URL:安全边界拒绝。
|
||||
|
||||
@@ -53,7 +53,7 @@ use bat_adapters::official::yostar_jp::{
|
||||
YostarJpResourceEndpointKind, YostarJpServerInfo, YostarJpSyncSnapshot,
|
||||
};
|
||||
use bat_adapters::official::{
|
||||
OfficialResourceBackend, PlatformCatalogInput, YostarJpBackend,
|
||||
InventoryParser, OfficialResourceBackend, PlatformCatalogInput, YostarJpBackend,
|
||||
YostarJpPlatformDownloadInventory,
|
||||
};
|
||||
use bat_core::ErrorCode;
|
||||
@@ -4141,7 +4141,7 @@ fn build_inventory_from_seed_catalogs(
|
||||
});
|
||||
}
|
||||
|
||||
Ok(YostarJpBackend.inventory(table_catalog, &platform_catalogs))
|
||||
Ok(YostarJpBackend.parse_inventory(table_catalog, &platform_catalogs))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
||||
Reference in New Issue
Block a user