feat: add xmly support
This commit is contained in:
parent
73bad51e8e
commit
e8b220b3df
@ -4,6 +4,7 @@ import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
||||
import { QQMusicV1Decipher, QQMusicV2Decipher } from '~/decrypt-worker/decipher/QQMusic.ts';
|
||||
import { KuwoMusicDecipher } from '~/decrypt-worker/decipher/KuwoMusic.ts';
|
||||
import { KugouMusicDecipher } from '~/decrypt-worker/decipher/KugouMusic.ts';
|
||||
import { XimalayaAndroidDecipher, XimalayaPCDecipher } from '~/decrypt-worker/decipher/Ximalaya.ts';
|
||||
|
||||
export enum Status {
|
||||
OK = 0,
|
||||
@ -46,6 +47,9 @@ export const allCryptoFactories: DecipherFactory[] = [
|
||||
// KWMv1 (*.kwm)
|
||||
KuwoMusicDecipher.make,
|
||||
|
||||
// Ximalaya PC (*.xm)
|
||||
XimalayaPCDecipher.make,
|
||||
|
||||
// Xiami (*.xm)
|
||||
// XiamiCrypto.make,
|
||||
|
||||
@ -67,8 +71,8 @@ export const allCryptoFactories: DecipherFactory[] = [
|
||||
QQMusicV1Decipher.create,
|
||||
|
||||
// Ximalaya (Android)
|
||||
// XimalayaAndroidCrypto.makeX2M,
|
||||
// XimalayaAndroidCrypto.makeX3M,
|
||||
XimalayaAndroidDecipher.makeX2M,
|
||||
XimalayaAndroidDecipher.makeX3M,
|
||||
|
||||
// QingTingFM (Android)
|
||||
// QingTingFM$Device.make,
|
||||
|
70
src/decrypt-worker/decipher/Ximalaya.ts
Normal file
70
src/decrypt-worker/decipher/Ximalaya.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { DecipherInstance, DecipherOK, DecipherResult, Status } from '~/decrypt-worker/Deciphers';
|
||||
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
|
||||
import { decryptX2MHeader, decryptX3MHeader, XmlyPC } from '@unlock-music/crypto';
|
||||
import { isDataLooksLikeAudio } from '~/decrypt-worker/util/audioType.ts';
|
||||
import { UnsupportedSourceFile } from '~/decrypt-worker/util/DecryptError.ts';
|
||||
|
||||
export class XimalayaAndroidDecipher implements DecipherInstance {
|
||||
cipherName: string;
|
||||
|
||||
constructor(
|
||||
private decipher: (buffer: Uint8Array) => void,
|
||||
private cipherType: string,
|
||||
) {
|
||||
this.cipherName = `Ximalaya (Android, ${cipherType})`;
|
||||
}
|
||||
|
||||
async decrypt(buffer: Uint8Array, _options: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
|
||||
// Detect with first 0x400 bytes
|
||||
const slice = buffer.slice(0, 0x400);
|
||||
this.decipher(slice);
|
||||
if (!isDataLooksLikeAudio(slice)) {
|
||||
throw new UnsupportedSourceFile(`Not a Xmly android file (${this.cipherType})`);
|
||||
}
|
||||
const result = new Uint8Array(buffer);
|
||||
result.set(slice, 0);
|
||||
return {
|
||||
cipherName: this.cipherName,
|
||||
status: Status.OK,
|
||||
data: result,
|
||||
};
|
||||
}
|
||||
|
||||
public static makeX2M() {
|
||||
return new XimalayaAndroidDecipher(decryptX2MHeader, 'X2M');
|
||||
}
|
||||
|
||||
public static makeX3M() {
|
||||
return new XimalayaAndroidDecipher(decryptX3MHeader, 'X3M');
|
||||
}
|
||||
}
|
||||
|
||||
export class XimalayaPCDecipher implements DecipherInstance {
|
||||
cipherName = 'Ximalaya (PC)';
|
||||
|
||||
async decrypt(buffer: Uint8Array, _options: DecryptCommandOptions): Promise<DecipherResult | DecipherOK> {
|
||||
// Detect with first 0x400 bytes
|
||||
const headerSize = XmlyPC.getHeaderSize(buffer.subarray(0, 1024));
|
||||
const xm = new XmlyPC(buffer.subarray(0, headerSize));
|
||||
const { audioHeader, encryptedHeaderOffset, encryptedHeaderSize } = xm;
|
||||
const plainAudioDataOffset = encryptedHeaderOffset + encryptedHeaderSize;
|
||||
const plainAudioDataLength = buffer.byteLength - plainAudioDataOffset;
|
||||
const encryptedAudioPart = buffer.slice(encryptedHeaderOffset, plainAudioDataOffset);
|
||||
const encryptedAudioPartLen = xm.decrypt(encryptedAudioPart);
|
||||
const audioSize = audioHeader.byteLength + encryptedAudioPartLen + plainAudioDataLength;
|
||||
|
||||
const result = new Uint8Array(audioSize);
|
||||
result.set(audioHeader);
|
||||
result.set(encryptedAudioPart, audioHeader.byteLength);
|
||||
result.set(buffer.subarray(plainAudioDataOffset), audioHeader.byteLength + encryptedAudioPartLen);
|
||||
return {
|
||||
status: Status.OK,
|
||||
data: result,
|
||||
cipherName: this.cipherName,
|
||||
};
|
||||
}
|
||||
|
||||
public static make() {
|
||||
return new XimalayaPCDecipher();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user