MengYX
3 years ago
34 changed files with 1656 additions and 1709 deletions
@ -1,26 +1,25 @@ |
|||
export interface DecryptResult { |
|||
title: string |
|||
album?: string |
|||
artist?: string |
|||
title: string; |
|||
album?: string; |
|||
artist?: string; |
|||
|
|||
mime: string |
|||
ext: string |
|||
mime: string; |
|||
ext: string; |
|||
|
|||
file: string |
|||
blob: Blob |
|||
picture?: string |
|||
|
|||
message?: string |
|||
rawExt?: string |
|||
rawFilename?: string |
|||
file: string; |
|||
blob: Blob; |
|||
picture?: string; |
|||
|
|||
message?: string; |
|||
rawExt?: string; |
|||
rawFilename?: string; |
|||
} |
|||
|
|||
export interface FileInfo { |
|||
status: string |
|||
name: string, |
|||
size: number, |
|||
percentage: number, |
|||
uid: number, |
|||
raw: File |
|||
status: string; |
|||
name: string; |
|||
size: number; |
|||
percentage: number; |
|||
uid: number; |
|||
raw: File; |
|||
} |
|||
|
@ -1,115 +1,117 @@ |
|||
import {QmcMapCipher, QmcRC4Cipher, QmcStaticCipher} from "@/decrypt/qmc_cipher"; |
|||
import fs from 'fs' |
|||
import { QmcMapCipher, QmcRC4Cipher, QmcStaticCipher } from '@/decrypt/qmc_cipher'; |
|||
import fs from 'fs'; |
|||
|
|||
test("static cipher [0x7ff8,0x8000) ", () => { |
|||
test('static cipher [0x7ff8,0x8000) ', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xD8, 0x52, 0xF7, 0x67, 0x90, 0xCA, 0xD6, 0x4A, |
|||
0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, 0xD8, |
|||
]) |
|||
|
|||
const c = new QmcStaticCipher() |
|||
const buf = new Uint8Array(16) |
|||
c.decrypt(buf, 0x7ff8) |
|||
const c = new QmcStaticCipher(); |
|||
const buf = new Uint8Array(16); |
|||
c.decrypt(buf, 0x7ff8); |
|||
|
|||
expect(buf).toStrictEqual(expected) |
|||
}) |
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
test("static cipher [0,0x10) ", () => { |
|||
test('static cipher [0,0x10) ', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xC3, 0x4A, 0xD6, 0xCA, 0x90, 0x67, 0xF7, 0x52, |
|||
0xD8, 0xA1, 0x66, 0x62, 0x9F, 0x5B, 0x09, 0x00, |
|||
]) |
|||
|
|||
const c = new QmcStaticCipher() |
|||
const buf = new Uint8Array(16) |
|||
c.decrypt(buf, 0) |
|||
const c = new QmcStaticCipher(); |
|||
const buf = new Uint8Array(16); |
|||
c.decrypt(buf, 0); |
|||
|
|||
expect(buf).toStrictEqual(expected) |
|||
}) |
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
|
|||
test("map cipher: get mask", () => { |
|||
test('map cipher: get mask', () => { |
|||
//prettier-ignore
|
|||
const expected = new Uint8Array([ |
|||
0xBB, 0x7D, 0x80, 0xBE, 0xFF, 0x38, 0x81, 0xFB, |
|||
0xBB, 0xFF, 0x82, 0x3C, 0xFF, 0xBA, 0x83, 0x79, |
|||
]) |
|||
const key = new Uint8Array(256) |
|||
for (let i = 0; i < 256; i++) key[i] = i |
|||
const buf = new Uint8Array(16) |
|||
const key = new Uint8Array(256); |
|||
for (let i = 0; i < 256; i++) key[i] = i; |
|||
const buf = new Uint8Array(16); |
|||
|
|||
const c = new QmcMapCipher(key) |
|||
c.decrypt(buf, 0) |
|||
expect(buf).toStrictEqual(expected) |
|||
}) |
|||
const c = new QmcMapCipher(key); |
|||
c.decrypt(buf, 0); |
|||
expect(buf).toStrictEqual(expected); |
|||
}); |
|||
|
|||
function loadTestDataCipher(name: string): { |
|||
key: Uint8Array, |
|||
cipherText: Uint8Array, |
|||
clearText: Uint8Array |
|||
key: Uint8Array; |
|||
cipherText: Uint8Array; |
|||
clearText: Uint8Array; |
|||
} { |
|||
return { |
|||
key: fs.readFileSync(`testdata/${name}_key.bin`), |
|||
cipherText: fs.readFileSync(`testdata/${name}_raw.bin`), |
|||
clearText: fs.readFileSync(`testdata/${name}_target.bin`) |
|||
} |
|||
clearText: fs.readFileSync(`testdata/${name}_target.bin`), |
|||
}; |
|||
} |
|||
|
|||
test("map cipher: real file", async () => { |
|||
const cases = ["mflac_map", "mgg_map"] |
|||
test('map cipher: real file', async () => { |
|||
const cases = ['mflac_map', 'mgg_map']; |
|||
for (const name of cases) { |
|||
const {key, clearText, cipherText} = loadTestDataCipher(name) |
|||
const c = new QmcMapCipher(key) |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcMapCipher(key); |
|||
|
|||
c.decrypt(cipherText, 0) |
|||
c.decrypt(cipherText, 0); |
|||
|
|||
expect(cipherText).toStrictEqual(clearText) |
|||
expect(cipherText).toStrictEqual(clearText); |
|||
} |
|||
}) |
|||
}); |
|||
|
|||
test("rc4 cipher: real file", async () => { |
|||
const cases = ["mflac0_rc4"] |
|||
test('rc4 cipher: real file', async () => { |
|||
const cases = ['mflac0_rc4']; |
|||
for (const name of cases) { |
|||
const {key, clearText, cipherText} = loadTestDataCipher(name) |
|||
const c = new QmcRC4Cipher(key) |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
c.decrypt(cipherText, 0) |
|||
c.decrypt(cipherText, 0); |
|||
|
|||
expect(cipherText).toStrictEqual(clearText) |
|||
expect(cipherText).toStrictEqual(clearText); |
|||
} |
|||
}) |
|||
}); |
|||
|
|||
test("rc4 cipher: first segment", async () => { |
|||
const cases = ["mflac0_rc4"] |
|||
test('rc4 cipher: first segment', async () => { |
|||
const cases = ['mflac0_rc4']; |
|||
for (const name of cases) { |
|||
const {key, clearText, cipherText} = loadTestDataCipher(name) |
|||
const c = new QmcRC4Cipher(key) |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(0, 128) |
|||
c.decrypt(buf, 0) |
|||
expect(buf).toStrictEqual(clearText.slice(0, 128)) |
|||
const buf = cipherText.slice(0, 128); |
|||
c.decrypt(buf, 0); |
|||
expect(buf).toStrictEqual(clearText.slice(0, 128)); |
|||
} |
|||
}) |
|||
}); |
|||
|
|||
test("rc4 cipher: align block (128~5120)", async () => { |
|||
const cases = ["mflac0_rc4"] |
|||
test('rc4 cipher: align block (128~5120)', async () => { |
|||
const cases = ['mflac0_rc4']; |
|||
for (const name of cases) { |
|||
const {key, clearText, cipherText} = loadTestDataCipher(name) |
|||
const c = new QmcRC4Cipher(key) |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(128, 5120) |
|||
c.decrypt(buf, 128) |
|||
expect(buf).toStrictEqual(clearText.slice(128, 5120)) |
|||
const buf = cipherText.slice(128, 5120); |
|||
c.decrypt(buf, 128); |
|||
expect(buf).toStrictEqual(clearText.slice(128, 5120)); |
|||
} |
|||
}) |
|||
}); |
|||
|
|||
test("rc4 cipher: simple block (5120~10240)", async () => { |
|||
const cases = ["mflac0_rc4"] |
|||
test('rc4 cipher: simple block (5120~10240)', async () => { |
|||
const cases = ['mflac0_rc4']; |
|||
for (const name of cases) { |
|||
const {key, clearText, cipherText} = loadTestDataCipher(name) |
|||
const c = new QmcRC4Cipher(key) |
|||
const { key, clearText, cipherText } = loadTestDataCipher(name); |
|||
const c = new QmcRC4Cipher(key); |
|||
|
|||
const buf = cipherText.slice(5120, 10240) |
|||
c.decrypt(buf, 5120) |
|||
expect(buf).toStrictEqual(clearText.slice(5120, 10240)) |
|||
const buf = cipherText.slice(5120, 10240); |
|||
c.decrypt(buf, 5120); |
|||
expect(buf).toStrictEqual(clearText.slice(5120, 10240)); |
|||
} |
|||
}) |
|||
}); |
|||
|
@ -1,30 +1,26 @@ |
|||
import {QmcDeriveKey, simpleMakeKey} from "@/decrypt/qmc_key"; |
|||
import fs from "fs"; |
|||
import { QmcDeriveKey, simpleMakeKey } from '@/decrypt/qmc_key'; |
|||
import fs from 'fs'; |
|||
|
|||
test("key dec: make simple key", () => { |
|||
expect( |
|||
simpleMakeKey(106, 8) |
|||
).toStrictEqual( |
|||
[0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b] |
|||
) |
|||
}) |
|||
test('key dec: make simple key', () => { |
|||
expect(simpleMakeKey(106, 8)).toStrictEqual([0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b]); |
|||
}); |
|||
|
|||
function loadTestDataKeyDecrypt(name: string): { |
|||
cipherText: Uint8Array, |
|||
clearText: Uint8Array |
|||
cipherText: Uint8Array; |
|||
clearText: Uint8Array; |
|||
} { |
|||
return { |
|||
cipherText: fs.readFileSync(`testdata/${name}_key_raw.bin`), |
|||
clearText: fs.readFileSync(`testdata/${name}_key.bin`) |
|||
} |
|||
clearText: fs.readFileSync(`testdata/${name}_key.bin`), |
|||
}; |
|||
} |
|||
|
|||
test("key dec: real file", async () => { |
|||
const cases = ["mflac_map", "mgg_map", "mflac0_rc4"] |
|||
test('key dec: real file', async () => { |
|||
const cases = ['mflac_map', 'mgg_map', 'mflac0_rc4']; |
|||
for (const name of cases) { |
|||
const {clearText, cipherText} = loadTestDataKeyDecrypt(name) |
|||
const buf = QmcDeriveKey(cipherText) |
|||
const { clearText, cipherText } = loadTestDataKeyDecrypt(name); |
|||
const buf = QmcDeriveKey(cipherText); |
|||
|
|||
expect(buf).toStrictEqual(clearText) |
|||
expect(buf).toStrictEqual(clearText); |
|||
} |
|||
}) |
|||
}); |
|||
|
@ -1,5 +1,2 @@ |
|||
const bs = chrome || browser |
|||
bs.tabs.create({ |
|||
url: bs.runtime.getURL('./index.html') |
|||
}, tab => console.log(tab)) |
|||
|
|||
const bs = chrome || browser; |
|||
bs.tabs.create({ url: bs.runtime.getURL('./index.html') }, (tab) => console.log(tab)); |
|||
|
@ -1,31 +1,30 @@ |
|||
/* eslint-disable no-console */ |
|||
|
|||
import {register} from 'register-service-worker' |
|||
|
|||
if (process.env.NODE_ENV === 'production' && window.location.protocol === "https:") { |
|||
import { register } from 'register-service-worker'; |
|||
|
|||
if (process.env.NODE_ENV === 'production' && window.location.protocol === 'https:') { |
|||
register(`${process.env.BASE_URL}service-worker.js`, { |
|||
ready() { |
|||
console.log('App is being served from cache by a service worker.') |
|||
console.log('App is being served from cache by a service worker.'); |
|||
}, |
|||
registered() { |
|||
console.log('Service worker has been registered.') |
|||
console.log('Service worker has been registered.'); |
|||
}, |
|||
cached() { |
|||
console.log('Content has been cached for offline use.') |
|||
console.log('Content has been cached for offline use.'); |
|||
}, |
|||
updatefound() { |
|||
console.log('New content is downloading.') |
|||
console.log('New content is downloading.'); |
|||
}, |
|||
updated() { |
|||
console.log('New content is available.'); |
|||
window.location.reload(); |
|||
}, |
|||
offline() { |
|||
console.log('No internet connection found. App is running in offline mode.') |
|||
console.log('No internet connection found. App is running in offline mode.'); |
|||
}, |
|||
error(error) { |
|||
console.error('Error during service worker registration:', error) |
|||
} |
|||
}) |
|||
console.error('Error during service worker registration:', error); |
|||
}, |
|||
}); |
|||
} |
|||
|
@ -1,17 +1,15 @@ |
|||
import Vue, {VNode} from 'vue' |
|||
import Vue, { VNode } from 'vue'; |
|||
|
|||
declare global { |
|||
namespace JSX { |
|||
// tslint:disable no-empty-interface
|
|||
interface Element extends VNode { |
|||
} |
|||
interface Element extends VNode {} |
|||
|
|||
// tslint:disable no-empty-interface
|
|||
interface ElementClass extends Vue { |
|||
} |
|||
interface ElementClass extends Vue {} |
|||
|
|||
interface IntrinsicElements { |
|||
[elem: string]: any |
|||
[elem: string]: any; |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,4 +1,4 @@ |
|||
declare module '*.vue' { |
|||
import Vue from 'vue' |
|||
export default Vue |
|||
import Vue from 'vue'; |
|||
export default Vue; |
|||
} |
|||
|
@ -1,56 +1,73 @@ |
|||
import {fromByteArray as Base64Encode} from "base64-js"; |
|||
import { fromByteArray as Base64Encode } from 'base64-js'; |
|||
|
|||
export const IXAREA_API_ENDPOINT = "https://um-api.ixarea.com" |
|||
export const IXAREA_API_ENDPOINT = 'https://um-api.ixarea.com'; |
|||
|
|||
export interface UpdateInfo { |
|||
Found: boolean |
|||
HttpsFound: boolean |
|||
Version: string |
|||
URL: string |
|||
Detail: string |
|||
Found: boolean; |
|||
HttpsFound: boolean; |
|||
Version: string; |
|||
URL: string; |
|||
Detail: string; |
|||
} |
|||
|
|||
export async function checkUpdate(version: string): Promise<UpdateInfo> { |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + "/music/app-version", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
body: JSON.stringify({"Version": version}) |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + '/music/app-version', { |
|||
method: 'POST', |
|||
headers: { 'Content-Type': 'application/json' }, |
|||
body: JSON.stringify({ Version: version }), |
|||
}); |
|||
return await resp.json(); |
|||
} |
|||
|
|||
export function reportKeyUsage(keyData: Uint8Array, maskData: number[], filename: string, format: string, title: string, artist?: string, album?: string) { |
|||
return fetch(IXAREA_API_ENDPOINT + "/qmcmask/usage", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
export function reportKeyUsage( |
|||
keyData: Uint8Array, |
|||
maskData: number[], |
|||
filename: string, |
|||
format: string, |
|||
title: string, |
|||
artist?: string, |
|||
album?: string, |
|||
) { |
|||
return fetch(IXAREA_API_ENDPOINT + '/qmcmask/usage', { |
|||
method: 'POST', |
|||
headers: { 'Content-Type': 'application/json' }, |
|||
body: JSON.stringify({ |
|||
Mask: Base64Encode(new Uint8Array(maskData)), Key: Base64Encode(keyData), |
|||
Artist: artist, Title: title, Album: album, Filename: filename, Format: format |
|||
Mask: Base64Encode(new Uint8Array(maskData)), |
|||
Key: Base64Encode(keyData), |
|||
Artist: artist, |
|||
Title: title, |
|||
Album: album, |
|||
Filename: filename, |
|||
Format: format, |
|||
}), |
|||
}) |
|||
}); |
|||
} |
|||
|
|||
interface KeyInfo { |
|||
Matrix44: string |
|||
Matrix44: string; |
|||
} |
|||
|
|||
export async function queryKeyInfo(keyData: Uint8Array, filename: string, format: string): Promise<KeyInfo> { |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + "/qmcmask/query", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
body: JSON.stringify({Format: format, Key: Base64Encode(keyData), Filename: filename, Type: 44}), |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + '/qmcmask/query', { |
|||
method: 'POST', |
|||
headers: { 'Content-Type': 'application/json' }, |
|||
body: JSON.stringify({ Format: format, Key: Base64Encode(keyData), Filename: filename, Type: 44 }), |
|||
}); |
|||
return await resp.json(); |
|||
} |
|||
|
|||
export interface CoverInfo { |
|||
Id: string |
|||
Type: number |
|||
Id: string; |
|||
Type: number; |
|||
} |
|||
|
|||
export async function queryAlbumCover(title: string, artist?: string, album?: string): Promise<CoverInfo> { |
|||
const endpoint = IXAREA_API_ENDPOINT + "/music/qq-cover" |
|||
const params = new URLSearchParams([["Title", title], ["Artist", artist ?? ""], ["Album", album ?? ""]]) |
|||
const resp = await fetch(`${endpoint}?${params.toString()}`) |
|||
return await resp.json() |
|||
const endpoint = IXAREA_API_ENDPOINT + '/music/qq-cover'; |
|||
const params = new URLSearchParams([ |
|||
['Title', title], |
|||
['Artist', artist ?? ''], |
|||
['Album', album ?? ''], |
|||
]); |
|||
const resp = await fetch(`${endpoint}?${params.toString()}`); |
|||
return await resp.json(); |
|||
} |
|||
|
@ -1,4 +1,4 @@ |
|||
import {expose} from "threads/worker"; |
|||
import {CommonDecrypt} from "@/decrypt/common"; |
|||
import { expose } from 'threads/worker'; |
|||
import { CommonDecrypt } from '@/decrypt/common'; |
|||
|
|||
expose(CommonDecrypt) |
|||
expose(CommonDecrypt); |
|||
|
Loading…
Reference in new issue