1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
import time import base64 import json from base64 import b64decode import os
import UnityPy from UnityPy.files.File import ObjectReader from Crypto.Util.strxor import strxor from xxhash import xxh32_intdigest
def calculate_hash(name: bytes | str) -> int: """Calculate a 32-bit hash using xxhash with UTF-8 encoding if needed.""" if isinstance(name, str): name = name.encode("utf8") return xxh32_intdigest(name)
def create_key(name: str, size: int = 8) -> bytes: """Create a random key based on a hashed name and a specific size.""" seed = calculate_hash(name) return MersenneTwister(seed).next_bytes(size)
def xor(value: bytes, key: bytes) -> bytes: """XOR operation between two byte arrays.""" if len(value) == len(key): return strxor(value, key) if len(value) < len(key): return strxor(value, key[: len(value)]) return b"".join( strxor(value[i: i + len(key)], key) for i in range(0, len(value) - len(key) + 1, len(key)) ) + strxor( value[len(value) - len(value) % len(key):], key[: len(value) % len(key)] )
def convert_string(value: bytes | str, key: bytes = b"") -> str: """Decrypt or decode a base64 string or raw bytes, depending on the input.""" if not value: return ""
try: raw = b64decode(value) if decoded := xor(raw, key).decode("utf16"): return decoded raise UnicodeError except: if isinstance(value, bytes): return value.decode("utf8")
return ""
class MersenneTwister: N = 624 M = 397 MATRIX_A = 0x9908B0DF UPPER_MASK = 0x80000000 LOWER_MASK = 0x7FFFFFFF
def __init__(self, seed: int | None = None) -> None: if seed is None: seed = int(time.time() * 1000) self.mt = [0] * self.N self.mti = self.N + 1 self.init_genrand(seed)
def init_genrand(self, seed: int) -> None: """Initializes the generator with a seed.""" self.mt[0] = seed & 0xFFFFFFFF for i in range(1, self.N): self.mt[i] = ( 1812433253 * (self.mt[i - 1] ^ (self.mt[i - 1] >> 30)) + i ) & 0xFFFFFFFF self.mti = self.N
def _generate_numbers(self) -> None: """Generates N words at a time.""" for i in range(self.N - self.M): y = (self.mt[i] & self.UPPER_MASK) | (self.mt[i + 1] & self.LOWER_MASK) self.mt[i] = ( self.mt[i + self.M] ^ (y >> 1) ^ (self.MATRIX_A if y % 2 else 0) ) for i in range(self.N - self.M, self.N - 1): y = (self.mt[i] & self.UPPER_MASK) | (self.mt[i + 1] & self.LOWER_MASK) self.mt[i] = ( self.mt[i + (self.M - self.N)] ^ (y >> 1) ^ (self.MATRIX_A if y % 2 else 0) ) y = (self.mt[self.N - 1] & self.UPPER_MASK) | (self.mt[0] & self.LOWER_MASK) self.mt[self.N - 1] = ( self.mt[self.M - 1] ^ (y >> 1) ^ (self.MATRIX_A if y % 2 else 0) ) self.mti = 0
def genrand_int32(self) -> int: """Generates a random number on [0, 0xFFFFFFFF]-interval.""" if self.mti >= self.N: self._generate_numbers()
y = self.mt[self.mti] self.mti += 1
y ^= y >> 11 y ^= (y << 7) & 0x9D2C5680 y ^= (y << 15) & 0xEFC60000 y ^= y >> 18
return y & 0xFFFFFFFF
def genrand_int31(self) -> int: """Generates a random number on [0, 0x7FFFFFFF]-interval.""" return self.genrand_int32() >> 1
def next_bytes(self, length: int) -> bytes: """Generates random bytes.""" return b"".join( self.genrand_int31().to_bytes(4, "little", signed=False) for _ in range(0, length, 4) )[:length]
class UnityUtils: @staticmethod def search_unity_pack( pack_path: str, data_type: list | None = None, data_name: list | None = None, condition_connect: bool = False, read_obj_anyway: bool = False, ) -> list[ObjectReader] | None: data_list: list[ObjectReader] = [] type_passed = False try: env = UnityPy.load(pack_path) for obj in env.objects: if data_type and obj.type.name in data_type: if condition_connect: type_passed = True else: data_list.append(obj) if read_obj_anyway or type_passed: data = obj.read() if data_name and data.m_Name in data_name: if not (condition_connect or type_passed): continue data_list.append(obj) except: pass return data_list
def decode_server_url(data: bytes) -> str: ciphers = { "ServerInfoDataUrl": "X04YXBFqd3ZpTg9cKmpvdmpOElwnamB2eE4cXDZqc3ZgTg==", "DefaultConnectionGroup": "tSrfb7xhQRKEKtZvrmFjEp4q1G+0YUUSkirOb7NhTxKfKv1vqGFPEoQqym8=", "SkipTutorial": "8AOaQvLC5wj3A4RC78L4CNEDmEL6wvsI", "Language": "wL4EWsDv8QX5vgRaye/zBQ==", } b64_data = base64.b64encode(data).decode() json_str = convert_string(b64_data, create_key("GameMainConfig")) obj = json.loads(json_str) encrypted_url = obj[ciphers["ServerInfoDataUrl"]] url = convert_string(encrypted_url, create_key("ServerInfoDataUrl")) return url
if __name__ == "__main__": for f in os.listdir(os.getcwd()): if url_obj := UnityUtils.search_unity_pack(f, ["TextAsset"], ["GameMainConfig"], True): url = decode_server_url(url_obj[0].read().m_Script.encode("utf-8", "surrogateescape")) print(url) break
|