From e0466d61680606738844bc332f14259674c9ef54 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:17:01 +0900 Subject: [PATCH] =?UTF-8?q?WIP=2086=20Android:=207-Zip=2025.01=20=EC=9D=84?= =?UTF-8?q?=20=EA=B3=B5=EC=8B=9D=20=EC=86=8C=EC=8A=A4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?NDK=20=EB=A1=9C=20=EB=B9=8C=EB=93=9C=20(lib7zz.so)=20-=20Androi?= =?UTF-8?q?d=20=EC=97=90=EC=84=9C=EB=8F=84=207z=20=C2=B7=20RAR=20=ED=92=80?= =?UTF-8?q?=EA=B8=B0=20(=EA=B2=80=ED=86=A0=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관리자 지시 4: 7z2501-src.7z (SHA-256 2aed39b8…0181 고정, 고치지 않음) 의 CPP/7zip/Bundles/Alone2 (7zz) 를 NDK clang (aarch64-linux-android24) 으로 빌드 - tool/build_7zz_android.sh. 어셈블리 끔, Windows 에서 빌드할 때 makefile 이 MinGW 로 여기지 않게 SystemDrive · SYSTEMDRIVE 를 비움, 새 clang 경고로 실패하지 않게 경고 묶음 끔. libc++ 는 정적 (libc · libm · libdl 만 씀). 결과 third_party/7zip/android/arm64-v8a/lib7zz.so (2.8MB) + License.txt. - APK: jniLibs 에 third_party/7zip/android, third_party_libs.txt · check_apk 목록에 lib7zz.so (check_apk 통과 - 실행 파일 5개). - 앱: bundledSevenZip 이 Android 에서는 네이티브 라이브러리 폴더의 lib7zz.so (rsync 와 같은 방식). 있으면 "다른 앱으로 열기" 안내 대신 실제 풀기 - 같은 검사 (zip slip · 링크 · 이름 · 공간 · 비밀번호 표준 입력) 그대로. 없으면 예전처럼 안내. - 고지: THIRD_PARTY_NOTICES_ANDROID 에 7-Zip 25.01 (LGPL 2.1+ · BSD · unRAR 제한, 소스 주소 · 빌드 방법 · 대응 소스 = 태그). - 시험: licenses_test (Android 고지 · 빌드 스크립트 해시 · APK 목록). Android 기기에서 실제로 푸는 것은 15 의 기기 확인 필요. Co-Authored-By: Claude Opus 5.5 --- android/app/build.gradle.kts | 4 +- android/app/third_party_libs.txt | 1 + .../licenses/THIRD_PARTY_NOTICES_ANDROID.txt | 6 +++ lib/ui/archive_ui.dart | 17 +++++++-- test/archive_ui_test.dart | 2 +- test/licenses_test.dart | 7 ++++ tool/build_7zz_android.sh | 37 +++++++++++++++++++ tool/check_apk.ps1 | 2 +- 8 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tool/build_7zz_android.sh diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index dc7226a..f8c580a 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -65,9 +65,11 @@ android { sourceSets["main"].jniLibs.srcDir("../../third_party/sdcpp/android") // Tor (libtor.so 라는 이름의 실행 파일): 공식 Tor Expert Bundle (Android aarch64) 의 libTor.so, tool/fetch_tor_android.ps1 (판 · SHA-256 고정) sourceSets["main"].jniLibs.srcDir("../../third_party/tor/android") + // 86: 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일 - 7z · RAR 풀기): tool/build_7zz_android.sh (공식 소스, SHA-256 고정) + sourceSets["main"].jniLibs.srcDir("../../third_party/7zip/android") // 195: third_party 의 실행 파일은 third_party_libs.txt 에 적은 것만 (공유 폴더에 다른 판이 있어도 섞이지 않게) val thirdPartyLibs = file("third_party_libs.txt").readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }.toSet() - listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android").forEach { dir -> + listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android", "../../third_party/7zip/android").forEach { dir -> fileTree(dir).matching { include("**/*.so") }.forEach { so -> if (so.name !in thirdPartyLibs) packaging.jniLibs.excludes += "**/${so.name}" } diff --git a/android/app/third_party_libs.txt b/android/app/third_party_libs.txt index d0a27dd..c591af0 100644 --- a/android/app/third_party_libs.txt +++ b/android/app/third_party_libs.txt @@ -6,3 +6,4 @@ librsync.so libsdcli.so libsdcli_v80.so libtor.so +lib7zz.so diff --git a/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt b/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt index 1607195..3c4eeea 100644 --- a/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt +++ b/assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt @@ -43,6 +43,12 @@ JJ_MKVMaker (Android) - 포함된 오픈 소스 구성 요소 및 라이선스 - rsync 3.4.1 (librsync.so 라는 이름의 실행 파일) GPL-3.0-or-later https://rsync.samba.org 공식 소스 (https://download.samba.org/pub/rsync/src/rsync-3.4.1.tar.gz) 를 고치지 않고 Android NDK 로 빌드 (빌드 방법: 소스의 tool/build_rsync_android.sh) +- 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일) LGPL-2.1-or-later · BSD-3-Clause · BSD-2-Clause · unRAR 라이선스 제한 + Copyright (C) 1999-2025 Igor Pavlov. https://www.7-zip.org + 7z · RAR 압축을 푸는 데만 씁니다. RAR 을 푸는 코드는 unRAR 라이선스 제한을 따르며 RAR 을 만드는 데 쓰지 않습니다 + (이 앱은 RAR 을 만들지 않습니다). 공식 소스 (https://www.7-zip.org/a/7z2501-src.7z) 를 고치지 않고 Android NDK 로 빌드 + (빌드 방법: 소스의 tool/build_7zz_android.sh). 따로 실행하는 프로그램이며, 대응 소스는 위 주소와 이 앱 판의 태그에 있습니다. + 원문: 앱 안 "오픈 소스 라이선스" 의 7-Zip (7ZIP_LICENSE.txt) · GNU LGPL 2.1 - stable-diffusion.cpp · ggml (libsdcli.so 라는 이름의 실행 파일) MIT https://github.com/leejet/stable-diffusion.cpp 커밋 228c707 을 고치지 않고 Android NDK 로 빌드 (AI 그림 · 해상도 올리기, 빌드 방법: 소스의 tool/build_sdcpp_android.sh) 함께 묶인 구성 요소: ggml (MIT) · libwebp · libwebm (BSD-3) · Oniguruma · Darts-clone (BSD-2) · utf8proc · nlohmann/json · diff --git a/lib/ui/archive_ui.dart b/lib/ui/archive_ui.dart index 62878a4..b34fe0e 100644 --- a/lib/ui/archive_ui.dart +++ b/lib/ui/archive_ui.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show MethodChannel; import 'package:path/path.dart' as p; import '../app/app_controller.dart'; @@ -12,8 +13,17 @@ import '../core/vfs.dart'; import '../l10n/tr.dart'; import 'theme.dart'; -/// 86: 동봉한 7-Zip (Windows: 실행 파일 옆 7zip\7z.exe). 개발 중에는 third_party 의 것. 없으면 null (Android - 아직 없음) -String? bundledSevenZip() { +/// 86: 동봉한 7-Zip. 없으면 null +/// - Windows: 실행 파일 옆 7zip\7z.exe (개발 중에는 third_party 의 것) +/// - Android: 네이티브 라이브러리 폴더의 lib7zz.so (tool/build_7zz_android.sh, APK 의 jniLibs - rsync 와 같은 방식) +Future bundledSevenZip() async { + if (Platform.isAndroid) { + try { + final dir = await const MethodChannel('jj_mkvmaker/android').invokeMethod('nativeLibDir'); + if (dir != null && File(p.join(dir, 'lib7zz.so')).existsSync()) return p.join(dir, 'lib7zz.so'); + } catch (_) {} + return null; + } if (!Platform.isWindows) return null; for (final f in [ p.join(p.dirname(Platform.resolvedExecutable), '7zip', '7z.exe'), @@ -98,7 +108,8 @@ Future extractArchiveFlow(BuildContext context, AppController c, String final local = isDav(path) ? await vLocalCopy(path, await c.services.storage.tempDirectory()) : path; if (!context.mounted) return null; // 7z · RAR: 동봉한 7-Zip 이 있어야 (Android 는 아직 - 막지 않고 다른 앱으로 안내) - final exe = kind == ArchiveKind.zip ? null : bundledSevenZip(); + final exe = kind == ArchiveKind.zip ? null : await bundledSevenZip(); + if (!context.mounted) return null; if (kind != ArchiveKind.zip && exe == null) { final open = await showDialog( context: context, diff --git a/test/archive_ui_test.dart b/test/archive_ui_test.dart index 41225ee..1239239 100644 --- a/test/archive_ui_test.dart +++ b/test/archive_ui_test.dart @@ -70,7 +70,7 @@ void main() { await tester.tap(find.text('취소')); await wait(tester, () => finished); expect(Directory(p.join(tmp.path, 'a')).existsSync(), isFalse); - if (bundledSevenZip() == null) { + if (await tester.runAsync(bundledSevenZip) == null) { final sz = File(p.join(tmp.path, 'x.7z'))..writeAsStringSync('7z'); finished = false; run((ctx) => extractArchiveFlow(ctx, c, sz.path)).then((_) => finished = true); diff --git a/test/licenses_test.dart b/test/licenses_test.dart index dfb3588..34fd901 100644 --- a/test/licenses_test.dart +++ b/test/licenses_test.dart @@ -96,6 +96,13 @@ void main() { expect(cmake, contains('third_party/7zip/windows')); expect(cmake, contains('License.txt')); expect(File('tool/fetch_7zip.ps1').readAsStringSync(), contains('e7eb0b7ed5efa4e087b7b17f191797f7af5b7f442d1290c66f3a21777005ef57')); + // Android 판 (lib7zz.so - 공식 소스를 NDK 로 빌드): 고지 · 빌드 스크립트의 소스 해시 · APK 목록 + final android = File('assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt').readAsStringSync(); + for (final s in ['7-Zip 25.01 (lib7zz.so', '7z2501-src.7z', 'build_7zz_android.sh', 'RAR 을 만들지 않습니다']) { + expect(android, contains(s), reason: s); + } + expect(File('tool/build_7zz_android.sh').readAsStringSync(), contains('2aed39b8f1238464475e9de7dda169a5e873a1dc8bbf4f664b943eaba5620181')); + expect(File('android/app/third_party_libs.txt').readAsLinesSync(), contains('lib7zz.so')); }); test('Windows 설치: 실행 파일 옆 · tools · rsync 에 원문을 둔다 (CMake)', () { diff --git a/tool/build_7zz_android.sh b/tool/build_7zz_android.sh new file mode 100644 index 0000000..9fba1fb --- /dev/null +++ b/tool/build_7zz_android.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# 86 · 관리자 지시 4: Android (arm64) 용 7-Zip (7zz - 7z · RAR 풀기) 을 공식 소스에서 빌드해 +# third_party/7zip/android/arm64-v8a/lib7zz.so 로 둔다 (rsync 와 같은 방식 - APK 의 jniLibs 로 들어가 앱의 네이티브 라이브러리 +# 폴더에서 실행된다. Android 의 libc · libm · libdl 만 쓰고 libc++ 는 정적으로). +# 소스: https://www.7-zip.org/a/7z2501-src.7z (7-Zip 25.01, SHA-256 고정, 고치지 않음) +# 라이선스: GNU LGPL 2.1+ · BSD 3/2-clause · unRAR 라이선스 제한 (RAR 은 풀기만 - 앱은 RAR 을 만들지 않는다) +# 빌드: CPP/7zip/Bundles/Alone2 (7zz) 를 NDK clang 으로 (cmpl_clang_arm64.mak). 어셈블리는 끄고 (USE_ASM=), +# Windows 에서 빌드할 때 makefile 이 SystemDrive 로 MinGW 빌드라고 여기지 않게 SystemDrive · SYSTEMDRIVE 를 비운다. +# 새 NDK clang 의 새 경고로 실패하지 않게 경고 묶음 (-Weverything -Werror) 은 끈다 (코드는 그대로). +# 사용법 (Git Bash): sh tool/build_7zz_android.sh - Android NDK 가 필요 (ANDROID_NDK 또는 tools/android-sdk/ndk/*) +# .7z 소스를 풀 7-Zip 이 필요 (Windows: third_party/7zip/windows/7z.exe - tool/fetch_7zip.ps1, 아니면 PATH 의 7z · 7zz) +set -e +APP=$(cd "$(dirname "$0")/.." && pwd) +VER=2501 +SHA=2aed39b8f1238464475e9de7dda169a5e873a1dc8bbf4f664b943eaba5620181 +NDK_ROOT=${ANDROID_NDK:-$(ls -d "$APP"/../tools/android-sdk/ndk/* | sort | tail -1)} +case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) HOST=windows-x86_64; EXE=.exe ;; Darwin) HOST=darwin-x86_64; EXE= ;; *) HOST=linux-x86_64; EXE= ;; esac +TC=$NDK_ROOT/toolchains/llvm/prebuilt/$HOST/bin +MAKE=$NDK_ROOT/prebuilt/$HOST/bin/make$EXE +[ -x "$MAKE" ] || MAKE=make +SZ="$APP/third_party/7zip/windows/7z.exe" +[ -x "$SZ" ] || SZ=$(command -v 7zz || command -v 7z) +WORK=${TMPDIR:-/tmp}/jj_7zz_android +rm -rf "$WORK" && mkdir -p "$WORK" && cd "$WORK" +curl -sSLf -o src.7z "https://www.7-zip.org/a/7z$VER-src.7z" +echo "$SHA src.7z" | sha256sum -c - +"$SZ" x -y -osrc src.7z > /dev/null +cd src/CPP/7zip/Bundles/Alone2 +mkdir -p b/c_arm64 +"$MAKE" -j8 -f ../../cmpl_clang_arm64.mak SHELL=/bin/sh SystemDrive= SYSTEMDRIVE= MSYSTEM= \ + CC="$TC/clang$EXE --target=aarch64-linux-android24" CXX="$TC/clang++$EXE --target=aarch64-linux-android24" \ + USE_ASM= LIB2=-ldl LDFLAGS_STATIC_3=-static-libstdc++ CFLAGS_WARN= CFLAGS_WARN_WALL= > make.log 2>&1 || { tail -30 make.log; exit 1; } +OUT="$APP/third_party/7zip/android/arm64-v8a" +mkdir -p "$OUT" +"$TC/llvm-strip$EXE" -o "$OUT/lib7zz.so" b/c_arm64/7zz +cp ../../../../DOC/License.txt "$OUT/../License.txt" +ls -l "$OUT/lib7zz.so" diff --git a/tool/check_apk.ps1 b/tool/check_apk.ps1 index 7def38d..31c86c1 100644 --- a/tool/check_apk.ps1 +++ b/tool/check_apk.ps1 @@ -39,7 +39,7 @@ if (-not $Libs) { $Libs = Join-Path $app 'android\app\third_party_libs.txt' } $listed = @(Get-Content $Libs | ForEach-Object { $_.Trim() } | Where-Object { $_ -and -not $_.StartsWith('#') }) # third_party 에 있는 파일 (목록 밖이면 APK 에 없어야 함) $tp = @() -foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android') { +foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android') { $dir = Join-Path $app $d if (Test-Path $dir) { $tp += @(Get-ChildItem $dir -Recurse -Filter *.so | ForEach-Object { $_.Name }) } }