Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e4885d4
Melhorias abrangentes de UI/UX, qualidade de código e novas funcional…
fredac100 Feb 16, 2026
6553149
Melhora interface inicial e corrige funcionalidade das tabs
fredac100 Feb 16, 2026
5bf4409
Desabilita controles de arquivos até seleção de domínio
fredac100 Feb 16, 2026
0f8fdd0
Corrige problemas críticos de segurança e qualidade do code review
fredac100 Feb 16, 2026
4c82032
Corrige checkboxes com traço na primeira seleção e delay
fredac100 Feb 16, 2026
e2873d0
Substitui aba File Search por Media Browser com navegação visual de f…
fredac100 Feb 16, 2026
8dec612
Correções de segurança e robustez na API
fredac100 Feb 16, 2026
eff5f8d
Melhorias na aba Media, suporte HEIC/MOV e dialog About
fredac100 Feb 16, 2026
c9b26c9
Adiciona aba Device para comunicação ao vivo com iPhone via USB
fredac100 Feb 17, 2026
a7597ba
Corrige cálculo de armazenamento e barras de progresso na aba Device
fredac100 Feb 17, 2026
56ef57f
Adiciona aba Mirror para espelhamento de tela do iPhone via USB
fredac100 Feb 17, 2026
9b45aa6
Adiciona espelhamento via AirPlay e otimiza pipeline de captura USB
fredac100 Feb 17, 2026
4cf793d
Corrige abertura de arquivos, remove botão Reset e estabiliza AirPlay…
fredac100 Feb 17, 2026
b54c1a7
Corrige screenshots do README com imagens reais da pasta docs
fredac100 Feb 17, 2026
be68fde
Reposiciona README como produto independente — iDevice Toolkit
fredac100 Feb 17, 2026
884663d
Corrige busca de arquivos, melhora UI da aba File Search e padroniza …
fredac100 Feb 18, 2026
4526f22
Traduz textos da tela de preferências para inglês e adiciona guard de…
fredac100 Feb 18, 2026
b2d1d44
Adiciona botão Create Backup com janela de progresso detalhada, corri…
fredac100 Feb 18, 2026
ff4d417
Corrige crash ao abrir com backups duplicados e melhora addBackupRoot
fredac100 Feb 18, 2026
c520079
Registra backup root automaticamente ao abrir backup e simplifica fil…
fredac100 Feb 18, 2026
eb26be2
Adiciona fallback pymobiledevice3 para backup e setup automático cros…
fredac100 Feb 18, 2026
473d040
Adiciona download automático de Python portátil no Windows para backup
fredac100 Feb 18, 2026
8cec282
Instala setuptools e wheel antes do pymobiledevice3 no Python portátil
fredac100 Feb 18, 2026
85f09c9
Melhora mensagem de dispositivo não detectado com instruções por SO
fredac100 Feb 18, 2026
3c581e1
Corrige comando pymobiledevice3: backup2 em vez de mobilebackup2
fredac100 Feb 18, 2026
2608fd7
Adiciona flag --full ao comando backup2 do pymobiledevice3
fredac100 Feb 18, 2026
73a4690
Adiciona parser de progresso tqdm para backups via pymobiledevice3
fredac100 Feb 18, 2026
aac3909
Corrige cancelamento e leitura de progresso tqdm no backup pymobilede…
fredac100 Feb 18, 2026
57048da
Adiciona log de debug e parser tqdm simplificado para progresso do ba…
fredac100 Feb 18, 2026
28ec3c1
Adiciona scripts compile e run para Windows e Linux
fredac100 Feb 18, 2026
42bb9a7
Corrige exibição de progresso tqdm: files, speed e transferred
fredac100 Feb 18, 2026
42b90ca
Corrige cancelamento do backup e adiciona diálogo de confirmação
fredac100 Feb 18, 2026
5082ebb
Corrige exibição de progresso do backup pymobiledevice3
fredac100 Feb 18, 2026
08002d1
Busca dados de disco e bateria via pymobiledevice3 e corrige speed
fredac100 Feb 18, 2026
917cf09
Corrige obtenção de dados de disco, cancelamento e variáveis mortas
fredac100 Feb 18, 2026
b18092a
Adiciona log de marcos de progresso a cada 5% na lista do backup
fredac100 Feb 18, 2026
991618b
Força output unbuffered do Python no backup pymobiledevice3
fredac100 Feb 18, 2026
45bcda9
Melhora log de progresso e corrige locale em formatSize/formatSpeed
fredac100 Feb 19, 2026
2b4cf4f
Adiciona download automático de ffmpeg/ImageMagick no Windows e docum…
fredac100 Feb 19, 2026
75e4c9c
Elimina ImageMagick como dependência e simplifica aba Files
fredac100 Feb 19, 2026
a9d7fdb
Adiciona ImageMagick portátil, corrige download e run.bat
fredac100 Feb 19, 2026
6e2047e
Corrige download do ImageMagick (7z) e aplica tema nos diálogos
fredac100 Feb 19, 2026
d946224
Adiciona fallback pymobiledevice3 na aba Device
fredac100 Feb 19, 2026
7e3545b
Corrige mirror no Windows e desativa AirPlay temporariamente
fredac100 Feb 19, 2026
c841898
Translate changelog to English and document recent changes
fredac100 Feb 19, 2026
c5b7c32
Corrige progresso de backup no Linux e melhora cálculo de velocidade
fredac100 Feb 19, 2026
80d9b2b
Otimiza FPS do mirror USB e atualiza README/CHANGELOG
fredac100 Feb 19, 2026
4e7e5ef
Corrige resize da galeria, traduz strings PT e restaura filtro Files
fredac100 Feb 19, 2026
6044338
Atualiza README e CHANGELOG com instruções simplificadas de build
fredac100 Feb 19, 2026
8df71ae
Corrige instruções de quick start: compile antes de run
fredac100 Feb 19, 2026
4a05c00
Adiciona quick start logo no início do README
fredac100 Feb 19, 2026
214a855
Consolida instruções de build no topo do README
fredac100 Feb 19, 2026
82c8129
Corrige âncora Installation para apontar ao Quick start
fredac100 Feb 19, 2026
309bd33
[WIP] Adiciona aba WhatsApp (Beta) para visualização de conversas de …
fredac100 Feb 19, 2026
e4b0a25
Corrige listagem de chats e carregamento de mídia na aba WhatsApp
fredac100 Feb 19, 2026
54eae11
Padroniza diálogo de progresso de backup entre Windows e Linux
fredac100 Feb 19, 2026
24b6a89
Remove CLAUDE.md do repositório
fredac100 Feb 19, 2026
239a8ea
Melhora exibição de mídia e visualização na aba WhatsApp
fredac100 Feb 19, 2026
7a41b33
Corrige filtro de chats e adiciona diagnóstico de banco WhatsApp
fredac100 Feb 19, 2026
364e413
Adiciona __pycache__ e *.pyc ao .gitignore
fredac100 Feb 19, 2026
3386737
Corrige query SQL de chats, melhora diálogos e fluxo de backup
fredac100 Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ $RECYCLE.BIN/

/local/
*.afphoto

### Python
__pycache__/
*.pyc
460 changes: 460 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

323 changes: 224 additions & 99 deletions README.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions compile.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@echo off
cd /d "%~dp0"
echo Compiling iTunes Backup Explorer...
call mvn -q -DskipTests compile assembly:single
echo.
echo Done! Run with: run.bat
7 changes: 7 additions & 0 deletions compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
echo "Compiling iTunes Backup Explorer..."
mvn -q -DskipTests compile assembly:single
echo ""
echo "Done! Run with: ./run.sh"
Binary file added docs/Itunes1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/Itunes7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion packaging/package-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ echo Packaging...
--app-image "target/app-image/$APP_NAME" \
--linux-menu-group "Utility;Archiving;" \
--linux-shortcut \
--linux-app-category "utils"
--linux-app-category "utils" \
--linux-package-deps "ffmpeg, imagemagick, libheif1, libimobiledevice-utils, usbmuxd, ideviceinstaller, python3, python3-venv, python3-pip"
15 changes: 15 additions & 0 deletions packaging/package-win.bat
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ CALL "%JAVA_HOME%\bin\jpackage" ^
--runtime-image "target\runtime-image" ^
--module "%MAIN_MODULE%/%MAIN_CLASS%"

REM Include portable media tools if available on the build machine
SET MEDIA_TOOLS_SRC=%USERPROFILE%\.config\itunes-backup-explorer
SET MEDIA_TOOLS_DEST=target\app-image\%APP_NAME%\app\media-tools

IF EXIST "%MEDIA_TOOLS_SRC%\ffmpeg-portable" (
echo Including bundled ffmpeg...
XCOPY /E /I /Q "%MEDIA_TOOLS_SRC%\ffmpeg-portable" "%MEDIA_TOOLS_DEST%\ffmpeg-portable"
)

IF EXIST "%MEDIA_TOOLS_SRC%\imagemagick-portable" (
echo Including bundled ImageMagick...
XCOPY /E /I /Q "%MEDIA_TOOLS_SRC%\imagemagick-portable" "%MEDIA_TOOLS_DEST%\imagemagick-portable"
)


CALL "%JAVA_HOME%\bin\jpackage" ^
--type msi ^
--dest "target\installer" ^
Expand Down
33 changes: 33 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@
<artifactId>slf4j-simple</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.27.1</version>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
Expand Down Expand Up @@ -206,6 +222,18 @@
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>bash</executable>
<arguments>
<argument>${project.basedir}/scripts/run-dev.sh</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>run-java</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
Expand Down Expand Up @@ -240,6 +268,11 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
Expand Down
35 changes: 35 additions & 0 deletions run.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@echo off
setlocal
cd /d "%~dp0"

set JAR=
if exist target (
for %%f in (target\*-jar-with-dependencies.jar) do set JAR=%%f
)

if not defined JAR (
echo.
echo Compiling iTunes Backup Explorer...
echo.
call mvn -q -DskipTests compile assembly:single
if errorlevel 1 (
echo.
echo ERROR: Compilation failed.
echo Make sure Java 18+ [JDK] and Maven are installed and in your PATH.
echo.
pause
exit /b 1
)
for %%f in (target\*-jar-with-dependencies.jar) do set JAR=%%f
)

if not defined JAR (
echo.
echo ERROR: Could not find the compiled JAR.
echo.
pause
exit /b 1
)

echo Starting iTunes Backup Explorer...
java -jar "%JAR%"
23 changes: 23 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"

JAR=$(find target -maxdepth 1 -name '*-jar-with-dependencies.jar' 2>/dev/null | head -1)

if [ -z "$JAR" ]; then
echo ""
echo " Compiling iTunes Backup Explorer..."
echo ""
mvn -q -DskipTests compile assembly:single
JAR=$(find target -maxdepth 1 -name '*-jar-with-dependencies.jar' | head -1)
fi

if [ -z "$JAR" ]; then
echo ""
echo " ERROR: Compilation failed."
echo " Make sure Java 18+ (JDK) and Maven are installed."
exit 1
fi

echo "Starting iTunes Backup Explorer..."
java -jar "$JAR"
5 changes: 5 additions & 0 deletions scripts/run-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail

# Always compile first so mvn exec:exec runs the latest code.
mvn -q -DskipTests compile exec:exec@run-java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import me.maxih.itunes_backup_explorer.ui.PreferencesController;
import me.maxih.itunes_backup_explorer.ui.WindowController;

import java.io.IOException;
Expand All @@ -19,9 +20,10 @@ public class ITunesBackupExplorer extends Application {
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(ITunesBackupExplorer.class.getResource("window.fxml"));
Parent root = fxmlLoader.load();
root.getStyleClass().add("Light".equalsIgnoreCase(PreferencesController.getTheme()) ? "theme-light" : "theme-dark");
controller = fxmlLoader.getController();

scene = new Scene(root, 800, 500);
scene = new Scene(root, 1400, 800);
stage.setScene(scene);
stage.setTitle("iTunes Backup Explorer");
stage.setMinWidth(500);
Expand Down
57 changes: 46 additions & 11 deletions src/main/java/me/maxih/itunes_backup_explorer/api/BackupFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.dd.plist.*;
import me.maxih.itunes_backup_explorer.util.BackupPathUtils;
import me.maxih.itunes_backup_explorer.util.UtilDict;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.ByteBuffer;
Expand All @@ -15,6 +17,7 @@
import java.util.Optional;

public class BackupFile {
private static final Logger logger = LoggerFactory.getLogger(BackupFile.class);
public final ITunesBackup backup;
public final UtilDict data;
public final String fileID;
Expand Down Expand Up @@ -85,19 +88,28 @@ public BackupFile(ITunesBackup backup, String fileID, String domain, String rela
}

private <T extends NSObject> T getObject(Class<T> type, UID uid) throws NoSuchElementException {
byte index = uid.getBytes()[0];
int index = uidToIndex(uid);
if (index < 0 || index >= this.objects.length) throw new NoSuchElementException();
Object obj = this.objects[index];
if (type.isInstance(obj)) return type.cast(obj);
throw new NoSuchElementException();
}

private void setObject(UID uid, NSObject object) {
byte index = uid.getBytes()[0];
int index = uidToIndex(uid);
this.objects[index] = object;
// theoretically not necessary, but don't rely on the array never being cloned
this.data.put("$objects", new NSArray(this.objects));
}

static int uidToIndex(UID uid) {
byte[] bytes = uid.getBytes();
int index = 0;
for (byte b : bytes) {
index = (index << 8) | (b & 0xFF);
}
return index;
}

public FileType getFileType() {
return fileType;
}
Expand Down Expand Up @@ -162,6 +174,11 @@ byte[] calcFileDigest() throws IOException, UnsupportedCryptoException {

public void extract(File destination)
throws IOException, BackupReadException, NotUnlockedException, UnsupportedCryptoException, UnsupportedOperationException {
extract(destination, true);
}

public void extract(File destination, boolean preserveTimestamps)
throws IOException, BackupReadException, NotUnlockedException, UnsupportedCryptoException, UnsupportedOperationException {

switch (this.fileType) {
case DIRECTORY:
Expand All @@ -178,14 +195,23 @@ public void extract(File destination)
throw new BackupReadException(e);
}

//noinspection ResultOfMethodCallIgnored
this.properties.get(NSNumber.class, "LastModified")
.map(NSNumber::longValue)
.map(seconds -> seconds * 1000)
.ifPresent(destination::setLastModified);
if (preserveTimestamps) {
//noinspection ResultOfMethodCallIgnored
this.properties.get(NSNumber.class, "LastModified")
.map(NSNumber::longValue)
.map(seconds -> seconds * 1000)
.ifPresent(destination::setLastModified);
}
} else {
Files.copy(this.contentFile.toPath(), destination.toPath(),
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);

if (preserveTimestamps) {
this.properties.get(NSNumber.class, "LastModified")
.map(NSNumber::longValue)
.map(seconds -> seconds * 1000)
.ifPresent(destination::setLastModified);
}
}
break;
case SYMBOLIC_LINK:
Expand All @@ -196,6 +222,11 @@ public void extract(File destination)

public void extractToFolder(File destinationFolder, boolean withRelativePath)
throws IOException, BackupReadException, NotUnlockedException, UnsupportedCryptoException, UnsupportedOperationException {
extractToFolder(destinationFolder, withRelativePath, true);
}

public void extractToFolder(File destinationFolder, boolean withRelativePath, boolean preserveTimestamps)
throws IOException, BackupReadException, NotUnlockedException, UnsupportedCryptoException, UnsupportedOperationException {

String relative;

Expand All @@ -206,17 +237,21 @@ public void extractToFolder(File destinationFolder, boolean withRelativePath)
relative = withRelativePath
? Paths.get(this.domain, BackupPathUtils.cleanPath(this.relativePath)).toString()
: BackupPathUtils.cleanPath(this.getFileName());
System.out.println("Continuing with invalid characters replaced: " + this.getFileName() + " -> " + relative);
logger.warn("Continuing with invalid characters replaced: {} -> {}", this.getFileName(), relative);
} catch (InvalidPathException e1) {
throw new IOException("Invalid character in filename, failed to replace", e1);
}
}
File destination = new File(destinationFolder.getAbsolutePath(), relative);
if (!destination.getCanonicalPath().startsWith(destinationFolder.getCanonicalPath() + File.separator)
&& !destination.getCanonicalPath().equals(destinationFolder.getCanonicalPath())) {
throw new IOException("Path traversal detected: " + relative);
}
if (destination.exists() && this.fileType != FileType.DIRECTORY)
throw new FileAlreadyExistsException(destination.getAbsolutePath());

Files.createDirectories(destination.getParentFile().toPath());
this.extract(destination);
this.extract(destination, preserveTimestamps);
}

public void replaceWith(File newFile) throws IOException, BackupReadException, UnsupportedCryptoException, NotUnlockedException, DatabaseConnectionException {
Expand Down Expand Up @@ -261,7 +296,7 @@ public void delete() throws IOException, DatabaseConnectionException {
try {
this.backupOriginal(true);
} catch (FileNotFoundException e) {
System.out.printf("Warning: Deleted backup file '%s' did not have a content file%n", this.relativePath);
logger.warn("Deleted backup file '{}' had no content file", this.relativePath);
}
this.backup.removeFileFromDatabase(this.fileID);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public BackupInfo(NSDictionary data) throws BackupReadException {
dict.getDict("Applications").orElseThrow().forTypedEntries(NSDictionary.class, (key, value) -> {
UtilDict info = new UtilDict(value);
ApplicationInfo app = new ApplicationInfo(
info.getData("PlaceholderIcon").orElseThrow(),
info.getData("PlaceholderIcon").orElse(null),
info.getData("iTunesMetadata").orElse(null),
info.getBoolean("IsDemotedApp").orElse(false),
info.getData("ApplicationSINF").orElse(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public BackupManifest(NSDictionary data) throws BackupReadException {
this.passcodeSet = dict.getBoolean("WasPasscodeSet").orElseThrow();
this.productVersion = lockdown.getString("ProductVersion").orElseThrow();
this.productType = lockdown.getString("ProductType").orElseThrow();
this.buildVersion = lockdown.getString("BuildVersion").orElseThrow();
this.uniqueDeviceID = lockdown.getString("UniqueDeviceID").orElseThrow();
this.serialNumber = lockdown.getString("SerialNumber").orElseThrow();
this.buildVersion = lockdown.getString("BuildVersion").orElse("");
this.uniqueDeviceID = lockdown.getString("UniqueDeviceID").orElse("");
this.serialNumber = lockdown.getString("SerialNumber").orElse("");
this.deviceName = lockdown.getString("DeviceName").orElseThrow();
this.applications = dict.get(NSDictionary.class, "Applications").orElseThrow();
if (this.encrypted) {
Expand Down
Loading