diff --git a/.gitattributes b/.gitattributes index d22e380e..d72b02d6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,4 +7,6 @@ gradlew.bat text eol=crlf gradlew text eol=lf # Tell GitHub’s linguist which files to ignore -gradle/InnoSetup6/* linguist-vendored \ No newline at end of file +gradle/InnoSetup6/* linguist-vendored +gradlew linguist-vendored +gradlew.bat linguist-vendored \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml index 25b90a03..aacdb0bb 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -13,7 +13,6 @@ staleLabel: Stale markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you - would like this to remain open, please comment, - otherwise this issue will be closed in 5 days.' + would like this issue to remain open, please comment. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 347636ad..e1e741bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,13 +9,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, windows-latest, macOS-latest] + os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v3 with: - java-version: '18' + java-version: '19' distribution: 'zulu' - name: Install Linux dependencies run: sudo apt-get install libplist-dev libimobiledevice-dev libirecovery-1.0-dev @@ -33,9 +33,9 @@ jobs: uses: actions/upload-artifact@v3 with: name: failure-${{ runner.os }} - path: build/reports/ + path: build/ release: - name: Create pubilc releases + name: Create public releases runs-on: ubuntu-latest needs: build if: "startsWith(github.event.head_commit.message, 'Release')" diff --git a/.idea/compiler.xml b/.idea/compiler.xml index c23d8222..12af3cdf 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index e482bc9f..af2d4c17 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -20,9 +20,8 @@ import org.apache.tools.ant.filters.ReplaceTokens import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform plugins { - id 'java' id 'application' - id 'com.github.ben-manes.versions' version '0.45.0' + id 'com.github.ben-manes.versions' version '0.46.0' id 'org.beryx.jlink' version '2.26.0' id 'org.openjfx.javafxplugin' version '0.0.13' @@ -34,17 +33,14 @@ idea.module.outputDir file("out/production/classes") // fix running via IntelliJ * REMEMBER: also update the version string in Main.java */ version = "3.5.0" -description = "A cross-platform GUI app for saving SHSH blobs" +description = "A cross-platform GUI and CLI app for saving SHSH blobs" String appIdentifier = "airsquared.blobsaver.app" -String copyright = "Copyright (c) 2021 airsquared" +String copyright = "Copyright (c) 2023 airsquared" def os = DefaultNativePlatform.currentOperatingSystem startScripts.enabled = distZip.enabled = distTar.enabled = false -java.toolchain.languageVersion = JavaLanguageVersion.of(18) -tasks.withType(JavaCompile).configureEach { - sourceCompatibility = 18 -} +java.toolchain.languageVersion = JavaLanguageVersion.of(19) repositories { mavenCentral() @@ -56,15 +52,15 @@ dependencies { exclude group: 'net.java.dev.jna', module: 'jna' //different jna version } implementation 'net.java.dev.jna:jna-jpms:5.13.0' - implementation 'org.apache.commons:commons-compress:1.22' - implementation 'info.picocli:picocli:4.7.1' + implementation 'org.apache.commons:commons-compress:1.23.0' + implementation 'info.picocli:picocli:4.7.2' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation group: 'org.testfx', name: 'openjfx-monocle', version: 'jdk-12.0.1+2' } javafx { - version = '19' + version = '20' modules = [ 'javafx.controls', 'javafx.fxml' ] } @@ -109,6 +105,7 @@ jlink { } jpackage { imageOptions = [ '--copyright', copyright, '--description', description] + installerOptions.addAll '--about-url', 'https://github.com/airsquared/blobsaver' vendor = 'airsquared' installerOutputDir = file("${buildDir}/distributions/") @@ -174,6 +171,9 @@ task createLinuxTargz(type: Tar, dependsOn: jpackageImage) { task windowsInstaller(dependsOn: jpackageImage) { // requires inno setup to be installed doFirst { + copy { + from "${projectDir}/dist/windows/blob.ico" into "${buildDir}/jpackage" + } copy { from "${projectDir}/dist/windows/blobsaver.iss" into "${buildDir}/jpackage" filter(ReplaceTokens, tokens: [AppName: project.name, AppVersion: version, AppCopyright: copyright, diff --git a/dist/windows/blobsaver.iss b/dist/windows/blobsaver.iss index 7ea87773..2e9e6822 100644 --- a/dist/windows/blobsaver.iss +++ b/dist/windows/blobsaver.iss @@ -22,14 +22,14 @@ AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} AppCopyright={#MyAppCopyright} -ArchitecturesAllowed=x64 -ArchitecturesInstallIn64BitMode=x64 +ArchitecturesAllowed=x64 arm64 +ArchitecturesInstallIn64BitMode=x64 arm64 Uninstallable=not IsTaskSelected('portableMode') DefaultDirName={pf}\{#MyAppName} DisableProgramGroupPage=yes OutputDir={#OutputDir} OutputBaseFilename=blobsaver-{#MyAppVersion} -SetupIconFile=blobsaver\blobsaver.ico +SetupIconFile=blob.ico Compression=lzma SolidCompression=yes diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a..c1962a79 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f42e62f3..0c85a1f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..aeb74cbb 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -143,12 +140,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +210,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/airsquared/blobsaver/app/Analytics.java b/src/main/java/airsquared/blobsaver/app/Analytics.java index f94420fa..0aa49830 100644 --- a/src/main/java/airsquared/blobsaver/app/Analytics.java +++ b/src/main/java/airsquared/blobsaver/app/Analytics.java @@ -18,9 +18,6 @@ package airsquared.blobsaver.app; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Locale; @@ -103,11 +100,9 @@ private static String getUUID() { return Prefs.getAnalyticsUUID(); } - @SuppressWarnings({"EmptyTryBlock", "unused"}) private static void sendRequest(String url) { - try (InputStream is = new URL(url).openStream()) { - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); + try { + Network.makeVoidRequest(url); } catch (Throwable e) { // don't interrupt application if error occurs e.printStackTrace(); } diff --git a/src/main/java/airsquared/blobsaver/app/Background.java b/src/main/java/airsquared/blobsaver/app/Background.java index d2f53f7c..449b1825 100644 --- a/src/main/java/airsquared/blobsaver/app/Background.java +++ b/src/main/java/airsquared/blobsaver/app/Background.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -33,8 +33,22 @@ class Background { private static final String backgroundLabel = "airsquared.blobsaver.BackgroundService"; - private static final Path plistFilePath = Path.of(System.getProperty("user.home"), "Library/LaunchAgents", - backgroundLabel + ".plist").toAbsolutePath(); + private static final Path plistFilePath = Platform.isMac() ? + Path.of(System.getProperty("user.home"), "Library/LaunchAgents", backgroundLabel + ".plist") + .toAbsolutePath() + : null; + + private static final Path systemdDir; + static { + if (!Platform.isMac() && !Platform.isWindows()) { + String dataHome = System.getenv("XDG_DATA_HOME"); + if (dataHome == null) { + dataHome = System.getProperty("user.home") + "/.local/share"; + } + systemdDir = Path.of(dataHome + "/systemd/user"); + } else systemdDir = null; + } + private static final String windowsTaskName = "\\airsquared\\blobsaver\\BackgroundService"; @@ -85,15 +99,10 @@ private static void linuxBackgroundFile() { [Install] WantedBy=timers.target """.formatted(Prefs.getBackgroundIntervalMinutes()); - String dataHome = System.getenv("XDG_DATA_HOME"); - if (dataHome == null) { - dataHome = System.getProperty("user.home") + "/.local/share"; - } - Path path = Path.of(dataHome + "/systemd/user"); try { - Files.createDirectories(path); - Files.writeString(path.resolve("blobsaver.service"), service); - Files.writeString(path.resolve("blobsaver.timer"), timer); + Files.createDirectories(systemdDir); + Files.writeString(systemdDir.resolve("blobsaver.service"), service); + Files.writeString(systemdDir.resolve("blobsaver.timer"), timer); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -223,6 +232,15 @@ public static void runOnce() { } } + public static void deleteBackgroundFile() throws IOException { + if (Platform.isMac()) { + Files.deleteIfExists(plistFilePath); + } else if (!Platform.isWindows()) { + Files.deleteIfExists(systemdDir.resolve("blobsaver.service")); + Files.deleteIfExists(systemdDir.resolve("blobsaver.timer")); + } + } + private static String executablePath() { return Utils.getBlobsaverExecutable().getAbsolutePath(); } diff --git a/src/main/java/airsquared/blobsaver/app/Controller.java b/src/main/java/airsquared/blobsaver/app/Controller.java index 8a5075d7..9ac9042d 100644 --- a/src/main/java/airsquared/blobsaver/app/Controller.java +++ b/src/main/java/airsquared/blobsaver/app/Controller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -33,11 +33,11 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.control.cell.CheckBoxListCell; +import javafx.scene.effect.DropShadow; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Modality; @@ -75,13 +75,23 @@ public void initialize() { if (Platform.isMac()) { useMacOSMenuBar(); } + deviceModelChoiceBox.itemsProperty().bind(deviceTypeChoiceBox.valueProperty().map(Devices::getModelsForType) + .orElse(FXCollections.emptyObservableList())); + versionLabel.textProperty().bind(deviceTypeChoiceBox.valueProperty().map(Devices::getOSNameForType) + .orElse("Version")); deviceList.getSelectionModel().selectedItemProperty().addListener((a, b, device) -> loadSavedDevice(device)); - deleteDeviceMenu.disableProperty().bind(Bindings.isNull(deviceList.getSelectionModel().selectedItemProperty())); + deleteDeviceMenu.disableProperty().bind(deviceList.getSelectionModel().selectedItemProperty().isNull()); backgroundSettingsMenu.textProperty().bind(Bindings.when(backgroundSettingsButton.selectedProperty()) .then("Hide Background Settings").otherwise("Show Background Settings")); backgroundSettingsMenu.setOnAction(e -> backgroundSettingsButton.fire()); - allSignedVersionsCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - if (!newValue) { + savedDevicesLabel.textProperty().bind(Bindings.when(backgroundSettingsButton.selectedProperty()) + .then("Select Devices").otherwise("Saved Devices")); + backgroundSettingsButton.textProperty().bind(Bindings.when(backgroundSettingsButton.selectedProperty()) + .then("Back").otherwise("Auto-Save Settings")); + savedDevicesVBox.effectProperty().bind(Bindings.when(backgroundSettingsButton.selectedProperty()) + .then(Utils.borderGlow).otherwise((DropShadow) null)); + allSignedVersionsCheckBox.selectedProperty().addListener(ignored -> { + if (!allSignedVersionsCheckBox.isSelected()) { saveToTSSSaverCheckBox.setSelected(false); saveToSHSHHostCheckBox.setSelected(false); betaCheckBox.setSelected(false); @@ -122,12 +132,6 @@ public void checkRequirements(ObservableValue ignored, String ignored2, Strin } } - @SuppressWarnings("unused") - public void deviceTypeChoiceBoxHandler() { - deviceModelChoiceBox.setItems(Devices.getModelsForType(deviceTypeChoiceBox.getValue())); - versionLabel.setText(Utils.defIfNull(Devices.getOSNameForType(deviceTypeChoiceBox.getValue()), "Version")); - } - public void checkForUpdatesHandler() { Utils.checkForUpdates(true); } public void apnonceCheckBoxHandler() { @@ -370,7 +374,7 @@ public void aboutMenuHandler(Event ignored) { } private void useMacOSMenuBar() { - ((VBox) menuBar.getParent()).getChildren().remove(menuBar); + ((Pane) menuBar.getParent()).getChildren().remove(menuBar); menuBar.getMenus().get(0).getItems().remove(5, 8); // clear old options menu MenuToolkit tk = MenuToolkit.toolkit(); @@ -399,6 +403,10 @@ private void updateBackgroundSettings() { forceCheckForBlobs.setDisable(disableBackgroundSettings || !Background.isBackgroundEnabled()); chooseTimeToRunButton.setDisable(disableBackgroundSettings); + if (disableBackgroundSettings && Background.isBackgroundEnabled()) { + Background.stopBackground(); + } + if (Background.isBackgroundEnabled()) { startBackgroundButton.setText("Stop background"); } else { @@ -409,19 +417,12 @@ private void updateBackgroundSettings() { public void backgroundSettingsHandler() { updateBackgroundSettings(); if (backgroundSettingsButton.isSelected()) { - savedDevicesLabel.setText("Select Devices"); - backgroundSettingsButton.setText("Back"); - savedDevicesVBox.setEffect(Utils.borderGlow); - deviceList.setCellFactory(CheckBoxListCell.forListView(device -> { final SimpleBooleanProperty property = new SimpleBooleanProperty(device.isBackground()); property.addListener((obs, old, newValue) -> addBackgroundHandler(device, property)); return property; })); } else { - savedDevicesVBox.setEffect(null); - savedDevicesLabel.setText("Saved Devices"); - backgroundSettingsButton.setText("Auto-Save Settings"); deviceList.setCellFactory(null); } } diff --git a/src/main/java/airsquared/blobsaver/app/Devices.java b/src/main/java/airsquared/blobsaver/app/Devices.java index 5a0c9d86..01bc075b 100644 --- a/src/main/java/airsquared/blobsaver/app/Devices.java +++ b/src/main/java/airsquared/blobsaver/app/Devices.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -173,7 +173,6 @@ private static ObservableList unmodifiableArrayList(T... items) { } private static void loadProperties() throws IOException { - var loader = new Properties() { private Consumer keyProcessor; @@ -196,7 +195,6 @@ public Object put(Object key, Object value) { loader.load("devicemodels/iPhones.properties", iPhones::add); loader.load("devicemodels/iPads.properties", iPads::add); loader.load("devicemodels/iBridges.properties", iBridgeList::add); - loader.load("devicemodels/others.properties", o -> { - }); + loader.load("devicemodels/others.properties", o -> {}); } } diff --git a/src/main/java/airsquared/blobsaver/app/LibimobiledeviceUtil.java b/src/main/java/airsquared/blobsaver/app/LibimobiledeviceUtil.java index 2dad5e7e..95e66127 100644 --- a/src/main/java/airsquared/blobsaver/app/LibimobiledeviceUtil.java +++ b/src/main/java/airsquared/blobsaver/app/LibimobiledeviceUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -67,7 +67,6 @@ public static void exitRecovery() throws LibimobiledeviceException { exitRecovery(irecvClient.getValue()); } - public static void exitRecovery(Pointer irecvClient) throws LibimobiledeviceException { throwIfNeeded(Libirecovery.setEnv(irecvClient, "auto-boot", "true"), ErrorCodeType.irecv_error); throwIfNeeded(Libirecovery.saveEnv(irecvClient), ErrorCodeType.irecv_error); @@ -321,14 +320,14 @@ private static void throwIfNeeded(int errorCode, ErrorCodeType errorType) throws String message = ""; boolean reportableError = false; - if (errorType.equals(ErrorCodeType.idevice_error)) { + if (errorType == ErrorCodeType.idevice_error) { if (errorCode == -3) { // IDEVICE_E_NO_DEVICE message = "No devices found/connected. Make sure your device is connected via USB and unlocked."; } else { message = "idevice error: code=" + errorCode; reportableError = true; } - } else if (errorType.equals(ErrorCodeType.lockdownd_error)) { + } else if (errorType == ErrorCodeType.lockdownd_error) { message = switch (errorCode) { case -17: // LOCKDOWN_E_PASSWORD_PROTECTED yield "The device is locked.\n\nPlease unlock your device and go to the homescreen then try again.\n\nLOCKDOWN_E_PASSWORD_PROTECTED (-17)"; @@ -345,7 +344,7 @@ private static void throwIfNeeded(int errorCode, ErrorCodeType errorType) throws reportableError = true; yield "lockdownd error: code=" + errorCode; }; - } else if (errorType.equals(ErrorCodeType.irecv_error)) { + } else if (errorType == ErrorCodeType.irecv_error) { message = "irecovery error: code=" + errorCode + "\n\nIf your device is still in recovery mode, use the \"Exit Recovery Mode\" option from the help menu."; reportableError = true; @@ -376,7 +375,7 @@ public void showErrorAlert() { } } else if (reportable) { Utils.showReportableError(message); - } else if (ErrorCodeType.idevice_error.equals(errorType) && errorCode == -3) { + } else if (errorType == ErrorCodeType.idevice_error && errorCode == -3 && Platform.isWindows()) { message += "\n\nEnsure iTunes or Apple's iOS Drivers are installed."; ButtonType downloadItunes = new ButtonType("Download iTunes"); if (downloadItunes.equals(Utils.showUnreportableError(message, downloadItunes, ButtonType.OK))) { diff --git a/src/main/java/airsquared/blobsaver/app/Main.java b/src/main/java/airsquared/blobsaver/app/Main.java index 76b7b4dd..7db58cc2 100644 --- a/src/main/java/airsquared/blobsaver/app/Main.java +++ b/src/main/java/airsquared/blobsaver/app/Main.java @@ -144,8 +144,7 @@ public void start(Stage primaryStage) { primaryStage.setTitle("blobsaver"); primaryStage.setScene(new Scene(root)); if (isWindows()) { - primaryStage.getIcons().clear(); - primaryStage.getIcons().add(new Image(Main.class.getResourceAsStream("blob_emoji.png"))); + primaryStage.getIcons().setAll(new Image(Main.class.getResourceAsStream("blob_emoji.png"))); } primaryStage.setResizable(false); Utils.checkForUpdates(false); diff --git a/src/main/java/airsquared/blobsaver/app/Network.java b/src/main/java/airsquared/blobsaver/app/Network.java index 6c0260f6..b90500a7 100644 --- a/src/main/java/airsquared/blobsaver/app/Network.java +++ b/src/main/java/airsquared/blobsaver/app/Network.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2023 airsquared + * + * This file is part of blobsaver. + * + * blobsaver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * blobsaver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with blobsaver. If not, see . + */ + package airsquared.blobsaver.app; import com.google.gson.Gson; @@ -20,25 +38,26 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Map; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; + public class Network { // one instance, reuse - private static final HttpClient httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_2) - .build(); + private static final HttpClient httpClient = HttpClient.newHttpClient(); // Performs a POST Request with the specified URL and Parameters public static HttpResponse makePOSTRequest(String url, Map parameters, Map headers, boolean convertParamtersToJSON) throws IOException, InterruptedException { - // convert Arguments to JSON (and use them if convertParametersToJSON is true) Gson gson = new Gson(); String JSONParameters = gson.toJson(parameters); - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .POST(convertParamtersToJSON ? BodyPublishers.ofString(JSONParameters) : buildFormDataFromMap(parameters)) - .uri(URI.create(url)); + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(url)) + .POST(convertParamtersToJSON ? BodyPublishers.ofString(JSONParameters) : buildFormDataFromMap(parameters)); for (Map.Entry entry : headers.entrySet()) requestBuilder.header(entry.getKey(), entry.getValue()); @@ -61,12 +80,21 @@ private static HttpRequest.BodyPublisher buildFormDataFromMap(Map downloadFile(String url, Path dir) throws IOException, InterruptedException { + return httpClient.send(HttpRequest.newBuilder(URI.create(url)).build(), + HttpResponse.BodyHandlers.ofFile(dir, WRITE, CREATE, TRUNCATE_EXISTING)); + } + /** * Source: https://github.com/jcodec/jcodec/blob/6e1ec651eca92d21b41f9790143a0e6e4d26811e/android/src/main/org/jcodec/common/io/HttpChannel.java * diff --git a/src/main/java/airsquared/blobsaver/app/Utils.java b/src/main/java/airsquared/blobsaver/app/Utils.java index e99a6006..1363a5dd 100644 --- a/src/main/java/airsquared/blobsaver/app/Utils.java +++ b/src/main/java/airsquared/blobsaver/app/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 airsquared + * Copyright (c) 2023 airsquared * * This file is part of blobsaver. * @@ -93,7 +93,7 @@ static void checkForUpdates(boolean forceCheck) { record LatestVersion(String version, String changelog) { static LatestVersion request() throws IOException { - JsonElement json = Network.makeRequest("https://api.github.com/repos/airsquared/blobsaver/releases/latest"); + JsonElement json = Network.makeJsonRequest("https://api.github.com/repos/airsquared/blobsaver/releases/latest"); String tempChangelog = json.getAsJsonObject().get("body").getAsString(); return new LatestVersion(json.getAsJsonObject().get("tag_name").getAsString(), tempChangelog.substring(tempChangelog.indexOf("Changelog"))); } @@ -200,6 +200,7 @@ static void clearAppData() { if (Background.isBackgroundEnabled()) { Background.stopBackground(); } + Background.deleteBackgroundFile(); } catch (Exception ignored) { } Prefs.resetPrefs(); @@ -218,13 +219,19 @@ static void openURL(String url) { } static String executeProgram(List command) throws IOException { + return executeProgram(command, true); + } + + static String executeProgram(List command, boolean print) throws IOException { Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); StringBuilder logBuilder = new StringBuilder(); try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { - System.out.println(line); + if (print) { + System.out.println(line); + } logBuilder.append(line).append("\n"); } process.waitFor(); @@ -354,7 +361,7 @@ static void runSafe(Runnable runnable) { static Stream getFirmwareList(String deviceIdentifier) throws IOException { String url = "https://api.ipsw.me/v4/device/" + deviceIdentifier; try { - return createVersionStream(Network.makeRequest(url).getAsJsonObject().getAsJsonArray("firmwares")); + return createVersionStream(Network.makeJsonRequest(url).getAsJsonObject().getAsJsonArray("firmwares")); } catch (IOException e) { try { return getBetaHubList(deviceIdentifier, false); @@ -368,7 +375,7 @@ static Stream getFirmwareList(String deviceIdentifier) throws IOExce static Stream getBetaList(String deviceIdentifier) throws IOException { try { String url = "https://api.m1sta.xyz/betas/" + deviceIdentifier; - return createVersionStream(Network.makeRequest(url).getAsJsonArray()); + return createVersionStream(Network.makeJsonRequest(url).getAsJsonArray()); } catch (Exception e) { try { return getBetaHubList(deviceIdentifier, true); @@ -381,7 +388,7 @@ static Stream getBetaList(String deviceIdentifier) throws IOExceptio static Stream getBetaHubList(String deviceIdentifier, boolean betas) throws IOException { String url = "https://www.betahub.cn/api/apple/firmwares/" + deviceIdentifier + "?type=" + (betas ? 2 : 1); - JsonArray firmwares = Network.makeRequest(url).getAsJsonObject().getAsJsonArray("firmwares"); + JsonArray firmwares = Network.makeJsonRequest(url).getAsJsonObject().getAsJsonArray("firmwares"); return StreamSupport.stream(firmwares.spliterator(), false) .map(JsonElement::getAsJsonObject) .map(o -> new IOSVersion(o.get("version").getAsString(), o.get("build_id").getAsString(), o.get("url").getAsString(), o.get("signing").getAsInt() == 1)); @@ -409,13 +416,12 @@ record IOSVersion(String versionString, String buildid, String ipswURL, Boolean static Path extractBuildManifest(String ipswUrl) throws IOException { Path buildManifest = Files.createTempFile("BuildManifest", ".plist"); + buildManifest.toFile().deleteOnExit(); if (ipswUrl.matches("https?://.*apple.*\\.ipsw")) { var fileName = Path.of(new URL(ipswUrl).getPath()).getFileName().toString(); - var manifestURL = new URL(ipswUrl.replace(fileName, "BuildManifest.plist")); - try (var stream = manifestURL.openStream()) { - Files.copy(stream, buildManifest, StandardCopyOption.REPLACE_EXISTING); - System.out.println("Directly downloaded to " + buildManifest); - return buildManifest.toRealPath(); + var manifestURL = ipswUrl.replace(fileName, "BuildManifest.plist"); + try { + return Network.downloadFile(manifestURL, buildManifest).body().toRealPath(); } catch (Exception e) { e.printStackTrace(); } @@ -423,11 +429,12 @@ static Path extractBuildManifest(String ipswUrl) throws IOException { Files.copy(Path.of(URI.create(ipswUrl)), buildManifest, StandardCopyOption.REPLACE_EXISTING); return buildManifest.toRealPath(); } else if (ipswUrl.endsWith(".plist")) { - var manifestURL = new URL(ipswUrl); - try (var stream = manifestURL.openStream()) { - Files.copy(stream, buildManifest, StandardCopyOption.REPLACE_EXISTING); + try { + buildManifest = Network.downloadFile(ipswUrl, buildManifest).body().toRealPath(); System.out.println("Directly downloaded to " + buildManifest); - return buildManifest.toRealPath(); + return buildManifest; + } catch (InterruptedException e) { + throw new RuntimeException(e); } } extractManifestFromZip(ipswUrl, buildManifest); diff --git a/src/main/resources/airsquared/blobsaver/app/blobsaver.fxml b/src/main/resources/airsquared/blobsaver/app/blobsaver.fxml index 3e16922a..c64cd039 100644 --- a/src/main/resources/airsquared/blobsaver/app/blobsaver.fxml +++ b/src/main/resources/airsquared/blobsaver/app/blobsaver.fxml @@ -127,7 +127,7 @@ + onAction="#clearEffectHandler" value="iPhone">