├── README.md ├── activate_ableton.py ├── config.json └── undoPatch.py /README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | 3 | This is an open-source implementation of the R2R Patch and `R2RLIVE.dll` of Ableton Live, written in Python 3. 4 | 5 | Like `R2RLIVE.dll`, this script uses Team R2R's signing key only. 6 | 7 | # Disclaimer 8 | 9 | This script is not the result of reverse engineering Ableton Live, and the output of this script **will not** circumvent the protection on an **unmodified** copy of Ableton Live. 10 | 11 | # Download Ableton Installers 12 | 13 | You can download the Ableton Installers directly from Ableton's servers. There's also a tool that you can use, it didnt get updated in some time. 14 | 15 | [Ableton Live Downloader](https://ableton-live.netlify.app/) 16 | 17 | [Ableton Live Downloader GitHub Page](https://github.com/montoulieu/ableton-live-downloader/) 18 | 19 | # Compatibility 20 | 21 | - Works on Windows and Linux (with wine) 22 | - Should work for all Ableton Live Versions above Live 9 (9,10,11,12) 23 | - Every Edition works too (Lite, Intro, Standard, Suite) 24 | 25 | # How to use 26 | 27 | 1. Run `pip install cryptograpghy` to install dependencies 28 | 2. Find your Ableton HWID, open Ableton, and press "Authorize Ableton offline". You will find your HWID. 29 | 2. Open `config.json` and change the variables to fit your Ableton Live installation. Make sure to follow the json language, for example double slash in the file path. 30 | 3. Save the file. 31 | 4. Run `activate_ableton.py`, your Ableton should be patched and the `Authorize.auz` file should generate. 32 | 5. Run Ableton, drag the `Authorize.auz` file into the activation Window 33 | 6. You're done. Ableton Live is now activated. 34 | 35 | If there are any permission erros, its recommended to move the Ableton.exe into the same folder where `activate_ableton.py` is located. 36 | 37 | # Credits 38 | 39 | The Implementation of the KeyGen was made by rufoa. Go leave a star on his Git page! -------------------------------------------------------------------------------- /activate_ableton.py: -------------------------------------------------------------------------------- 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) 177 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_path": "//your//ableton//path.exe", 3 | "hwid": "1111-1111-1111-1111-1111-1111", 4 | "edition": "Suite", 5 | "version": 11, 6 | 7 | "_comment1": "Don't change if you dont know what you're doing!", 8 | 9 | "authorize_file_output": "Authorize.auz", 10 | "old_signkey":"33303832303142373330383230313242303630373241383634384345333830343031333038323031314530323831383130304343454441393938434243363636463044323836313133333230454130344342374431424236364345384243323230364331303933354534353134314534373846303639363543313141464241303944343043463943323145364345354231324439414433384532433842424430453444464641413642373239314432464230333230334639343543354145463231423745433238383134373132353144324231363344364444374539414245313344413938363933314646423436384537353243444641423038303845364142363437304442373438443746444638433038434533414346423833463646323338373543353232373945433938353230304430323135303038304437423338413141324438413843324141363843434136444341313738433637433630363335303238313830303532394431443946373637443735423745364435393838444438373445323942314531314635413638413831443233453934353130354346464635363637463235453445324335323736313341303230303833443746334533363430384139463442383444384338454242364134443041383241393236313238353833313443424339464338463235364142353131353142344246394333364637353242424241353534394642333234344233343539424346453243324132464342313032373534333542414136323531463643423539393536424638453946363033343443444532423333413743334333313842393345424541454246333542463642423033383138353030303238313831303039313836443932453734433530373034334339374438454136394638314436313132454643343439313638374635333431463932443641424134464337363445304346424143443943364436443533353738423146413838383231374646364442433743433538303934413832434434433141384238364245353734353739414639364637343035364433343741344131353145323339443443443238394146354238394144433741413338373246444541444538363533453945374144393736373844354330343239314438424134423137373534393036353644363243374433354232413642363044463144373131393142313442453537433238454436", 11 | "new_signkey":"33303832303142373330383230313243303630373241383634384345333830343031333038323031314630323831383130304241423541313039373046303833453236364131323532383937444141433144363733373437313245373944334446314243384330384133343933433641413941324646333342453435313344384236373637414236414145324146364343393130373937364641373546454531333445384237424530334437384343363445303839433834353230374433303641363033354631373243354237353032373546303042443343413233333142384135394435344645373933393338353444443838344238443333344435353342333842433545383836433041324444304534454333324637443838444531413743394446354334323445453742314345364430323135303043333742453930453346384536344530334134324341384436384144354338334542343744334139303238313831303041333343383733374634324532353136413135323535343445363131443731323935383035434544393444323630443537373744423937364636373231463532343739313538453234373745464230454136464633304433344431354232333636394630393637443239413243373436323838454534324338443931464534444245373941373345453838333132353141333536363836343835384535383941444344343143333836334541313138464242434446333442443634454630453741453230423030313932373039413833343643383136423534413531443830344136453036464345314441344230343343324235323730443445343431363232303338313834303030323831383033334644313246443435394645364335433142433039393145393135463842463439393937373136424445354333424446394130393642444342463741343235454636413439353638334343383446334441464142374131443543463946333737464441383443303432453437453743363038323938433639313741334341414234304233433632363235353946453639393039314335424236414338444530314630413946383837433733394646413341314138353830303046383541313831314543333341323139303036333334314538433230414241303638423930333833463843413237443330414138394144463430444539434537333544454442", 12 | "dsa_parameters": { 13 | "p": "0xbab5a10970f083e266a1252897daac1d67374712e79d3df1bc8c08a3493c6aa9a2ff33be4513d8b6767ab6aae2af6cc9107976fa75fee134e8b7be03d78cc64e089c845207d306a6035f172c5b750275f00bd3ca2331b8a59d54fe79393854dd884b8d334d553b38bc5e886c0a2dd0e4ec32f7d88de1a7c9df5c424ee7b1ce6d", 14 | "q": "0xc37be90e3f8e64e03a42ca8d68ad5c83eb47d3a9", 15 | "g": "0xa33c8737f42e2516a1525544e611d71295805ced94d260d5777db976f6721f52479158e2477efb0ea6ff30d34d15b23669f0967d29a2c746288ee42c8d91fe4dbe79a73ee8831251a3566864858e589adcd41c3863ea118fbbcdf34bd64ef0e7ae20b00192709a8346c816b54a51d804a6e06fce1da4b043c2b5270d4e441622", 16 | "y": "0x33fd12fd459fe6c5c1bc0991e915f8bf49997716bde5c3bdf9a096bdcbf7a425ef6a495683cc84f3dafab7a1d5cf9f377fda84c042e47e7c608298c6917a3caab40b3c6262559fe699091c5bb6ac8de01f0a9f887c739ffa3a1a858000f85a1811ec33a2190063341e8c20aba068b90383f8ca27d30aa89adf40de9ce735dedb", 17 | "x": "0xc369ea757b46484d1df3819cc4183f6f9a9bcf3c" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /undoPatch.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | def read_config_from_json(json_file_path): 5 | try: 6 | with open(json_file_path, 'r') as json_file: 7 | data = json.load(json_file) 8 | file_path = data.get("file_path") 9 | new_signkey = data.get("old_signkey") 10 | old_signkey = data.get("new_signkey") 11 | 12 | if not file_path or not old_signkey or not new_signkey: 13 | raise ValueError("JSON file must contain 'file_path', 'old_signkey', and 'new_signkey'.") 14 | 15 | return file_path, old_signkey, new_signkey 16 | 17 | except FileNotFoundError: 18 | print(f"The JSON file {json_file_path} was not found.") 19 | raise 20 | except json.JSONDecodeError: 21 | print(f"Error parsing the JSON file {json_file_path}.") 22 | raise 23 | 24 | def replace_signkey_in_file(file_path, old_signkey, new_signkey): 25 | if len(old_signkey) != len(new_signkey): 26 | raise ValueError("The new signkey must be the same length as the old signkey.") 27 | 28 | if old_signkey.startswith("0x"): 29 | old_signkey = old_signkey[2:] 30 | if new_signkey.startswith("0x"): 31 | new_signkey = new_signkey[2:] 32 | 33 | if not re.fullmatch(r'[0-9a-fA-F]+', old_signkey): 34 | raise ValueError("The old signkey is not valid.") 35 | if not re.fullmatch(r'[0-9a-fA-F]+', new_signkey): 36 | raise ValueError("The new signkey is not valid.") 37 | 38 | try: 39 | with open(file_path, 'rb') as file: 40 | content = file.read() 41 | 42 | old_signkey_bytes = bytes.fromhex(old_signkey) 43 | new_signkey_bytes = bytes.fromhex(new_signkey) 44 | 45 | if old_signkey_bytes not in content: 46 | print(f"The old signkey '{old_signkey}' was not found in the file.") 47 | else: 48 | print(f"The old signkey '{old_signkey}' was found. Replacing...") 49 | 50 | content = content.replace(old_signkey_bytes, new_signkey_bytes) 51 | 52 | with open(file_path, 'wb') as file: 53 | file.write(content) 54 | 55 | if old_signkey_bytes in content: 56 | print("Error: The old signkey is still present in the file.") 57 | else: 58 | print("Signkey successfully replaced.") 59 | 60 | except FileNotFoundError: 61 | print(f"The file '{file_path}' was not found.") 62 | except Exception as e: 63 | print(f"An error occurred: {e}") 64 | 65 | json_file_path = 'config.json' 66 | 67 | try: 68 | file_path, old_signkey, new_signkey = read_config_from_json(json_file_path) 69 | replace_signkey_in_file(file_path, old_signkey, new_signkey) 70 | except Exception as e: 71 | print(f"Error: {e}") 72 | --------------------------------------------------------------------------------