diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7ac8729..e502436 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,6 +23,7 @@ jobs: run: | mkdir -p alac cp agent.js alac/ + cp agent-arm64.js alac/ cp config.yaml alac/ cp README.md alac/ cp main.exe alac/ @@ -47,6 +48,7 @@ jobs: run: | mkdir -p alac cp agent.js alac/ + cp agent-arm64.js alac/ cp config.yaml alac/ cp README.md alac/ cp main alac/ @@ -55,3 +57,28 @@ jobs: with: name: apple-music-alac-atmos-downloader-linux path: alac/* + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.20' + - name: Build + run: | + go build -o main -v ./main.go + - name: Create a new directory and copy files + run: | + mkdir -p alac + cp agent.js alac/ + cp agent-arm64.js alac/ + cp config.yaml alac/ + cp README.md alac/ + cp main alac/ + - name: Upload apple-music-alac-atmos-downloader + uses: actions/upload-artifact@v4 + with: + name: apple-music-alac-atmos-downloader-macos + path: alac/* diff --git a/.gitignore b/.gitignore index 9de1fcb..cd35375 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ * !.gitignore !agent.js +!agent-arm64.js !go.mod !go.sum !main.go diff --git a/README.md b/README.md index 5e220e4..2f0c6f1 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,34 @@ ### !!必须先安装[MP4Box](https://gpac.io/downloads/gpac-nightly-builds/),并确认[MP4Box](https://gpac.io/downloads/gpac-nightly-builds/)已正确添加到环境变量 ### 添加功能 + 1. 调用外部MP4Box自动封装ec3为m4a 2. 更改目录结构为 歌手名\专辑名 ;Atmos下载文件则另外移动到AM-DL-Atmos downloads,并更改目录结构为 歌手名\专辑名 [Atmos] 3. 运行结束后显示总体完成情况 4. 自动内嵌封面和LRC歌词(需要media-user-token,获取方式看最后的说明) 5. 自动构建 可以到 [Actions](https://github.com/zhaarey/apple-music-alac-atmos-downloader/actions) 页面下载最新自动构建版本 可以直接`main.exe url` 6. main 支持check 可以填入文本地址 或API数据库. -7. 新增get-m3u8-from-device 改为true 且设置端口`adb forward tcp:20020 tcp:20020`即从模拟器获取m3u8 +7. 新增get-m3u8-from-device 改为true 且设置端口`adb forward tcp:20020 tcp:20020`即从模拟器获取m3u8 8. 文件夹和文件支持模板 9. 支持下载歌手 `go run main.go https://music.apple.com/us/artist/taylor-swift/159260351` `--all-album` 自动选择歌手的所有专辑 10. 新增[wrapper](https://github.com/zhaarey/wrapper/releases)模式 目前只能linux运行,解密速度超快,基本秒解 11. `limit-max`支持限制长度 默认200 12. 支持逐词与未同步歌词 +13. 现已支持arm64解密 + +### Special thanks to `chocomint` for creating `agent-arm64.js` 本项目仅支持ALAC和Atmos + - `alac (audio-alac-stereo)` - `ec3 (audio-atmos / audio-ec3)` ### Python项目 + 如需下载AAC推荐使用WorldObservationLog的[AppleMusicDecrypt](https://github.com/WorldObservationLog/AppleMusicDecrypt) [AppleMusicDecrypt](https://github.com/WorldObservationLog/AppleMusicDecrypt)支持以下编码 + - `alac (audio-alac-stereo)` - `ec3 (audio-atmos / audio-ec3)` - `ac3 (audio-ac3)` @@ -29,18 +36,29 @@ - `aac-binaural (audio-stereo-binaural)` - `aac-downmix (audio-stereo-downmix)` - # Apple Music ALAC / Dolby Atmos Downloader Original script by Sorrow. Modified by me to include some fixes and improvements. ## How to use + 1. Create a virtual device on Android Studio with a image that doesn't have Google APIs. -2. Install this version of Apple Music: https://www.apkmirror.com/apk/apple/apple-music/apple-music-3-6-0-beta-release/apple-music-3-6-0-beta-4-android-apk-download/. You will also need SAI to install it: https://f-droid.org/pt_BR/packages/com.aefyr.sai.fdroid/. +2. Install Apple Music + + for x86 install this version of [Apple Music 3.6.0 beta4](https://www.apkmirror.com/apk/apple/apple-music/apple-music-3-6-0-beta-release/apple-music-3-6-0-beta-4-android-apk-download/). You will also need [SAI](https://f-droid.org/pt_BR/packages/com.aefyr.sai.fdroid/) to install it. + + for arm64 install the last version of [Apple Music](https://www.apkmirror.com/apk/apple/apple-music/). + 3. Launch Apple Music and sign in to your account. Subscription required. 4. Port forward 10020 TCP: `adb forward tcp:10020 tcp:10020`. 5. Start frida server. -6. Start the frida agent: `frida -U -l agent.js -f com.apple.android.music`. +6. Start the frida agent: + + for x86 `frida -U -l agent.js -f com.apple.android.music` + + for arm64 `frida -U -l agent-arm64.js -f com.apple.android.music` + + 7. Start downloading some albums: `go run main.go https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511`. 8. Start downloading singles: `go run main.go --select https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511` input numbers separated by spaces. 9. Start downloading some playlists: `go run main.go https://music.apple.com/us/playlist/taylor-swift-essentials/pl.3950454ced8c45a3b0cc693c2a7db97b` or `go run main.go https://music.apple.com/us/playlist/hi-res-lossless-24-bit-192khz/pl.u-MDAWvpjt38370N`. @@ -49,6 +67,7 @@ Original script by Sorrow. Modified by me to include some fixes and improvements [中文教程-详见方法三](https://telegra.ph/Apple-Music-Alac高解析度无损音乐下载教程-04-02-2) ## Downloading lyrics + 1. Open [Apple Music](https://music.apple.com) and log in 2. Open the Developer tools, Click `Application -> Storage -> Cookies -> https://music.apple.com` 3. Find the cookie named `media-user-token` and copy its value diff --git a/agent-arm64.js b/agent-arm64.js new file mode 100644 index 0000000..1cf0b63 --- /dev/null +++ b/agent-arm64.js @@ -0,0 +1,323 @@ +'use strict'; + +const fairplayCert = "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"; +const kdContextMap = new Map(); +let persistentKeyPtr = null; + +function newStdStringFromArrayBuffer(content) { + return newStdString(String.fromCharCode(...new Uint8Array(content))); +} + +function newStdString(content) { + const size = content.length; + if (size >= 0x17) { + const capacity = 2 ** Math.ceil(Math.log2(size + 1)); + const buffer = malloc(capacity); + buffer.writeUtf8String(content); + + const str = malloc(Process.pointerSize * 3); + str.writeULong(capacity | 0x1); + str.add(Process.pointerSize).writeULong(size); + str.add(Process.pointerSize * 2).writePointer(buffer); + + return { buffer, str }; + } else { + const str = malloc(size + 2); + str.writeU8(size * 2); + str.add(1).writeUtf8String(content); + str.add(size + 1).writeU8(0); + + return { buffer: null, str }; + } +} + +function getStrFromStdString(content) { + const mem = new NativePointer(content); + const size = mem.readU8(); + if ((size & 0x1) === 1) { + const bufferSize = mem.add(Process.pointerSize).readULong(); + const bufferPtr = mem.add(Process.pointerSize * 2).readPointer(); + return bufferPtr.readUtf8String(bufferSize); + } else { + return mem.add(1).readUtf8String(size / 2); + } +} + +function getPersistentKeyASM( + sessionCtrlInstance, + adamIdStr, keyUriStr, + keyFormatStr, keyFormatVerStr, + serverUriStr, protocolTypeStr, + fpsCertStr, persistentKeyPtr +) { + const impl = malloc(Process.pageSize); + Memory.patchCode(impl, Process.pageSize, code => { + const writer = new Arm64Writer(code, { pc: impl }); + writer.putLdrRegAddress("x0", sessionCtrlInstance); + writer.putLdrRegAddress("x1", adamIdStr.str); + writer.putLdrRegAddress("x2", adamIdStr.str); + writer.putLdrRegAddress("x3", keyUriStr.str); + writer.putLdrRegAddress("x4", keyFormatStr.str); + writer.putLdrRegAddress("x5", keyFormatVerStr.str); + writer.putLdrRegAddress("x6", serverUriStr.str); + writer.putLdrRegAddress("x7", protocolTypeStr.str); + writer.putLdrRegAddress("x8", persistentKeyPtr); + writer.putSubRegRegImm("sp", "sp", 0x10); + writer.putLdrRegAddress("x9", fpsCertStr.str); + writer.putStrRegRegOffset("x9", "sp", 0); + writer.putCallAddressWithArguments(getPersistentKeyAddr, ["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8"]); + writer.putAddRegRegImm("sp", "sp", 0x10); + writer.flush(); + }); + + const implFunc = new NativeFunction(impl, 'void', []); + try { + implFunc(); + } catch (e) { } // Ignore errors + dealloc(impl); +} + +function decryptContextASM(sessionCtrlInstance, persistentKeyPtr, decryptedKeyPtr) { + const impl = malloc(Process.pageSize); + Memory.patchCode(impl, Process.pageSize, code => { + const writer = new Arm64Writer(code, { pc: impl }); + writer.putLdrRegAddress("x0", sessionCtrlInstance); + writer.putLdrRegAddress("x1", persistentKeyPtr); + writer.putLdrRegAddress("x8", decryptedKeyPtr); + writer.putCallAddressWithArguments(decryptContextAddr, ["x0", "x1", "x8"]); + writer.flush(); + }); + + const implFunc = new NativeFunction(impl, 'void', []); + try { + implFunc(); + } catch (e) { } // Ignore errors + dealloc(impl); +} + +function getKdContext(adamId, uri) { + const uriStr = String.fromCharCode(...new Uint8Array(uri)); + if (kdContextMap.has(uriStr)) { + return kdContextMap.get(uriStr); + } + + const adamIdStr = newStdStringFromArrayBuffer(adamId); + const keyUriStr = newStdStringFromArrayBuffer(uri); + const keyFormatStr = newStdString("com.apple.streamingkeydelivery"); + const keyFormatVerStr = newStdString("1"); + const serverUriStr = newStdString("https://play.itunes.apple.com/WebObjects/MZPlay.woa/music/fps"); + const protocolTypeStr = newStdString("simplified"); + const fpsCertStr = newStdString(fairplayCert); + + const persistentKeyPtr = malloc(Process.pointerSize * 2); + getPersistentKeyASM( + sessionCtrlInstance, + adamIdStr, keyUriStr, + keyFormatStr, keyFormatVerStr, + serverUriStr, protocolTypeStr, + fpsCertStr, persistentKeyPtr + ); + + const decryptedKeyPtr = malloc(Process.pointerSize * 2); + decryptContextASM(sessionCtrlInstance, persistentKeyPtr.readPointer(), decryptedKeyPtr); + const kdContext = decryptedKeyPtr.readPointer().add(0x18).readPointer(); + + if (kdContext.isNull()) { + console.error("kdContext is null"); + return null; + } + + kdContextMap.set(uriStr, kdContext); + return kdContext; +} + +async function handleDecryptionConnection(socket) { + try { + while (true) { + const adamIdSize = (await socket.input.readAll(1)).unwrap().readU8(); + if (adamIdSize === 0) break; + + const adamId = await socket.input.readAll(adamIdSize); + const uriSize = (await socket.input.readAll(1)).unwrap().readU8(); + const uri = await socket.input.readAll(uriSize); + + const kdContext = getKdContext(adamId, uri); + if (!kdContext) { + console.error("Failed to get kdContext"); + break; + } + + + while (true) { + const sizeBuffer = await socket.input.readAll(4); + if (sizeBuffer.byteLength === 0) break; + const size = sizeBuffer.unwrap().readU32(); + if (size === 0) break; + + const sample = await socket.input.readAll(size); + const sampleUnwrapped = sample.unwrap(); + decryptSample(kdContext.readPointer(), 5, sampleUnwrapped, sampleUnwrapped, sample.byteLength); + await socket.output.writeAll(sample); + } + } + } catch (e) { + console.error("Connection handling error:", e); + console.error(e.stack); + } finally { + await socket.close(); + } +} + +global.getM3U8fromDownload = function(adamID) { + var C8717f + Java.choose("of.f", { + onMatch: function (x) { + C8717f = x + }, + onComplete: function (x) {} + }); + var response = C8717f.q(0, "", adamID, false) + if (response.get().getError().get() == null){ + var item = response.get().getItems().get(0) + var assets = item.get().getAssets() + var size = assets.size() + return assets.get(size - 1).get().getURL() + } else { + return response.get().getError().get().errorCode() + } +}; + +const stringToByteArray = str => { + const byteArray = []; + for (let i = 0; i < str.length; ++i) { + byteArray.push(str.charCodeAt(i)); + } + return byteArray; +}; + +global.getM3U8 = function(adamID) { + Java.use("com.apple.android.music.common.MainContentActivity"); + var SVPlaybackLeaseManagerProxy; + Java.choose("com.apple.android.music.playback.SVPlaybackLeaseManagerProxy", { + onMatch: function (x) { + SVPlaybackLeaseManagerProxy = x + }, + onComplete: function (x) {} + }); + var HLSParam = Java.array('java.lang.String', ["HLS"]) + var MediaAssetInfo = SVPlaybackLeaseManagerProxy.requestAsset(parseInt(adamID), null, HLSParam, false) + if (MediaAssetInfo === null) { + return -1 + } + return MediaAssetInfo.getDownloadUrl() +}; + +function performJavaOperations(adamID) { + return new Promise((resolve, reject) => { + Java.performNow(function () { + const url = getM3U8(adamID); + if (url === -1) { + const url = getM3U8fromDownload(adamID); + resolve(url); + } else { + resolve(url); + } + }); + }); +} + + +async function handleM3U8Connection(s) { + console.log("New M3U8 connection!"); + try { + const adamSize = (await s.input.readAll(1)).unwrap().readU8(); + if (adamSize !== 0) { + const adam = await s.input.readAll(adamSize); + const byteArray = new Uint8Array(adam); + let adamID = ""; + for (let i = 0; i < byteArray.length; i++) { + adamID += String.fromCharCode(byteArray[i]); + } + console.log("adamID:", adamID); + let m3u8Url; + performJavaOperations(adamID) + .then(async (url) => { + m3u8Url = url; + console.log("M3U8 URL: ", m3u8Url); + const m3u8Array = stringToByteArray(m3u8Url + "\n"); + // console.log("M3U8 ARRAY:", m3u8Array); + await s.output.writeAll(m3u8Array); + }) + .catch((error) => { + console.error("Error performing Java operations:", error); + }); + } + } catch (err) { + console.error("Error handling M3U8 connection:", err); + } + await s.close(); +} + +function initializeFridaFunctions(androidappmusic) { + // Utility functions + global.malloc = new NativeFunction( + Module.findExportByName(null, "_Znwm"), + 'pointer', + ['ulong'] + ); + global.dealloc = new NativeFunction( + Module.findExportByName(null, "_ZdlPv"), + 'void', + ['pointer'] + ); + + // Apple Music specific functions + global.sessionCtrlInstance = new NativeFunction( + androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl8instanceEv"), + 'pointer', + [] + )(); + + + global.getPersistentKeyAddr = androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl16getPersistentKeyERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_S8_S8_S8_S8_S8_"); + global.decryptContextAddr = androidappmusic.getExportByName("_ZN21SVFootHillSessionCtrl14decryptContextERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEERKN11SVDecryptor15SVDecryptorTypeERKb"); + + global.decryptSample = new NativeFunction( + androidappmusic.getExportByName("NfcRKVnxuKZy04KWbdFu71Ou"), + 'ulong', + ['pointer', 'uint', 'pointer', 'pointer', 'size_t'] + ); +} + +function startListeners() { + Socket.listen({ family: "ipv4", port: 20020 }) + .then(async (listener) => { + while (true) { + await handleM3U8Connection(await listener.accept()); + } + }) + .catch(console.error); + + Socket.listen({ family: "ipv4", port: 10020 }) + .then(async (listener) => { + while (true) { + await handleDecryptionConnection(await listener.accept()); + } + }) + .catch(console.error); +} + +function waitForModule() { + const moduleName = "libandroidappmusic.so"; + try { + const androidappmusic = Process.getModuleByName(moduleName); + initializeFridaFunctions(androidappmusic); + startListeners(); + console.log(`Module ${moduleName} loaded successfully and listeners started`); + } catch (e) { + console.log(`Module ${moduleName} not loaded yet, waiting...`); + setTimeout(waitForModule, 1000); + } +} + +waitForModule(); \ No newline at end of file