|
| 1 | +import json |
| 2 | +import re |
| 3 | +from random import randint |
| 4 | +from cryptography.hazmat.backends import default_backend |
| 5 | +from cryptography.hazmat.primitives.asymmetric import dsa |
| 6 | +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature |
| 7 | +from cryptography.hazmat.primitives.hashes import SHA1 |
| 8 | + |
| 9 | +def load_config(filename: str): |
| 10 | + try: |
| 11 | + with open(filename, 'r') as f: |
| 12 | + data = json.load(f) |
| 13 | + |
| 14 | + # Extract values from JSON |
| 15 | + file_path = data.get("file_path") |
| 16 | + old_signkey = data.get("old_signkey") |
| 17 | + new_signkey = data.get("new_signkey") |
| 18 | + hwid = data.get('hwid', '').upper() |
| 19 | + edition = data.get('edition', 'Suite') |
| 20 | + version = data.get('version', 12) |
| 21 | + authorize_file_output = data.get('authorize_file_output', 'Authorize.auz') |
| 22 | + dsa_params = data.get('dsa_parameters') |
| 23 | + |
| 24 | + # Validate essential keys |
| 25 | + if not file_path or not old_signkey or not new_signkey: |
| 26 | + raise ValueError("JSON file must contain 'file_path', 'old_signkey', and 'new_signkey'.") |
| 27 | + if len(hwid) == 24: |
| 28 | + hwid = "-".join(hwid[i:i+4] for i in range(0, 24, 4)) |
| 29 | + assert re.fullmatch(r"([0-9A-F]{4}-){5}[0-9A-F]{4}", hwid), f"Expected hardware ID like 1111-1111-1111-1111-1111-1111, not {hwid}" |
| 30 | + |
| 31 | + if not dsa_params: |
| 32 | + raise ValueError("DSA parameters are missing in the config file.") |
| 33 | + |
| 34 | + return file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params |
| 35 | + |
| 36 | + except FileNotFoundError: |
| 37 | + print(f"The JSON file {filename} was not found.") |
| 38 | + raise |
| 39 | + except json.JSONDecodeError: |
| 40 | + print(f"Error parsing the JSON file {filename}.") |
| 41 | + raise |
| 42 | + |
| 43 | +def construct_key(dsa_params) -> dsa.DSAPrivateKey: |
| 44 | + p = int(dsa_params['p'], 16) |
| 45 | + q = int(dsa_params['q'], 16) |
| 46 | + g = int(dsa_params['g'], 16) |
| 47 | + y = int(dsa_params['y'], 16) |
| 48 | + x = int(dsa_params['x'], 16) |
| 49 | + |
| 50 | + params = dsa.DSAParameterNumbers(p, q, g) |
| 51 | + pub = dsa.DSAPublicNumbers(y, params) |
| 52 | + priv = dsa.DSAPrivateNumbers(x, pub) |
| 53 | + return priv.private_key(backend=default_backend()) |
| 54 | + |
| 55 | +def replace_signkey_in_file(file_path, old_signkey, new_signkey): |
| 56 | + if len(old_signkey) != len(new_signkey): |
| 57 | + raise ValueError("The new signkey must be the same length as the old signkey.") |
| 58 | + |
| 59 | + if old_signkey.startswith("0x"): |
| 60 | + old_signkey = old_signkey[2:] |
| 61 | + if new_signkey.startswith("0x"): |
| 62 | + new_signkey = new_signkey[2:] |
| 63 | + |
| 64 | + if not re.fullmatch(r'[0-9a-fA-F]+', old_signkey): |
| 65 | + raise ValueError("The old signkey is not valid.") |
| 66 | + if not re.fullmatch(r'[0-9a-fA-F]+', new_signkey): |
| 67 | + raise ValueError("The new signkey is not valid.") |
| 68 | + |
| 69 | + try: |
| 70 | + with open(file_path, 'rb') as file: |
| 71 | + content = file.read() |
| 72 | + |
| 73 | + old_signkey_bytes = bytes.fromhex(old_signkey) |
| 74 | + new_signkey_bytes = bytes.fromhex(new_signkey) |
| 75 | + |
| 76 | + if old_signkey_bytes not in content: |
| 77 | + print(f"The old signkey '{old_signkey}' was not found in the file.") |
| 78 | + else: |
| 79 | + print(f"The old signkey '{old_signkey}' was found. Replacing...") |
| 80 | + |
| 81 | + content = content.replace(old_signkey_bytes, new_signkey_bytes) |
| 82 | + |
| 83 | + with open(file_path, 'wb') as file: |
| 84 | + file.write(content) |
| 85 | + |
| 86 | + if old_signkey_bytes in content: |
| 87 | + print("Error: The old signkey is still present in the file.") |
| 88 | + else: |
| 89 | + print("Signkey successfully replaced.") |
| 90 | + |
| 91 | + except FileNotFoundError: |
| 92 | + print(f"The file '{file_path}' was not found.") |
| 93 | + except Exception as e: |
| 94 | + print(f"An error occurred: {e}") |
| 95 | + |
| 96 | +def sign(k: dsa.DSAPrivateKey, m: str) -> str: |
| 97 | + """P1363 format sig over m as a string of hex digits""" |
| 98 | + assert k.key_size == 1024 |
| 99 | + sig = k.sign(m.encode(), SHA1()) |
| 100 | + r, s = decode_dss_signature(sig) |
| 101 | + return "{:040X}{:040X}".format(r, s) |
| 102 | + |
| 103 | +def fix_group_checksum(group_number: int, n: int) -> int: |
| 104 | + checksum = n >> 4 & 0xf ^ \ |
| 105 | + n >> 5 & 0x8 ^ \ |
| 106 | + n >> 9 & 0x7 ^ \ |
| 107 | + n >> 11 & 0xe ^ \ |
| 108 | + n >> 15 & 0x1 ^ \ |
| 109 | + group_number |
| 110 | + return n & 0xfff0 | checksum |
| 111 | + |
| 112 | +def overall_checksum(groups: list[int]) -> int: |
| 113 | + r = 0 |
| 114 | + for i in range(20): |
| 115 | + g, digit = divmod(i, 4) |
| 116 | + v = groups[g] >> (digit * 8) & 0xff |
| 117 | + r ^= v << 8 |
| 118 | + for _ in range(8): |
| 119 | + r <<= 1 |
| 120 | + if r & 0x10000: |
| 121 | + r ^= 0x8005 |
| 122 | + return r & 0xffff |
| 123 | + |
| 124 | +def random_serial(): |
| 125 | + """ |
| 126 | + 3xxc-xxxc-xxxc-xxxc-xxxc-dddd |
| 127 | + x is random |
| 128 | + c is a checksum over each group |
| 129 | + d is a checksum over all groups |
| 130 | + """ |
| 131 | + groups = [randint(0x3000, 0x3fff), |
| 132 | + randint(0x0000, 0xffff), |
| 133 | + randint(0x0000, 0xffff), |
| 134 | + randint(0x0000, 0xffff), |
| 135 | + randint(0x0000, 0xffff)] |
| 136 | + for i in range(5): |
| 137 | + groups[i] = fix_group_checksum(i, groups[i]) |
| 138 | + d = overall_checksum(groups) |
| 139 | + return "{:04X}-{:04X}-{:04X}-{:04X}-{:04X}-{:04X}".format(*groups, d) |
| 140 | + |
| 141 | +def generate_single(k: dsa.DSAPrivateKey, id1: int, id2: int, hwid: str) -> str: |
| 142 | + f = "{},{:02X},{:02X},Standard,{}" |
| 143 | + serial = random_serial() |
| 144 | + msg = f.format(serial, id1, id2, hwid) |
| 145 | + sig = sign(k, msg) |
| 146 | + return f.format(serial, id1, id2, sig) |
| 147 | + |
| 148 | +def generate_all(k: dsa.DSAPrivateKey, edition: str, version: int, hwid: str) -> str: |
| 149 | + yield generate_single(k, EDITIONS[edition], version << 4, hwid) |
| 150 | + for i in range(0x40, 0xff + 1): |
| 151 | + yield generate_single(k, i, 0x10, hwid) |
| 152 | + for i in range(0x8000, 0x80ff + 1): |
| 153 | + yield generate_single(k, i, 0x10, hwid) |
| 154 | + |
| 155 | +# Mapping for the editions |
| 156 | +EDITIONS = { |
| 157 | + "Lite": 4, |
| 158 | + "Intro": 3, |
| 159 | + "Standard": 0, |
| 160 | + "Suite": 2, |
| 161 | +} |
| 162 | + |
| 163 | +# Load configuration |
| 164 | +config_file = 'config.json' |
| 165 | +file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params = load_config(config_file) |
| 166 | + |
| 167 | +# Construct the key from the loaded parameters |
| 168 | +team_r2r_key = construct_key(dsa_params) |
| 169 | + |
| 170 | +# Generate keys and save the authorize file |
| 171 | +lines = generate_all(team_r2r_key, edition, version, hwid) |
| 172 | +with open(authorize_file_output, mode="w", newline="\n") as f: |
| 173 | + f.write("\n".join(lines)) |
| 174 | + |
| 175 | +# Replace the signkey in the binary file |
| 176 | +replace_signkey_in_file(file_path, old_signkey, new_signkey) |
0 commit comments