forked from um/web
feat(QMCv2): add map cipher
(cherry picked from commit 7306bf031f8bc07168197c00e332bf89c8d611dd)
This commit is contained in:
parent
8c11f47aa4
commit
fe89710968
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -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
0
testdata/mflac_map_key.bin
vendored
Normal file
0
testdata/mflac_map_key_raw.bin
vendored
Normal file
0
testdata/mflac_map_key_raw.bin
vendored
Normal file
BIN
testdata/mflac_map_raw.bin
vendored
Normal file
BIN
testdata/mflac_map_raw.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/mflac_map_suffix.bin
vendored
Normal file
BIN
testdata/mflac_map_suffix.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/mflac_map_target.bin
vendored
Normal file
BIN
testdata/mflac_map_target.bin
vendored
Normal file
Binary file not shown.
0
testdata/mgg_map_key.bin
vendored
Normal file
0
testdata/mgg_map_key.bin
vendored
Normal file
0
testdata/mgg_map_key_raw.bin
vendored
Normal file
0
testdata/mgg_map_key_raw.bin
vendored
Normal file
BIN
testdata/mgg_map_raw.bin
vendored
Normal file
BIN
testdata/mgg_map_raw.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/mgg_map_suffix.bin
vendored
Normal file
BIN
testdata/mgg_map_suffix.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/mgg_map_target.bin
vendored
Normal file
BIN
testdata/mgg_map_target.bin
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user