Compare commits
No commits in common. "92a52c1565d35c34fa899798b6c44657da8dd45a" and "fd73e8b9a3eca60a394fd918c5d79ec4183953f6" have entirely different histories.
92a52c1565
...
fd73e8b9a3
@ -11,8 +11,6 @@ pub struct Header {
|
|||||||
pub key_slot: u32,
|
pub key_slot: u32,
|
||||||
pub decrypt_test_data: [u8; 0x10],
|
pub decrypt_test_data: [u8; 0x10],
|
||||||
pub file_key: [u8; 0x10],
|
pub file_key: [u8; 0x10],
|
||||||
|
|
||||||
challenge_data: [u8; 0x10],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
@ -27,8 +25,6 @@ impl Header {
|
|||||||
|
|
||||||
let mut magic = [0u8; 0x10];
|
let mut magic = [0u8; 0x10];
|
||||||
magic.copy_from_slice(&buffer[..0x10]);
|
magic.copy_from_slice(&buffer[..0x10]);
|
||||||
let challenge_data = get_challenge_data(&magic)?;
|
|
||||||
|
|
||||||
let offset_to_data = LE::read_u32(&buffer[0x10..0x14]) as usize;
|
let offset_to_data = LE::read_u32(&buffer[0x10..0x14]) as usize;
|
||||||
let crypto_version = LE::read_u32(&buffer[0x14..0x18]);
|
let crypto_version = LE::read_u32(&buffer[0x14..0x18]);
|
||||||
let key_slot = LE::read_u32(&buffer[0x18..0x1C]);
|
let key_slot = LE::read_u32(&buffer[0x18..0x1C]);
|
||||||
@ -44,7 +40,6 @@ impl Header {
|
|||||||
key_slot,
|
key_slot,
|
||||||
decrypt_test_data,
|
decrypt_test_data,
|
||||||
file_key,
|
file_key,
|
||||||
challenge_data,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,37 +54,6 @@ impl Header {
|
|||||||
3 => Box::from(DecipherV3::new(self, slot_key)?),
|
3 => Box::from(DecipherV3::new(self, slot_key)?),
|
||||||
version => Err(KugouError::UnsupportedCipherVersion(version))?,
|
version => Err(KugouError::UnsupportedCipherVersion(version))?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut test_data = self.decrypt_test_data;
|
|
||||||
decipher.decrypt(&mut test_data, 0);
|
|
||||||
if self.challenge_data != test_data {
|
|
||||||
Err(KugouError::SelfTestFailed)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(decipher)
|
Ok(decipher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_challenge_data(magic_header: &[u8; 0x10]) -> Result<[u8; 0x10], KugouError> {
|
|
||||||
match magic_header {
|
|
||||||
&KGM_HEADER => Ok(KGM_TEST_DATA),
|
|
||||||
&VPR_HEADER => Ok(VPR_TEST_DATA),
|
|
||||||
_ => Err(KugouError::NotKGMFile)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const KGM_HEADER: [u8; 16] = [
|
|
||||||
0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B, 0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const KGM_TEST_DATA: [u8; 16] = [
|
|
||||||
0x38, 0x85, 0xED, 0x92, 0x79, 0x5F, 0xF8, 0x4C, 0xB3, 0x03, 0x61, 0x41, 0x16, 0xA0, 0x1D, 0x47,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const VPR_HEADER: [u8; 16] = [
|
|
||||||
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, 0x91, 0xAA, 0xBD, 0xD0, 0x7A, 0xF5, 0x36, 0x31,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const VPR_TEST_DATA: [u8; 16] = [
|
|
||||||
0x1D, 0x5A, 0x05, 0x34, 0x0C, 0x41, 0x8D, 0x42, 0x9C, 0x83, 0x92, 0x6C, 0xAE, 0x16, 0xFE, 0x56,
|
|
||||||
];
|
|
||||||
|
@ -14,12 +14,6 @@ pub enum KugouError {
|
|||||||
|
|
||||||
#[error("Unsupported cipher version: {0}")]
|
#[error("Unsupported cipher version: {0}")]
|
||||||
UnsupportedCipherVersion(u32),
|
UnsupportedCipherVersion(u32),
|
||||||
|
|
||||||
#[error("Not KGM File (magic mismatch)")]
|
|
||||||
NotKGMFile,
|
|
||||||
|
|
||||||
#[error("Unsupported cipher (self-test failed)")]
|
|
||||||
SelfTestFailed,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Decipher {
|
pub trait Decipher {
|
||||||
|
@ -15,8 +15,9 @@ impl DecipherV2 {
|
|||||||
|
|
||||||
impl Decipher for DecipherV2 {
|
impl Decipher for DecipherV2 {
|
||||||
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
|
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
|
||||||
for (datum, offset) in buffer.iter_mut().zip(offset..) {
|
let key_stream = self.key.iter().cycle().skip(offset % self.key.len());
|
||||||
*datum ^= self.key[offset % self.key.len()];
|
for (datum, &k) in buffer.iter_mut().zip(key_stream) {
|
||||||
|
*datum ^= k;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,27 +25,34 @@ impl DecipherV3 {
|
|||||||
|
|
||||||
Ok(Self { slot_key, file_key })
|
Ok(Self { slot_key, file_key })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset_key(offset: usize) -> u8 {
|
|
||||||
let offset_key = (offset as u32).to_ne_bytes();
|
|
||||||
offset_key[0] ^ offset_key[1] ^ offset_key[2] ^ offset_key[3]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decipher for DecipherV3 {
|
impl Decipher for DecipherV3 {
|
||||||
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
|
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
|
||||||
for (datum, offset) in buffer.iter_mut().zip(offset..) {
|
let slot_key_stream = self
|
||||||
let offset_key = Self::offset_key(offset);
|
.slot_key
|
||||||
|
.iter()
|
||||||
|
.cycle()
|
||||||
|
.skip(offset % self.slot_key.len());
|
||||||
|
let file_key_stream = self
|
||||||
|
.file_key
|
||||||
|
.iter()
|
||||||
|
.cycle()
|
||||||
|
.skip(offset % self.file_key.len());
|
||||||
|
|
||||||
let file_key = self.file_key[offset % self.file_key.len()];
|
let mut offset = offset as u32;
|
||||||
let slot_key = self.slot_key[offset % self.slot_key.len()];
|
let key_stream = slot_key_stream.zip(file_key_stream);
|
||||||
|
for (datum, (&slot_key, &file_key)) in buffer.iter_mut().zip(key_stream) {
|
||||||
|
let offset_key = offset.to_ne_bytes().iter().fold(0, |acc, &x| acc ^ x);
|
||||||
|
|
||||||
let mut temp = *datum;
|
let mut temp = *datum;
|
||||||
temp ^= file_key;
|
temp ^= file_key;
|
||||||
temp ^= temp << 4;
|
temp ^= temp.wrapping_shl(4);
|
||||||
temp ^= slot_key;
|
temp ^= slot_key;
|
||||||
temp ^= offset_key;
|
temp ^= offset_key;
|
||||||
*datum = temp;
|
*datum = temp;
|
||||||
|
|
||||||
|
offset = offset.wrapping_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,10 @@ const KEY: [u8; 0x20] = [
|
|||||||
impl CipherV1 {
|
impl CipherV1 {
|
||||||
pub fn new(resource_id: u32) -> Self {
|
pub fn new(resource_id: u32) -> Self {
|
||||||
let mut key = KEY;
|
let mut key = KEY;
|
||||||
let resource_id = resource_id.to_string();
|
for (k, r) in key
|
||||||
for (k, &r) in key.iter_mut().zip(resource_id.as_bytes().iter().cycle()) {
|
.iter_mut()
|
||||||
|
.zip(resource_id.to_string().as_bytes().iter().cycle())
|
||||||
|
{
|
||||||
*k ^= r;
|
*k ^= r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,9 +26,10 @@ impl CipherV1 {
|
|||||||
T: AsMut<[u8]> + ?Sized,
|
T: AsMut<[u8]> + ?Sized,
|
||||||
{
|
{
|
||||||
let data = data.as_mut();
|
let data = data.as_mut();
|
||||||
|
let key_stream = self.key.iter().cycle().skip(offset % self.key.len());
|
||||||
|
|
||||||
for (datum, offset) in data.iter_mut().zip(offset..) {
|
for (datum, key) in data.iter_mut().zip(key_stream) {
|
||||||
*datum ^= self.key[offset % self.key.len()];
|
*datum ^= *key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,8 +182,9 @@ impl NCMFile {
|
|||||||
where
|
where
|
||||||
T: AsMut<[u8]> + ?Sized,
|
T: AsMut<[u8]> + ?Sized,
|
||||||
{
|
{
|
||||||
for (datum, offset) in data.as_mut().iter_mut().zip(offset..) {
|
let key_stream = self.audio_rc4_key_stream.iter().cycle().skip(offset & 0xff);
|
||||||
*datum ^= self.audio_rc4_key_stream[offset % self.audio_rc4_key_stream.len()];
|
for (datum, &key) in data.as_mut().iter_mut().zip(key_stream) {
|
||||||
|
*datum ^= key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user