diff --git a/Config/products/macpacker.json b/Config/products/macpacker.json
index f7e661ab..774e8bf5 100644
--- a/Config/products/macpacker.json
+++ b/Config/products/macpacker.json
@@ -35,6 +35,70 @@
"tr": "Bütünlük kontrolü ve kurtarma"
},
"versions": [
+ {
+ "version": "1.2.0",
+ "items": [
+ {
+ "type": "feat",
+ "highlight": true,
+ "title": {
+ "en": "Open and extract LZMA files",
+ "zh-Hans": "打开并解压 LZMA 文件",
+ "fr": "Ouvrir et extraire les fichiers LZMA",
+ "de": "LZMA-Dateien öffnen und entpacken",
+ "it": "Aprire ed estrarre i file LZMA",
+ "ja": "LZMA ファイルを開いて展開",
+ "fa": "باز کردن و استخراج فایلهای LZMA",
+ "pl": "Otwieranie i wypakowywanie plików LZMA",
+ "pt-BR": "Abrir e extrair arquivos LZMA",
+ "ru": "Открытие и извлечение файлов LZMA",
+ "uk": "Відкриття та розпакування файлів LZMA",
+ "es-MX": "Abrir y extraer archivos LZMA",
+ "ko": "LZMA 파일 열기 및 압축 해제",
+ "nl": "LZMA-bestanden openen en uitpakken",
+ "tr": "LZMA dosyalarını açma ve çıkarma",
+ "vi": "Mở và giải nén tệp LZMA",
+ "ar": "فتح ملفات LZMA واستخراجها",
+ "ca": "Obre i extreu fitxers LZMA",
+ "el": "Άνοιγμα και εξαγωγή αρχείων LZMA",
+ "nb": "Åpne og pakk ut LZMA-filer",
+ "zh-TW": "開啟並解壓縮 LZMA 檔案"
+ },
+ "issues": [
+ "340"
+ ]
+ },
+ {
+ "type": "fix",
+ "title": {
+ "en": "Deb files open as one “unknown” file",
+ "zh-Hans": "deb 文件打开后只有一个“unknown”文件",
+ "fr": "Les fichiers deb s’ouvrent en un seul fichier « unknown »",
+ "de": "Deb-Dateien öffnen sich als eine Datei „unknown“",
+ "it": "I file deb si aprono come un unico file «unknown»",
+ "ja": "deb ファイルを開くと「unknown」だけが表示される",
+ "fa": "فایلهای deb فقط بهصورت یک فایل «unknown» باز میشوند",
+ "pl": "Pliki deb otwierają się jako jeden plik „unknown”",
+ "pt-BR": "Arquivos deb abrem como um único arquivo “unknown”",
+ "ru": "Deb-файлы открываются как один файл «unknown»",
+ "uk": "Deb-файли відкриваються як один файл «unknown»",
+ "es-MX": "Los archivos deb se abren como un solo archivo «unknown»",
+ "ko": "deb 파일이 “unknown” 파일 하나로 열림",
+ "nl": "Deb-bestanden openen als één bestand “unknown”",
+ "tr": "Deb dosyaları tek bir “unknown” dosyası olarak açılıyor",
+ "vi": "Tệp deb mở ra chỉ với một tệp “unknown”",
+ "ar": "ملفات deb تُفتح كملف واحد باسم «unknown»",
+ "ca": "Els fitxers deb s’obren com un sol fitxer «unknown»",
+ "el": "Τα αρχεία deb ανοίγουν ως ένα μόνο αρχείο «unknown»",
+ "nb": "Deb-filer åpnes som én fil kalt «unknown»",
+ "zh-TW": "deb 檔案開啟後只有一個「unknown」檔案"
+ },
+ "issues": [
+ "340"
+ ]
+ }
+ ]
+ },
{
"version": "1.1.0",
"items": [
diff --git a/MacPacker/Info.plist b/MacPacker/Info.plist
index dc325bd9..95a63597 100644
--- a/MacPacker/Info.plist
+++ b/MacPacker/Info.plist
@@ -205,6 +205,43 @@
LSTypeIsPackage
+
+ CFBundleTypeExtensions
+
+ lzma
+
+ CFBundleTypeName
+ LZMA Archive
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Default
+ LSItemContentTypes
+
+ org.tukaani.lzma-archive
+
+ LSTypeIsPackage
+
+
+
+ CFBundleTypeExtensions
+
+ tlzma
+ tar.lzma
+
+ CFBundleTypeName
+ LZMA Tarball Archive
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Default
+ LSItemContentTypes
+
+ org.tukaani.tar-lzma-archive
+
+ LSTypeIsPackage
+
+
CFBundleTypeExtensions
@@ -1060,6 +1097,46 @@
+
+ UTTypeConformsTo
+
+ public.archive
+ public.data
+
+ UTTypeDescription
+ LZMA File
+ UTTypeIcons
+
+ UTTypeIdentifier
+ org.tukaani.lzma-archive
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ lzma
+
+
+
+
+ UTTypeConformsTo
+
+ public.archive
+ public.data
+
+ UTTypeDescription
+ LZMA Tar Archive
+ UTTypeIcons
+
+ UTTypeIdentifier
+ org.tukaani.tar-lzma-archive
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ tlzma
+
+
+
ITSAppUsesNonExemptEncryption
diff --git a/MacPacker/Info_Store.plist b/MacPacker/Info_Store.plist
index e7c10304..2e708e6b 100644
--- a/MacPacker/Info_Store.plist
+++ b/MacPacker/Info_Store.plist
@@ -205,6 +205,43 @@
LSTypeIsPackage
+
+ CFBundleTypeExtensions
+
+ lzma
+
+ CFBundleTypeName
+ LZMA Archive
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Default
+ LSItemContentTypes
+
+ org.tukaani.lzma-archive
+
+ LSTypeIsPackage
+
+
+
+ CFBundleTypeExtensions
+
+ tlzma
+ tar.lzma
+
+ CFBundleTypeName
+ LZMA Tarball Archive
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Default
+ LSItemContentTypes
+
+ org.tukaani.tar-lzma-archive
+
+ LSTypeIsPackage
+
+
CFBundleTypeExtensions
@@ -1054,6 +1091,46 @@
+
+ UTTypeConformsTo
+
+ public.archive
+ public.data
+
+ UTTypeDescription
+ LZMA File
+ UTTypeIcons
+
+ UTTypeIdentifier
+ org.tukaani.lzma-archive
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ lzma
+
+
+
+
+ UTTypeConformsTo
+
+ public.archive
+ public.data
+
+ UTTypeDescription
+ LZMA Tar Archive
+ UTTypeIcons
+
+ UTTypeIdentifier
+ org.tukaani.tar-lzma-archive
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ tlzma
+
+
+
ITSAppUsesNonExemptEncryption
diff --git a/Modules/Sources/CSevenZip/sevenzip_bridge.cpp b/Modules/Sources/CSevenZip/sevenzip_bridge.cpp
index 464b8367..7e324ff8 100644
--- a/Modules/Sources/CSevenZip/sevenzip_bridge.cpp
+++ b/Modules/Sources/CSevenZip/sevenzip_bridge.cpp
@@ -1125,6 +1125,24 @@ static void sz_indexAppleDoubleSidecars(SZArchiveHandle *handle) {
}
}
+/// Open an archive at the given filesystem path.
+///
+/// Tries each registered 7-Zip handler on the file, then drills into nested
+/// containers (disk images containing filesystems) up to a fixed depth via
+/// the `kpidMainSubfile` stream. A `deb` level is not drilled into: its
+/// payload is a tar under a compression wrapper with no usable entry paths,
+/// which would otherwise list as a lone entry named "unknown" instead of the
+/// deb's members.
+///
+/// @param path Filesystem path of the archive (UTF-8).
+/// @param password Password for reading encrypted headers (7z `-mhe=on`,
+/// RAR `-hp`), or NULL. Also pre-sets the extraction password.
+/// @param needs_password_out If non-NULL, set to true when a handler asked
+/// for a password during the open. Meaningful only on failure: it tells the
+/// caller to prompt and retry rather than report an unsupported file.
+/// @param error_out If non-NULL, set to a malloc'd UTF-8 error string on
+/// failure; caller must free() it.
+/// @return Opaque archive handle on success, NULL on failure.
SZArchiveRef sz_open(const char *path, const char *password,
bool *needs_password_out, char **error_out) {
if (needs_password_out) *needs_password_out = false;
@@ -1196,6 +1214,15 @@ SZArchiveRef sz_open(const char *path, const char *password,
for (int depth = 0; depth < kMaxNestingDepth; depth++) {
IInArchive *currentArc = handle->levels.back().archive;
+ // Do not drill into a deb: its payload is a tar under a compression
+ // wrapper whose level has no usable kpidPath, which would render a
+ // lone entry named "unknown" instead of the deb's members.
+ NWindows::NCOM::CPropVariant ownExt;
+ currentArc->GetArchiveProperty(kpidExtension, &ownExt);
+ if (ownExt.vt == VT_BSTR && ownExt.bstrVal
+ && wcscmp(ownExt.bstrVal, L"deb") == 0)
+ break;
+
// Check if this archive signals a nested container
NWindows::NCOM::CPropVariant prop;
currentArc->GetArchiveProperty(kpidMainSubfile, &prop);
diff --git a/Modules/Sources/Core/Formats/Catalog.json b/Modules/Sources/Core/Formats/Catalog.json
index 7a1b49fe..55c61e36 100644
--- a/Modules/Sources/Core/Formats/Catalog.json
+++ b/Modules/Sources/Core/Formats/Catalog.json
@@ -202,6 +202,18 @@
{ "id": "swc", "capabilities": ["listContents", "extractFiles"], "default": true }
]
},
+ {
+ "id": "lzma",
+ "name": "LZMA File",
+ "kind": "compression",
+ "uti": ["org.tukaani.lzma-archive"],
+ "extensions": ["lzma"],
+ "mime": ["application/x-lzma"],
+ "rules": [],
+ "engines": [
+ { "id": "7zip", "capabilities": ["listContents", "extractFiles"], "default": true }
+ ]
+ },
{
"id": "lzx",
"name": "LZX Archive",
@@ -571,6 +583,13 @@
"extensions": ["tlz4","tar.lz4"],
"components": ["tar","lz4"]
},
+ {
+ "id": "tar.lzma",
+ "name": "LZMA Tar Archive",
+ "uti": ["org.tukaani.tar-lzma-archive"],
+ "extensions": ["tlzma", "tar.lzma"],
+ "components": ["tar","lzma"]
+ },
{
"id": "tar.xz",
"name": "XZ Tar Archive",
diff --git a/Modules/Tests/CoreTests/DefaultArchiveTests.swift b/Modules/Tests/CoreTests/DefaultArchiveTests.swift
index e2dd99c0..9edf1a80 100644
--- a/Modules/Tests/CoreTests/DefaultArchiveTests.swift
+++ b/Modules/Tests/CoreTests/DefaultArchiveTests.swift
@@ -68,11 +68,13 @@ extension AllCoreTests {
// compounds
("tar.bz2", "tar", 5, 2),
("tar.gz", "tar", 5, 2),
+ ("tar.lzma", "tar", 5, 2),
("tar.xz", "tar", 5, 2),
("tar.Z", "tar", 5, 2),
// comounds with one ending
("tbz2", "tar", 5, 2),
("tgz", "tar", 5, 2),
+ ("tlzma", "tar", 5, 2),
("txz", "tar", 5, 2),
("taz", "tar", 5, 2)
])
@@ -97,6 +99,27 @@ extension AllCoreTests {
#expect(state.root === state.selectedItem)
}
+ /// A deb lists its members, not a lone "unknown". dpkg-deb names the
+ /// first member `debian-binary`, and 7-Zip then offers the compressed
+ /// `data.tar.*` as the main subfile, whose one nameless stream was all
+ /// that showed. fpm names it `debian-binary/`, which 7-Zip reads as a
+ /// plain ar, so that deb always listed its members.
+ @Test(arguments: [
+ ("defaultarchive-dpkg.deb", ["control.tar.xz", "data.tar.xz"]),
+ ("defaultarchive.deb", ["control.tar.gz", "data.tar.gz", "debian-binary"])
+ ])
+ func debListsItsMembers(arg: (String, [String])) async throws {
+ let state = ArchiveState(catalog: ArchiveTypeCatalog(), engineSelector: ArchiveEngineSelector7zip())
+ let url = Bundle.module.url(forResource: "defaultArchives", withExtension: nil)!
+ .appendingPathComponent(arg.0)
+
+ state.open(url: url)
+ try await state.openTask?.value
+
+ let names = state.root?.children?.compactMap { state.entries[$0]?.name }.sorted()
+ #expect(names == arg.1)
+ }
+
@Test("Test all defaultArchives on xad", arguments: [
("zip", "zip"),
("7z", "7zip"),
diff --git a/Modules/Tests/CoreTests/TestArchives b/Modules/Tests/CoreTests/TestArchives
index a4f5a291..00abea7c 160000
--- a/Modules/Tests/CoreTests/TestArchives
+++ b/Modules/Tests/CoreTests/TestArchives
@@ -1 +1 @@
-Subproject commit a4f5a291276cb1257f3f918971d8a9c2d5c586c3
+Subproject commit 00abea7ccff3411420ed05ebe2bafd096e780160
diff --git a/QuickLookExtension/Info.plist b/QuickLookExtension/Info.plist
index 50a7d6a7..5b4853e2 100644
--- a/QuickLookExtension/Info.plist
+++ b/QuickLookExtension/Info.plist
@@ -35,6 +35,7 @@
org.gnu.gnu-zip-archive
org.gnu.gnu-zip-tar-archive
org.tukaani.lzma-archive
+ org.tukaani.tar-lzma-archive
org.tukaani.tar-xz-archive
org.tukaani.xz-archive
public.archive
diff --git a/README.md b/README.md
index 89e826ee..58ffea4b 100644
--- a/README.md
+++ b/README.md
@@ -59,8 +59,8 @@ MacPacker reads and extracts 40+ formats across archives, disk images, and compr
| --- | --- |
| **Archives** | 7z · aar · apk · ar · arj · cab · cbr · cbz · chm · cpio · deb · exe · jar · lha · lzh · lzx · msi · pkg · rar · rpm · sea · sit · sitx · tar · xar · zip · zipx |
| **Disk images** | dmg · fat · iso · ntfs · qcow2 · squashfs · vdi · vhd · vhdx · vmdk · wim |
-| **Compression** | bz2 · gz · lz4 · xz · z |
-| **Tarballs** | tar.gz (tgz) · tar.bz2 (tbz2) · tar.xz (txz) · tar.lz4 · tar.z (taz) |
+| **Compression** | bz2 · gz · lz4 · lzma · xz · z |
+| **Tarballs** | tar.gz (tgz) · tar.bz2 (tbz2) · tar.lzma (tlzma) · tar.xz (txz) · tar.lz4 · tar.z (taz) |
| **Split archives** | zip.001 · z01 · 7z.001 |
See the [full format reference and per-format guides](https://macpacker.app/en/docs) on the website.