mirror of
https://git.um-react.app/um/um-react.git
synced 2025-11-28 03:23:02 +00:00
exp: use @unlock-music/crypto backend for NCM decryption
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
export const NCM_KEY = 'hzHRAmso5kInbaxW';
|
||||
export const NCM_MAGIC_HEADER = new Uint8Array([0x43, 0x54, 0x45, 0x4e, 0x46, 0x44, 0x41, 0x4d]);
|
||||
@@ -1,18 +1,27 @@
|
||||
import { transformBlob } from '~/decrypt-worker/util/transformBlob';
|
||||
import type { CryptoBase } from '../CryptoBase';
|
||||
import { NCM_KEY, NCM_MAGIC_HEADER } from './ncm_pc.key';
|
||||
import { NCMFile } from '@unlock-music/crypto';
|
||||
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
|
||||
|
||||
export class NCMCrypto implements CryptoBase {
|
||||
cryptoName = 'NCM/PC';
|
||||
checkByDecryptHeader = false;
|
||||
ncm = new NCMFile();
|
||||
|
||||
async checkBySignature(buffer: ArrayBuffer) {
|
||||
const view = new DataView(buffer, 0, NCM_MAGIC_HEADER.byteLength);
|
||||
return NCM_MAGIC_HEADER.every((value, i) => value === view.getUint8(i));
|
||||
try {
|
||||
this.ncm.open(new Uint8Array(buffer));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async decrypt(buffer: ArrayBuffer): Promise<Blob> {
|
||||
return transformBlob(buffer, (p) => p.make.NeteaseNCM(NCM_KEY));
|
||||
const audioBuffer = new Uint8Array(buffer.slice(this.ncm.audioOffset));
|
||||
for (const [block, offset] of chunkBuffer(audioBuffer)) {
|
||||
this.ncm.decrypt(block, offset);
|
||||
}
|
||||
return new Blob([audioBuffer]);
|
||||
}
|
||||
|
||||
public static make() {
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
export const toArrayBuffer = async (src: Blob | ArrayBuffer) => (src instanceof Blob ? await src.arrayBuffer() : src);
|
||||
export const toBlob = (src: Blob | ArrayBuffer) => (src instanceof Blob ? src : new Blob([src]));
|
||||
|
||||
export function* chunkBuffer(buffer: Uint8Array, blockLen = 4096): Generator<[Uint8Array, number], void> {
|
||||
const len = buffer.byteLength;
|
||||
for (let i = 0; i < len; i += blockLen) {
|
||||
const idxEnd = Math.min(i + blockLen, len);
|
||||
const slice = buffer.subarray(i, idxEnd);
|
||||
yield [slice, i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { allCryptoFactories } from '../../crypto/CryptoFactory';
|
||||
import { toArrayBuffer, toBlob } from '~/decrypt-worker/util/buffer';
|
||||
import { CryptoBase, CryptoFactory } from '~/decrypt-worker/crypto/CryptoBase';
|
||||
import { UnsupportedSourceFile } from '~/decrypt-worker/util/DecryptError';
|
||||
import { ready as umCryptoReady } from '@unlock-music/crypto';
|
||||
|
||||
// Use first 4MiB of the file to perform check.
|
||||
const TEST_FILE_HEADER_LEN = 4 * 1024 * 1024;
|
||||
@@ -30,7 +31,7 @@ class DecryptCommandHandler {
|
||||
const decryptor = factory();
|
||||
|
||||
try {
|
||||
const result = await this.decryptFile(decryptor);
|
||||
const result = await this.tryDecryptFile(decryptor);
|
||||
if (result === null) {
|
||||
continue;
|
||||
}
|
||||
@@ -47,7 +48,7 @@ class DecryptCommandHandler {
|
||||
throw new UnsupportedSourceFile('could not decrypt file: no working decryptor found');
|
||||
}
|
||||
|
||||
async decryptFile(crypto: CryptoBase) {
|
||||
async tryDecryptFile(crypto: CryptoBase) {
|
||||
if (crypto.checkBySignature && !(await crypto.checkBySignature(this.buffer, this.options))) {
|
||||
return null;
|
||||
}
|
||||
@@ -95,6 +96,7 @@ class DecryptCommandHandler {
|
||||
export const workerDecryptHandler = async ({ id, blobURI, options }: DecryptCommandPayload) => {
|
||||
const label = `decrypt(${id})`;
|
||||
return withTimeGroupedLogs(label, async () => {
|
||||
await umCryptoReady;
|
||||
const parakeet = await timedLogger(`${label}/init`, fetchParakeet);
|
||||
const blob = await timedLogger(`${label}/fetch-src`, async () => fetch(blobURI).then((r) => r.blob()));
|
||||
const buffer = await timedLogger(`${label}/read-src`, async () => blob.arrayBuffer());
|
||||
|
||||
Reference in New Issue
Block a user