mirror of
https://git.um-react.app/um/web.git
synced 2026-07-11 21:51:13 +00:00
feat(QMCv2): add map cipher
(cherry picked from commit 7306bf031f8bc07168197c00e332bf89c8d611dd)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
Vendored
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
Vendored
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Reference in New Issue
Block a user