xhacker-zzz
2 years ago
10 changed files with 94 additions and 11 deletions
@ -0,0 +1,71 @@ |
|||||
|
import { Decrypt as RawDecrypt } from './raw'; |
||||
|
import { GetArrayBuffer } from '@/decrypt/utils'; |
||||
|
import { DecryptResult } from '@/decrypt/entity'; |
||||
|
|
||||
|
const segmentSize = 0x20; |
||||
|
|
||||
|
function isPrintableAsciiChar(ch: number) { |
||||
|
return ch >= 0x20 && ch <= 0x7E; |
||||
|
} |
||||
|
|
||||
|
function isUpperHexChar(ch: number) { |
||||
|
return (ch >= 0x30 && ch <= 0x39) || (ch >= 0x41 && ch <= 0x46); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param {Buffer} data |
||||
|
* @param {Buffer} key |
||||
|
* @param {boolean} copy |
||||
|
* @returns Buffer |
||||
|
*/ |
||||
|
function decryptSegment(data: Uint8Array, key: Uint8Array) { |
||||
|
for (let i = 0; i < data.byteLength; i++) { |
||||
|
data[i] -= key[i % segmentSize]; |
||||
|
} |
||||
|
return Buffer.from(data); |
||||
|
} |
||||
|
|
||||
|
export async function Decrypt(file: File, raw_filename: string): Promise<DecryptResult> { |
||||
|
const buf = new Uint8Array(await GetArrayBuffer(file)); |
||||
|
|
||||
|
// 咪咕编码的 WAV 文件有很多“空洞”内容,尝试密钥。
|
||||
|
const header = buf.slice(0, 0x100); |
||||
|
const bytesRIFF = Buffer.from('RIFF', 'ascii'); |
||||
|
const bytesWaveFormat = Buffer.from('WAVEfmt ', 'ascii'); |
||||
|
const possibleKeys = []; |
||||
|
|
||||
|
for (let i = segmentSize; i < segmentSize * 20; i += segmentSize) { |
||||
|
const possibleKey = buf.slice(i, i + segmentSize); |
||||
|
if (!possibleKey.every(isUpperHexChar)) continue; |
||||
|
|
||||
|
const tempHeader = decryptSegment(header, possibleKey); |
||||
|
if (tempHeader.slice(0, 4).compare(bytesRIFF)) continue; |
||||
|
if (tempHeader.slice(8, 16).compare(bytesWaveFormat)) continue; |
||||
|
|
||||
|
// fmt chunk 大小可以是 16 / 18 / 40。
|
||||
|
const fmtChunkSize = tempHeader.readUInt32LE(0x10); |
||||
|
if (![16, 18, 40].includes(fmtChunkSize)) continue; |
||||
|
|
||||
|
// 下一个 chunk
|
||||
|
const firstDataChunkOffset = 0x14 + fmtChunkSize; |
||||
|
const chunkName = tempHeader.slice(firstDataChunkOffset, firstDataChunkOffset + 4); |
||||
|
if (!chunkName.every(isPrintableAsciiChar)) continue; |
||||
|
|
||||
|
const secondDataChunkOffset = firstDataChunkOffset + 8 + tempHeader.readUInt32LE(firstDataChunkOffset + 4); |
||||
|
if (secondDataChunkOffset <= header.byteLength) { |
||||
|
const secondChunkName = tempHeader.slice(secondDataChunkOffset, secondDataChunkOffset + 4); |
||||
|
if (!secondChunkName.every(isPrintableAsciiChar)) continue; |
||||
|
} |
||||
|
|
||||
|
possibleKeys.push(Buffer.from(possibleKey).toString('ascii')); |
||||
|
} |
||||
|
|
||||
|
if (possibleKeys.length <= 0) { |
||||
|
throw new Error(`ERROR: no suitable key discovered`); |
||||
|
} |
||||
|
|
||||
|
const decryptionKey = Buffer.from(possibleKeys[0], 'ascii'); |
||||
|
decryptSegment(buf, decryptionKey); |
||||
|
const musicData = new Blob([buf], { type: 'audio/x-wav' }); |
||||
|
return await RawDecrypt(musicData, raw_filename, 'wav', false); |
||||
|
} |
Loading…
Reference in new issue