mirror of
https://github.com/zhaarey/AppleMusicDecrypt.git
synced 2025-10-23 15:11:06 +00:00
feat: hyper decryption device
This commit is contained in:
37
src/adb.py
37
src/adb.py
@@ -14,8 +14,24 @@ from src.exceptions import ADBConnectException, FailedGetAuthParamException, \
|
||||
from src.types import AuthParams
|
||||
|
||||
|
||||
class HyperDecryptDevice:
|
||||
host: str
|
||||
fridaPort: int
|
||||
decryptLock: asyncio.Lock
|
||||
serial: str
|
||||
_father_device = None
|
||||
|
||||
def __init__(self, host: str, port: int, father_device):
|
||||
self.host = host
|
||||
self.fridaPort = port
|
||||
self.decryptLock = asyncio.Lock()
|
||||
self.serial = f"{host}:{port}"
|
||||
self._father_device = father_device
|
||||
|
||||
|
||||
class Device:
|
||||
host: str
|
||||
serial: str
|
||||
client: AdbClient
|
||||
device: AdbDevice
|
||||
fridaPort: int
|
||||
@@ -25,6 +41,7 @@ class Device:
|
||||
authParams: AuthParams = None
|
||||
suMethod: str
|
||||
decryptLock: asyncio.Lock
|
||||
hyperDecryptDevices: list[HyperDecryptDevice] = []
|
||||
|
||||
def __init__(self, host="127.0.0.1", port=5037, su_method: str = "su -c"):
|
||||
self.client = AdbClient(host, port)
|
||||
@@ -41,6 +58,7 @@ class Device:
|
||||
if not status:
|
||||
raise ADBConnectException
|
||||
self.device = self.client.device(f"{host}:{port}")
|
||||
self.serial = self.device.serial
|
||||
|
||||
def _execute_command(self, cmd: str, su: bool = False, sh: bool = False) -> Optional[str]:
|
||||
whoami = self.device.shell("whoami")
|
||||
@@ -143,3 +161,22 @@ class Device:
|
||||
self.authParams = AuthParams(dsid=dsid, accountToken=token,
|
||||
accountAccessToken=access_token, storefront=storefront)
|
||||
return self.authParams
|
||||
|
||||
def hyper_decrypt(self, ports: list[int]):
|
||||
if not self._if_frida_running():
|
||||
raise FridaNotRunningException
|
||||
logger.debug("injecting agent script with hyper decrypt")
|
||||
self.fridaPort = ports[0]
|
||||
if not self.fridaDevice:
|
||||
frida.get_device_manager().add_remote_device(self.device.serial)
|
||||
self.fridaDevice = frida.get_device_manager().get_device(self.device.serial)
|
||||
self.pid = self.fridaDevice.spawn("com.apple.android.music")
|
||||
self.fridaSession = self.fridaDevice.attach(self.pid)
|
||||
for port in ports:
|
||||
self._start_forward(port, port)
|
||||
with open("agent.js", "r") as f:
|
||||
agent = f.read().replace("2147483647", str(port))
|
||||
script: frida.core.Script = self.fridaSession.create_script(agent)
|
||||
script.load()
|
||||
self.hyperDecryptDevices.append(HyperDecryptDevice(host=self.host, port=port, father_device=self))
|
||||
self.fridaDevice.resume(self.pid)
|
||||
|
||||
@@ -67,7 +67,10 @@ class NewInteractiveShell:
|
||||
if not self.storefront_device_mapping.get(auth_params.storefront.lower()):
|
||||
self.storefront_device_mapping.update({auth_params.storefront.lower(): []})
|
||||
self.storefront_device_mapping[auth_params.storefront.lower()].append(device)
|
||||
device.start_inject_frida(device_info.agentPort)
|
||||
if device_info.hyperDecrypt:
|
||||
device.hyper_decrypt(list(range(device_info.agentPort, device_info.agentPort + device_info.hyperDecryptNum)))
|
||||
else:
|
||||
device.start_inject_frida(device_info.agentPort)
|
||||
|
||||
async def command_parser(self, cmd: str):
|
||||
if not cmd.strip():
|
||||
|
||||
@@ -13,6 +13,8 @@ class Device(BaseModel):
|
||||
port: int
|
||||
agentPort: int
|
||||
suMethod: str
|
||||
hyperDecrypt: bool
|
||||
hyperDecryptNum: int
|
||||
|
||||
|
||||
class M3U8Api(BaseModel):
|
||||
|
||||
@@ -2,26 +2,31 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from loguru import logger
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log, RetryCallState
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
||||
|
||||
from src.adb import Device
|
||||
from src.adb import Device, HyperDecryptDevice
|
||||
from src.exceptions import DecryptException, RetryableDecryptException
|
||||
from src.models.song_data import Datum
|
||||
from src.mp4 import SongInfo, SampleInfo
|
||||
from src.types import defaultId, prefetchKey
|
||||
from src.utils import timeit
|
||||
|
||||
retry_count = {}
|
||||
|
||||
|
||||
@retry(retry=retry_if_exception_type(RetryableDecryptException), stop=stop_after_attempt(3),
|
||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device) -> bytes:
|
||||
@timeit
|
||||
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device | HyperDecryptDevice) -> bytes:
|
||||
async with device.decryptLock:
|
||||
logger.info(f"Decrypting song: {manifest.attributes.artistName} - {manifest.attributes.name}")
|
||||
if isinstance(device, HyperDecryptDevice):
|
||||
logger.info(f"Using hyperDecryptDevice {device.serial} to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}")
|
||||
else:
|
||||
logger.info(f"Using device {device.serial} to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}")
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(device.host, device.fridaPort)
|
||||
except ConnectionRefusedError:
|
||||
logger.warning(f"Failed to connect to device {device.device.serial}, re-injecting")
|
||||
logger.warning(f"Failed to connect to device {device.serial}, re-injecting")
|
||||
device.restart_inject_frida()
|
||||
raise RetryableDecryptException
|
||||
decrypted = bytes()
|
||||
@@ -42,7 +47,7 @@ async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Devi
|
||||
try:
|
||||
result = await decrypt_sample(writer, reader, sample)
|
||||
except RetryableDecryptException as e:
|
||||
if 0 <= retry_count.get(device.device.serial, 0) < 3 or 4 <= retry_count.get(device.device.serial, 0) < 6:
|
||||
if 0 <= retry_count.get(device.serial, 0) < 3 or 4 <= retry_count.get(device.serial, 0) < 6:
|
||||
logger.warning(f"Failed to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}, retrying")
|
||||
writer.write(bytes([0, 0, 0, 0]))
|
||||
writer.close()
|
||||
|
||||
14
src/rip.py
14
src/rip.py
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from loguru import logger
|
||||
@@ -15,7 +16,7 @@ from src.mp4 import extract_media, extract_song, encapsulate, write_metadata, fi
|
||||
from src.save import save
|
||||
from src.types import GlobalAuthParams, Codec
|
||||
from src.url import Song, Album, URLType, Artist, Playlist
|
||||
from src.utils import check_song_exists, if_raw_atmos, playlist_write_song_index, get_codec_from_codec_id
|
||||
from src.utils import check_song_exists, if_raw_atmos, playlist_write_song_index, get_codec_from_codec_id, timeit
|
||||
|
||||
task_lock = asyncio.Semaphore(16)
|
||||
|
||||
@@ -80,7 +81,16 @@ async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config
|
||||
codec = get_codec_from_codec_id(codec_id)
|
||||
raw_song = await download_song(song_uri)
|
||||
song_info = await extract_song(raw_song, codec)
|
||||
decrypted_song = await decrypt(song_info, keys, song_data, device)
|
||||
if device.hyperDecryptDevices:
|
||||
if all([hyper_device.decryptLock.locked() for hyper_device in device.hyperDecryptDevices]):
|
||||
decrypted_song = await decrypt(song_info, keys, song_data, random.choice(device.hyperDecryptDevices))
|
||||
else:
|
||||
for hyperDecryptDevice in device.hyperDecryptDevices:
|
||||
if not hyperDecryptDevice.decryptLock.locked():
|
||||
decrypted_song = await decrypt(song_info, keys, song_data, hyperDecryptDevice)
|
||||
break
|
||||
else:
|
||||
decrypted_song = await decrypt(song_info, keys, song_data, device)
|
||||
song = await encapsulate(song_info, decrypted_song, config.download.atmosConventToM4a)
|
||||
if not if_raw_atmos(codec, config.download.atmosConventToM4a):
|
||||
metadata_song = await write_metadata(song, song_metadata, config.metadata.embedMetadata, config.download.coverFormat)
|
||||
|
||||
Reference in New Issue
Block a user