|
|
@ -7,6 +7,7 @@ import { |
|
|
|
SniffAudioExt |
|
|
|
} from "@/decrypt/utils.ts"; |
|
|
|
import {parseBlob as metaParseBlob} from "music-metadata-browser"; |
|
|
|
import {DecryptResult} from "@/decrypt/entity"; |
|
|
|
|
|
|
|
const VprHeader = [ |
|
|
|
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, |
|
|
@ -14,27 +15,22 @@ const VprHeader = [ |
|
|
|
const KgmHeader = [ |
|
|
|
0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B, |
|
|
|
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14] |
|
|
|
const VprMaskDiff = [0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E, |
|
|
|
const VprMaskDiff = [ |
|
|
|
0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E, |
|
|
|
0x2F, 0x80, 0xF3, 0x2D, 0xB8, 0xB6, 0xE3, 0x11, |
|
|
|
0x00] |
|
|
|
|
|
|
|
export async function Decrypt(file, raw_filename, raw_ext) { |
|
|
|
try { |
|
|
|
if (window.location.protocol === "file:") { |
|
|
|
return { |
|
|
|
status: false, |
|
|
|
message: "请使用<a target='_blank' href='https://github.com/ix64/unlock-music/wiki/其他音乐格式工具'>CLI版本</a>进行解锁" |
|
|
|
} |
|
|
|
} |
|
|
|
} catch { |
|
|
|
|
|
|
|
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> { |
|
|
|
if (window?.location?.protocol === "file:") { |
|
|
|
throw Error("请使用 <a target='_blank' href='https://github.com/unlock-music/cli'>CLI版本</a> 进行解锁") |
|
|
|
} |
|
|
|
|
|
|
|
const oriData = new Uint8Array(await GetArrayBuffer(file)); |
|
|
|
if (raw_ext === "vpr") { |
|
|
|
if (!BytesHasPrefix(oriData, VprHeader)) |
|
|
|
return {status: false, message: "Not a valid vpr file!"} |
|
|
|
if (!BytesHasPrefix(oriData, VprHeader)) throw Error("Not a valid vpr file!") |
|
|
|
} else { |
|
|
|
if (!BytesHasPrefix(oriData, KgmHeader)) |
|
|
|
return {status: false, message: "Not a valid kgm/kgma file!"} |
|
|
|
if (!BytesHasPrefix(oriData, KgmHeader)) throw Error("Not a valid kgm(a) file!") |
|
|
|
} |
|
|
|
let bHeaderLen = new DataView(oriData.slice(0x10, 0x14).buffer) |
|
|
|
let headerLen = bHeaderLen.getUint32(0, true) |
|
|
@ -42,18 +38,13 @@ export async function Decrypt(file, raw_filename, raw_ext) { |
|
|
|
let audioData = oriData.slice(headerLen) |
|
|
|
let dataLen = audioData.length |
|
|
|
if (audioData.byteLength > 1 << 26) { |
|
|
|
return { |
|
|
|
status: false, |
|
|
|
message: "文件过大,请使用<a target='_blank' href='https://github.com/ix64/unlock-music/wiki/其他音乐格式工具'>CLI版本</a>进行解锁" |
|
|
|
} |
|
|
|
throw Error("文件过大,请使用 <a target='_blank' href='https://github.com/unlock-music/cli'>CLI版本</a> 进行解锁") |
|
|
|
} |
|
|
|
|
|
|
|
let key1 = new Uint8Array(17) |
|
|
|
key1.set(oriData.slice(0x1c, 0x2c), 0) |
|
|
|
if (MaskV2 == null) { |
|
|
|
if (!await LoadMaskV2()) { |
|
|
|
return {status: false, message: "加载Kgm/Vpr Mask数据失败"} |
|
|
|
} |
|
|
|
if (MaskV2.length === 0) { |
|
|
|
if (!await LoadMaskV2()) throw Error("加载Kgm/Vpr Mask数据失败") |
|
|
|
} |
|
|
|
|
|
|
|
for (let i = 0; i < dataLen; i++) { |
|
|
@ -85,17 +76,16 @@ export async function Decrypt(file, raw_filename, raw_ext) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function GetMask(pos) { |
|
|
|
|
|
|
|
function GetMask(pos: number) { |
|
|
|
return MaskV2PreDef[pos % 272] ^ MaskV2[pos >> 4] |
|
|
|
} |
|
|
|
|
|
|
|
let MaskV2 = null; |
|
|
|
let MaskV2: Uint8Array = new Uint8Array(0); |
|
|
|
|
|
|
|
async function LoadMaskV2() { |
|
|
|
async function LoadMaskV2(): Promise<boolean> { |
|
|
|
try { |
|
|
|
let resp = await fetch("./static/kgm.mask", { |
|
|
|
method: "GET" |
|
|
|
}) |
|
|
|
const resp = await fetch("./static/kgm.mask", {method: "GET"}) |
|
|
|
MaskV2 = new Uint8Array(await resp.arrayBuffer()); |
|
|
|
return true |
|
|
|
} catch (e) { |