Skip to content

Commit 2eb0824

Browse files
committed
Add colors, beautify output and refactor code
1 parent e8c7d64 commit 2eb0824

File tree

3 files changed

+110
-133
lines changed

3 files changed

+110
-133
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ You can download the Ableton Installers directly from Ableton's servers. I made
2323
# How to use
2424

2525
1. Install Python (3.10 upwards) at [python.org](https://www.python.org/downloads/)
26-
2. Open your Terminal and run `python -m pip install cryptography` to install dependencies
26+
2. Open your Terminal and run `python -m pip install cryptography colorama` to install dependencies
2727
3. Find your Ableton HWID, open Ableton, and press "Authorize Ableton offline". You will find your HWID.
2828
4. Open `config.json` and change the variables to fit your Ableton Live installation.
2929
5. Run `patch_ableton.py`, your Ableton should be patched and the `Authorize.auz` file should generate.

patch_ableton.py

Lines changed: 62 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from cryptography.hazmat.primitives.asymmetric import dsa
1111
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
1212
from cryptography.hazmat.primitives.hashes import SHA1
13-
# Color support
13+
1414
try:
1515
from colorama import init, Fore, Style
1616
init(autoreset=True)
@@ -19,6 +19,12 @@ class Dummy:
1919
RESET = RED = WHITE = GREEN = LIGHTBLACK_EX = BRIGHT = ''
2020
Fore = Style = Dummy()
2121

22+
RED = Fore.RED + Style.BRIGHT
23+
WHITE = Fore.WHITE + Style.BRIGHT
24+
GREY = Fore.LIGHTBLACK_EX + Style.NORMAL
25+
GREEN = Fore.GREEN + Style.BRIGHT
26+
RESET = Style.RESET_ALL
27+
2228
def is_admin():
2329
try:
2430
return ctypes.windll.shell32.IsUserAnAdmin() != 0
@@ -35,32 +41,27 @@ def load_config(filename: str):
3541
try:
3642
with open(filename, 'r') as f:
3743
data = json.load(f)
38-
39-
file_path = data.get("file_path")
40-
old_signkey = data.get("old_signkey")
41-
new_signkey = data.get("new_signkey")
42-
hwid = data.get('hwid', '').upper()
43-
edition = data.get('edition', 'Suite')
44-
version = data.get('version', 12)
45-
authorize_file_output = data.get('authorize_file_output', 'Authorize.auz')
46-
dsa_params = data.get('dsa_parameters')
47-
48-
if not file_path or not old_signkey or not new_signkey:
49-
raise ValueError("JSON file must contain 'file_path', 'old_signkey', and 'new_signkey'.")
50-
if len(hwid) == 24:
51-
hwid = "-".join(hwid[i:i+4] for i in range(0, 24, 4))
52-
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}"
53-
54-
if not dsa_params:
55-
raise ValueError("DSA parameters are missing in the config file.")
56-
57-
return file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params
58-
44+
file_path = data.get("file_path")
45+
old_signkey = data.get("old_signkey")
46+
new_signkey = data.get("new_signkey")
47+
hwid = data.get('hwid', '').upper()
48+
edition = data.get('edition', 'Suite')
49+
version = data.get('version', 12)
50+
authorize_file_output = data.get('authorize_file_output', 'Authorize.auz')
51+
dsa_params = data.get('dsa_parameters')
52+
if not file_path or not old_signkey or not new_signkey:
53+
raise ValueError("JSON file must contain 'file_path', 'old_signkey', and 'new_signkey'.")
54+
if len(hwid) == 24:
55+
hwid = "-".join(hwid[i:i+4] for i in range(0, 24, 4))
56+
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}"
57+
if not dsa_params:
58+
raise ValueError("DSA parameters are missing in the config file.")
59+
return file_path, old_signkey, new_signkey, hwid, edition, version, authorize_file_output, dsa_params
5960
except FileNotFoundError:
60-
print(f"The JSON file {filename} was not found.")
61+
print(RED + f"The JSON file {filename} was not found." + RESET)
6162
raise
6263
except json.JSONDecodeError:
63-
print(f"Error parsing the JSON file {filename}.")
64+
print(RED + f"Error parsing the JSON file {filename}." + RESET)
6465
raise
6566

6667
def construct_key(dsa_params) -> dsa.DSAPrivateKey:
@@ -69,7 +70,6 @@ def construct_key(dsa_params) -> dsa.DSAPrivateKey:
6970
g = int(dsa_params['g'], 16)
7071
y = int(dsa_params['y'], 16)
7172
x = int(dsa_params['x'], 16)
72-
7373
params = dsa.DSAParameterNumbers(p, q, g)
7474
pub = dsa.DSAPublicNumbers(y, params)
7575
priv = dsa.DSAPrivateNumbers(x, pub)
@@ -78,55 +78,46 @@ def construct_key(dsa_params) -> dsa.DSAPrivateKey:
7878
def replace_signkey_in_file(file_path, old_signkey, new_signkey):
7979
if len(old_signkey) != len(new_signkey):
8080
raise ValueError("The new signkey must be the same length as the old signkey.")
81-
8281
if old_signkey.startswith("0x"):
8382
old_signkey = old_signkey[2:]
8483
if new_signkey.startswith("0x"):
8584
new_signkey = new_signkey[2:]
86-
8785
if not re.fullmatch(r'[0-9a-fA-F]+', old_signkey):
8886
raise ValueError("The old signkey is not valid.")
8987
if not re.fullmatch(r'[0-9a-fA-F]+', new_signkey):
9088
raise ValueError("The new signkey is not valid.")
91-
9289
try:
9390
with open(file_path, 'rb') as file:
9491
content = file.read()
95-
9692
old_signkey_bytes = bytes.fromhex(old_signkey)
9793
new_signkey_bytes = bytes.fromhex(new_signkey)
98-
9994
if old_signkey_bytes not in content:
10095
if new_signkey_bytes in content:
101-
print(f"The new signkey \n'{new_signkey}' \nis already present in the file. Ableton is already patched.")
96+
print(WHITE + "The new signkey is already present in the file. Ableton is already patched." + RESET)
10297
else:
103-
print(f"Neither the old nor the new signkey was found in the file. You may be running an unsupported version or a different patch.")
98+
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)
10499
else:
105-
print(f"The old signkey '{old_signkey}' was found. Replacing...")
106-
100+
print(WHITE + "The old signkey was found. Replacing..." + RESET)
107101
content = content.replace(old_signkey_bytes, new_signkey_bytes)
108-
109102
with open(file_path, 'wb') as file:
110103
file.write(content)
111-
112104
if old_signkey_bytes in content:
113-
print("Error: The old signkey is still present in the file.")
105+
print(RED + "Error: The old signkey is still present in the file." + RESET)
114106
else:
115-
print("Signkey successfully replaced.")
116-
107+
print(GREEN + "Signkey successfully replaced." + RESET)
117108
except PermissionError:
118-
print("\nPermission denied! Try running the script as Administrator.")
109+
print(RED + "\nPermission denied! Try running the script as Administrator." + RESET)
119110
if platform.system() == "Windows":
120-
print("Relaunching with admin privileges...")
111+
print(GREY + "Relaunching with admin privileges..." + RESET)
121112
run_as_admin()
122113
else:
123-
print("On Linux/macOS, try running with sudo.")
124-
raise
114+
print(GREY + "On Linux/macOS, try running with sudo." + RESET)
115+
raise
125116
except FileNotFoundError:
126-
print(f"The file '{file_path}' was not found.")
117+
print(RED + f"The file '{file_path}' was not found." + RESET)
127118
raise
128119
except Exception as e:
129-
print(f"An error occurred: {e}")
120+
print(RED + f"An error occurred: {e}" + RESET)
130121
raise
131122

132123
def sign(k: dsa.DSAPrivateKey, m: str) -> str:
@@ -138,11 +129,11 @@ def sign(k: dsa.DSAPrivateKey, m: str) -> str:
138129

139130
def fix_group_checksum(group_number: int, n: int) -> int:
140131
checksum = n >> 4 & 0xf ^ \
141-
n >> 5 & 0x8 ^ \
142-
n >> 9 & 0x7 ^ \
143-
n >> 11 & 0xe ^ \
144-
n >> 15 & 0x1 ^ \
145-
group_number
132+
n >> 5 & 0x8 ^ \
133+
n >> 9 & 0x7 ^ \
134+
n >> 11 & 0xe ^ \
135+
n >> 15 & 0x1 ^ \
136+
group_number
146137
return n & 0xfff0 | checksum
147138

148139
def overall_checksum(groups: list[int]) -> int:
@@ -158,17 +149,8 @@ def overall_checksum(groups: list[int]) -> int:
158149
return r & 0xffff
159150

160151
def random_serial():
161-
"""
162-
3xxc-xxxc-xxxc-xxxc-xxxc-dddd
163-
x is random
164-
c is a checksum over each group
165-
d is a checksum over all groups
166-
"""
167-
groups = [randint(0x3000, 0x3fff),
168-
randint(0x0000, 0xffff),
169-
randint(0x0000, 0xffff),
170-
randint(0x0000, 0xffff),
171-
randint(0x0000, 0xffff)]
152+
""" 3xxc-xxxc-xxxc-xxxc-xxxc-dddd x is random c is a checksum over each group d is a checksum over all groups """
153+
groups = [randint(0x3000, 0x3fff), randint(0x0000, 0xffff), randint(0x0000, 0xffff), randint(0x0000, 0xffff), randint(0x0000, 0xffff)]
172154
for i in range(5):
173155
groups[i] = fix_group_checksum(i, groups[i])
174156
d = overall_checksum(groups)
@@ -201,18 +183,16 @@ def get_user_config_dir():
201183
return os.getenv('APPDATA')
202184
elif system == "Darwin":
203185
return os.path.join(os.path.expanduser("~"), "Library", "Application Support")
204-
else:
186+
else:
205187
return os.getenv('XDG_CONFIG_HOME', os.path.join(os.path.expanduser("~"), ".config"))
206188

207189
def find_installations():
208190
system = platform.system()
209191
installations = []
210-
211192
if system == "Windows":
212193
base_dir = "C:\\ProgramData\\Ableton"
213194
if not os.path.exists(base_dir):
214195
return installations
215-
216196
for entry in os.listdir(base_dir):
217197
if "Live" in entry:
218198
entry_path = os.path.join(base_dir, entry)
@@ -223,61 +203,47 @@ def find_installations():
223203
if file.endswith(".exe") and "Live" in file:
224204
exe_path = os.path.join(program_dir, file)
225205
installations.append((exe_path, entry))
226-
227206
elif system == "Darwin":
228207
base_dir = "/Applications"
229208
if not os.path.exists(base_dir):
230209
return installations
231-
232210
for entry in os.listdir(base_dir):
233211
if entry.endswith(".app") and "Ableton Live" in entry:
234212
app_path = os.path.join(base_dir, entry)
235213
exe_path = os.path.join(app_path, "Contents", "MacOS", "Live")
236214
if os.path.exists(exe_path):
237215
name = entry.replace(".app", "")
238216
installations.append((exe_path, name))
239-
240217
return installations
241218

242219
def find_installation_data():
243220
config_dir = get_user_config_dir()
244221
base_dir = os.path.join(config_dir, "Ableton")
245222
data_dirs = []
246-
247223
if not os.path.exists(base_dir):
248224
return data_dirs
249-
250225
for entry in os.listdir(base_dir):
251226
entry_path = os.path.join(base_dir, entry)
252227
if os.path.isdir(entry_path) and "Live" in entry:
253228
data_dirs.append((entry_path, entry))
254-
255229
return data_dirs
256230

257231
def main():
258-
# Colors
259-
RED = Fore.RED + Style.BRIGHT
260-
WHITE = Fore.WHITE + Style.BRIGHT
261-
GREY = Fore.LIGHTBLACK_EX + Style.NORMAL
262-
LIME = Fore.GREEN + Style.BRIGHT
263-
RESET = Style.RESET_ALL
264-
265232
if platform.system() == "Windows" and not is_admin():
266233
print(RED + "\nThis operation requires administrator privileges on Windows." + RESET)
267234
print(GREY + "Relaunching with admin rights..." + RESET)
268235
run_as_admin()
269236
return
270-
# ASCII art in bright red
271-
print(RED + r"""
272-
___. .__ __ _________ __
237+
238+
print(RED + r""" ___. .__ __ _________ __
273239
_____ \_ |__ | | _____/ |_ ____ ____ \_ ___ \____________ ____ | | __ ___________
274-
\__ \ | __ \| | _/ __ \ __\/ _ \ / \/ \ \/_ __ \__ \ _/ ___\| |/ // __ \_ __ \
240+
\__ \ | __ \| | _/ __ \ __\/ _ \ / \/ \ \/\_ __ \__ \ _/ ___\| |/ // __ \_ __ \
275241
/ __ \| \_\ \ |_\ ___/| | ( <_> ) | \ \____| | \// __ \\ \___| <\ ___/| | \/
276242
(____ /___ /____/\___ >__| \____/|___| /\______ /|__| (____ /\___ >__|_ \\___ >__|
277243
\/ \/ \/ \/ \/ \/ \/ \/ \/
278244
""" + RESET)
279-
print(LIME + "Made by devilAPI" + RESET)
280-
print(GREY + "GitHub: " + LIME + "https://github.com/devilAPI/abletonCracker/" + RESET + "\n")
245+
print(WHITE + "Made by " + RED + "devilAPI" + RESET)
246+
print(WHITE + "GitHub: " + GREY + "https://github.com/devilAPI/abletonCracker/" + RESET + "\n")
281247

282248
config_file = 'config.json'
283249
try:
@@ -293,18 +259,16 @@ def main():
293259
print(RED + "\nNo Ableton Live installations found. Please specify the path manually." + RESET)
294260
input(GREY + "Press Enter to exit..." + RESET)
295261
return
296-
297-
print(LIME + "\nFound Ableton installations:" + RESET)
262+
print(WHITE + "\nFound Ableton installations:" + RESET)
298263
for i, (path, name) in enumerate(installations):
299-
print(WHITE + f"{i+1}. " + LIME + f"{name}" + GREY + f" at {path}" + RESET)
300-
264+
print(WHITE + f"{i+1}. " + WHITE + f"{name}" + GREY + f" at {path}" + RESET)
301265
try:
302-
selection = int(input(LIME + "\nSelect installation to patch: " + RESET)) - 1
266+
selection = int(input(WHITE + "\nSelect installation to patch: " + RED)) - 1
303267
if selection < 0 or selection >= len(installations):
304268
print(RED + "Invalid selection. Using first installation." + RESET)
305269
selection = 0
306270
file_path = installations[selection][0]
307-
print(LIME + f"Selected: {file_path}" + RESET)
271+
print(WHITE + f"Selected: {file_path}" + RESET)
308272
except ValueError:
309273
print(RED + "Invalid input. Using first installation found." + RESET)
310274
file_path = installations[0][0]
@@ -317,21 +281,20 @@ def main():
317281
unlock_dir = os.path.join(default_dir, "Unlock")
318282
os.makedirs(unlock_dir, exist_ok=True)
319283
authorize_file_output = os.path.join(unlock_dir, "Authorize.auz")
320-
print(LIME + f"\nUsing default authorization file location: " + WHITE + f"{authorize_file_output}" + RESET)
284+
print(WHITE + f"\nUsing default authorization file location: " + WHITE + f"{authorize_file_output}" + RESET)
321285
else:
322-
print(LIME + "\nFound Ableton data directories:" + RESET)
286+
print(WHITE + "\nFound Ableton data directories:" + RESET)
323287
for i, (path, name) in enumerate(data_dirs):
324-
print(WHITE + f"{i+1}. " + LIME + f"{name}" + GREY + f" at {path}" + RESET)
325-
288+
print(WHITE + f"{i+1}. " + WHITE + f"{name}" + GREY + f" at {path}" + RESET)
326289
try:
327-
selection = int(input(LIME + "\nSelect data directory: " + RESET)) - 1
290+
selection = int(input(WHITE + "\nSelect data directory: " + RESET)) - 1
328291
if selection < 0 or selection >= len(data_dirs):
329292
print(RED + "Invalid selection. Using first directory." + RESET)
330293
selection = 0
331294
unlock_dir = os.path.join(data_dirs[selection][0], "Unlock")
332295
os.makedirs(unlock_dir, exist_ok=True)
333296
authorize_file_output = os.path.join(unlock_dir, "Authorize.auz")
334-
print(LIME + f"Selected: " + WHITE + f"{authorize_file_output}" + RESET)
297+
print(WHITE + f"Selected: " + GREY + f"{authorize_file_output}" + RESET)
335298
except ValueError:
336299
print(RED + "Invalid input. Using first data directory found." + RESET)
337300
unlock_dir = os.path.join(data_dirs[0][0], "Unlock")
@@ -345,21 +308,21 @@ def main():
345308
input(GREY + "Press Enter to exit..." + RESET)
346309
return
347310

348-
print(LIME + "\nGenerating authorization keys..." + RESET)
311+
print(WHITE + "\nGenerating authorization keys..." + RESET)
349312
try:
350313
lines = list(generate_all(team_r2r_key, edition, version, hwid))
351314
with open(authorize_file_output, "w", newline="\n") as f:
352315
f.write("\n".join(lines))
353-
print(LIME + "Authorization file created: " + WHITE + f"{authorize_file_output}" + RESET)
316+
print("Authorization file created: " + WHITE + f"{authorize_file_output}" + RESET)
354317
except Exception as e:
355318
print(RED + f"Error generating authorization keys: {e}" + RESET)
356319
input(GREY + "Press Enter to exit..." + RESET)
357320
return
358321

359-
print(LIME + "\nPatching executable..." + RESET)
322+
print(WHITE + "\nPatching executable..." + RESET)
360323
try:
361324
replace_signkey_in_file(file_path, old_signkey, new_signkey)
362-
print(LIME + "\nPatch completed successfully!" + RESET)
325+
print(WHITE + "\nPatch completed successfully!" + RESET)
363326
input(GREY + "Press Enter to exit..." + RESET)
364327
except Exception as e:
365328
print(RED + f"\nPatch failed: {e}" + RESET)

0 commit comments

Comments
 (0)