mirror of
				https://git.unlock-music.dev/um/web.git
				synced 2025-11-04 23:13:30 +08:00 
			
		
		
		
	Add mg3d & Fix Bugs
This commit is contained in:
		
							parent
							
								
									a5de3829db
								
							
						
					
					
						commit
						fee35d4a95
					
				
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -20,3 +20,10 @@ yarn-error.log*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
 | 
			
		||||
/src/KgmWasm/build
 | 
			
		||||
/src/KgmWasm/build
 | 
			
		||||
/src/KgmWasm/*.js
 | 
			
		||||
/src/KgmWasm/*.wasm
 | 
			
		||||
/src/QmcWasm/*.js
 | 
			
		||||
/src/QmcWasm/*.wasm
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { Decrypt as Mg3dDecrypt } from '@/decrypt/mg3d';
 | 
			
		||||
import { Decrypt as NcmDecrypt } from '@/decrypt/ncm';
 | 
			
		||||
import { Decrypt as NcmCacheDecrypt } from '@/decrypt/ncmcache';
 | 
			
		||||
import { Decrypt as XmDecrypt } from '@/decrypt/xm';
 | 
			
		||||
@ -22,6 +23,9 @@ export async function Decrypt(file: FileInfo, config: Record<string, any>): Prom
 | 
			
		||||
  const raw = SplitFilename(file.name);
 | 
			
		||||
  let rt_data: DecryptResult;
 | 
			
		||||
  switch (raw.ext) {
 | 
			
		||||
    case 'mg3d': // Migu Wav
 | 
			
		||||
      rt_data = await Mg3dDecrypt(file.raw, raw.name);
 | 
			
		||||
      break;
 | 
			
		||||
    case 'ncm': // Netease Mp3/Flac
 | 
			
		||||
      rt_data = await NcmDecrypt(file.raw, raw.name, raw.ext);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,7 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string)
 | 
			
		||||
  const mime = AudioMimeType[ext];
 | 
			
		||||
  let musicBlob = new Blob([musicDecoded], { type: mime });
 | 
			
		||||
  const musicMeta = await metaParseBlob(musicBlob);
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, musicMeta.common.artists == undefined ? musicMeta.common.artist : musicMeta.common.artists.toString());
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, String(musicMeta.common.artists || musicMeta.common.artist || ""));
 | 
			
		||||
  return {
 | 
			
		||||
    album: musicMeta.common.album,
 | 
			
		||||
    picture: GetCoverFromFile(musicMeta),
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ export async function Decrypt(file: File, raw_filename: string, _: string): Prom
 | 
			
		||||
  let musicBlob = new Blob([audioData], { type: mime });
 | 
			
		||||
 | 
			
		||||
  const musicMeta = await metaParseBlob(musicBlob);
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, musicMeta.common.artists == undefined ? musicMeta.common.artist : musicMeta.common.artists.toString());
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, String(musicMeta.common.artists || musicMeta.common.artist || ""));
 | 
			
		||||
  return {
 | 
			
		||||
    album: musicMeta.common.album,
 | 
			
		||||
    picture: GetCoverFromFile(musicMeta),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										71
									
								
								src/decrypt/mg3d.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/decrypt/mg3d.ts
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
			
		||||
}
 | 
			
		||||
@ -13,7 +13,7 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string)
 | 
			
		||||
  const ext = SniffAudioExt(buffer, raw_ext);
 | 
			
		||||
  if (ext !== raw_ext) file = new Blob([buffer], { type: AudioMimeType[ext] });
 | 
			
		||||
  const tag = await metaParseBlob(file);
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artists == undefined ? tag.common.artist : tag.common.artists.toString());
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ""));
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    title,
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string)
 | 
			
		||||
    throw '不支持的QQ音乐缓存格式';
 | 
			
		||||
  }
 | 
			
		||||
  const tag = await metaParseBlob(audioBlob);
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist));
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ""));
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    title,
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ export async function Decrypt(
 | 
			
		||||
    if (ext !== raw_ext) file = new Blob([buffer], { type: AudioMimeType[ext] });
 | 
			
		||||
  }
 | 
			
		||||
  const tag = await metaParseBlob(file);
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist));
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ''));
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    title,
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,7 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string)
 | 
			
		||||
  const { title, artist } = GetMetaFromFile(
 | 
			
		||||
    raw_filename,
 | 
			
		||||
    musicMeta.common.title,
 | 
			
		||||
    String(musicMeta.common.artists || musicMeta.common.artist),
 | 
			
		||||
    String(musicMeta.common.artists || musicMeta.common.artist || ""),
 | 
			
		||||
    raw_filename.indexOf('_') === -1 ? '-' : '_',
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,8 @@ interface MetaResult {
 | 
			
		||||
  blob: Blob;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fromGBK = (text?: string) => iconv.decode(new Buffer(text || ''), 'gbk');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param musicBlob 音乐文件(解密后)
 | 
			
		||||
@ -41,14 +43,13 @@ export async function extractQQMusicMeta(
 | 
			
		||||
      console.warn('try using gbk encoding to decode meta');
 | 
			
		||||
      musicMeta.common.artist = '';
 | 
			
		||||
      if (!musicMeta.common.artists) {
 | 
			
		||||
        musicMeta.common.artist = iconv.decode(new Buffer(musicMeta.common.artist ?? ''), 'gbk');
 | 
			
		||||
        musicMeta.common.artist = fromGBK(musicMeta.common.artist);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        musicMeta.common.artists.forEach((artist) => artist = iconv.decode(new Buffer(artist ?? ''), 'gbk'));
 | 
			
		||||
        musicMeta.common.artist = musicMeta.common.artists.toString();
 | 
			
		||||
        musicMeta.common.artist = musicMeta.common.artists.map(fromGBK).join();
 | 
			
		||||
      }
 | 
			
		||||
      musicMeta.common.title = iconv.decode(new Buffer(musicMeta.common.title ?? ''), 'gbk');
 | 
			
		||||
      musicMeta.common.album = iconv.decode(new Buffer(musicMeta.common.album ?? ''), 'gbk');
 | 
			
		||||
      musicMeta.common.title = fromGBK(musicMeta.common.title);
 | 
			
		||||
      musicMeta.common.album = fromGBK(musicMeta.common.album);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user