def as_str(self) -> str: try: b = bitpacking.pack_value(self) b += bytes([single_byte_hash(b)]) return base64.b64encode(b).decode("utf-8") except ValueError as e: return "Unable to create Permalink: {}".format(e)
def as_str(self) -> str: cached_result = object.__getattribute__(self, "__cached_as_str") if cached_result is not None: return cached_result try: b = bitpacking.pack_value(self) b += bytes([single_byte_hash(b)]) result = base64.b64encode(b).decode("utf-8") object.__setattr__(self, "__cached_as_str", result) return result except ValueError as e: return "Unable to create Permalink: {}".format(e)
def from_bytes(cls, b: bytes) -> "Permalink": Permalink.validate_version(b) byte_hash = b[-1] new_bytes = [b[0]] new_bytes.extend(rotate_bytes(b[1:-1], byte_hash, byte_hash, inverse=True)) decoded = bytes(new_bytes) if single_byte_hash(decoded) != byte_hash: raise ValueError("Incorrect checksum") decoder = BitPackDecoder(decoded) return Permalink.bit_pack_unpack(decoder, {})
def from_str(cls, param: str) -> "Permalink": try: b = base64.b64decode(param.encode("utf-8"), validate=True) if len(b) < 2: raise ValueError("Data too small") decoder = BitPackDecoder(b) Permalink.validate_version(decoder) checksum = single_byte_hash(b[:-1]) if checksum != b[-1]: raise ValueError("Incorrect checksum") return Permalink.bit_pack_unpack(decoder) except binascii.Error as e: raise ValueError("Unable to base64 decode: {}".format(e))
def as_bytes(self) -> bytes: cached_result = object.__getattribute__(self, "__cached_as_bytes") if cached_result is not None: return cached_result encoded = bitpacking.pack_value(self) # Add extra bytes so the base64 encoding never uses == at the end encoded += b"\x00" * (3 - (len(encoded) + 1) % 3) # Rotate bytes, so the slightest change causes a cascading effect. # But skip the first byte so the version check can be done early in decoding. byte_hash = single_byte_hash(encoded) new_bytes = [encoded[0]] new_bytes.extend(rotate_bytes(encoded[1:], byte_hash, byte_hash, inverse=False)) # Append the hash, so the rotation can be reversed and the checksum verified new_bytes.append(byte_hash) result = bytes(new_bytes) object.__setattr__(self, "__cached_as_bytes", result) return result
def _dictionary_byte_hash(data: dict) -> int: return single_byte_hash(json.dumps(data, separators=(',', ':')).encode("UTF-8"))
inverse)) PermalinkBinary = construct.FocusedSeq( "fields", schema_version=construct.Const(_CURRENT_SCHEMA_VERSION, construct.Byte), fields=construct.RawCopy( construct.Aligned( 3, construct.Struct( header=construct.BitStruct( has_seed_hash=construct.Rebuild( construct.Flag, construct.this._.seed_hash != None), bytes_rotation=construct.Rebuild( construct.BitsInteger(7), lambda ctx: single_byte_hash(ctx._.generator_params) >> 1, )), seed_hash=construct.If(construct.this.header.has_seed_hash, construct.Bytes(5)), randovania_version=construct.Bytes(4), # short git hash generator_params=construct.ExprAdapter( construct.Prefixed(construct.VarInt, construct.GreedyBytes), # parsing decoder=create_rotator(inverse=True), # building encoder=create_rotator(inverse=False), ), ))), permalink_checksum=construct.Checksum(
def _game_db_hash(game: RandovaniaGame) -> int: data = default_data.read_json_then_binary(game)[1] return bitpacking.single_byte_hash(json.dumps(data, separators=(',', ':')).encode("UTF-8"))