Fix multi-part RAR extraction: use VolumedArchiveInStream for .partN.rar

The JVM extractor used RandomAccessFileInStream for multi-part RAR archives,
which only provides a single file stream. 7z-JBinding requires
VolumedArchiveInStream to access additional volume parts via callback.

Added RAR_MULTIPART_RE and RAR_OLDSPLIT_RE patterns to detect multi-volume
RAR archives and route them through VolumedArchiveInStream, fixing
"Archive file can't be opened with any of the registered codecs" errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-03 15:54:09 +01:00
parent d3ec000da5
commit d4bf574370
11 changed files with 41 additions and 3 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.5.48", "version": "1.5.54",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.5.48", "version": "1.5.54",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",

View File

@ -42,6 +42,8 @@ public final class JBindExtractorMain {
private static final Pattern NUMBERED_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.zip\\.\\d{3}$"); private static final Pattern NUMBERED_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.zip\\.\\d{3}$");
private static final Pattern OLD_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.z\\d{2,3}$"); private static final Pattern OLD_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.z\\d{2,3}$");
private static final Pattern SEVEN_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.7z\\.001$"); private static final Pattern SEVEN_ZIP_SPLIT_RE = Pattern.compile("(?i).*\\.7z\\.001$");
private static final Pattern RAR_MULTIPART_RE = Pattern.compile("(?i).*\\.part\\d+\\.rar$");
private static final Pattern RAR_OLDSPLIT_RE = Pattern.compile("(?i).*\\.r\\d{2,3}$");
private static volatile boolean sevenZipInitialized = false; private static volatile boolean sevenZipInitialized = false;
private JBindExtractorMain() { private JBindExtractorMain() {
@ -326,7 +328,13 @@ public final class JBindExtractorMain {
String effectivePassword = password == null ? "" : password; String effectivePassword = password == null ? "" : password;
SevenZipVolumeCallback callback = new SevenZipVolumeCallback(archiveFile, effectivePassword); SevenZipVolumeCallback callback = new SevenZipVolumeCallback(archiveFile, effectivePassword);
if (SEVEN_ZIP_SPLIT_RE.matcher(nameLower).matches()) { // Multi-volume archives need VolumedArchiveInStream so 7z-JBinding can
// request additional volumes via the IArchiveOpenVolumeCallback.
boolean isMultiVolume = SEVEN_ZIP_SPLIT_RE.matcher(nameLower).matches()
|| RAR_MULTIPART_RE.matcher(nameLower).matches()
|| hasOldStyleRarSplits(archiveFile);
if (isMultiVolume) {
VolumedArchiveInStream volumed = new VolumedArchiveInStream(archiveFile.getName(), callback); VolumedArchiveInStream volumed = new VolumedArchiveInStream(archiveFile.getName(), callback);
IInArchive archive = SevenZip.openInArchive(null, volumed, callback); IInArchive archive = SevenZip.openInArchive(null, volumed, callback);
return new SevenZipArchiveContext(archive, null, volumed, callback); return new SevenZipArchiveContext(archive, null, volumed, callback);
@ -338,6 +346,36 @@ public final class JBindExtractorMain {
return new SevenZipArchiveContext(archive, stream, null, callback); return new SevenZipArchiveContext(archive, stream, null, callback);
} }
private static boolean hasOldStyleRarSplits(File archiveFile) {
// Old-style RAR splits: main.rar + main.r01, main.r02, ...
String name = archiveFile.getName();
if (!name.toLowerCase(Locale.ROOT).endsWith(".rar")) {
return false;
}
File parent = archiveFile.getParentFile();
if (parent == null || !parent.exists()) {
return false;
}
File[] siblings = parent.listFiles();
if (siblings == null) {
return false;
}
String stem = name.substring(0, name.length() - 4);
for (File sibling : siblings) {
if (!sibling.isFile()) {
continue;
}
String sibName = sibling.getName();
if (sibName.length() > stem.length() + 1 && sibName.substring(0, stem.length()).equalsIgnoreCase(stem)) {
String suffix = sibName.substring(stem.length());
if (RAR_OLDSPLIT_RE.matcher(suffix).matches() || suffix.toLowerCase(Locale.ROOT).matches("\\.r\\d{2,3}")) {
return true;
}
}
}
return false;
}
private static boolean isWrongPassword(ZipException error, boolean encrypted) { private static boolean isWrongPassword(ZipException error, boolean encrypted) {
if (error == null) { if (error == null) {
return false; return false;