feat(QMCv2): add map cipher

(cherry picked from commit 7306bf031f8bc07168197c00e332bf89c8d611dd)
This commit is contained in:
MengYX 2021-12-16 23:07:59 +08:00
parent d5ac9ad56e
commit 183ac63864
No known key found for this signature in database
GPG Key ID: E63F9C7303E8F604
12 changed files with 74 additions and 3 deletions

View File

@ -1,4 +1,5 @@
import {QmcStaticCipher} from "@/decrypt/qmc_cipher"; import {QmcMapCipher, QmcStaticCipher} from "@/decrypt/qmc_cipher";
import fs from 'fs'
test("static cipher [0x7ff8,0x8000) ", () => { test("static cipher [0x7ff8,0x8000) ", () => {
const expected = new Uint8Array([ const expected = new Uint8Array([
@ -25,3 +26,41 @@ test("static cipher [0,0x10) ", () => {
expect(buf).toStrictEqual(expected) expect(buf).toStrictEqual(expected)
}) })
function loadTestDataMapCipher(name: string): {
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`)
}
}
test("map cipher: get mask", () => {
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 c = new QmcMapCipher(key)
c.decrypt(buf, 0)
expect(buf).toStrictEqual(expected)
})
test("map cipher: real file", async () => {
const cases = ["mflac_map", "mgg_map"]
for (const name of cases) {
const {key, clearText, cipherText} = loadTestDataMapCipher(name)
const c = new QmcMapCipher(key)
c.decrypt(cipherText, 0)
expect(cipherText).toStrictEqual(clearText)
}
})

View File

@ -33,11 +33,11 @@ const staticCipherBox = new Uint8Array([
0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, //0xF8 0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, //0xF8
]) ])
interface streamCipher { export interface StreamCipher {
decrypt(buf: Uint8Array, offset: number): void decrypt(buf: Uint8Array, offset: number): void
} }
export class QmcStaticCipher implements streamCipher { export class QmcStaticCipher implements StreamCipher {
public getMask(offset: number) { public getMask(offset: number) {
if (offset > 0x7FFF) offset %= 0x7FFF if (offset > 0x7FFF) offset %= 0x7FFF
@ -51,3 +51,35 @@ export class QmcStaticCipher implements streamCipher {
} }
} }
export class QmcMapCipher implements StreamCipher {
key: Uint8Array
n: number
constructor(key: Uint8Array) {
if (key.length == 0) throw Error("qmc/cipher_map: invalid key size")
this.key = key
this.n = key.length
}
private static rotate(value: number, bits: number) {
let rotate = (bits + 4) % 8;
let left = value << rotate;
let right = value >> rotate;
return (left | right) & 0xff;
}
decrypt(buf: Uint8Array, offset: number): void {
for (let i = 0; i < buf.length; i++) {
buf[i] ^= this.getMask(offset + i)
}
}
private getMask(offset: number) {
if (offset > 0x7fff) offset %= 0x7fff;
const idx = (offset * offset + 71214) % this.n;
return QmcMapCipher.rotate(this.key[idx], idx & 0x7)
}
}

0
testdata/mflac_map_key.bin vendored Normal file
View File

0
testdata/mflac_map_key_raw.bin vendored Normal file
View File

BIN
testdata/mflac_map_raw.bin vendored Normal file

Binary file not shown.

BIN
testdata/mflac_map_suffix.bin vendored Normal file

Binary file not shown.

BIN
testdata/mflac_map_target.bin vendored Normal file

Binary file not shown.

0
testdata/mgg_map_key.bin vendored Normal file
View File

0
testdata/mgg_map_key_raw.bin vendored Normal file
View File

BIN
testdata/mgg_map_raw.bin vendored Normal file

Binary file not shown.

BIN
testdata/mgg_map_suffix.bin vendored Normal file

Binary file not shown.

BIN
testdata/mgg_map_target.bin vendored Normal file

Binary file not shown.