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