Browse Source

feat(QMCv2): add map cipher

(cherry picked from commit 7306bf031f8bc07168197c00e332bf89c8d611dd)
20230320
MengYX 3 years ago
parent
commit
183ac63864
No known key found for this signature in database GPG Key ID: E63F9C7303E8F604
  1. 41
      src/decrypt/qmc_cipher.test.ts
  2. 36
      src/decrypt/qmc_cipher.ts
  3. 0
      testdata/mflac_map_key.bin
  4. 0
      testdata/mflac_map_key_raw.bin
  5. BIN
      testdata/mflac_map_raw.bin
  6. BIN
      testdata/mflac_map_suffix.bin
  7. BIN
      testdata/mflac_map_target.bin
  8. 0
      testdata/mgg_map_key.bin
  9. 0
      testdata/mgg_map_key_raw.bin
  10. BIN
      testdata/mgg_map_raw.bin
  11. BIN
      testdata/mgg_map_suffix.bin
  12. BIN
      testdata/mgg_map_target.bin

41
src/decrypt/qmc_cipher.test.ts

@ -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) ", () => {
const expected = new Uint8Array([
@ -25,3 +26,41 @@ test("static cipher [0,0x10) ", () => {
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)
}
})

36
src/decrypt/qmc_cipher.ts

@ -33,11 +33,11 @@ const staticCipherBox = new Uint8Array([
0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, //0xF8
])
interface streamCipher {
export interface StreamCipher {
decrypt(buf: Uint8Array, offset: number): void
}
export class QmcStaticCipher implements streamCipher {
export class QmcStaticCipher implements StreamCipher {
public getMask(offset: number) {
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

0
testdata/mflac_map_key_raw.bin

BIN
testdata/mflac_map_raw.bin

Binary file not shown.

BIN
testdata/mflac_map_suffix.bin

Binary file not shown.

BIN
testdata/mflac_map_target.bin

Binary file not shown.

0
testdata/mgg_map_key.bin

0
testdata/mgg_map_key_raw.bin

BIN
testdata/mgg_map_raw.bin

Binary file not shown.

BIN
testdata/mgg_map_suffix.bin

Binary file not shown.

BIN
testdata/mgg_map_target.bin

Binary file not shown.
Loading…
Cancel
Save