Source code for livemaker.scramble

# -*- coding: utf-8
#
# Copyright (C) 2019 Peter Rowlands <peter@pmrowla.com>
# Copyright (C) 2014 tinfoil <https://bitbucket.org/tinfoil/>
#
# This file is a part of pylivemaker.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""LiveMaker scramble (encryption) module."""

import math
import struct

import numpy as np


# constant XOR key for LM3
LIVEMAKER3_SCRAMBLE_KEY = 0xF8EA


[docs]class LMScramble(object): """PRNG used for LM TScramble encryption. RE'd from TScrambleInts, TGetRandom classes in LM3 code. """ # constants for LM3 FACTORS = ( 0x7DD4FFC7, 0x000005D4, 0x000006F0, 0x000013FB, ) def __init__(self, seed=0): if seed == 0: seed = 0xFFFFFFFF seed = np.uint32(seed & 0xFFFFFFFF) self.seed = seed self.state = [] for i in range(5): seed ^= seed << np.uint32(13) & 0xFFFFFFFF seed ^= seed >> np.uint32(17) & 0xFFFFFFFF seed ^= seed << np.uint32(5) & 0xFFFFFFFF self.state.append(np.uint32(seed)) for i in range(19): self.rand()
[docs] def rand(self): """Return a random integer in the range [0, uint32_max).""" x = np.sum( [ np.multiply(np.uint64(self.state[3]), self.FACTORS[0], dtype=np.uint64), np.multiply(self.state[2], self.FACTORS[1], dtype=np.uint64), np.multiply(self.state[1], self.FACTORS[2], dtype=np.uint64), np.multiply(self.state[0], self.FACTORS[3], dtype=np.uint64), self.state[4], ], dtype=np.uint64, ) self.state[4] = np.uint32((x >> np.uint64(32)) & np.uint64(0xFFFFFFFF)) self.state[3] = self.state[2] self.state[2] = self.state[1] self.state[1] = self.state[0] self.state[0] = np.uint32(x & np.uint64(0xFFFFFFFF)) return self.state[0]
[docs] def random(self): """Return a random float in the range [0.0, 1.0).""" return self.rand() / 0x100000000
[docs] def randint(self, low, high): """Return a random integer in the range [low, high].""" if low > high: raise ValueError("invalid range") return low + int(self.random() * (high - low + 1))
[docs] @classmethod def randseq(self, count, seed): """Generate a random sequence of the specified length.""" scramble = LMScramble(seed) values = list(range(count)) seq = [0] * count i = 0 while values: if len(values) == 1: n = 0 else: n = scramble.randint(0, len(values) - 2) seq[values.pop(n)] = i i += 1 return seq
[docs]def decrypt(data): """Unscramble the specified data stream and return the result.""" if len(data) < 8: return data chunk_size, seed = struct.unpack("<iI", data[:8]) total_chunks = math.ceil((len(data) - 8) / chunk_size) chunks = [] for i in LMScramble.randseq(total_chunks, seed ^ LIVEMAKER3_SCRAMBLE_KEY): offset = 8 + i * chunk_size chunk = data[offset : offset + chunk_size] chunks.append(chunk) out = b"".join(chunks) if len(out) != len(data) - 8: raise ValueError("data mismatch") return out