Compare commits
2 Commits
a75ca7aabb
...
1b116a8db3
Author | SHA1 | Date | |
---|---|---|---|
1b116a8db3 | |||
7fef8da083 |
@ -24,7 +24,7 @@
|
|||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@um/libparakeet": "0.4.5",
|
"@um/libparakeet": "0.4.5",
|
||||||
"@unlock-music/crypto": "0.0.0-alpha.11",
|
"@unlock-music/crypto": "0.0.0-alpha.12",
|
||||||
"framer-motion": "^10.16.16",
|
"framer-motion": "^10.16.16",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"radash": "^11.0.0",
|
"radash": "^11.0.0",
|
||||||
|
@ -42,8 +42,8 @@ importers:
|
|||||||
specifier: 0.4.5
|
specifier: 0.4.5
|
||||||
version: 0.4.5
|
version: 0.4.5
|
||||||
'@unlock-music/crypto':
|
'@unlock-music/crypto':
|
||||||
specifier: 0.0.0-alpha.11
|
specifier: 0.0.0-alpha.12
|
||||||
version: 0.0.0-alpha.11
|
version: 0.0.0-alpha.12
|
||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^10.16.16
|
specifier: ^10.16.16
|
||||||
version: 10.16.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 10.16.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
@ -1927,8 +1927,8 @@ packages:
|
|||||||
'@ungap/structured-clone@1.2.0':
|
'@ungap/structured-clone@1.2.0':
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
|
|
||||||
'@unlock-music/crypto@0.0.0-alpha.11':
|
'@unlock-music/crypto@0.0.0-alpha.12':
|
||||||
resolution: {integrity: sha512-lA3xryziHULhkPbuQFI2HrfwDREUD9YoaZOTMQqcu/8mKF2/hA3sCK0Uoq0miYr+7VUbE5sMBvl9dcrnCI1UWA==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.11/crypto-0.0.0-alpha.11.tgz}
|
resolution: {integrity: sha512-Q24cq653CmD8sj/D1M6wHYtXJIX3YIgnvbPtO+aHnY07J0ZXvkqNh+6a3hBrGGLYzcSWioAw2xxf2rFEQ3q35A==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.12/crypto-0.0.0-alpha.12.tgz}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.2.1':
|
'@vitejs/plugin-react@4.2.1':
|
||||||
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
|
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
|
||||||
@ -6118,7 +6118,7 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@ungap/structured-clone@1.2.0': {}
|
||||||
|
|
||||||
'@unlock-music/crypto@0.0.0-alpha.11': {}
|
'@unlock-music/crypto@0.0.0-alpha.12': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.2.1(vite@5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.27.0))':
|
'@vitejs/plugin-react@4.2.1(vite@5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.27.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3,6 +3,7 @@ import { TransparentDecipher } from './decipher/Transparent.ts';
|
|||||||
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
||||||
import { QQMusicV1Decipher, QQMusicV2Decipher } from '~/decrypt-worker/decipher/QQMusic.ts';
|
import { QQMusicV1Decipher, QQMusicV2Decipher } from '~/decrypt-worker/decipher/QQMusic.ts';
|
||||||
import { KuwoMusicDecipher } from '~/decrypt-worker/decipher/KuwoMusic.ts';
|
import { KuwoMusicDecipher } from '~/decrypt-worker/decipher/KuwoMusic.ts';
|
||||||
|
import { KugouMusicDecipher } from '~/decrypt-worker/decipher/KugouMusic.ts';
|
||||||
|
|
||||||
export enum Status {
|
export enum Status {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
@ -40,7 +41,7 @@ export const allCryptoFactories: DecipherFactory[] = [
|
|||||||
NetEaseCloudMusicDecipher.make,
|
NetEaseCloudMusicDecipher.make,
|
||||||
|
|
||||||
// KGM (*.kgm, *.vpr)
|
// KGM (*.kgm, *.vpr)
|
||||||
// KGMCrypto.make,
|
KugouMusicDecipher.make,
|
||||||
|
|
||||||
// KWMv1 (*.kwm)
|
// KWMv1 (*.kwm)
|
||||||
KuwoMusicDecipher.make,
|
KuwoMusicDecipher.make,
|
||||||
|
36
src/decrypt-worker/decipher/KugouMusic.ts
Normal file
36
src/decrypt-worker/decipher/KugouMusic.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers';
|
||||||
|
import { KuGouDecipher, KuGouHeader } from '@unlock-music/crypto';
|
||||||
|
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
||||||
|
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
|
||||||
|
|
||||||
|
export class KugouMusicDecipher implements DecipherInstance {
|
||||||
|
cipherName = 'Kugou';
|
||||||
|
|
||||||
|
async decrypt(buffer: Uint8Array, _options: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
|
||||||
|
let kgm: KuGouDecipher | undefined;
|
||||||
|
let header: KuGouHeader | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
header = KuGouHeader.parse(buffer.subarray(0, 0x400));
|
||||||
|
kgm = new KuGouDecipher(header);
|
||||||
|
|
||||||
|
const audioBuffer = new Uint8Array(buffer.subarray(0x400));
|
||||||
|
for (const [block, offset] of chunkBuffer(audioBuffer)) {
|
||||||
|
kgm.decrypt(block, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: Status.OK,
|
||||||
|
cipherName: this.cipherName,
|
||||||
|
data: audioBuffer,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
kgm?.free();
|
||||||
|
header?.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static make() {
|
||||||
|
return new KugouMusicDecipher();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers';
|
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers';
|
||||||
import { KuwoHeader, KWMCipher } from '@unlock-music/crypto';
|
import { KuwoHeader, KWMDecipher } from '@unlock-music/crypto';
|
||||||
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
||||||
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
|
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
|
||||||
|
|
||||||
@ -8,11 +8,11 @@ export class KuwoMusicDecipher implements DecipherInstance {
|
|||||||
|
|
||||||
async decrypt(buffer: Uint8Array, options: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
|
async decrypt(buffer: Uint8Array, options: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
|
||||||
let header: KuwoHeader | undefined;
|
let header: KuwoHeader | undefined;
|
||||||
let kwm: KWMCipher | undefined;
|
let kwm: KWMDecipher | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
header = KuwoHeader.parse(buffer.subarray(0, 0x400));
|
header = KuwoHeader.parse(buffer.subarray(0, 0x400));
|
||||||
kwm = header.makeCipher(options.kwm2key);
|
kwm = new KWMDecipher(header, options.kwm2key);
|
||||||
|
|
||||||
const audioBuffer = new Uint8Array(buffer.subarray(0x400));
|
const audioBuffer = new Uint8Array(buffer.subarray(0x400));
|
||||||
for (const [block, offset] of chunkBuffer(audioBuffer)) {
|
for (const [block, offset] of chunkBuffer(audioBuffer)) {
|
||||||
|
@ -19,7 +19,7 @@ class DecryptCommandHandler {
|
|||||||
this.label = `DecryptCommandHandler(${label})`;
|
this.label = `DecryptCommandHandler(${label})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
log<R>(label: string, fn: () => R): R {
|
log<R>(label: string, fn: () => Promise<R>): Promise<R> {
|
||||||
return timedLogger(`${this.label}: ${label}`, fn);
|
return timedLogger(`${this.label}: ${label}`, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class DecryptCommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async tryDecryptWith(decipher: DecipherInstance) {
|
async tryDecryptWith(decipher: DecipherInstance) {
|
||||||
const result = await this.log(`decrypt ${decipher.cipherName}`, async () =>
|
const result = await this.log(`try decrypt with ${decipher.cipherName}`, async () =>
|
||||||
decipher.decrypt(this.buffer, this.options),
|
decipher.decrypt(this.buffer, this.options),
|
||||||
);
|
);
|
||||||
switch (result.status) {
|
switch (result.status) {
|
||||||
@ -79,8 +79,9 @@ class DecryptCommandHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workerDecryptHandler = async ({ id, blobURI, options }: DecryptCommandPayload) => {
|
export const workerDecryptHandler = async ({ id: payloadId, blobURI, options }: DecryptCommandPayload) => {
|
||||||
await umCryptoReady;
|
await umCryptoReady;
|
||||||
|
const id = payloadId.replace('://', ':');
|
||||||
const label = `decrypt(${id})`;
|
const label = `decrypt(${id})`;
|
||||||
return withTimeGroupedLogs(label, async () => {
|
return withTimeGroupedLogs(label, async () => {
|
||||||
const buffer = await fetch(blobURI).then((r) => r.arrayBuffer());
|
const buffer = await fetch(blobURI).then((r) => r.arrayBuffer());
|
||||||
|
@ -1,26 +1,16 @@
|
|||||||
import { fetchParakeet, FooterParserState } from '@um/libparakeet';
|
import { fetchParakeet, FooterParserState } from '@um/libparakeet';
|
||||||
import type { FetchMusicExNamePayload } from '~/decrypt-worker/types';
|
import type { FetchMusicExNamePayload } from '~/decrypt-worker/types';
|
||||||
import { makeQMCv2FooterParser } from '~/decrypt-worker/util/qmc2KeyCrypto';
|
import { makeQMCv2FooterParser } from '~/decrypt-worker/util/qmc2KeyCrypto';
|
||||||
import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils';
|
import { timedLogger } from '~/util/logUtils.ts';
|
||||||
|
|
||||||
export const workerParseMusicExMediaName = async ({ id, blobURI }: FetchMusicExNamePayload) => {
|
export const workerParseMusicExMediaName = async ({ id, blobURI }: FetchMusicExNamePayload) => {
|
||||||
const label = `decrypt(${id})`;
|
const label = `qmcMusixEx(${id.replace('://', ':')})`;
|
||||||
return withTimeGroupedLogs(label, async () => {
|
return timedLogger(label, async () => {
|
||||||
const parakeet = await timedLogger(`${label}/init`, fetchParakeet);
|
const parakeet = await fetchParakeet();
|
||||||
const blob = await timedLogger(`${label}/fetch-src`, async () =>
|
const blob = await fetch(blobURI, { headers: { Range: 'bytes=-1024' } }).then((r) => r.blob());
|
||||||
fetch(blobURI, { headers: { Range: 'bytes=-1024' } }).then((r) => r.blob()),
|
const buffer = await blob.arrayBuffer();
|
||||||
);
|
|
||||||
|
|
||||||
const buffer = await timedLogger(`${label}/read-src`, async () => {
|
const parsed = makeQMCv2FooterParser(parakeet).parse(buffer.slice(-1024));
|
||||||
// Firefox: the range header does not work...?
|
|
||||||
const blobBuffer = await blob.arrayBuffer();
|
|
||||||
if (blobBuffer.byteLength > 1024) {
|
|
||||||
return blobBuffer.slice(-1024);
|
|
||||||
}
|
|
||||||
return blobBuffer;
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsed = makeQMCv2FooterParser(parakeet).parse(buffer);
|
|
||||||
if (parsed.state === FooterParserState.OK) {
|
if (parsed.state === FooterParserState.OK) {
|
||||||
return parsed.mediaName;
|
return parsed.mediaName;
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
function isPromise<T = unknown>(p: unknown): p is Promise<T> {
|
export async function wrapFunctionCall<R = unknown>(
|
||||||
return !!p && typeof p === 'object' && 'then' in p && 'catch' in p && 'finally' in p;
|
pre: () => void,
|
||||||
}
|
post: () => void,
|
||||||
|
fn: () => Promise<R>,
|
||||||
export function wrapFunctionCall<R = unknown>(pre: () => void, post: () => void, fn: () => R): R {
|
): Promise<R> {
|
||||||
pre();
|
pre();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = fn();
|
return await fn();
|
||||||
|
} finally {
|
||||||
if (isPromise(result)) {
|
|
||||||
result.finally(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
post();
|
post();
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { wrapFunctionCall } from './fnWrapper';
|
import { wrapFunctionCall } from './fnWrapper';
|
||||||
|
|
||||||
export function timedLogger<R = unknown>(label: string, fn: () => R): R {
|
export async function timedLogger<R = unknown>(label: string, fn: () => Promise<R>): Promise<R> {
|
||||||
if (import.meta.env.VITE_ENABLE_PERF_LOG !== '1') {
|
if (import.meta.env.VITE_ENABLE_PERF_LOG !== '1') {
|
||||||
return fn();
|
return fn();
|
||||||
} else {
|
} else {
|
||||||
@ -12,13 +12,13 @@ export function timedLogger<R = unknown>(label: string, fn: () => R): R {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withGroupedLogs<R = unknown>(label: string, fn: () => R): R {
|
export async function withGroupedLogs<R = unknown>(label: string, fn: () => Promise<R>): Promise<R> {
|
||||||
if (import.meta.env.VITE_ENABLE_PERF_LOG !== '1') {
|
if (import.meta.env.VITE_ENABLE_PERF_LOG !== '1') {
|
||||||
return fn();
|
return fn();
|
||||||
} else {
|
} else {
|
||||||
return wrapFunctionCall(
|
return wrapFunctionCall(
|
||||||
() => console.group(label),
|
() => console.group(label),
|
||||||
() => (console.groupEnd as (label: string) => void)(label),
|
() => console.groupEnd(),
|
||||||
() => timedLogger(`${label}/total`, fn),
|
() => timedLogger(`${label}/total`, fn),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user