├── .gitignore ├── requirements.txt ├── README.md ├── config.json ├── quickstart.cmd └── abletonCracker.py /.gitignore: -------------------------------------------------------------------------------- 1 | Authorize.auz 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography colorama -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # abletonPatcher - What is this? 2 | 3 | This is an open-source implementation of the R2R patch and `R2RLIVE.dll` of Ableton Live, written in Python. 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. I made a small HTML file to make this easier for you. 14 | 15 | [StaticAbletonDownloader](https://devilapi.github.io/StaticAbletonDownloader) 16 | 17 | # Compatibility 18 | 19 | - Works on Windows and Linux (with wine) 20 | - Should work for all Ableton Live Versions above Live 9 (9,10,11,12) 21 | - Every Edition works too (Lite, Intro, Standard, Suite) 22 | 23 | # Quickstart Guide 24 | 25 | 1. Find your Ableton HWID: Open Ableton, and press "Authorize Ableton offline". You will find your HWID. 26 | 2. Right click `quickstart.cmd` and select `Run as Administrator`. 27 | 3. When the script asks you if you want to edit the config file, select `y`. 28 | 4. You will only need to change the top 3 variables. Enter your HWID, the Live version and edition and **save the file (Ctrl+S)** 29 | 5. The script will now ask you if you want to run the patcher. Select `y`. 30 | 6. Select the installation of Ableton you want to patch 31 | 7. The script will now ask if you want to open the folder, where `Authorize.auz` is located. Select `y` 32 | 5. Run Ableton, drag the `Authorize.auz` file into the Activation window 33 | 34 | #### Hooray, you're done! 35 | 36 | # Command Line Arguments 37 | | Parameter | Type | Description | Default/Config | 38 | |-----------|------|-------------|----------------| 39 | | `--undo` | flag | Revert the patch (swap signkeys and skip authorization file) | Uses config.json values | 40 | | `--file_path` | string | Path to Ableton Live executable or "auto" for auto-detection | `config.json: file_path` | 41 | | `--old_signkey` | string | Old signkey (hex string) | `config.json: old_signkey` | 42 | | `--new_signkey` | string | New signkey (hex string) | `config.json: new_signkey` | 43 | | `--hwid` | string | Hardware ID (24 hex chars or 6 groups of 4) | `config.json: hwid` | 44 | | `--edition` | string | Ableton edition (Lite, Intro, Standard, Suite) | `config.json: edition` | 45 | | `--version` | integer | Ableton version (e.g., 12) | `config.json: version` | 46 | | `--authorize_file_output` | string | Output path for Authorize.auz or "auto" | `config.json: authorize_file_output` | 47 | | `--help` | flag | Show help message | N/A | 48 | 49 | # Troubleshooting 50 | #### I don't have administrator on my PC. 51 | 1. Copy your Ableton executable to the same folder where patch_ableton.py is located. 52 | 2. In config.json, change your file path from "auto" to the new file path of your Ableton exe. 53 | 3. Retry 54 | 4. It should work now. Then copy your Ableton exe back to the folder you got it from. 55 | 56 | # Support 57 | I do offer support on Discord (https://discord.gg/akswvyUk) and on Reddit (@devilAPIOnReddit) 58 | 59 | # Credits 60 | 61 | The Implementation of the KeyGen was made by [rufoa](https://github.com/rufoa). Go leave a star on his Git page! 62 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hwid": "1111-1111-1111-1111-1111-1111", 3 | 4 | "_comment0": "Possible versions: 9, 10, 11, 12", 5 | "version": 12, 6 | 7 | "_comment1": "Possible editions: 'Intro', 'Standard', 'Suite'", 8 | "edition": "Suite", 9 | 10 | 11 | 12 | 13 | "_comment2": "From here on only advanced settings! Don't change if you dont know what you're doing!", 14 | 15 | "_comment3": "Advanced Dependency Installer Settings", 16 | "skipPythonCheck": false, 17 | "skipPythonDependencies": false, 18 | 19 | "_comment4": "Advanced Patcher Settings", 20 | "file_path": "auto", 21 | "authorize_file_output": "output/Authorize.auz", 22 | "old_signkey": "33303832303142373330383230313242303630373241383634384345333830343031333038323031314530323831383130304343454441393938434243363636463044323836313133333230454130344342374431424236364345384243323230364331303933354534353134314534373846303639363543313141464241303944343043463943323145364345354231324439414433384532433842424430453444464641413642373239314432464230333230334639343543354145463231423745433238383134373132353144324231363344364444374539414245313344413938363933314646423436384537353243444641423038303845364142363437304442373438443746444638433038434533414346423833463646323338373543353232373945433938353230304430323135303038304437423338413141324438413843324141363843434136444341313738433637433630363335303238313830303532394431443946373637443735423745364435393838444438373445323942314531314635413638413831443233453934353130354346464635363637463235453445324335323736313341303230303833443746334533363430384139463442383444384338454242364134443041383241393236313238353833313443424339464338463235364142353131353142344246394333364637353242424241353534394642333234344233343539424346453243324132464342313032373534333542414136323531463643423539393536424638453946363033343443444532423333413743334333313842393345424541454246333542463642423033383138353030303238313831303039313836443932453734433530373034334339374438454136394638314436313132454643343439313638374635333431463932443641424134464337363445304346424143443943364436443533353738423146413838383231374646364442433743433538303934413832434434433141384238364245353734353739414639364637343035364433343741344131353145323339443443443238394146354238394144433741413338373246444541444538363533453945374144393736373844354330343239314438424134423137373534393036353644363243374433354232413642363044463144373131393142313442453537433238454436", 23 | "new_signkey": "33303832303142373330383230313243303630373241383634384345333830343031333038323031314630323831383130304241423541313039373046303833453236364131323532383937444141433144363733373437313245373944334446314243384330384133343933433641413941324646333342453435313344384236373637414236414145324146364343393130373937364641373546454531333445384237424530334437384343363445303839433834353230374433303641363033354631373243354237353032373546303042443343413233333142384135394435344645373933393338353444443838344238443333344435353342333842433545383836433041324444304534454333324637443838444531413743394446354334323445453742314345364430323135303043333742453930453346384536344530334134324341384436384144354338334542343744334139303238313831303041333343383733374634324532353136413135323535343445363131443731323935383035434544393444323630443537373744423937364636373231463532343739313538453234373745464230454136464633304433344431354232333636394630393637443239413243373436323838454534324338443931464534444245373941373345453838333132353141333536363836343835384535383941444344343143333836334541313138464242434446333442443634454630453741453230423030313932373039413833343643383136423534413531443830344136453036464345314441344230343343324235323730443445343431363232303338313834303030323831383033334644313246443435394645364335433142433039393145393135463842463439393937373136424445354333424446394130393642444342463741343235454636413439353638334343383446334441464142374131443543463946333737464441383443303432453437453743363038323938433639313741334341414234304233433632363235353946453639393039314335424236414338444530314630413946383837433733394646413341314138353830303046383541313831314543333341323139303036333334314538433230414241303638423930333833463843413237443330414138394144463430444539434537333544454442", 24 | "dsa_parameters": { 25 | "p": "0xbab5a10970f083e266a1252897daac1d67374712e79d3df1bc8c08a3493c6aa9a2ff33be4513d8b6767ab6aae2af6cc9107976fa75fee134e8b7be03d78cc64e089c845207d306a6035f172c5b750275f00bd3ca2331b8a59d54fe79393854dd884b8d334d553b38bc5e886c0a2dd0e4ec32f7d88de1a7c9df5c424ee7b1ce6d", 26 | "q": "0xc37be90e3f8e64e03a42ca8d68ad5c83eb47d3a9", 27 | "g": "0xa33c8737f42e2516a1525544e611d71295805ced94d260d5777db976f6721f52479158e2477efb0ea6ff30d34d15b23669f0967d29a2c746288ee42c8d91fe4dbe79a73ee8831251a3566864858e589adcd41c3863ea118fbbcdf34bd64ef0e7ae20b00192709a8346c816b54a51d804a6e06fce1da4b043c2b5270d4e441622", 28 | "y": "0x33fd12fd459fe6c5c1bc0991e915f8bf49997716bde5c3bdf9a096bdcbf7a425ef6a495683cc84f3dafab7a1d5cf9f377fda84c042e47e7c608298c6917a3caab40b3c6262559fe699091c5bb6ac8de01f0a9f887c739ffa3a1a858000f85a1811ec33a2190063341e8c20aba068b90383f8ca27d30aa89adf40de9ce735dedb", 29 | "x": "0xc369ea757b46484d1df3819cc4183f6f9a9bcf3c" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /quickstart.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | :: Temporary PowerShell script path 5 | set "TEMP_PS=%TEMP%\install_dependencies_temp.ps1" 6 | 7 | ( 8 | echo $ErrorActionPreference = "Stop" 9 | echo # ASCII Logo in Red 10 | echo Write-Host " ___. .__ __ _________ __ " -ForegroundColor Red 11 | echo Write-Host "_____ \_ |__ | | _____/ |_ ____ ____ \_ ___ \____________ ____ | | __ ___________ " -ForegroundColor Red 12 | echo Write-Host "\__ \ | __ \| | _/ __ \ __\/ _ \ / \/ \ \/\_ __ \__ \ _/ ___\| |/ // __ \_ __ \" -ForegroundColor Red 13 | echo Write-Host " / __ \| \_\ \ |_\ ___/| | ( <_> ) | \ \____| | \// __ \\ \___| <\ ___/| | \/" -ForegroundColor Red 14 | echo Write-Host "(____ /___ /____/\___ >__| \____/|___| /\______ /|__| (____ /\___ >__|_ \\___ >__| " -ForegroundColor Red 15 | echo Write-Host " \/ \/ \/ \/ \/ \/ \/ \/ \/ " -ForegroundColor Red 16 | echo Write-Host "________ ____ ___.____________ ____ __. _________________________ _____________________" -ForegroundColor Red 17 | echo Write-Host "\_____ \ | | \ \_ ___ \| |/ _| / _____/\__ ___/ _ \\______ \__ ___/" -ForegroundColor Red 18 | echo Write-Host " / / \ \| | / / \ \/| < \_____ \ | | / /_\ \| _/ | | " -ForegroundColor Red 19 | echo Write-Host "/ \_/. \ | /| \ \___| | \ / \ | |/ | \ | \ | | " -ForegroundColor Red 20 | echo Write-Host "\_____\ \_/______/ |___|\______ /____|__ \/_______ / |____|\____|__ /____|_ / |____| " -ForegroundColor Red 21 | echo Write-Host " \__> \/ \/ \/ \/ \/ " -ForegroundColor Red 22 | echo Write-Host "" 23 | echo Write-Host "=========================================================" -ForegroundColor White 24 | echo Write-Host " Checking configuration..." -ForegroundColor White 25 | echo Write-Host "=========================================================" -ForegroundColor White 26 | echo. 27 | 28 | :: Read config.json with manual parsing (compatible with older PowerShell) 29 | echo $skipPythonCheck = $false 30 | echo $skipPythonDependencies = $false 31 | echo if ^(Test-Path "config.json"^) { 32 | echo try { 33 | echo $configContent = Get-Content "config.json" -Raw 34 | echo # Manual JSON parsing for compatibility 35 | echo if ^($configContent -match '"skipPythonCheck"\s*:\s*true'^) { 36 | echo $skipPythonCheck = $true 37 | echo } 38 | echo if ^($configContent -match '"skipPythonDependencies"\s*:\s*true'^) { 39 | echo $skipPythonDependencies = $true 40 | echo } 41 | echo } catch { 42 | echo Write-Host "Warning: Error reading config.json, using default settings" -ForegroundColor Yellow 43 | echo } 44 | echo } 45 | echo. 46 | 47 | :: Check if Python exists (unless skipped) 48 | echo if ^(-not $skipPythonCheck^) { 49 | echo Write-Host "" 50 | echo Write-Host "=========================================================" -ForegroundColor White 51 | echo Write-Host " Checking for Python installation..." -ForegroundColor White 52 | echo Write-Host "=========================================================" -ForegroundColor White 53 | echo $pythonPath = ^(Get-Command python -ErrorAction SilentlyContinue^).Source 54 | echo if ^(-not $pythonPath^) { 55 | echo Write-Host "Python not found. Installing via winget..." -ForegroundColor White 56 | echo if ^(-not ^(Get-Command winget -ErrorAction SilentlyContinue^)^) { 57 | echo Write-Host "ERROR: winget not available. Install Windows Package Manager first." -ForegroundColor Red 58 | echo exit 1 59 | echo } 60 | echo winget install --id Python.Python.3 -e --source winget 61 | echo $pythonPath = ^(Get-Command python -ErrorAction SilentlyContinue^).Source 62 | echo if ^(-not $pythonPath^) { 63 | echo Write-Host "ERROR: Python installation failed." -ForegroundColor Red 64 | echo exit 1 65 | echo } 66 | echo Write-Host 'Python installed successfully.' -ForegroundColor Green 67 | echo } else { 68 | echo Write-Host 'Python is already installed at: ' + $pythonPath -ForegroundColor Green 69 | echo } 70 | echo } else { 71 | echo Write-Host "Skipping Python check as per config.json" -ForegroundColor Yellow 72 | echo $pythonPath = ^(Get-Command python -ErrorAction SilentlyContinue^).Source 73 | echo } 74 | 75 | echo if ^(-not $skipPythonDependencies^) { 76 | echo if ^($pythonPath^) { 77 | echo Write-Host "" 78 | echo Write-Host "Installing required Python packages: cryptography, colorama..." -ForegroundColor White 79 | echo Write-Host "" 80 | echo Write-Host "Upgrading pip..." -ForegroundColor Gray 81 | echo ^& $pythonPath -m pip install --upgrade pip 82 | echo Write-Host "Installing required Python packages: cryptography, colorama..." -ForegroundColor Gray 83 | echo ^& $pythonPath -m pip install cryptography colorama 84 | echo if ^($LASTEXITCODE -eq 0^) { 85 | echo Write-Host "" 86 | echo Write-Host "=========================================================" -ForegroundColor Green 87 | echo Write-Host " Python and required packages installed successfully!" -ForegroundColor Green 88 | echo Write-Host "=========================================================" -ForegroundColor Green 89 | echo Write-Host "" 90 | echo } else { 91 | echo Write-Host "ERROR: Failed to install one or more packages." -ForegroundColor Red 92 | echo } 93 | echo } else { 94 | echo Write-Host "ERROR: Python not found and cannot install dependencies." -ForegroundColor Red 95 | echo } 96 | echo } else { 97 | echo Write-Host "Skipping Python dependencies installation as per config.json" -ForegroundColor Yellow 98 | echo } 99 | 100 | :: Ask to run patcher 101 | echo Write-Host "=========================================================" -ForegroundColor White 102 | echo Write-Host " Dependencies installed!" -ForegroundColor DarkGreen 103 | echo Write-Host "=========================================================" -ForegroundColor White 104 | echo Write-Host "" 105 | echo $runPatcher = Read-Host "Do you want to run the edit the config.json? (Needed for first run) (y/n)" 106 | echo if ($runPatcher -eq "y" -or $runPatcher -eq "Y"^) { 107 | echo Write-Host "Opening config.json in Notepad..." -ForegroundColor Green 108 | echo Start-Process notepad.exe -ArgumentList "config.json" 109 | echo } 110 | echo $runPatcher = Read-Host "Do you want to run the patcher now? (y/n)" 111 | echo if ^($runPatcher -eq "y" -or $runPatcher -eq "Y"^) { 112 | echo if ^($pythonPath^) { 113 | echo Write-Host "Running patcher..." -ForegroundColor Green 114 | echo ^& $pythonPath abletonCracker.py 115 | echo } else { 116 | echo Write-Host "ERROR: Python not found. Cannot run patcher." -ForegroundColor Red 117 | echo } 118 | echo } 119 | echo else { 120 | echo Write-Host "You can run the patcher later by executing: python abletonCracker.py" -ForegroundColor Yellow 121 | echo Write-Host "You can also always undo the patch by running: python abletonCracker.py --undo" -ForegroundColor Yellow 122 | echo } 123 | ) > "%TEMP_PS%" 124 | 125 | :: Run the PowerShell script with execution policy bypass 126 | powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP_PS%" 127 | 128 | :: Delete temporary script 129 | del "%TEMP_PS%" >nul 2>&1 130 | 131 | pause 132 | exit /b 0 -------------------------------------------------------------------------------- /abletonCracker.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import os 4 | import platform 5 | import sys 6 | import ctypes 7 | import subprocess 8 | import argparse 9 | from random import randint 10 | from cryptography.hazmat.backends import default_backend 11 | from cryptography.hazmat.primitives.asymmetric import dsa 12 | from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature 13 | from cryptography.hazmat.primitives.hashes import SHA1 14 | 15 | try: 16 | from colorama import init, Fore, Style 17 | init(autoreset=True) 18 | except ImportError: 19 | class Dummy: 20 | RESET = RED = WHITE = GREEN = LIGHTBLACK_EX = BRIGHT = '' 21 | Fore = Style = Dummy() 22 | 23 | patcher_version = "v3.0.0" 24 | 25 | RED = Fore.RED + Style.BRIGHT 26 | WHITE = Fore.WHITE + Style.BRIGHT 27 | GREY = Fore.LIGHTBLACK_EX + Style.NORMAL 28 | GREEN = Fore.GREEN + Style.BRIGHT 29 | YELLOW = Fore.YELLOW + Style.BRIGHT 30 | PURPLE = Fore.MAGENTA + Style.BRIGHT 31 | RESET = Style.RESET_ALL 32 | 33 | os.chdir(os.path.dirname(os.path.abspath(sys.argv[0]))) 34 | 35 | def is_admin(): 36 | try: 37 | return ctypes.windll.shell32.IsUserAnAdmin() != 0 38 | except: 39 | return False 40 | 41 | def run_as_admin(undo=False): 42 | script = os.path.abspath(sys.argv[0]) 43 | params = sys.argv[1:] 44 | if undo and "--undo" not in params: 45 | params.append("--undo") 46 | params = subprocess.list2cmdline(params) 47 | ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}" {params}', None, 1) 48 | sys.exit(0) 49 | 50 | def load_config(filename: str, args): 51 | try: 52 | with open(filename, 'r') as f: 53 | data = json.load(f) 54 | 55 | # Use command line arguments if provided, otherwise use config file 56 | file_path = args.file_path if args.file_path else data.get("file_path") 57 | old_signkey = args.old_signkey if args.old_signkey else data.get("old_signkey") 58 | new_signkey = args.new_signkey if args.new_signkey else data.get("new_signkey") 59 | hwid = args.hwid if args.hwid else data.get('hwid', '').upper() 60 | edition = args.edition if args.edition else data.get('edition', 'Suite') 61 | version = args.version if args.version else data.get('version', 12) 62 | authorize_file_output = args.authorize_file_output if args.authorize_file_output else data.get('authorize_file_output', 'output/Authorize.auz') 63 | dsa_params = data.get('dsa_parameters') # Keep DSA params in config only 64 | 65 | if args.undo: 66 | # For undo, swap the signkeys 67 | old_signkey, new_signkey = new_signkey, old_signkey 68 | 69 | if not file_path or not old_signkey or not new_signkey: 70 | raise ValueError("JSON file must contain 'file_path', 'old_signkey', and 'new_signkey'.") 71 | 72 | if len(hwid) == 24: 73 | hwid = "-".join(hwid[i:i+4] for i in range(0, 24, 4)) 74 | 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}" 75 | 76 | if not dsa_params and not args.undo: 77 | raise ValueError("DSA parameters are missing in the config file.") 78 | 79 | return file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params 80 | except FileNotFoundError: 81 | print(RED + f"The JSON file {filename} was not found." + RESET) 82 | raise 83 | except json.JSONDecodeError: 84 | print(RED + f"Error parsing the JSON file {filename}." + RESET) 85 | raise 86 | 87 | def construct_key(dsa_params) -> dsa.DSAPrivateKey: 88 | p = int(dsa_params['p'], 16) 89 | q = int(dsa_params['q'], 16) 90 | g = int(dsa_params['g'], 16) 91 | y = int(dsa_params['y'], 16) 92 | x = int(dsa_params['x'], 16) 93 | params = dsa.DSAParameterNumbers(p, q, g) 94 | pub = dsa.DSAPublicNumbers(y, params) 95 | priv = dsa.DSAPrivateNumbers(x, pub) 96 | return priv.private_key(backend=default_backend()) 97 | 98 | def replace_signkey_in_file(file_path, old_signkey, new_signkey, undo: bool = False): 99 | if len(old_signkey) != len(new_signkey): 100 | raise ValueError("The new signkey must be the same length as the old signkey.") 101 | if old_signkey.startswith("0x"): 102 | old_signkey = old_signkey[2:] 103 | if new_signkey.startswith("0x"): 104 | new_signkey = new_signkey[2:] 105 | if not re.fullmatch(r'[0-9a-fA-F]+', old_signkey): 106 | raise ValueError("The old signkey is not valid.") 107 | if not re.fullmatch(r'[0-9a-fA-F]+', new_signkey): 108 | raise ValueError("The new signkey is not valid.") 109 | try: 110 | with open(file_path, 'rb') as file: 111 | content = file.read() 112 | old_signkey_bytes = bytes.fromhex(old_signkey) 113 | new_signkey_bytes = bytes.fromhex(new_signkey) 114 | 115 | if old_signkey_bytes not in content: 116 | if undo: 117 | print(RED + f"The old signkey was not found in the file." + RESET) 118 | else: 119 | if new_signkey_bytes in content: 120 | print(YELLOW + "The new signkey is already present in the file. Ableton is already patched." + RESET) 121 | else: 122 | print(RED + "Neither the old nor the new signkey was found in the file. You may be running an unsupported version or a different patch." + RESET) 123 | else: 124 | if undo: 125 | print(WHITE + f"The old signkey was found. Replacing..." + RESET) 126 | else: 127 | print(WHITE + "The old signkey was found. Replacing..." + RESET) 128 | 129 | content = content.replace(old_signkey_bytes, new_signkey_bytes) 130 | with open(file_path, 'wb') as file: 131 | file.write(content) 132 | 133 | if old_signkey_bytes in content: 134 | print(RED + "Error: The old signkey is still present in the file." + RESET) 135 | else: 136 | print(GREEN + "Signkey successfully replaced." + RESET) 137 | except PermissionError: 138 | print(RED + "\nPermission denied! Try running the script as Administrator." + RESET) 139 | if platform.system() == "Windows": 140 | print(GREY + "Relaunching with admin privileges..." + RESET) 141 | run_as_admin(undo) 142 | else: 143 | print(GREY + "On Linux/macOS, try running with sudo." + RESET) 144 | raise 145 | except FileNotFoundError: 146 | print(RED + f"The file '{file_path}' was not found." + RESET) 147 | raise 148 | except Exception as e: 149 | print(RED + f"An error occurred: {e}" + RESET) 150 | raise 151 | 152 | def sign(k: dsa.DSAPrivateKey, m: str) -> str: 153 | assert k.key_size == 1024 154 | sig = k.sign(m.encode(), SHA1()) 155 | r, s = decode_dss_signature(sig) 156 | return "{:040X}{:040X}".format(r, s) 157 | 158 | def fix_group_checksum(group_number: int, n: int) -> int: 159 | checksum = n >> 4 & 0xf ^ \ 160 | n >> 5 & 0x8 ^ \ 161 | n >> 9 & 0x7 ^ \ 162 | n >> 11 & 0xe ^ \ 163 | n >> 15 & 0x1 ^ \ 164 | group_number 165 | return n & 0xfff0 | checksum 166 | 167 | def overall_checksum(groups: list[int]) -> int: 168 | r = 0 169 | for i in range(20): 170 | g, digit = divmod(i, 4) 171 | v = groups[g] >> (digit * 8) & 0xff 172 | r ^= v << 8 173 | for _ in range(8): 174 | r <<= 1 175 | if r & 0x10000: 176 | r ^= 0x8005 177 | return r & 0xffff 178 | 179 | def random_serial(): 180 | """ 3xxc-xxxc-xxxc-xxxc-xxxc-dddd x is random c is a checksum over each group d is a checksum over all groups """ 181 | groups = [randint(0x3000, 0x3fff), randint(0x0000, 0xffff), randint(0x0000, 0xffff), randint(0x0000, 0xffff), randint(0x0000, 0xffff)] 182 | for i in range(5): 183 | groups[i] = fix_group_checksum(i, groups[i]) 184 | d = overall_checksum(groups) 185 | return "{:04X}-{:04X}-{:04X}-{:04X}-{:04X}-{:04X}".format(*groups, d) 186 | 187 | def generate_single(k: dsa.DSAPrivateKey, id1: int, id2: int, hwid: str) -> str: 188 | f = "{},{:02X},{:02X},Standard,{}" 189 | serial = random_serial() 190 | msg = f.format(serial, id1, id2, hwid) 191 | sig = sign(k, msg) 192 | return f.format(serial, id1, id2, sig) 193 | 194 | def generate_all(k: dsa.DSAPrivateKey, edition: str, version: int, hwid: str) -> str: 195 | yield generate_single(k, EDITIONS[edition], version << 4, hwid) 196 | for i in range(0x40, 0xff + 1): 197 | yield generate_single(k, i, 0x10, hwid) 198 | for i in range(0x8000, 0x80ff + 1): 199 | yield generate_single(k, i, 0x10, hwid) 200 | 201 | EDITIONS = { 202 | "Lite": 4, 203 | "Intro": 3, 204 | "Standard": 0, 205 | "Suite": 2, 206 | } 207 | 208 | def get_user_config_dir(): 209 | system = platform.system() 210 | if system == "Windows": 211 | return os.getenv('APPDATA') 212 | elif system == "Darwin": 213 | return os.path.join(os.path.expanduser("~"), "Library", "Application Support") 214 | else: 215 | return os.getenv('XDG_CONFIG_HOME', os.path.join(os.path.expanduser("~"), ".config")) 216 | 217 | def find_installations(): 218 | system = platform.system() 219 | installations = [] 220 | if system == "Windows": 221 | base_dir = "C:\\ProgramData\\Ableton" 222 | if not os.path.exists(base_dir): 223 | return installations 224 | for entry in os.listdir(base_dir): 225 | if entry.startswith('.'): 226 | continue 227 | if "Live" in entry: 228 | entry_path = os.path.join(base_dir, entry) 229 | if os.path.isdir(entry_path): 230 | program_dir = os.path.join(entry_path, "Program") 231 | if os.path.exists(program_dir): 232 | for file in os.listdir(program_dir): 233 | if file.endswith(".exe") and "Live" in file: 234 | exe_path = os.path.join(program_dir, file) 235 | installations.append((exe_path, entry)) 236 | elif system == "Darwin": 237 | base_dir = "/Applications" 238 | if not os.path.exists(base_dir): 239 | return installations 240 | for entry in os.listdir(base_dir): 241 | if entry.startswith('.'): 242 | continue 243 | if entry.endswith(".app") and "Ableton Live" in entry: 244 | app_path = os.path.join(base_dir, entry) 245 | exe_path = os.path.join(app_path, "Contents", "MacOS", "Live") 246 | if os.path.exists(exe_path): 247 | name = entry.replace(".app", "") 248 | installations.append((exe_path, name)) 249 | 250 | installations.reverse() 251 | return installations 252 | 253 | def find_installation_data(): 254 | config_dir = get_user_config_dir() 255 | base_dir = os.path.join(config_dir, "Ableton") 256 | data_dirs = [] 257 | if not os.path.exists(base_dir): 258 | return data_dirs 259 | for entry in os.listdir(base_dir): 260 | if entry.startswith('.'): # Ignore folders starting with . 261 | continue 262 | entry_path = os.path.join(base_dir, entry) 263 | if os.path.isdir(entry_path) and "Live" in entry: 264 | data_dirs.append((entry_path, entry)) 265 | 266 | # Reverse the list so newest versions are at the top 267 | data_dirs.reverse() 268 | return data_dirs 269 | 270 | def open_folder(path): 271 | folder_path = os.path.dirname(path) 272 | if not os.path.exists(folder_path): 273 | print(RED + f"Folder does not exist: {folder_path}" + RESET) 274 | return False 275 | 276 | try: 277 | if platform.system() == "Windows": 278 | os.startfile(folder_path) 279 | elif platform.system() == "Darwin": 280 | subprocess.Popen(["open", folder_path]) 281 | else: # Linux and other Unix-like 282 | subprocess.Popen(["xdg-open", folder_path]) 283 | return True 284 | except Exception as e: 285 | print(RED + f"Failed to open folder: {e}" + RESET) 286 | return False 287 | 288 | def main(): 289 | # Set up command line argument parser 290 | parser = argparse.ArgumentParser(description='Ableton Live Patcher', add_help=False) 291 | parser.add_argument('--undo', action='store_true', help='Revert the patch (swap signkeys and skip authorization file)') 292 | parser.add_argument('--file_path', type=str, help='Path to Ableton Live executable (or "auto")') 293 | parser.add_argument('--old_signkey', type=str, help='Old signkey (hex string)') 294 | parser.add_argument('--new_signkey', type=str, help='New signkey (hex string)') 295 | parser.add_argument('--hwid', type=str, help='Hardware ID (24 hex chars or 6 groups of 4)') 296 | parser.add_argument('--edition', type=str, choices=['Lite', 'Intro', 'Standard', 'Suite'], help='Ableton edition') 297 | parser.add_argument('--version', type=int, help='Ableton version (e.g., 12)') 298 | parser.add_argument('--authorize_file_output', type=str, help='Output path for Authorize.auz (or "auto")') 299 | parser.add_argument('--help', action='store_true', help='Show this help message') 300 | 301 | # Parse arguments 302 | args, unknown = parser.parse_known_args() 303 | 304 | # Show help if requested 305 | if args.help: 306 | print(WHITE + "Ableton Live Patcher " + RED + patcher_version + RESET) 307 | print(WHITE + "Usage: python patcher.py [OPTIONS]" + RESET) 308 | print("\nOptions:") 309 | print(" --undo Revert the patch (swap signkeys and skip authorization file)") 310 | print(" --file_path PATH Path to Ableton Live executable or 'auto'") 311 | print(" --old_signkey HEX Old signkey (hex string)") 312 | print(" --new_signkey HEX New signkey (hex string)") 313 | print(" --hwid ID Hardware ID (24 hex chars or 6 groups of 4)") 314 | print(" --edition EDITION Ableton edition (Lite, Intro, Standard, Suite)") 315 | print(" --version NUMBER Ableton version (e.g., 12)") 316 | print(" --authorize_file_output PATH Output path for Authorize.auz or 'auto'") 317 | print(" --help Show this help message") 318 | print("\n" + YELLOW + "Note: Command line arguments override values in config.json" + RESET) 319 | return 320 | 321 | if platform.system() == "Windows" and not is_admin(): 322 | print(RED + "\nThis operation requires administrator privileges on Windows." + RESET) 323 | print(GREY + "Relaunching with admin rights..." + RESET) 324 | run_as_admin(args.undo) 325 | return 326 | 327 | print(RED + r""" ___. .__ __ _________ __ 328 | _____ \_ |__ | | _____/ |_ ____ ____ \_ ___ \____________ ____ | | __ ___________ 329 | \__ \ | __ \| | _/ __ \ __\/ _ \ / \/ \ \/\_ __ \__ \ _/ ___\| |/ // __ \_ __ \ 330 | / __ \| \_\ \ |_\ ___/| | ( <_> ) | \ \____| | \// __ \\ \___| <\ ___/| | \/ 331 | (____ /___ /____/\___ >__| \____/|___| /\______ /|__| (____ /\___ >__|_ \\___ >__| 332 | \/ \/ \/ \/ \/ \/ \/ \/ \/ 333 | """ + RESET) 334 | print(WHITE + "Made by " + RED + "devilAPI" + RESET) 335 | print(WHITE + "Version: " + RED + patcher_version + RESET) 336 | print(WHITE + "GitHub: " + GREY + "https://github.com/devilAPI/abletonCracker/" + RESET + "\n") 337 | 338 | if args.undo: 339 | print(PURPLE + "UNDO MODE: Reverting patch and skipping authorization file generation." + RESET) 340 | 341 | print(YELLOW + "NOTE: Make sure Ableton Live is not running while patching." + RESET) 342 | 343 | config_file = 'config.json' 344 | try: 345 | file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params = load_config(config_file, args) 346 | except Exception as e: 347 | print(RED + f"Error loading configuration: {e}" + RESET) 348 | input(GREY + "Press Enter to exit..." + RESET) 349 | return 350 | 351 | if file_path.lower() == "auto": 352 | installations = find_installations() 353 | if not installations: 354 | print(RED + "\nNo Ableton Live installations found. Please specify the path manually." + RESET) 355 | input(GREY + "Press Enter to exit..." + RESET) 356 | return 357 | print(WHITE + "\nFound Ableton installations:" + RESET) 358 | for i, (path, name) in enumerate(installations): 359 | print(WHITE + f"{i+1}. " + WHITE + f"{name}" + GREY + f" at {path}" + RESET) 360 | try: 361 | selection = int(input(WHITE + "\nSelect installation to patch: " + RED)) - 1 362 | if selection < 0 or selection >= len(installations): 363 | print(RED + "Invalid selection. Using first installation." + RESET) 364 | selection = 0 365 | file_path = installations[selection][0] 366 | print(WHITE + f"Selected: {file_path}" + RESET) 367 | except ValueError: 368 | print(RED + "Invalid input. Using first installation found." + RESET) 369 | file_path = installations[0][0] 370 | 371 | # Skip authorization file generation in undo mode 372 | if not args.undo: 373 | if authorize_file_output.lower() == "auto": 374 | data_dirs = find_installation_data() 375 | if not data_dirs: 376 | config_dir = get_user_config_dir() 377 | default_dir = os.path.join(config_dir, "Ableton", f"Live {version} {edition}") 378 | unlock_dir = os.path.join(default_dir, "Unlock") 379 | os.makedirs(unlock_dir, exist_ok=True) 380 | authorize_file_output = os.path.join(unlock_dir, "Authorize.auz") 381 | print(WHITE + f"\nUsing default authorization file location: " + WHITE + f"{authorize_file_output}" + RESET) 382 | else: 383 | print(WHITE + "\nFound Ableton data directories:" + RESET) 384 | for i, (path, name) in enumerate(data_dirs): 385 | print(WHITE + f"{i+1}. " + WHITE + f"{name}" + GREY + f" at {path}" + RESET) 386 | try: 387 | selection = int(input(WHITE + "\nSelect data directory: " + RESET)) - 1 388 | if selection < 0 or selection >= len(data_dirs): 389 | print(RED + "Invalid selection. Using first directory." + RESET) 390 | selection = 0 391 | unlock_dir = os.path.join(data_dirs[selection][0], "Unlock") 392 | os.makedirs(unlock_dir, exist_ok=True) 393 | authorize_file_output = os.path.join(unlock_dir, "Authorize.auz") 394 | print(WHITE + f"Selected: " + GREY + f"{authorize_file_output}" + RESET) 395 | except ValueError: 396 | print(RED + "Invalid input. Using first data directory found." + RESET) 397 | unlock_dir = os.path.join(data_dirs[0][0], "Unlock") 398 | os.makedirs(unlock_dir, exist_ok=True) 399 | authorize_file_output = os.path.join(unlock_dir, "Authorize.auz") 400 | 401 | try: 402 | team_r2r_key = construct_key(dsa_params) 403 | except Exception as e: 404 | print(RED + f"Error constructing DSA key: {e}" + RESET) 405 | input(GREY + "Press Enter to exit..." + RESET) 406 | return 407 | 408 | print(WHITE + "\nGenerating authorization keys..." + RESET) 409 | try: 410 | lines = list(generate_all(team_r2r_key, edition, version, hwid)) 411 | # Ensure the output directory exists 412 | os.makedirs(os.path.dirname(authorize_file_output), exist_ok=True) 413 | with open(authorize_file_output, "w", newline="\n") as f: 414 | f.write("\n".join(lines)) 415 | print("Authorization file created: " + WHITE + f"{authorize_file_output}" + RESET) 416 | except Exception as e: 417 | print(RED + f"Error generating authorization keys: {e}" + RESET) 418 | input(GREY + "Press Enter to exit..." + RESET) 419 | return 420 | 421 | print(WHITE + "\nPatching executable..." + RESET) 422 | try: 423 | replace_signkey_in_file(file_path, old_signkey, new_signkey, args.undo) 424 | print(GREEN + "\nPatch completed successfully!" + RESET) 425 | 426 | # Only show the authorization file instructions and folder opening in normal mode 427 | if not args.undo: 428 | print(GREEN + "SUCCESS! Your Ableton Live is now patched." + RESET) 429 | print(WHITE + "\nTo complete the activation:" + RESET) 430 | print(WHITE + "1. Start Ableton Live" + RESET) 431 | print(WHITE + "2. Simply drag and drop the " + YELLOW + "Authorize.auz" + WHITE + " file into the activation window" + RESET) 432 | print(YELLOW + "\nThat's it! No need to copy files manually." + RESET) 433 | 434 | try: 435 | response = input(WHITE + "\nDo you want to open the folder containing Authorize.auz? (y/N): " + RESET).strip().lower() 436 | if response in ['y', 'yes']: 437 | if open_folder(authorize_file_output): 438 | print(GREEN + "Folder opened successfully!" + RESET) 439 | else: 440 | print(RED + "Could not open the folder automatically." + RESET) 441 | print(WHITE + "Please navigate to the folder manually. (usually in the same folder where the python file is located in the 'output' folder)" + RESET) 442 | except: 443 | pass # If input fails, just continue 444 | 445 | input(GREY + "\nPress Enter to exit..." + RESET) 446 | except Exception as e: 447 | print(RED + f"\nPatch failed: {e}" + RESET) 448 | input(GREY + "Press Enter to exit..." + RESET) 449 | 450 | if __name__ == "__main__": 451 | main() --------------------------------------------------------------------------------