diff --git a/src/decrypt/qmc_cipher.test.ts b/src/decrypt/qmc_cipher.test.ts index 1e40c60..a4d5513 100644 --- a/src/decrypt/qmc_cipher.test.ts +++ b/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) + } +}) diff --git a/src/decrypt/qmc_cipher.ts b/src/decrypt/qmc_cipher.ts index eef68a7..532625a 100644 --- a/src/decrypt/qmc_cipher.ts +++ b/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) + } + +} diff --git a/testdata/mflac_map_key.bin b/testdata/mflac_map_key.bin new file mode 100644 index 0000000..e69de29 diff --git a/testdata/mflac_map_key_raw.bin b/testdata/mflac_map_key_raw.bin new file mode 100644 index 0000000..e69de29 diff --git a/testdata/mflac_map_raw.bin b/testdata/mflac_map_raw.bin new file mode 100644 index 0000000..18f70e5 Binary files /dev/null and b/testdata/mflac_map_raw.bin differ diff --git a/testdata/mflac_map_suffix.bin b/testdata/mflac_map_suffix.bin new file mode 100644 index 0000000..f83e4d9 Binary files /dev/null and b/testdata/mflac_map_suffix.bin differ diff --git a/testdata/mflac_map_target.bin b/testdata/mflac_map_target.bin new file mode 100644 index 0000000..7919057 Binary files /dev/null and b/testdata/mflac_map_target.bin differ diff --git a/testdata/mgg_map_key.bin b/testdata/mgg_map_key.bin new file mode 100644 index 0000000..e69de29 diff --git a/testdata/mgg_map_key_raw.bin b/testdata/mgg_map_key_raw.bin new file mode 100644 index 0000000..e69de29 diff --git a/testdata/mgg_map_raw.bin b/testdata/mgg_map_raw.bin new file mode 100644 index 0000000..fa8704d Binary files /dev/null and b/testdata/mgg_map_raw.bin differ diff --git a/testdata/mgg_map_suffix.bin b/testdata/mgg_map_suffix.bin new file mode 100644 index 0000000..671d61e Binary files /dev/null and b/testdata/mgg_map_suffix.bin differ diff --git a/testdata/mgg_map_target.bin b/testdata/mgg_map_target.bin new file mode 100644 index 0000000..f318eb9 Binary files /dev/null and b/testdata/mgg_map_target.bin differ