Now support to detect default path

This commit is contained in:
2026-05-06 10:44:52 +08:00
parent e1ead588ea
commit 191d7af25d
6 changed files with 109 additions and 3 deletions

1
Cargo.lock generated
View File

@@ -1573,6 +1573,7 @@ dependencies = [
"serde_yaml_ng",
"sha1",
"sha2",
"siphasher",
"stylua",
"tendril",
"unicode-segmentation",

View File

@@ -57,6 +57,7 @@ serde_json = "1"
serde_yaml_ng = "0.10"
sha1 = { version = "0.11", optional = true }
sha2 = { version = "0.11", optional = true }
siphasher = { version = "1.0", optional = true }
stylua = { version = "2.1", optional = true, default-features = false}
tendril = { version = "0.5", optional = true }
unicode-segmentation = "1.12"
@@ -101,7 +102,7 @@ hexen-haus = ["memchr", "utils-str"]
hexen-haus-arc = ["hexen-haus"]
hexen-haus-img = ["hexen-haus", "image"]
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"]
kirikiri-arc = ["kirikiri", "adler", "aes", "base64", "blake2", "bytes", "cbc", "chacha20/legacy", "fastcdc", "flate2", "hex", "int-enum", "md5", "msg_tool_macro/kirikiri-arc", "msg_tool_xp3data", "parse-size", "sha2", "utils-case-insensitive-string", "utils-lzss", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"]
kirikiri-arc = ["kirikiri", "adler", "aes", "base64", "blake2", "bytes", "cbc", "chacha20/legacy", "fastcdc", "flate2", "hex", "int-enum", "md5", "msg_tool_macro/kirikiri-arc", "msg_tool_xp3data", "parse-size", "sha2", "siphasher", "utils-case-insensitive-string", "utils-lzss", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"]
kirikiri-img = ["kirikiri", "image", "libtlg-rs"]
musica = []
musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"]

View File

@@ -151,6 +151,20 @@
"TpmFileName": "plugin/AmairoIsleNauts.tpm",
"Title": "天色*アイルノーツ | 天色幻想岛"
},
"AMBITIOUS MISSION After Episode 1": {
"$type": "HxCrypt",
"Mask": 454,
"Offset": 656,
"PrologOrder": "AAEC",
"OddBranchOrder": "BAMCBQAB",
"EvenBranchOrder": "AAIHAwQBBgU=",
"Key": "Wa7phHMk/a0hEztfLDpfcWbN1n7e0DYitcdCV1m/2EQ=",
"Nonce": "DDEouJ4BezmeMVKZf7WVqA==",
"FilterKey": 375708990042069900,
"RandomType": 1,
"ControlBlockName": "amb_after_ep1.bin",
"Title": "AMBITIOUS MISSION アフターエピソード1 かぐや&あてな | Ambitious Mission FD1"
},
"Anata no Milk Kudasai na": {
"$type": "HashCrypt",
"Title": "あなたの精液くださいな ~私とボクとどっちがお好み?~"
@@ -310,6 +324,67 @@
"ControlBlockName": "cafe_stella.bin",
"Title": "喫茶ステラと死神の蝶 | 星光咖啡馆与死神之蝶 | 星光咖啡館與死神之蝶"
},
"Café Stella to Shinigami no Chou [Steam]": {
"$type": "HxCrypt",
"Mask": 486,
"Offset": 101,
"PrologOrder": "AgAB",
"OddBranchOrder": "BAEFAAMC",
"EvenBranchOrder": "BgMBAgQFAAc=",
"FilterKey": 9413765695581088110,
"IndexKeyDict": {
"data.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"video.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"bgm.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"fgimage.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"bgimage.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"scn.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"voice.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"main.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"evimage.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"uipsd.xp3": {
"Key": "sd8wA9myXvbA4ATJnq6JYia3V5otV84MkuSiW4aYOog=",
"Nonce": "HXJsNtEkHsOPDBcx/QODdQ=="
},
"video2.xp3": {
"Key": "9lU4Kgw+IvRM3Zm0ubsa161RAxURD9ghXmSvYH7f/VE=",
"Nonce": "r5eGhOPufNjqgZMwtggGhg=="
},
"adult.xp3": {
"Key": "9lU4Kgw+IvRM3Zm0ubsa161RAxURD9ghXmSvYH7f/VE=",
"Nonce": "r5eGhOPufNjqgZMwtggGhg=="
}
},
"ControlBlockName": "cafe_stella_steam.bin",
"Title": "喫茶ステラと死神の蝶 [Steam] | 星光咖啡馆与死神之蝶 [Steam] | 星光咖啡館與死神之蝶 [Steam]"
},
"CharaBration!": {
"$type": "AkabeiCrypt",
"Seed": 3854429322,
@@ -420,7 +495,8 @@
"Key": "4ozUw00nStHvknH8ECpqL1BVC9nOrpdryY4wHKtBTFE=",
"Nonce": "pjhv8KmiW/gnbdoy6c5J6Q==",
"FilterKey": 12271333071625965214,
"ControlBlockName": "dc5.bin"
"ControlBlockName": "dc5.bin",
"Title": "D.C.5 ~ダ・カーポ5~ | 初音岛5 | D.C.5 Plus Happiness ~ダ・カーポ5~プラスハピネス | D.C.5 ~Da Capo 5~ Future Link | D.C.5 Future Link ~ダ・カーポ5~ フューチャーリンク | D.C.5 Sweet Happiness ~ダ・カーポ5~スイートハピネス"
},
"Deatte 5-fun wa Ore no Mono!": {
"$type": "XorCrypt",

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -2111,7 +2111,7 @@ impl HxCrypt {
index_key = Some(ind.clone())
}
let index_key = index_key.ok_or_else(|| anyhow::anyhow!("Can not find index key."))?;
let (file_map, path_map) = if let Some(path) = file_list_path {
let (file_map, mut path_map) = if let Some(path) = file_list_path {
let data = std::fs::read(path)?;
let data = decode_to_string(Encoding::Utf8, &data, true)?;
Self::read_names(&data, s)?
@@ -2128,6 +2128,10 @@ impl HxCrypt {
(HashMap::new(), HashMap::new())
}
};
let default_path_hash = calculate_path_hash("", "xp3hnp");
if !path_map.contains_key(&default_path_hash) {
path_map.insert(default_path_hash, String::new());
}
Ok(Self {
base: CxEncryption::new_inner(
base,
@@ -2952,6 +2956,18 @@ fn create_garbage_filename_set(file_hash_salt: &str) -> HashSet<FileHash> {
set
}
fn calculate_path_hash(pathname: &str, path_hash_salt: &str) -> PathHash {
use std::hash::Hasher;
let mut hasher = siphasher::sip::SipHasher24::new();
(pathname.to_lowercase() + path_hash_salt)
.encode_utf16()
.for_each(|b| {
hasher.write(&b.to_le_bytes());
});
let data = hasher.finish().to_ne_bytes();
PathHash(u64::from_be_bytes(data))
}
#[test]
fn test_filehash_deserialize() {
assert_eq!(
@@ -2965,3 +2981,15 @@ fn test_filehash_deserialize() {
.unwrap()
);
}
#[test]
fn test_calculate_path_hash() {
assert_eq!(
calculate_path_hash("", "xp3hnp"),
PathHash::try_from("94d4a97c61498621").unwrap(),
);
assert_eq!(
calculate_path_hash("scenario/scripts/", "xp3hnp"),
PathHash::try_from("c81c19411c1a5e54").unwrap(),
);
}