Compare commits

..

No commits in common. "4396f8876266d0f78b781f1863d8187776d22687" and "6f229ff39e49a8d11bddb07995e1c6c32a1f3e17" have entirely different histories.

15 changed files with 43 additions and 145 deletions

View File

@ -23,7 +23,7 @@
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.0.1",
"@unlock-music/crypto": "0.0.0-alpha.16", "@unlock-music/crypto": "0.0.0-alpha.15",
"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",

View File

@ -39,8 +39,8 @@ importers:
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1(react-redux@9.0.4(@types/react@18.2.45)(react@18.2.0)(redux@5.0.0))(react@18.2.0) version: 2.0.1(react-redux@9.0.4(@types/react@18.2.45)(react@18.2.0)(redux@5.0.0))(react@18.2.0)
'@unlock-music/crypto': '@unlock-music/crypto':
specifier: 0.0.0-alpha.16 specifier: 0.0.0-alpha.15
version: 0.0.0-alpha.16 version: 0.0.0-alpha.15
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)
@ -1921,8 +1921,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.16': '@unlock-music/crypto@0.0.0-alpha.15':
resolution: {integrity: sha512-QDhDWTsZOOkSQ64d3+pBwPCfesit6MiO63SzV7TQB8rQ/lPb78MMz6C4ECIjP9ijCBXds4PT4CBXp8vJDifAEg==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.16/crypto-0.0.0-alpha.16.tgz} resolution: {integrity: sha512-ST3Vbv5ITWE2n8W+07DiPUSpNy2qsK6nELsKcAeAXlulL/HirTfky5xpJl3Q6Zy/34crMsG8jKvP5QB6ELbsSw==, tarball: https://git.unlock-music.dev/api/packages/um/npm/%40unlock-music%2Fcrypto/-/0.0.0-alpha.15/crypto-0.0.0-alpha.15.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==}
@ -6110,7 +6110,7 @@ snapshots:
'@ungap/structured-clone@1.2.0': {} '@ungap/structured-clone@1.2.0': {}
'@unlock-music/crypto@0.0.0-alpha.16': {} '@unlock-music/crypto@0.0.0-alpha.15': {}
'@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:

View File

@ -6,8 +6,6 @@ import { KuwoMusicDecipher } from '~/decrypt-worker/decipher/KuwoMusic.ts';
import { KugouMusicDecipher } from '~/decrypt-worker/decipher/KugouMusic.ts'; import { KugouMusicDecipher } from '~/decrypt-worker/decipher/KugouMusic.ts';
import { XimalayaAndroidDecipher, XimalayaPCDecipher } from '~/decrypt-worker/decipher/Ximalaya.ts'; import { XimalayaAndroidDecipher, XimalayaPCDecipher } from '~/decrypt-worker/decipher/Ximalaya.ts';
import { XiamiDecipher } from '~/decrypt-worker/decipher/XiamiMusic.ts'; import { XiamiDecipher } from '~/decrypt-worker/decipher/XiamiMusic.ts';
import { QignTingFMDecipher } from '~/decrypt-worker/decipher/QingTingFM.ts';
import { Migu3DKeylessDecipher } from '~/decrypt-worker/decipher/Migu3d.ts';
export enum Status { export enum Status {
OK = 0, OK = 0,
@ -56,9 +54,6 @@ export const allCryptoFactories: DecipherFactory[] = [
// Xiami (*.xm) // Xiami (*.xm)
XiamiDecipher.make, XiamiDecipher.make,
// QingTingFM Android (*.qta)
QignTingFMDecipher.make,
/// File with a fixed footer goes second /// File with a fixed footer goes second
// QMCv2 (*.mflac) // QMCv2 (*.mflac)
@ -68,7 +63,7 @@ export const allCryptoFactories: DecipherFactory[] = [
/// File without an obvious header or footer goes last. /// File without an obvious header or footer goes last.
// Migu3D/Keyless (*.wav; *.m4a) // Migu3D/Keyless (*.wav; *.m4a)
Migu3DKeylessDecipher.make, // MiguCrypto.make,
// Crypto that does not implement "checkBySignature" or need to decrypt the entire file and then check audio type, // Crypto that does not implement "checkBySignature" or need to decrypt the entire file and then check audio type,
// should be moved to the bottom of the list for performance reasons. // should be moved to the bottom of the list for performance reasons.
@ -80,6 +75,9 @@ export const allCryptoFactories: DecipherFactory[] = [
XimalayaAndroidDecipher.makeX2M, XimalayaAndroidDecipher.makeX2M,
XimalayaAndroidDecipher.makeX3M, XimalayaAndroidDecipher.makeX3M,
// QingTingFM (Android)
// QingTingFM$Device.make,
// Transparent crypto (not encrypted) // Transparent crypto (not encrypted)
TransparentDecipher.make, TransparentDecipher.make,
]; ];

View File

@ -1,7 +1,6 @@
export enum DECRYPTION_WORKER_ACTION_NAME { export enum DECRYPTION_WORKER_ACTION_NAME {
DECRYPT = 'DECRYPT', DECRYPT = 'DECRYPT',
FIND_QMC_MUSICEX_NAME = 'FIND_QMC_MUSICEX_NAME', FIND_QMC_MUSICEX_NAME = 'FIND_QMC_MUSICEX_NAME',
QINGTING_FM_GET_DEVICE_KEY = 'QINGTING_FM_GET_DEVICE_KEY',
VERSION = 'VERSION', VERSION = 'VERSION',
} }

View File

@ -1,27 +0,0 @@
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers.ts';
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
import { Migu3D } from '@unlock-music/crypto';
export class Migu3DKeylessDecipher implements DecipherInstance {
cipherName = 'Migu3D (Keyless)';
async decrypt(buffer: Uint8Array): Promise<DecipherResult | DecipherOK> {
const mg3d = Migu3D.fromHeader(buffer.subarray(0, 0x100));
const audioBuffer = new Uint8Array(buffer);
for (const [block, i] of chunkBuffer(audioBuffer)) {
mg3d.decrypt(block, i);
}
mg3d.free();
return {
cipherName: this.cipherName,
status: Status.OK,
data: audioBuffer,
};
}
public static make() {
return new Migu3DKeylessDecipher();
}
}

View File

@ -1,37 +0,0 @@
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers.ts';
import { QingTingFM } from '@unlock-music/crypto';
import { chunkBuffer } from '~/decrypt-worker/util/buffer.ts';
import { unhex } from '~/util/hex.ts';
export class QignTingFMDecipher implements DecipherInstance {
cipherName = 'QingTingFM (Android, qta)';
async decrypt(buffer: Uint8Array, opts: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
const key = unhex(opts.qingTingAndroidKey || '');
const iv = QingTingFM.getFileIV(opts.fileName);
if (key.byteLength !== 16 || iv.byteLength !== 16) {
return {
status: Status.FAILED,
message: 'device key or iv invalid',
};
}
const qtfm = new QingTingFM(key, iv);
const audioBuffer = new Uint8Array(buffer);
for (const [block, i] of chunkBuffer(audioBuffer)) {
qtfm.decrypt(block, i);
}
return {
cipherName: this.cipherName,
status: Status.OK,
data: audioBuffer,
};
}
public static make() {
return new QignTingFMDecipher();
}
}

View File

@ -15,12 +15,3 @@ export interface FetchMusicExNamePayload {
id: string; id: string;
blobURI: string; blobURI: string;
} }
export interface GetQingTingFMDeviceKeyPayload {
product: string;
device: string;
manufacturer: string;
brand: string;
board: string;
model: string;
}

View File

@ -2,9 +2,8 @@ import { WorkerServerBus } from '~/util/WorkerEventBus';
import { DECRYPTION_WORKER_ACTION_NAME } from './constants'; import { DECRYPTION_WORKER_ACTION_NAME } from './constants';
import { getUmcVersion } from '@unlock-music/crypto'; import { getUmcVersion } from '@unlock-music/crypto';
import { workerDecryptHandler } from './worker/decrypt.ts'; import { workerDecryptHandler } from './worker/handler/decrypt';
import { workerParseMusicExMediaName } from './worker/qmcv2_parser.ts'; import { workerParseMusicExMediaName } from './worker/handler/qmcv2_parser';
import { workerGetQtfmDeviceKey } from '~/decrypt-worker/worker/qtfm_device_key.ts';
const bus = new WorkerServerBus(); const bus = new WorkerServerBus();
onmessage = bus.onmessage; onmessage = bus.onmessage;
@ -12,4 +11,3 @@ onmessage = bus.onmessage;
bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.DECRYPT, workerDecryptHandler); bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.DECRYPT, workerDecryptHandler);
bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.FIND_QMC_MUSICEX_NAME, workerParseMusicExMediaName); bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.FIND_QMC_MUSICEX_NAME, workerParseMusicExMediaName);
bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.VERSION, getUmcVersion); bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.VERSION, getUmcVersion);
bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.QINGTING_FM_GET_DEVICE_KEY, workerGetQtfmDeviceKey);

View File

@ -1,10 +1,10 @@
import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils.ts'; import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils';
import type { DecryptCommandOptions, DecryptCommandPayload } from '~/decrypt-worker/types.ts'; import type { DecryptCommandOptions, DecryptCommandPayload } from '~/decrypt-worker/types';
import { allCryptoFactories } from '../Deciphers.ts'; import { allCryptoFactories } from '../../Deciphers.ts';
import { toBlob } from '~/decrypt-worker/util/buffer.ts'; import { toBlob } from '~/decrypt-worker/util/buffer';
import { DecipherFactory, DecipherInstance, Status } from '~/decrypt-worker/Deciphers.ts'; import { DecipherFactory, DecipherInstance, Status } from '~/decrypt-worker/Deciphers.ts';
import { UnsupportedSourceFile } from '~/decrypt-worker/util/DecryptError.ts'; import { UnsupportedSourceFile } from '~/decrypt-worker/util/DecryptError';
import { ready as umCryptoReady } from '../../../../lib_um_crypto_rust/um_wasm_loader'; import { ready as umCryptoReady } from '@unlock-music/crypto';
import { go } from '~/util/go.ts'; import { go } from '~/util/go.ts';
import { detectAudioExtension } from '~/decrypt-worker/util/audioType.ts'; import { detectAudioExtension } from '~/decrypt-worker/util/audioType.ts';

View File

@ -1,4 +1,4 @@
import type { FetchMusicExNamePayload } from '~/decrypt-worker/types.ts'; import type { FetchMusicExNamePayload } from '~/decrypt-worker/types';
import { timedLogger } from '~/util/logUtils.ts'; import { timedLogger } from '~/util/logUtils.ts';
import { QMCFooter } from '@unlock-music/crypto'; import { QMCFooter } from '@unlock-music/crypto';

View File

@ -1,15 +0,0 @@
import { GetQingTingFMDeviceKeyPayload } from '~/decrypt-worker/types.ts';
import { QingTingFM } from '@unlock-music/crypto';
import { hex } from '~/util/hex.ts';
export async function workerGetQtfmDeviceKey({
device,
brand,
model,
product,
manufacturer,
board,
}: GetQingTingFMDeviceKeyPayload) {
const buffer = QingTingFM.getDeviceKey(device, brand, model, product, manufacturer, board);
return hex(buffer);
}

View File

@ -18,9 +18,6 @@ import { ChangeEvent, ClipboardEvent } from 'react';
import { VQuote } from '~/components/HelpText/VQuote'; import { VQuote } from '~/components/HelpText/VQuote';
import { selectStagingQtfmAndroidKey } from '../settingsSelector'; import { selectStagingQtfmAndroidKey } from '../settingsSelector';
import { qtfmAndroidUpdateKey } from '../settingsSlice'; import { qtfmAndroidUpdateKey } from '../settingsSlice';
import { workerClientBus } from '~/decrypt-worker/client.ts';
import { GetQingTingFMDeviceKeyPayload } from '~/decrypt-worker/types.ts';
import { DECRYPTION_WORKER_ACTION_NAME } from '~/decrypt-worker/constants.ts';
const QTFM_DEVICE_ID_URL = 'https://github.com/parakeet-rs/qtfm-device-id/releases/latest'; const QTFM_DEVICE_ID_URL = 'https://github.com/parakeet-rs/qtfm-device-id/releases/latest';
@ -40,23 +37,29 @@ export function PanelQingTing() {
return; return;
} }
const dataMap = Object.create(null); const dataMap = new Map();
for (const [, key, value] of plainText.matchAll(/^(PRODUCT|DEVICE|MANUFACTURER|BRAND|BOARD|MODEL): (.+)/gim)) { for (const [_unused, key, value] of plainText.matchAll(
dataMap[key.toLowerCase()] = value; /^(PRODUCT|DEVICE|MANUFACTURER|BRAND|BOARD|MODEL): (.+)/gim,
)) {
dataMap.set(key.toLowerCase(), value);
} }
const { product, device, manufacturer, brand, board, model } = dataMap;
if (product && device && manufacturer && brand && board && model) { const product = dataMap.get('product') ?? null;
const device = dataMap.get('device') ?? null;
const manufacturer = dataMap.get('manufacturer') ?? null;
const brand = dataMap.get('brand') ?? null;
const board = dataMap.get('board') ?? null;
const model = dataMap.get('model') ?? null;
if (
product !== null &&
device !== null &&
manufacturer !== null &&
brand !== null &&
board !== null &&
model !== null
) {
e.preventDefault(); e.preventDefault();
workerClientBus alert('TODO!');
.request<string, GetQingTingFMDeviceKeyPayload>(
DECRYPTION_WORKER_ACTION_NAME.QINGTING_FM_GET_DEVICE_KEY,
dataMap,
)
.then(setSecretKey)
.catch((err) => {
alert(`生成设备密钥时发生错误: ${err}`);
});
} }
}; };

View File

@ -1,5 +1,5 @@
import { bytesToUTF8String } from '~/decrypt-worker/util/utf8Encoder'; import { bytesToUTF8String } from '~/decrypt-worker/util/utf8Encoder';
import { formatHex } from './hex.ts'; import { formatHex } from './formatHex';
export class MMKVParser { export class MMKVParser {
private offset = 4; private offset = 4;

3
src/util/formatHex.ts Normal file
View File

@ -0,0 +1,3 @@
export function formatHex(value: number, len = 8) {
return '0x' + (value | 0).toString(16).padStart(len, '0');
}

View File

@ -1,15 +0,0 @@
export function formatHex(value: number, len = 8) {
return '0x' + (value | 0).toString(16).padStart(len, '0');
}
export function hex(value: Uint8Array): string {
return Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');
}
export function unhex(value: string): Uint8Array {
const bytes = [];
for (const [byte] of value.matchAll(/[0-9a-fA-F]{2}/g)) {
bytes.push(parseInt(byte, 16));
}
return new Uint8Array(bytes);
}