mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-09 14:28:53 +08:00
Add PuCaCrypt
Fix CaseInsensitive cause BTreeMap not works correctly
This commit is contained in:
@@ -18,6 +18,8 @@ use std::collections::{BTreeMap, HashMap};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::sync::Arc;
|
||||
|
||||
type CIS = CaseInsensitiveStr;
|
||||
|
||||
pub fn default_init_crypt(archive: &mut Xp3Archive) -> Result<()> {
|
||||
if archive.extras.iter().any(|extra| extra.is_filename_hash()) {
|
||||
let mut filename_map = HashMap::new();
|
||||
@@ -216,6 +218,11 @@ enum CryptType {
|
||||
YuzuCrypt,
|
||||
HighRunningCrypt,
|
||||
KissCrypt,
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
PuCaCrypt {
|
||||
hash_table: Vec<u32>,
|
||||
key_table: Base64Bytes,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
@@ -331,6 +338,14 @@ impl Schema {
|
||||
CryptType::YuzuCrypt => Box::new(YuzuCrypt::new(self.base.clone())),
|
||||
CryptType::HighRunningCrypt => Box::new(HighRunningCrypt::new(self.base.clone())),
|
||||
CryptType::KissCrypt => Box::new(cz::KissCrypt::new(self.base.clone())),
|
||||
CryptType::PuCaCrypt {
|
||||
hash_table,
|
||||
key_table,
|
||||
} => Box::new(PuCaCrypt::new(
|
||||
self.base.clone(),
|
||||
hash_table.clone(),
|
||||
key_table.bytes.clone(),
|
||||
)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -387,10 +402,10 @@ pub fn get_supported_games_with_title() -> Vec<(&'static str, Option<&'static st
|
||||
}
|
||||
|
||||
pub fn query_crypt_schema(game: &str) -> Option<&'static Schema> {
|
||||
CRYPT_SCHEMA.get(game).or_else(|| {
|
||||
CRYPT_SCHEMA.get(CIS::from_str(game)).or_else(|| {
|
||||
ALIAS_TABLE
|
||||
.get(game)
|
||||
.and_then(|real_game| CRYPT_SCHEMA.get(real_game))
|
||||
.and_then(|real_game| CRYPT_SCHEMA.get(CIS::from_str(real_game)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1375,11 +1390,134 @@ impl<R: Read> Read for HighRunningCryptReader<R> {
|
||||
|
||||
seek_reader_key_impl!(KissCryptReader<T>, u32);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PuCaCrypt {
|
||||
base: BaseSchema,
|
||||
hash_table: Vec<u32>,
|
||||
key_table: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PuCaCrypt {
|
||||
pub fn new(base: BaseSchema, hash_table: Vec<u32>, key_table: Vec<u8>) -> Result<Self> {
|
||||
if hash_table.len() != key_table.len() {
|
||||
anyhow::bail!(
|
||||
"Hash table and key table must have the same length, but got {} and {}",
|
||||
hash_table.len(),
|
||||
key_table.len()
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
base,
|
||||
hash_table,
|
||||
key_table,
|
||||
})
|
||||
}
|
||||
fn get_key_table(&self, file_hash: u32) -> [u8; 0x400] {
|
||||
let mut hash_table = [0u8; 32];
|
||||
let mut hash = file_hash;
|
||||
for k in (0..32).step_by(4) {
|
||||
if hash & 1 != 0 {
|
||||
hash |= 0x80000000;
|
||||
} else {
|
||||
hash &= 0x7FFFFFFF;
|
||||
}
|
||||
hash_table[k..k + 4].copy_from_slice(&hash.to_le_bytes());
|
||||
hash >>= 1;
|
||||
}
|
||||
let mut key_table = [0u8; 0x400];
|
||||
for l in 0..32 {
|
||||
for m in 0..32 {
|
||||
key_table[l * 32 + m] = (!hash_table[l]) ^ hash_table[m];
|
||||
}
|
||||
}
|
||||
key_table
|
||||
}
|
||||
}
|
||||
|
||||
impl Crypt for PuCaCrypt {
|
||||
base_schema_impl!();
|
||||
fn decrypt_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn decrypt_seek_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn decrypt<'a>(
|
||||
&self,
|
||||
entry: &Xp3Entry,
|
||||
cur_seg: &Segment,
|
||||
stream: Box<dyn Read + 'a>,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>> {
|
||||
if let Some(pos) = self.hash_table.iter().position(|&h| h == entry.file_hash) {
|
||||
Ok(Box::new(PuCaCryptReader::new(
|
||||
stream,
|
||||
cur_seg,
|
||||
self.key_table[pos],
|
||||
)))
|
||||
} else {
|
||||
Ok(Box::new(PuCaCryptReader2::new(
|
||||
stream,
|
||||
cur_seg,
|
||||
self.get_key_table(entry.file_hash),
|
||||
)))
|
||||
}
|
||||
}
|
||||
fn decrypt_with_seek<'a>(
|
||||
&self,
|
||||
entry: &Xp3Entry,
|
||||
cur_seg: &Segment,
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
) -> Result<Box<dyn ReadSeek + 'a>> {
|
||||
if let Some(pos) = self.hash_table.iter().position(|&h| h == entry.file_hash) {
|
||||
Ok(Box::new(PuCaCryptReader::new(
|
||||
stream,
|
||||
cur_seg,
|
||||
self.key_table[pos],
|
||||
)))
|
||||
} else {
|
||||
Ok(Box::new(PuCaCryptReader2::new(
|
||||
stream,
|
||||
cur_seg,
|
||||
self.get_key_table(entry.file_hash),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seek_reader_key_impl!(PuCaCryptReader<T>, u8);
|
||||
|
||||
impl<T: Read> Read for PuCaCryptReader<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let readed = self.inner.read(buf)?;
|
||||
for t in (&mut buf[..readed]).iter_mut() {
|
||||
*t ^= self.key;
|
||||
}
|
||||
self.pos += readed as u64;
|
||||
Ok(readed)
|
||||
}
|
||||
}
|
||||
|
||||
seek_reader_key_impl!(PuCaCryptReader2<T>, [u8; 0x400]);
|
||||
|
||||
impl<R: Read> Read for PuCaCryptReader2<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let readed = self.inner.read(buf)?;
|
||||
let mut offset = ((self.seg_start + self.pos) & 0x3FF) as usize;
|
||||
for t in (&mut buf[..readed]).iter_mut() {
|
||||
*t ^= self.key[offset];
|
||||
offset = (offset + 1) & 0x3FF;
|
||||
}
|
||||
self.pos += readed as u64;
|
||||
Ok(readed)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_crypt() {
|
||||
for (key, schema) in CRYPT_SCHEMA.iter() {
|
||||
println!("Title: {}, Schema: {:?}", key, schema);
|
||||
}
|
||||
assert!(CRYPT_SCHEMA.contains_key(CIS::from_str("PURELY x CATION")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -134,7 +134,7 @@ impl ScriptBuilder for Xp3ArchiveBuilder {
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["xp3", "bin"]
|
||||
&["xp3", "bin", "dat"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Borrow;
|
||||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -57,20 +58,97 @@ impl DerefMut for CaseInsensitiveString {
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for CaseInsensitiveString {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<String> for CaseInsensitiveString {
|
||||
fn borrow(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CaseInsensitiveString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CaseInsensitiveString {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_ascii_lowercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<CaseInsensitiveStr> for CaseInsensitiveString {
|
||||
fn borrow(&self) -> &CaseInsensitiveStr {
|
||||
CaseInsensitiveStr::from_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct CaseInsensitiveStr(str);
|
||||
|
||||
impl CaseInsensitiveStr {
|
||||
pub fn from_str(s: &str) -> &Self {
|
||||
// SAFETY: CaseInsensitiveStr has the same memory layout as str, so this transmute is safe.
|
||||
unsafe { &*(s as *const str as *const Self) }
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CaseInsensitiveStr {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.eq_ignore_ascii_case(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CaseInsensitiveStr {}
|
||||
|
||||
impl PartialOrd for CaseInsensitiveStr {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for CaseInsensitiveStr {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&other.0.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CaseInsensitiveStr {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CaseInsensitiveStr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CaseInsensitiveStr {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_ascii_lowercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_btree_map() {
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
map.insert(CaseInsensitiveString("hella".to_string()), 0);
|
||||
map.insert(CaseInsensitiveString("Hello".to_string()), 1);
|
||||
map.insert(CaseInsensitiveString("world".to_string()), 2);
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("hello")), Some(&1));
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("WORLD")), Some(&2));
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("hella")), Some(&0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_map() {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
map.insert(CaseInsensitiveString("hells".to_string()), 0);
|
||||
map.insert(CaseInsensitiveString("Hello".to_string()), 1);
|
||||
map.insert(CaseInsensitiveString("world".to_string()), 2);
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("hello")), Some(&1));
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("WORLD")), Some(&2));
|
||||
assert_eq!(map.get(CaseInsensitiveStr::from_str("hells")), Some(&0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user