Add complete guide and all config variants
This commit is contained in:
8
hackintosh-guide/Utilities/macrecovery/README.md
Executable file
8
hackintosh-guide/Utilities/macrecovery/README.md
Executable file
@@ -0,0 +1,8 @@
|
||||
## macrecovery
|
||||
|
||||
macrecovery is a tool that helps to automate recovery interaction. It can be used to download diagnostics and recovery as well as analyse MLB.
|
||||
|
||||
Requires python3 to run. Run with `-h` argument to see all available arguments.
|
||||
|
||||
To create a disk image for a virtual machine installation use `build-image.sh`.
|
||||
|
||||
79
hackintosh-guide/Utilities/macrecovery/boards.json
Executable file
79
hackintosh-guide/Utilities/macrecovery/boards.json
Executable file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"Mac-EE2EBD4B90B839A8": "13.7.7",
|
||||
"Mac-BE0E8AC46FE800CC": "11.7.10",
|
||||
"Mac-9AE82516C7C6B903": "12.7.6",
|
||||
"Mac-942452F5819B1C1B": "10.13.6",
|
||||
"Mac-942C5DF58193131B": "10.13.6",
|
||||
"Mac-C08A6BB70A942AC2": "10.13.6",
|
||||
"Mac-742912EFDBEE19B3": "10.13.6",
|
||||
"Mac-66F35F19FE2A0D05": "10.15.7",
|
||||
"Mac-2E6FAB96566FE58C": "10.15.7",
|
||||
"Mac-35C1E88140C3E6CF": "11.7.10",
|
||||
"Mac-7DF21CB3ED6977E5": "11.7.10",
|
||||
"Mac-9F18E312C5C2BF0B": "12.7.6",
|
||||
"Mac-937CB26E2E02BB01": "12.7.6",
|
||||
"Mac-827FAC58A8FDFA22": "14.7.7",
|
||||
"Mac-226CB3C6A851A671": "14.7.7",
|
||||
"Mac-0CFF9C7C2B63DF8D": "15.6",
|
||||
"Mac-C3EC7CD22292981F": "10.15.7",
|
||||
"Mac-AFD8A9D944EA4843": "10.15.7",
|
||||
"Mac-189A3D4F975D5FFC": "11.7.10",
|
||||
"Mac-3CBD00234E554E41": "11.7.10",
|
||||
"Mac-2BD1B31983FE1663": "11.7.10",
|
||||
"Mac-06F11FD93F0323C5": "12.7.6",
|
||||
"Mac-06F11F11946D27C5": "12.7.6",
|
||||
"Mac-E43C1C25D4880AD6": "12.7.6",
|
||||
"Mac-473D31EABEB93F9B": "12.7.6",
|
||||
"Mac-66E35819EE2D0D05": "12.7.6",
|
||||
"Mac-A5C67F76ED83108C": "12.7.6",
|
||||
"Mac-B4831CEBD52A0C4C": "13.7.7",
|
||||
"Mac-CAD6701F7CEA0921": "13.7.7",
|
||||
"Mac-551B86E5744E2388": "13.7.7",
|
||||
"Mac-937A206F2EE63C01": "15.6",
|
||||
"Mac-827FB448E656EC26": "15.6",
|
||||
"Mac-1E7E29AD0135F9BC": "15.6",
|
||||
"Mac-53FDB3D8DB8CA971": "15.6",
|
||||
"Mac-E1008331FDC96864": "latest",
|
||||
"Mac-5F9802EFE386AA28": "latest",
|
||||
"Mac-E7203C0F68AA0004": "15.6",
|
||||
"Mac-A61BADE1FDAD7B05": "latest",
|
||||
"Mac-F22589C8": "10.13.6",
|
||||
"Mac-94245B3640C91C81": "10.13.6",
|
||||
"Mac-94245A3940C91C80": "10.13.6",
|
||||
"Mac-942459F5819B171B": "10.13.6",
|
||||
"Mac-4B7AC7E43945597E": "10.15.7",
|
||||
"Mac-6F01561E16C75D06": "10.15.7",
|
||||
"Mac-F60DEB81FF30ACF6": "12.7.6",
|
||||
"Mac-27AD2F918AE68F61": "latest",
|
||||
"Mac-F2208EC8": "10.13.6",
|
||||
"Mac-8ED6AF5B48C039E1": "10.13.6",
|
||||
"Mac-4BC72D62AD45599E": "10.13.6",
|
||||
"Mac-7BA5B2794B2CDB12": "10.13.6",
|
||||
"Mac-031AEE4D24BFF0B1": "10.15.7",
|
||||
"Mac-F65AE981FFA204ED": "10.15.7",
|
||||
"Mac-35C5E08120C7EEAF": "12.7.6",
|
||||
"Mac-7BA5B2DFE22DDD8C": "15.6",
|
||||
"Mac-942B5BF58194151B": "10.13.6",
|
||||
"Mac-942B59F58194171B": "10.13.6",
|
||||
"Mac-00BE6ED71E35EB86": "10.15.7",
|
||||
"Mac-FC02E91DDD3FA6A4": "10.15.7",
|
||||
"Mac-7DF2A3B5E5D671ED": "10.15.7",
|
||||
"Mac-031B6874CF7F642A": "10.15.7",
|
||||
"Mac-27ADBB7B4CEE8E61": "10.15.7",
|
||||
"Mac-77EB7D7DAF985301": "10.15.7",
|
||||
"Mac-81E3E92DD6088272": "11.7.10",
|
||||
"Mac-42FD25EABCABB274": "11.7.10",
|
||||
"Mac-A369DDC4E67F1C45": "12.7.6",
|
||||
"Mac-FFE5EF870D7BA81A": "12.7.6",
|
||||
"Mac-DB15BD556843C820": "12.7.6",
|
||||
"Mac-65CE76090165799A": "12.7.6",
|
||||
"Mac-B809C3757DA9BB8D": "12.7.6",
|
||||
"Mac-4B682C642B45593E": "13.7.7",
|
||||
"Mac-77F17D7DA9285301": "13.7.7",
|
||||
"Mac-BE088AF8C5EB4FA2": "13.7.7",
|
||||
"Mac-AA95B1DDAB278B95": "15.6",
|
||||
"Mac-63001698E7A34814": "15.6",
|
||||
"Mac-CFF7D910A743CAAF": "latest",
|
||||
"Mac-AF89B6D9451A490B": "latest",
|
||||
"Mac-7BA5B2D9E42DDD94": "15.6"
|
||||
}
|
||||
24
hackintosh-guide/Utilities/macrecovery/build-image.sh
Executable file
24
hackintosh-guide/Utilities/macrecovery/build-image.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
FORMAT="raw"
|
||||
else
|
||||
FORMAT="${1}"
|
||||
fi
|
||||
|
||||
rm -rf Recovery.RO.dmg Recovery.raw "Recovery.${FORMAT}" Recovery.dmg.sparseimage
|
||||
hdiutil create -size 900m -layout "UNIVERSAL HD" -type SPARSE -o Recovery.dmg
|
||||
newDevice=$(hdiutil attach -nomount Recovery.dmg.sparseimage | head -n 1 | awk '{print $1}')
|
||||
echo newdevice "$newDevice"
|
||||
diskutil partitionDisk "${newDevice}" 1 MBR fat32 RECOVERY R
|
||||
N=$(echo "$newDevice" | tr -dc '0-9')
|
||||
diskutil mount disk"${N}"s1
|
||||
MOUNT="$(diskutil info disk"${N}"s1 | sed -n 's/.*Mount Point: *//p')"
|
||||
mkdir -p "$MOUNT/com.apple.recovery.boot"
|
||||
cp ./*.dmg ./*.chunklist "$MOUNT/com.apple.recovery.boot/"
|
||||
diskutil umount disk"${N}"s1
|
||||
hdiutil detach "$newDevice"
|
||||
hdiutil convert -format UDZO Recovery.dmg.sparseimage -o Recovery.RO.dmg
|
||||
rm Recovery.dmg.sparseimage
|
||||
qemu-img convert -f dmg -O "${FORMAT}" Recovery.RO.dmg "Recovery.${FORMAT}"
|
||||
rm Recovery.RO.dmg
|
||||
519
hackintosh-guide/Utilities/macrecovery/macrecovery.py
Executable file
519
hackintosh-guide/Utilities/macrecovery/macrecovery.py
Executable file
@@ -0,0 +1,519 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Gather recovery information for Macs.
|
||||
|
||||
Copyright (c) 2019, vit9696
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import linecache
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
import string
|
||||
import sys
|
||||
|
||||
try:
|
||||
from urllib.request import Request, HTTPError, urlopen
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
print('ERROR: Python 2 is not supported, please use Python 3')
|
||||
sys.exit(1)
|
||||
|
||||
SELF_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# MacPro7,1
|
||||
RECENT_MAC = 'Mac-27AD2F918AE68F61'
|
||||
MLB_ZERO = '00000000000000000'
|
||||
MLB_VALID = 'F5K105303J9K3F71M'
|
||||
MLB_PRODUCT = 'F5K00000000K3F700'
|
||||
|
||||
TYPE_SID = 16
|
||||
TYPE_K = 64
|
||||
TYPE_FG = 64
|
||||
|
||||
INFO_PRODUCT = 'AP'
|
||||
INFO_IMAGE_LINK = 'AU'
|
||||
INFO_IMAGE_HASH = 'AH'
|
||||
INFO_IMAGE_SESS = 'AT'
|
||||
INFO_SIGN_LINK = 'CU'
|
||||
INFO_SIGN_HASH = 'CH'
|
||||
INFO_SIGN_SESS = 'CT'
|
||||
INFO_REQURED = [INFO_PRODUCT, INFO_IMAGE_LINK, INFO_IMAGE_HASH, INFO_IMAGE_SESS, INFO_SIGN_LINK, INFO_SIGN_HASH, INFO_SIGN_SESS]
|
||||
|
||||
# Use -2 for better resize stability on Windows
|
||||
TERMINAL_MARGIN = 2
|
||||
|
||||
def run_query(url, headers, post=None, raw=False):
|
||||
if post is not None:
|
||||
data = '\n'.join(entry + '=' + post[entry] for entry in post).encode()
|
||||
else:
|
||||
data = None
|
||||
req = Request(url=url, headers=headers, data=data)
|
||||
try:
|
||||
response = urlopen(req)
|
||||
if raw:
|
||||
return response
|
||||
return dict(response.info()), response.read()
|
||||
except HTTPError as e:
|
||||
print(f'ERROR: "{e}" when connecting to {url}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_id(id_type, id_value=None):
|
||||
return id_value or ''.join(random.choices(string.hexdigits[:16].upper(), k=id_type))
|
||||
|
||||
|
||||
def product_mlb(mlb):
|
||||
return '00000000000' + mlb[11:15] + '00'
|
||||
|
||||
|
||||
def mlb_from_eeee(eeee):
|
||||
if len(eeee) != 4:
|
||||
print('ERROR: Invalid EEEE code length!')
|
||||
sys.exit(1)
|
||||
|
||||
return f'00000000000{eeee}00'
|
||||
|
||||
|
||||
# zhangyoufu https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7#gistcomment-3061078 2021-10-08
|
||||
Apple_EFI_ROM_public_key_1 = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF
|
||||
|
||||
ChunkListHeader = struct.Struct('<4sIBBBxQQQ')
|
||||
assert ChunkListHeader.size == 0x24
|
||||
|
||||
Chunk = struct.Struct('<I32s')
|
||||
assert Chunk.size == 0x24
|
||||
|
||||
|
||||
def verify_chunklist(cnkpath):
|
||||
with open(cnkpath, 'rb') as f:
|
||||
hash_ctx = hashlib.sha256()
|
||||
data = f.read(ChunkListHeader.size)
|
||||
hash_ctx.update(data)
|
||||
magic, header_size, file_version, chunk_method, signature_method, chunk_count, chunk_offset, signature_offset = ChunkListHeader.unpack(data)
|
||||
assert magic == b'CNKL'
|
||||
assert header_size == ChunkListHeader.size
|
||||
assert file_version == 1
|
||||
assert chunk_method == 1
|
||||
assert signature_method in [1, 2]
|
||||
assert chunk_count > 0
|
||||
assert chunk_offset == 0x24
|
||||
assert signature_offset == chunk_offset + Chunk.size * chunk_count
|
||||
for _ in range(chunk_count):
|
||||
data = f.read(Chunk.size)
|
||||
hash_ctx.update(data)
|
||||
chunk_size, chunk_sha256 = Chunk.unpack(data)
|
||||
yield chunk_size, chunk_sha256
|
||||
digest = hash_ctx.digest()
|
||||
if signature_method == 1:
|
||||
data = f.read(256)
|
||||
assert len(data) == 256
|
||||
signature = int.from_bytes(data, 'little')
|
||||
plaintext = int(f'0x1{"f"*404}003031300d060960864801650304020105000420{"0"*64}', 16) | int.from_bytes(digest, 'big')
|
||||
assert pow(signature, 0x10001, Apple_EFI_ROM_public_key_1) == plaintext
|
||||
elif signature_method == 2:
|
||||
data = f.read(32)
|
||||
assert data == digest
|
||||
raise RuntimeError('Chunklist missing digital signature')
|
||||
else:
|
||||
raise NotImplementedError
|
||||
assert f.read(1) == b''
|
||||
|
||||
|
||||
def get_session(args):
|
||||
headers = {
|
||||
'Host': 'osrecovery.apple.com',
|
||||
'Connection': 'close',
|
||||
'User-Agent': 'InternetRecovery/1.0',
|
||||
}
|
||||
|
||||
headers, _ = run_query('http://osrecovery.apple.com/', headers)
|
||||
|
||||
if args.verbose:
|
||||
print('Session headers:')
|
||||
for header in headers:
|
||||
print(f'{header}: {headers[header]}')
|
||||
|
||||
for header in headers:
|
||||
if header.lower() == 'set-cookie':
|
||||
cookies = headers[header].split('; ')
|
||||
for cookie in cookies:
|
||||
return cookie if cookie.startswith('session=') else ...
|
||||
|
||||
raise RuntimeError('No session in headers ' + str(headers))
|
||||
|
||||
|
||||
def get_image_info(session, bid, mlb=MLB_ZERO, diag=False, os_type='default', cid=None):
|
||||
headers = {
|
||||
'Host': 'osrecovery.apple.com',
|
||||
'Connection': 'close',
|
||||
'User-Agent': 'InternetRecovery/1.0',
|
||||
'Cookie': session,
|
||||
'Content-Type': 'text/plain',
|
||||
}
|
||||
|
||||
post = {
|
||||
'cid': generate_id(TYPE_SID, cid),
|
||||
'sn': mlb,
|
||||
'bid': bid,
|
||||
'k': generate_id(TYPE_K),
|
||||
'fg': generate_id(TYPE_FG)
|
||||
}
|
||||
|
||||
if diag:
|
||||
url = 'http://osrecovery.apple.com/InstallationPayload/Diagnostics'
|
||||
else:
|
||||
url = 'http://osrecovery.apple.com/InstallationPayload/RecoveryImage'
|
||||
post['os'] = os_type
|
||||
|
||||
headers, output = run_query(url, headers, post)
|
||||
|
||||
output = output.decode('utf-8')
|
||||
info = {}
|
||||
for line in output.split('\n'):
|
||||
try:
|
||||
key, value = line.split(': ')
|
||||
info[key] = value
|
||||
except KeyError:
|
||||
continue
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
for k in INFO_REQURED:
|
||||
if k not in info:
|
||||
raise RuntimeError(f'Missing key {k}')
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def save_image(url, sess, filename='', directory=''):
|
||||
purl = urlparse(url)
|
||||
headers = {
|
||||
'Host': purl.hostname,
|
||||
'Connection': 'close',
|
||||
'User-Agent': 'InternetRecovery/1.0',
|
||||
'Cookie': '='.join(['AssetToken', sess])
|
||||
}
|
||||
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
if filename == '':
|
||||
filename = os.path.basename(purl.path)
|
||||
if filename.find(os.sep) >= 0 or filename == '':
|
||||
raise RuntimeError('Invalid save path ' + filename)
|
||||
|
||||
print(f'Saving {url} to {directory}{os.sep}{filename}...')
|
||||
|
||||
with open(os.path.join(directory, filename), 'wb') as fh:
|
||||
response = run_query(url, headers, raw=True)
|
||||
headers = dict(response.headers)
|
||||
totalsize = -1
|
||||
for header in headers:
|
||||
if header.lower() == 'content-length':
|
||||
totalsize = int(headers[header])
|
||||
break
|
||||
size = 0
|
||||
oldterminalsize = 0
|
||||
while True:
|
||||
chunk = response.read(2**20)
|
||||
if not chunk:
|
||||
break
|
||||
fh.write(chunk)
|
||||
size += len(chunk)
|
||||
try:
|
||||
terminalsize = max(os.get_terminal_size().columns - TERMINAL_MARGIN, 0)
|
||||
except OSError:
|
||||
terminalsize = 80
|
||||
if oldterminalsize != terminalsize:
|
||||
print(f'\r{"":<{terminalsize}}', end='')
|
||||
oldterminalsize = terminalsize
|
||||
if totalsize > 0:
|
||||
progress = size / totalsize
|
||||
barwidth = terminalsize // 3
|
||||
print(f'\r{size / (2**20):.1f}/{totalsize / (2**20):.1f} MB ', end='')
|
||||
if terminalsize > 55:
|
||||
print(f'|{"=" * int(barwidth * progress):<{barwidth}}|', end='')
|
||||
print(f' {progress*100:.1f}% downloaded', end='')
|
||||
else:
|
||||
# Fallback if Content-Length isn't available
|
||||
print(f'\r{size / (2**20)} MB downloaded...', end='')
|
||||
sys.stdout.flush()
|
||||
print('\nDownload complete!')
|
||||
|
||||
return os.path.join(directory, os.path.basename(filename))
|
||||
|
||||
|
||||
def verify_image(dmgpath, cnkpath):
|
||||
print('Verifying image with chunklist...')
|
||||
|
||||
with open(dmgpath, 'rb') as dmgf:
|
||||
for cnkcount, (cnksize, cnkhash) in enumerate(verify_chunklist(cnkpath), 1):
|
||||
terminalsize = max(os.get_terminal_size().columns - TERMINAL_MARGIN, 0)
|
||||
print(f'\r{f"Chunk {cnkcount} ({cnksize} bytes)":<{terminalsize}}', end='')
|
||||
sys.stdout.flush()
|
||||
cnk = dmgf.read(cnksize)
|
||||
if len(cnk) != cnksize:
|
||||
raise RuntimeError(f'Invalid chunk {cnkcount} size: expected {cnksize}, read {len(cnk)}')
|
||||
if hashlib.sha256(cnk).digest() != cnkhash:
|
||||
raise RuntimeError(f'Invalid chunk {cnkcount}: hash mismatch')
|
||||
if dmgf.read(1) != b'':
|
||||
raise RuntimeError('Invalid image: larger than chunklist')
|
||||
print('\nImage verification complete!')
|
||||
|
||||
|
||||
def action_download(args):
|
||||
"""
|
||||
Reference information for queries:
|
||||
|
||||
Recovery latest:
|
||||
cid=3076CE439155BA14
|
||||
sn=...
|
||||
bid=Mac-E43C1C25D4880AD6
|
||||
k=4BE523BB136EB12B1758C70DB43BDD485EBCB6A457854245F9E9FF0587FB790C
|
||||
os=latest
|
||||
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
|
||||
|
||||
Recovery default:
|
||||
cid=4A35CB95FF396EE7
|
||||
sn=...
|
||||
bid=Mac-E43C1C25D4880AD6
|
||||
k=0A385E6FFC3DDD990A8A1F4EC8B98C92CA5E19C9FF1DD26508C54936D8523121
|
||||
os=default
|
||||
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
|
||||
|
||||
Diagnostics:
|
||||
cid=050C59B51497CEC8
|
||||
sn=...
|
||||
bid=Mac-E43C1C25D4880AD6
|
||||
k=37D42A8282FE04A12A7D946304F403E56A2155B9622B385F3EB959A2FBAB8C93
|
||||
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
|
||||
"""
|
||||
|
||||
session = get_session(args)
|
||||
info = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=args.diagnostics, os_type=args.os_type)
|
||||
if args.verbose:
|
||||
print(info)
|
||||
print(f'Downloading {info[INFO_PRODUCT]}...')
|
||||
cnkname = '' if args.basename == '' else args.basename + '.chunklist'
|
||||
cnkpath = save_image(info[INFO_SIGN_LINK], info[INFO_SIGN_SESS], cnkname, args.outdir)
|
||||
dmgname = '' if args.basename == '' else args.basename + '.dmg'
|
||||
dmgpath = save_image(info[INFO_IMAGE_LINK], info[INFO_IMAGE_SESS], dmgname, args.outdir)
|
||||
try:
|
||||
verify_image(dmgpath, cnkpath)
|
||||
return 0
|
||||
except Exception as err:
|
||||
if isinstance(err, AssertionError) and str(err) == '':
|
||||
try:
|
||||
tb = sys.exc_info()[2]
|
||||
while tb.tb_next:
|
||||
tb = tb.tb_next
|
||||
err = linecache.getline(tb.tb_frame.f_code.co_filename, tb.tb_lineno, tb.tb_frame.f_globals).strip()
|
||||
except Exception:
|
||||
err = "Invalid chunklist"
|
||||
print(f'\rImage verification failed. ({err})')
|
||||
return 1
|
||||
|
||||
|
||||
def action_selfcheck(args):
|
||||
"""
|
||||
Sanity check server logic for recovery:
|
||||
|
||||
if not valid(bid):
|
||||
return error()
|
||||
ppp = get_ppp(sn)
|
||||
if not valid(ppp):
|
||||
return latest_recovery(bid = bid) # Returns newest for bid.
|
||||
if valid(sn):
|
||||
if os == 'default':
|
||||
return default_recovery(sn = sn, ppp = ppp) # Returns oldest for sn.
|
||||
else:
|
||||
return latest_recovery(sn = sn, ppp = ppp) # Returns newest for sn.
|
||||
return default_recovery(ppp = ppp) # Returns oldest.
|
||||
"""
|
||||
|
||||
session = get_session(args)
|
||||
valid_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='default')
|
||||
valid_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='latest')
|
||||
product_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='default')
|
||||
product_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='latest')
|
||||
generic_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='default')
|
||||
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
|
||||
|
||||
if args.verbose:
|
||||
print(valid_default)
|
||||
print(valid_latest)
|
||||
print(product_default)
|
||||
print(product_latest)
|
||||
print(generic_default)
|
||||
print(generic_latest)
|
||||
|
||||
if valid_default[INFO_PRODUCT] == valid_latest[INFO_PRODUCT]:
|
||||
# Valid MLB must give different default and latest if this is not a too new product.
|
||||
print(f'ERROR: Cannot determine any previous product, got {valid_default[INFO_PRODUCT]}')
|
||||
return 1
|
||||
|
||||
if product_default[INFO_PRODUCT] != product_latest[INFO_PRODUCT]:
|
||||
# Product-only MLB must give the same value for default and latest.
|
||||
print(f'ERROR: Latest and default do not match for product MLB, got {product_default[INFO_PRODUCT]} and {product_latest[INFO_PRODUCT]}')
|
||||
return 1
|
||||
|
||||
if generic_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
|
||||
# Zero MLB always give the same value for default and latest.
|
||||
print(f'ERROR: Generic MLB gives different product, got {generic_default[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
|
||||
return 1
|
||||
|
||||
if valid_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
|
||||
# Valid MLB must always equal generic MLB.
|
||||
print(f'ERROR: Cannot determine unified latest product, got {valid_latest[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
|
||||
return 1
|
||||
|
||||
if product_default[INFO_PRODUCT] != valid_default[INFO_PRODUCT]:
|
||||
# Product-only MLB can give the same value with valid default MLB.
|
||||
# This is not an error for all models, but for our chosen code it is.
|
||||
print(f'ERROR: Valid and product MLB give mismatch, got {product_default[INFO_PRODUCT]} and {valid_default[INFO_PRODUCT]}')
|
||||
return 1
|
||||
|
||||
print('SUCCESS: Found no discrepancies with MLB validation algorithm!')
|
||||
return 0
|
||||
|
||||
|
||||
def action_verify(args):
|
||||
"""
|
||||
Try to verify MLB serial number.
|
||||
"""
|
||||
session = get_session(args)
|
||||
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
|
||||
uvalid_default = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='default')
|
||||
uvalid_latest = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='latest')
|
||||
uproduct_default = get_image_info(session, bid=args.board_id, mlb=product_mlb(args.mlb), diag=False, os_type='default')
|
||||
|
||||
if args.verbose:
|
||||
print(generic_latest)
|
||||
print(uvalid_default)
|
||||
print(uvalid_latest)
|
||||
print(uproduct_default)
|
||||
|
||||
# Verify our MLB number.
|
||||
if uvalid_default[INFO_PRODUCT] != uvalid_latest[INFO_PRODUCT]:
|
||||
print(f'SUCCESS: {args.mlb} MLB looks valid and supported!' if uvalid_latest[INFO_PRODUCT] == generic_latest[INFO_PRODUCT] else f'SUCCESS: {args.mlb} MLB looks valid, but probably unsupported!')
|
||||
return 0
|
||||
|
||||
print('UNKNOWN: Run selfcheck, check your board-id, or try again later!')
|
||||
|
||||
# Here we have matching default and latest products. This can only be true for very
|
||||
# new models. These models get either latest or special builds.
|
||||
if uvalid_default[INFO_PRODUCT] == generic_latest[INFO_PRODUCT]:
|
||||
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new!')
|
||||
return 0
|
||||
if uproduct_default[INFO_PRODUCT] != uvalid_default[INFO_PRODUCT]:
|
||||
print(f'UNKNOWN: {args.mlb} MLB looks invalid, other models use product {uproduct_default[INFO_PRODUCT]} instead of {uvalid_default[INFO_PRODUCT]}!')
|
||||
return 0
|
||||
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new and using special builds!')
|
||||
return 0
|
||||
|
||||
|
||||
def action_guess(args):
|
||||
"""
|
||||
Attempt to guess which model does this MLB belong.
|
||||
"""
|
||||
|
||||
mlb = args.mlb
|
||||
anon = mlb.startswith('000')
|
||||
|
||||
with open(args.board_db, 'r', encoding='utf-8') as fh:
|
||||
db = json.load(fh)
|
||||
|
||||
supported = {}
|
||||
|
||||
session = get_session(args)
|
||||
|
||||
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
|
||||
|
||||
for model in db:
|
||||
try:
|
||||
if anon:
|
||||
# For anonymous lookup check when given model does not match latest.
|
||||
model_latest = get_image_info(session, bid=model, mlb=MLB_ZERO, diag=False, os_type='latest')
|
||||
|
||||
if model_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
|
||||
if db[model] == 'current':
|
||||
print(f'WARN: Skipped {model} due to using latest product {model_latest[INFO_PRODUCT]} instead of {generic_latest[INFO_PRODUCT]}')
|
||||
continue
|
||||
|
||||
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
|
||||
|
||||
if user_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
|
||||
supported[model] = [db[model], user_default[INFO_PRODUCT], generic_latest[INFO_PRODUCT]]
|
||||
else:
|
||||
# For normal lookup check when given model has mismatching normal and latest.
|
||||
user_latest = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='latest')
|
||||
|
||||
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
|
||||
|
||||
if user_latest[INFO_PRODUCT] != user_default[INFO_PRODUCT]:
|
||||
supported[model] = [db[model], user_default[INFO_PRODUCT], user_latest[INFO_PRODUCT]]
|
||||
|
||||
except Exception as e:
|
||||
print(f'WARN: Failed to check {model}, exception: {e}')
|
||||
|
||||
if len(supported) > 0:
|
||||
print(f'SUCCESS: MLB {mlb} looks supported for:')
|
||||
for model in supported.items():
|
||||
print(f'- {model}, up to {supported[model][0]}, default: {supported[model][1]}, latest: {supported[model][2]}')
|
||||
return 0
|
||||
|
||||
print(f'UNKNOWN: Failed to determine supported models for MLB {mlb}!')
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Gather recovery information for Macs')
|
||||
parser.add_argument('action', choices=['download', 'selfcheck', 'verify', 'guess'],
|
||||
help='Action to perform: "download" - performs recovery downloading,'
|
||||
' "selfcheck" checks whether MLB serial validation is possible, "verify" performs'
|
||||
' MLB serial verification, "guess" tries to find suitable mac model for MLB.')
|
||||
parser.add_argument('-o', '--outdir', type=str, default='com.apple.recovery.boot',
|
||||
help='customise output directory for downloading, defaults to com.apple.recovery.boot')
|
||||
parser.add_argument('-n', '--basename', type=str, default='',
|
||||
help='customise base name for downloading, defaults to remote name')
|
||||
parser.add_argument('-b', '--board-id', type=str, default=RECENT_MAC,
|
||||
help=f'use specified board identifier for downloading, defaults to {RECENT_MAC}')
|
||||
parser.add_argument('-m', '--mlb', type=str, default=MLB_ZERO,
|
||||
help=f'use specified logic board serial for downloading, defaults to {MLB_ZERO}')
|
||||
parser.add_argument('-e', '--code', type=str, default='',
|
||||
help='generate product logic board serial with specified product EEEE code')
|
||||
parser.add_argument('-os', '--os-type', type=str, default='default', choices=['default', 'latest'],
|
||||
help=f'use specified os type, defaults to default {MLB_ZERO}')
|
||||
parser.add_argument('-diag', '--diagnostics', action='store_true', help='download diagnostics image')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='print debug information')
|
||||
parser.add_argument('-db', '--board-db', type=str, default=os.path.join(SELF_DIR, 'boards.json'),
|
||||
help='use custom board list for checking, defaults to boards.json')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.code != '':
|
||||
args.mlb = mlb_from_eeee(args.code)
|
||||
|
||||
if len(args.mlb) != 17:
|
||||
print('ERROR: Cannot use MLBs in non 17 character format!')
|
||||
sys.exit(1)
|
||||
|
||||
if args.action == 'download':
|
||||
return action_download(args)
|
||||
if args.action == 'selfcheck':
|
||||
return action_selfcheck(args)
|
||||
if args.action == 'verify':
|
||||
return action_verify(args)
|
||||
if args.action == 'guess':
|
||||
return action_guess(args)
|
||||
|
||||
assert False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
60
hackintosh-guide/Utilities/macrecovery/recovery_urls.txt
Executable file
60
hackintosh-guide/Utilities/macrecovery/recovery_urls.txt
Executable file
@@ -0,0 +1,60 @@
|
||||
Lion
|
||||
./macrecovery.py -b Mac-2E6FAB96566FE58C -m 00000000000F25Y00 download
|
||||
./macrecovery.py -b Mac-C3EC7CD22292981F -m 00000000000F0HM00 download
|
||||
|
||||
Mountain Lion:
|
||||
./macrecovery.py -b Mac-7DF2A3B5E5D671ED -m 00000000000F65100 download
|
||||
|
||||
Mavericks
|
||||
./macrecovery.py -b Mac-F60DEB81FF30ACF6 -m 00000000000FNN100 download
|
||||
|
||||
Yosemite:
|
||||
./macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000GDVW00 download
|
||||
|
||||
El Capitan
|
||||
./macrecovery.py -b Mac-FFE5EF870D7BA81A -m 00000000000GQRX00 download
|
||||
|
||||
Sierra
|
||||
./macrecovery.py -b Mac-77F17D7DA9285301 -m 00000000000J0DX00 download
|
||||
|
||||
High Sierra
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000J80300 download
|
||||
./macrecovery.py -b Mac-BE088AF8C5EB4FA2 -m 00000000000J80300 download
|
||||
|
||||
Mojave
|
||||
./macrecovery.py -b Mac-7BA5B2DFE22DDD8C -m 00000000000KXPG00 download
|
||||
|
||||
Catalina
|
||||
./macrecovery.py -b Mac-CFF7D910A743CAAF -m 00000000000PHCD00 download
|
||||
./macrecovery.py -b Mac-00BE6ED71E35EB86 -m 00000000000000000 download
|
||||
|
||||
Big Sur
|
||||
./macrecovery.py -b Mac-2BD1B31983FE1663 -m 00000000000000000 download
|
||||
|
||||
Monterey
|
||||
./macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
|
||||
|
||||
Ventura
|
||||
./macrecovery.py -b Mac-B4831CEBD52A0C4C -m 00000000000000000 download
|
||||
|
||||
Sonoma
|
||||
./macrecovery.py -b Mac-827FAC58A8FDFA22 -m 00000000000000000 download
|
||||
|
||||
Sequoia
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000000000 download
|
||||
|
||||
Tahoe
|
||||
./macrecovery.py -b Mac-CFF7D910A743CAAF -m 00000000000000000 -os latest download
|
||||
|
||||
Diagnostics
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000000000 -diag download
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000JG3600 -diag download
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 <real MLB> -diag download
|
||||
|
||||
Default version
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000JG3600 download (oldest)
|
||||
./macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m <real MLB> -os default download (newer)
|
||||
|
||||
Latest version
|
||||
./macrecovery.py -b Mac-CFF7D910A743CAAF -m 00000000000000000 -os latest download
|
||||
./macrecovery.py -b Mac-CFF7D910A743CAAF -m <real MLB> -os latest
|
||||
Reference in New Issue
Block a user