diff --git a/.github/Dark.png b/.github/Dark.png index 95382b6..7efe14f 100644 Binary files a/.github/Dark.png and b/.github/Dark.png differ diff --git a/.github/Light.png b/.github/Light.png index 899826c..d23694f 100644 Binary files a/.github/Light.png and b/.github/Light.png differ diff --git a/.github/workflows/build-pack-manual.yml b/.github/workflows/build-pack-manual.yml index 0eb2e16..42ab00e 100644 --- a/.github/workflows/build-pack-manual.yml +++ b/.github/workflows/build-pack-manual.yml @@ -23,9 +23,10 @@ jobs: echo '::set-output name=version::(echo $version)' - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -53,9 +54,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Download final jar uses: actions/download-artifact@v2 @@ -116,9 +118,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Install create-dmg run: brew install create-dmg @@ -175,9 +178,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Download final jar uses: actions/download-artifact@v2 diff --git a/.github/workflows/build-pack-prerelease.yml b/.github/workflows/build-pack-prerelease.yml new file mode 100644 index 0000000..bf186e0 --- /dev/null +++ b/.github/workflows/build-pack-prerelease.yml @@ -0,0 +1,317 @@ +name: Build, Pack, Prerelease + +# Controls when the action will run. +on: + workflow_dispatch: + +jobs: + build-and-create-draft: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Parse version + id: parse-version + run: | + version=$(cat src/main/java/Main.java | grep -Po '(?<=futureRestoreGUIVersion = \").*(?=\")') + echo $version > version.txt + echo "::set-output name=version::$version" + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: 'temurin' + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build Jar + run: ./gradlew shadowJar + # Upload this built jar + - name: Upload final jar + uses: actions/upload-artifact@v2 + with: + name: fat-jar + path: ./build/libs/FutureRestore GUI-1.0-all.jar + + - name: Upload version + uses: actions/upload-artifact@v2 + with: + name: version + path: version.txt + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.parse-version.outputs.version }} + release_name: FutureRestore GUI Prerelease — v${{ steps.parse-version.outputs.version }} + draft: true + prerelease: true + + - name: Create upload URL file + env: + UPLOAD_URL: ${{ steps.create_release.outputs.upload_url }} + run: echo $UPLOAD_URL > upload-url.txt + - name: Upload release's upload URL + uses: actions/upload-artifact@v2 + with: + name: upload-url + path: upload-url.txt + + - name: Create release ID file + env: + RELEASE_ID: ${{ steps.create_release.outputs.id }} + run: echo $RELEASE_ID > release-id.txt + - name: Upload release's upload URL + uses: actions/upload-artifact@v2 + with: + name: release-id + path: release-id.txt + + ubuntu-pack: + needs: [ build-and-create-draft ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: 'temurin' + + - name: Download final jar + uses: actions/download-artifact@v2 + with: + name: fat-jar + path: ./input + + - name: Download version + uses: actions/download-artifact@v2 + with: + name: version + - name: Output version + id: output-version + run: | + version=$(tail version.txt) + echo "::set-output name=version::$version" + + - name: Run JPackage deb + env: + RUN_NUMBER: ${{ github.run_number }} + VERSION: ${{ steps.output-version.outputs.version }} + run: jpackage --input ./input --name "FutureRestore GUI" --main-jar "FutureRestore GUI-1.0-all.jar" --main-class Main --type deb --icon ./.github/workflows/ubuntu/Icon-1024.png --copyright "© CoocooFroggy 2021" --verbose --name "FutureRestore GUI" --module-path ./.github/workflows/ubuntu/javafx-jmods-11.0.2 --add-modules javafx.swing,java.logging,java.sql,java.base,jdk.crypto.ec,java.naming --app-version $VERSION 2>&1 | tee /tmp/jpackoutput.txt + - name: Grep DEB name + id: grep_deb_name + run: | + deb_name=$(grep -Po '(?<=Package \(.deb\) saved to: ).*(?=.)' /tmp/jpackoutput.txt) + echo "::set-output name=deb_filename::$deb_name" + + - name: Run JPackage app-image + env: + RUN_NUMBER: ${{ github.run_number }} + VERSION: ${{ steps.output-version.outputs.version }} + run: jpackage --input ./input --name "FutureRestore GUI" --main-jar "FutureRestore GUI-1.0-all.jar" --main-class Main --type app-image --icon ./.github/workflows/ubuntu/Icon-1024.png --copyright "© CoocooFroggy 2021" --verbose --name "FutureRestore GUI" --module-path ./.github/workflows/ubuntu/javafx-jmods-11.0.2 --add-modules javafx.swing,java.logging,java.sql,java.base,jdk.crypto.ec,java.naming --app-version $VERSION + + - name: Zip app-image and script + run: | + cp ./.github/workflows/ubuntu/"Run FutureRestore GUI.sh" ./ + zip -r FutureRestore-GUI-Linux-Universal.zip "FutureRestore GUI" "Run FutureRestore GUI.sh" + + - name: Download URL to upload to + uses: actions/download-artifact@v2 + with: + name: upload-url + # Make it usable in Github actions + - name: Set output for upload-url + id: set_upload_url + run: | + upload_url=$(tail upload-url.txt) + echo "::set-output name=upload-url::$upload_url" + + - name: Upload DEB to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload-url }} + asset_path: ${{ steps.grep_deb_name.outputs.deb_filename }} + asset_name: FutureRestore-GUI-Debian-${{ steps.output-version.outputs.version }}.deb + asset_content_type: application/octet-stream + + - name: Upload app-image zip to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload-url }} + asset_path: FutureRestore-GUI-Linux-Universal.zip + asset_name: FutureRestore-GUI-Linux-Universal-${{ steps.output-version.outputs.version }}.zip + asset_content_type: application/zip + + + + + macos-pack: + needs: [ build-and-create-draft ] + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: 'temurin' + + - name: Install create-dmg + run: brew install create-dmg + + - name: Download final jar + uses: actions/download-artifact@v2 + with: + name: fat-jar + path: ./input + + - name: Download version + uses: actions/download-artifact@v2 + with: + name: version + - name: Output version + id: output-version + run: | + version=$(tail version.txt) + echo "::set-output name=version::$version" + + - name: Run JPackage and remove signature + env: + RUN_NUMBER: ${{ github.run_number}} + VERSION: ${{ steps.output-version.outputs.version }} + run: | + jpackage --input ./input --main-jar "FutureRestore GUI-1.0-all.jar" --main-class Main --type app-image --icon ./.github/workflows/mac/FutureRestoreGUIIcons.icns --copyright "© CoocooFroggy 2021" --verbose --mac-package-identifier com.coocoofroggy.futurerestoregui --name "FutureRestore GUI" --module-path ./.github/workflows/mac/javafx-jmods-11.0.2 --add-modules javafx.swing,java.logging,java.sql,java.base,jdk.crypto.ec,java.naming --app-version $VERSION + codesign --remove-signature FutureRestore\ GUI.app + mkdir dmg-input + mv "FutureRestore GUI.app" dmg-input + + - name: Create DMG from .app + run: | + create-dmg --hdiutil-quiet --volname "FutureRestore GUI Installer" --volicon ".github/workflows/mac/DMG Images/FRGUI-Drive-Icon.icns" --background ".github/workflows/mac/DMG Images/DMG-Background.png" --window-pos 200 120 --window-size 600 342 --icon-size 100 --icon "FutureRestore GUI.app" 80 200 --hide-extension "FutureRestore GUI.app" --app-drop-link 450 200 "FutureRestore GUI Installer.dmg" "dmg-input/" + +# - name: Zip .app +# id: zip_app +# run: zip -r FutureRestore-GUI-Mac.zip "FutureRestore GUI.app" + + - name: Download URL to upload to + uses: actions/download-artifact@v2 + with: + name: upload-url + # Make it usable in Github actions + - name: Set output for upload-url + id: set_upload_url + run: | + upload_url=$(tail upload-url.txt) + echo "::set-output name=upload-url::$upload_url" + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload-url }} + asset_path: FutureRestore GUI Installer.dmg + asset_name: FutureRestore-GUI-Mac-${{ steps.output-version.outputs.version }}.dmg + asset_content_type: application/octet-stream + + windows-pack: + needs: [ build-and-create-draft ] + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: 'temurin' + + - name: Download final jar + uses: actions/download-artifact@v2 + with: + name: fat-jar + path: ./input + + - name: Download version + uses: actions/download-artifact@v2 + with: + name: version + - name: Output version + id: output-version + run: | + FOR /F "tokens=*" %%g IN ('tail version.txt') do (SET version=%%g) + echo ::set-output name=version::%version% + shell: cmd + + - name: Run JPackage + env: + RUN_NUMBER: ${{ github.run_number }} + VERSION: ${{ steps.output-version.outputs.version }} + run: jpackage --input ./input --name "FutureRestore GUI" --main-jar "FutureRestore GUI-1.0-all.jar" --main-class Main --type msi --icon ./.github/workflows/windows/FRWindows.ico --copyright "© CoocooFroggy 2021" --vendor "CoocooFroggy" --verbose --name "FutureRestore GUI" --win-shortcut --win-menu --module-path ./.github/workflows/windows/javafx-jmods-11.0.2 --add-modules javafx.swing,java.logging,java.sql,java.base,jdk.crypto.ec,java.naming --app-version %VERSION% + shell: cmd + + - name: Find MSI name + id: grep_msi_name + run: | + FOR /F "tokens=*" %%g IN ('ls *.msi') do (SET msi_name=%%g) + echo ::set-output name=msi_filename::%msi_name% + # Otherwise it tries powershell + shell: cmd + + - name: Download URL to upload to + uses: actions/download-artifact@v2 + with: + name: upload-url + # Make it usable in Github actions + - name: Set output for upload-url + id: set_upload_url + run: | + FOR /F "tokens=*" %%g IN ('tail upload-url.txt') do (SET upload_url=%%g) + echo ::set-output name=upload-url::%upload_url% + # Otherwise it tries powershell + shell: cmd + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload-url }} + asset_path: ${{ steps.grep_msi_name.outputs.msi_filename }} + asset_name: FutureRestore-GUI-Windows-${{ steps.output-version.outputs.version }}.msi + asset_content_type: application/octet-stream + +# publish-release: +# needs: [ ubuntu-pack, macos-pack, windows-pack ] +# runs-on: ubuntu-latest +# steps: +# - name: Download release ID +# uses: actions/download-artifact@v2 +# with: +# name: release-id +# - name: Output release ID +# id: output-release-id +# run: | +# release_id=$(tail release-id.txt) +# echo "::set-output name=release-id::$release_id" + +# # - name: Publish release from draft +# # uses: StuYarrow/publish-release@v1 +# # env: +# # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# # with: +# # id: ${{ steps.output-release-id.outputs.release-id }} diff --git a/.github/workflows/build-pack-release.yml b/.github/workflows/build-pack-release.yml index 1cf4cf3..a4e39fd 100644 --- a/.github/workflows/build-pack-release.yml +++ b/.github/workflows/build-pack-release.yml @@ -2,20 +2,18 @@ name: Build, Pack, Release # Controls when the action will run. on: - # Triggers the workflow on push but only for the master branch pull_request: + branches: [ master ] types: [ closed ] jobs: build-and-create-draft: # Only run if it was a PR merge if: github.event.pull_request.merged == true - # The type of runner that the job will run on + runs-on: ubuntu-latest - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Parse version @@ -26,9 +24,10 @@ jobs: echo "::set-output name=version::$version" - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -82,14 +81,13 @@ jobs: needs: [ build-and-create-draft ] runs-on: ubuntu-latest steps: - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Download final jar uses: actions/download-artifact@v2 @@ -167,14 +165,13 @@ jobs: needs: [ build-and-create-draft ] runs-on: macos-latest steps: - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Install create-dmg run: brew install create-dmg @@ -239,14 +236,13 @@ jobs: needs: [ build-and-create-draft ] runs-on: windows-latest steps: - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 17 + distribution: 'temurin' - name: Download final jar uses: actions/download-artifact@v2 diff --git a/.github/workflows/compile-java-11.yml b/.github/workflows/compile-java-11.yml index 8e7618d..daf7fa8 100644 --- a/.github/workflows/compile-java-11.yml +++ b/.github/workflows/compile-java-11.yml @@ -22,9 +22,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11 + distribution: "temurin" - name: Grant execute permission for gradlew run: chmod +x gradlew diff --git a/.gitignore b/.gitignore index 8afb7c2..9f04fec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.gradle/ /build/ /out/ -/out/production/classes/ \ No newline at end of file +/out/production/classes/ +/.idea/ diff --git a/README.md b/README.md index 4c97586..7be1f17 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Download from [releases](https://github.com/CoocooFroggy/FutureRestore-GUI/relea - **Download FutureRestore** will automatically fetch the latest FutureRestore for your operating system, extract it, and select it. - **Exit Recovery** button to run `futurerestore --exit-recovery` - **Stop FutureRestore** to kill the FutureRestore process. Button dynamically changes to "Stop FutureRestore (Unsafe)" while the process is running. Pop-up to confirm killing the process if it's currently running. -- Automatically launch with **Dark or Light mode theme** (not supported on Linux). +- Automatically launch with **Dark or Light mode theme**. - **Error parsing** such as iBEC, APTicket – APNonce mismatch, unable to place device in recovery mode. Will show a pop-up with some help and a link on where to get help. ![Error Parsing Example](.github/AP%20Nonce%20Error.png?raw=true "FutureRestore GUI AP Nonce Error") - **Automatically retry** FutureRestore only once if error received is "unable to place device in recovery mode." diff --git a/build.gradle b/build.gradle index d0927b0..cffb85e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,9 @@ jar { group 'com.coocoofroggy' version '1.0' +project.sourceCompatibility = '11' +project.targetCompatibility = '11' + repositories { mavenCentral() maven { url 'https://jitpack.io' } diff --git a/src/main/java/FRUtils.java b/src/main/java/FRUtils.java index f0a1f9e..c876946 100644 --- a/src/main/java/FRUtils.java +++ b/src/main/java/FRUtils.java @@ -119,11 +119,19 @@ public static File downloadFRGUI(MainMenu mainMenuInstance, String frguiDownload String frguiDownloadName = null; String frguiDownloadUrl = null; try { - System.out.println("Finding download..."); - URL releasesApiUrl = new URL("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases/latest"); - String releasesApiResponse = IOUtils.toString(releasesApiUrl.openConnection().getInputStream(), StandardCharsets.UTF_8); Gson gson = new Gson(); - Map latestReleaseApi = gson.fromJson(releasesApiResponse, Map.class); + System.out.println("Finding download..."); + Map latestReleaseApi; + if (Main.futureRestoreGUIPrerelease) { + URL releasesApiUrl = new URL("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases"); + String releasesApiResponse = IOUtils.toString(releasesApiUrl.openConnection().getInputStream(), StandardCharsets.UTF_8); + ArrayList> releasesApi = gson.fromJson(releasesApiResponse, ArrayList.class); + latestReleaseApi = releasesApi.get(0); + } else { + URL releasesApiUrl = new URL("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases/latest"); + String releasesApiResponse = IOUtils.toString(releasesApiUrl.openConnection().getInputStream(), StandardCharsets.UTF_8); + latestReleaseApi = gson.fromJson(releasesApiResponse, Map.class); + } ArrayList> assetsApi = (ArrayList>) latestReleaseApi.get("assets"); @@ -378,4 +386,13 @@ static void githubAuthorizeWithAccount(HttpURLConnection con) { String authHeaderValue = "Basic " + new String(encodedAuth); con.setRequestProperty("Authorization", authHeaderValue); } + + public static String getLatestFrguiReleaseBody() throws IOException { + String content = MainMenu.getRequestUrl("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases"); + + Gson gson = new Gson(); + ArrayList> result = gson.fromJson(content, ArrayList.class); + Map newestRelease = result.get(0); + return (String) newestRelease.get("body"); + } } diff --git a/src/main/java/FutureRestoreWorker.java b/src/main/java/FutureRestoreWorker.java index 2f755eb..eeaf0fa 100644 --- a/src/main/java/FutureRestoreWorker.java +++ b/src/main/java/FutureRestoreWorker.java @@ -109,7 +109,8 @@ public static void runFutureRestore(String futureRestoreFilePath, ArrayList currentTaskTextField.setText(finalLine)); break; } - case "unknown option -- custom-latest": { - JOptionPane.showMessageDialog(mainMenuView, - "Looks like there is no --custom-latest argument on this version of FutureRestore.\n" + - "Ensure you're using a FutureRestore version which supports this argument, or turn off \"Pwned Restore.\"", - "FutureRestore PWNDFU Unknown", JOptionPane.ERROR_MESSAGE); + case "unrecognized option `": { + Pattern pattern = Pattern.compile("(?<=unrecognized option `).*(?=')"); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + JOptionPane.showMessageDialog(mainMenuView, + "Looks like there is no " + matcher.group() + " argument on this version of FutureRestore.\n" + + "Ensure you're using a FutureRestore version which supports this argument, or turn off the option.", + "FutureRestore Unknown Option", JOptionPane.ERROR_MESSAGE); + } + String finalLine = line; + SwingUtilities.invokeLater(() -> currentTaskTextField.setText(finalLine)); break; } case "timeout waiting for command": { @@ -302,7 +316,7 @@ public static void uploadLog(String logPath, String logName, String command) thr rootJson.put("log", logString); rootJson.put("logName", logName); rootJson.put("discord", discordName); - rootJson.put("guiVersion", MainMenu.futureRestoreGUIVersion); + rootJson.put("guiVersion", Main.futureRestoreGUIVersion); String rootJsonString = gson.toJson(rootJson); HttpClient httpClient = HttpClients.createDefault(); diff --git a/src/main/java/Main.java b/src/main/java/Main.java index adfab6a..91064f2 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,6 +1,7 @@ public class Main { + static final String futureRestoreGUIVersion = "1.98"; + static final boolean futureRestoreGUIPrerelease = false; public static void main(String[] args) { - MainMenu.futureRestoreGUIVersion = "1.97.2"; MainMenu.main(); /* diff --git a/src/main/java/MainMenu.form b/src/main/java/MainMenu.form index ef0bd7f..bfeffa7 100644 --- a/src/main/java/MainMenu.form +++ b/src/main/java/MainMenu.form @@ -2,7 +2,7 @@
- + @@ -30,35 +30,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -73,22 +44,27 @@ - + - + + + + - + + + - - + + @@ -97,8 +73,8 @@ - - + + @@ -106,8 +82,8 @@ - - + + @@ -115,8 +91,8 @@ - - + + @@ -124,7 +100,7 @@ - + @@ -134,8 +110,8 @@ - - + + @@ -147,7 +123,7 @@ - + @@ -157,8 +133,8 @@ - - + + @@ -166,37 +142,13 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - @@ -208,53 +160,17 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -263,7 +179,7 @@ - + @@ -275,8 +191,8 @@ - - + + @@ -285,7 +201,7 @@ - + @@ -294,24 +210,34 @@ - - + + - - + + + + + + + + + + + + - - + + @@ -320,8 +246,8 @@ - - + + @@ -330,8 +256,8 @@ - - + + @@ -339,8 +265,8 @@ - - + + @@ -349,8 +275,8 @@ - - + + @@ -358,71 +284,156 @@ - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + - + - - + + + - + - + - - - - + + - - - + + - + - - + + - - + + - - + + - - + + @@ -431,8 +442,8 @@ - - + + @@ -441,8 +452,8 @@ - - + + @@ -451,8 +462,8 @@ - - + + @@ -462,8 +473,8 @@ - - + + @@ -471,135 +482,234 @@ - + - - + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - + - - - - + + - - + + - - - - - - - - - - + - - + + - - - - - - - - - - - - + - + - - + + - - + + + + + - + - - + + - + + + + + - - - - - - - + + + + + + + + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + - - + + + + + + + + + + + + + + + + + + - - - - + + - - - - - + - - - - - - diff --git a/src/main/java/MainMenu.java b/src/main/java/MainMenu.java index 9c10170..9141b52 100644 --- a/src/main/java/MainMenu.java +++ b/src/main/java/MainMenu.java @@ -28,8 +28,6 @@ import java.util.regex.Pattern; public class MainMenu { - static String futureRestoreGUIVersion = ""; - private JButton selectBlobFileButton; private JButton selectTargetIPSWFileButton; private JCheckBox updateUCheckBox; @@ -64,6 +62,12 @@ public class MainMenu { private JCheckBox customLatestCheckBox; private JPanel allArgumentsPanel; private JTextField setNonceTextField; + private JCheckBox customLatestBuildIdCheckBox; + private JTextField customLatestBuildIdTextField; + private JCheckBox customLatestBetaCheckBox; + private JCheckBox serialOutputCheckBox; + private JLabel serialLabel; + private JCheckBox noRestoreCheckBox; private String futureRestoreFilePath; private String blobName; @@ -80,8 +84,12 @@ public class MainMenu { private boolean optionUpdateState = false; private boolean optionWaitState = false; private boolean optionCustomLatestState = false; + private boolean optionCustomLatestBuildIdState = false; + private boolean optionCustomLatestBetaState = false; + private boolean optionNoRestoreState = false; private boolean optionPwndfuState = false; private boolean optionNoIbssState = false; + private boolean optionSerialOutputState = false; private boolean optionSetNonceState = false; public MainMenu() { @@ -104,6 +112,9 @@ public MainMenu() { mainMenuFrame.requestFocus(); })); downloadFutureRestoreButton.addActionListener(event -> { + // Go to Controls tab so we can see the log + SwingUtilities.invokeLater(() -> tabbedPane.setSelectedIndex(2)); + final String osName = System.getProperty("os.name").toLowerCase(); final String osArch = System.getProperty("os.arch").toLowerCase(); String urlString = null; @@ -186,7 +197,7 @@ public MainMenu() { messageToLog("Unable to decompress " + downloadedFr); exception.printStackTrace(); } - // If it fail, set the current task to nothing + // If it fails, set the current task to nothing if (futureRestoreExecutable == null) { SwingUtilities.invokeLater(() -> currentTaskTextField.setText("")); } else { @@ -199,6 +210,8 @@ public MainMenu() { // Set name of button to blob file name selectFutureRestoreBinaryExecutableButton.setText("✓ " + futureRestoreExecutable.getName()); } + // Return to first tab + SwingUtilities.invokeLater(() -> tabbedPane.setSelectedIndex(0)); }).start(); }); @@ -274,59 +287,74 @@ public MainMenu() { optionUpdateState = updateUCheckBox.isSelected(); optionWaitState = waitWCheckBox.isSelected(); optionCustomLatestState = customLatestCheckBox.isSelected(); + optionCustomLatestBuildIdState = customLatestBuildIdCheckBox.isSelected(); + optionCustomLatestBetaState = customLatestBetaCheckBox.isSelected(); + optionNoRestoreState = noRestoreCheckBox.isSelected(); optionPwndfuState = pwndfuCheckBox.isSelected(); optionNoIbssState = noIbssCheckBox.isSelected(); + optionSerialOutputState = serialOutputCheckBox.isSelected(); optionSetNonceState = setNonceCheckBox.isSelected(); if (optionPwndfuState) { noIbssCheckBox.setEnabled(true); noIbssLabel.setEnabled(true); + serialOutputCheckBox.setEnabled(true); + serialLabel.setEnabled(true); setNonceCheckBox.setEnabled(true); setNonceLabel.setEnabled(true); - // If they have --set-nonce selected, show the TextField for custom generator - if (optionSetNonceState) { - setNonceTextField.setVisible(true); - setNonceTextField.setEnabled(true); - setNonceTextField.setEditable(true); - } else { - setNonceTextField.setVisible(false); - setNonceTextField.setEnabled(false); - setNonceTextField.setEditable(false); - setNonceTextField.setText(""); - } + // Hide or show the TextField next to --set-nonce depending on its state + updateBoxBasedOnState(setNonceTextField, optionSetNonceState); } else { // Deselect and disable pwndfu boxes noIbssCheckBox.setSelected(false); noIbssCheckBox.setEnabled(false); noIbssLabel.setEnabled(false); + serialOutputCheckBox.setSelected(false); + serialOutputCheckBox.setEnabled(false); + serialLabel.setEnabled(false); setNonceCheckBox.setSelected(false); setNonceCheckBox.setEnabled(false); setNonceLabel.setEnabled(false); + // Clear and hide the box for --set-nonce + setNonceTextField.setVisible(false); + setNonceTextField.setEnabled(false); + setNonceTextField.setEditable(false); + setNonceTextField.setText(""); // Since we turn off the switches for pwndfu required items, also turn them off internally optionNoIbssState = false; + optionSerialOutputState = false; optionSetNonceState = false; } - // If custom latest is turned on/off, update the little box next to it - if (optionCustomLatestState) { - customLatestTextField.setVisible(true); - customLatestTextField.setEnabled(true); - customLatestTextField.setEditable(true); + // Disable --custom-latest-buildid if --custom-latest is on + customLatestBuildIdCheckBox.setEnabled(!optionCustomLatestState); + // Disable --custom-latest if --custom-latest-buildid is on + customLatestCheckBox.setEnabled(!optionCustomLatestBuildIdState); + // Enable beta checkbox if either of them is on + if (optionCustomLatestState || optionCustomLatestBuildIdState) { + customLatestBetaCheckBox.setEnabled(true); } else { - customLatestTextField.setVisible(false); - customLatestTextField.setEnabled(false); - customLatestTextField.setEditable(false); - customLatestTextField.setText(""); + customLatestBetaCheckBox.setEnabled(false); + customLatestBetaCheckBox.setSelected(false); } + + updateBoxBasedOnState(customLatestTextField, optionCustomLatestState); + updateBoxBasedOnState(customLatestBuildIdTextField, optionCustomLatestBuildIdState); + + mainMenuFrame.repaint(); }; debugDCheckBox.addActionListener(optionsListener); updateUCheckBox.addActionListener(optionsListener); waitWCheckBox.addActionListener(optionsListener); + noRestoreCheckBox.addActionListener(optionsListener); customLatestCheckBox.addActionListener(optionsListener); + customLatestBuildIdCheckBox.addActionListener(optionsListener); + customLatestBetaCheckBox.addActionListener(optionsListener); pwndfuCheckBox.addActionListener(optionsListener); noIbssCheckBox.addActionListener(optionsListener); + serialOutputCheckBox.addActionListener(optionsListener); setNonceCheckBox.addActionListener(optionsListener); startFutureRestoreButton.addActionListener(e -> { @@ -365,6 +393,13 @@ public MainMenu() { return; } } + // Ensure they typed in a custom latest version if selected + if (optionCustomLatestBuildIdState) { + if (customLatestBuildIdTextField.getText().trim().length() == 0) { + JOptionPane.showMessageDialog(mainMenuView, "Specify a custom latest version or disable the \"Custom Latest Build ID\" option.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + } // If blob name has a build number in it Pattern blobPattern = Pattern.compile("(?<=[_-])[A-Z0-9]{5,10}[a-z]?(?=[_-])"); @@ -402,15 +437,25 @@ public MainMenu() { allArgs.add("--update"); if (optionWaitState) allArgs.add("--wait"); + if (optionNoRestoreState) + allArgs.add("--no-restore"); if (optionCustomLatestState) { allArgs.add("--custom-latest"); // Remove trailing and leading whitespace with .trim() allArgs.add(customLatestTextField.getText().trim()); + } else if (optionCustomLatestBuildIdState) { // Else if because both can't be selected + allArgs.add("--custom-latest-buildid"); + // Remove trailing and leading whitespace with .trim() + allArgs.add(customLatestBuildIdTextField.getText().trim()); } + if (optionCustomLatestBetaState) + allArgs.add("--custom-latest-beta"); if (optionPwndfuState) allArgs.add("--use-pwndfu"); if (optionNoIbssState) allArgs.add("--no-ibss"); + if (optionSerialOutputState) + allArgs.add("--serial"); if (optionSetNonceState) { // If they specified a generator String customGenerator = setNonceTextField.getText().trim(); @@ -479,10 +524,11 @@ public MainMenu() { case "Manual Baseband": { Platform.runLater(() -> { FRUtils.setEnabled(mainMenuView, false, true); + // If they chose a file if (chooseBbfw()) { bbState = "manual"; selectBuildManifestButton.setEnabled(true); - } else { + } else { // Otherwise, they cancelled, set it back to latest bbState = "latest"; basebandComboBox.setSelectedItem("Latest Baseband"); if (sepState.equals("latest") || sepState.equals("none")) @@ -513,10 +559,11 @@ public MainMenu() { case "Manual SEP": { Platform.runLater(() -> { FRUtils.setEnabled(mainMenuView, false, true); + // If they chose a file if (chooseSep()) { sepState = "manual"; selectBuildManifestButton.setEnabled(true); - } else { + } else { // Otherwise, they cancelled, set it back to latest sepState = "latest"; sepComboBox.setSelectedItem("Latest SEP"); if (bbState.equals("latest") || bbState.equals("none")) @@ -568,6 +615,32 @@ public MainMenu() { ActionListener nextButtonListener = e -> tabbedPane.setSelectedIndex(tabbedPane.getSelectedIndex() + 1); nextButtonFiles.addActionListener(nextButtonListener); nextButtonOptions.addActionListener(nextButtonListener); + + tabbedPane.addChangeListener(e -> shrinkWrapTabbedPane()); + } + + private void updateBoxBasedOnState(JTextField textField, boolean state) { + // Hide or show the TextField next to --custom-latest depending on its state + if (state) { + textField.setVisible(true); + textField.setEnabled(true); + textField.setEditable(true); + } else { + textField.setVisible(false); + textField.setEnabled(false); + textField.setEditable(false); + textField.setText(""); + } + } + + private void shrinkWrapTabbedPane() { + // https://stackoverflow.com/a/20754740/13668740 + Component mCompo = tabbedPane.getSelectedComponent(); + tabbedPane.setPreferredSize(new Dimension( + tabbedPane.getPreferredSize().width, + Math.max(mCompo.getPreferredSize().height, 100) // We don't want it getting too small! + )); + mainMenuFrame.pack(); } public static final Properties properties = new Properties(); @@ -657,24 +730,27 @@ public static void main() { } // Set text for version - mainMenuInstance.authorAndVersionLabel.setText("by CoocooFroggy — v" + futureRestoreGUIVersion); + mainMenuInstance.authorAndVersionLabel.setText("by CoocooFroggy — v" + Main.futureRestoreGUIVersion); - // Hide the TextFields for custom generator and custom latest + // Hide the TextFields for custom generator and custom latests mainMenuInstance.setNonceTextField.setVisible(false); mainMenuInstance.customLatestTextField.setVisible(false); + mainMenuInstance.customLatestBuildIdTextField.setVisible(false); // Packs mainMenuFrame.pack(); // Centers it on screen mainMenuFrame.setLocationRelativeTo(null); - // Shows it + // We shrink wrap after packing so that when the options tab is pressed, it's centered. Not off-screen + mainMenuInstance.shrinkWrapTabbedPane(); + // Shows the window mainMenuFrame.setVisible(true); // Only if they have the setting enabled, check for updates if (properties.getProperty("check_updates").equals("true")) { System.out.println("Checking for FutureRestore GUI updates in the background..."); mainMenuInstance.messageToLog("Checking for FutureRestore GUI updates in the background..."); - alertIfNewerFRGUIAvailable(mainMenuInstance, futureRestoreGUIVersion); + alertIfNewerFRGUIAvailable(mainMenuInstance); } // If they previously downloaded FR, set it @@ -692,6 +768,7 @@ public static void main() { } // region Utilities + // What a mess! I should clean up later. static void turnDark(MainMenu mainMenuInstance) { JPanel mainMenuView = mainMenuInstance.mainMenuView; @@ -768,7 +845,6 @@ boolean chooseBbfw() { basebandFilePath = file.getAbsolutePath(); return true; } else { - System.out.println("Cancelled"); return false; } } @@ -791,7 +867,6 @@ boolean chooseSep() { sepFilePath = file.getAbsolutePath(); return true; } else { - System.out.println("Cancelled"); return false; } } @@ -1036,11 +1111,6 @@ File extractFutureRestore(File fileToExtract, String frguiDirPath, String operat archiver.extract(fileToExtract, destinationDir); break; } - case "exe": - case "": { - FileUtils.copyFileToDirectory(fileToExtract, destinationDir); - break; - } default: { System.out.println("Cannot decompress, unknown file format :("); messageToLog("Cannot decompress, unknown file format :("); @@ -1052,42 +1122,88 @@ File extractFutureRestore(File fileToExtract, String frguiDirPath, String operat // Actions artifacts (beta FR) are in a .zip then in a .tar.xz. Extract again if we need to File[] files = destinationDir.listFiles(); - if (files == null) return null; - File unzippedFile = files[0]; - String unzippedExtension = FilenameUtils.getExtension(unzippedFile.getName()); - if (unzippedExtension.equals("xz") || unzippedExtension.equals("zip")) { - // Move the archive from /FRGUI/extracted to /FRGUI - FileUtils.moveFileToDirectory(unzippedFile, new File(frguiDirPath), false); - // Declare this file - File nestedArchive = new File(frguiDirPath + "/" + unzippedFile.getName()); - // Extract the new one (run this method with it) and return the extracted file - return extractFutureRestore(nestedArchive, frguiDirPath, operatingSystem); - } + if (files == null || files.length == 0) return null; + + File futureRestoreBinary = null; + for (File file : files) { + String unzippedExtension = FilenameUtils.getExtension(file.getName()); + if (unzippedExtension.equals("xz") || unzippedExtension.equals("zip")) { + // Move the archive from /FRGUI/extracted to /FRGUI + FileUtils.moveFileToDirectory(file, new File(frguiDirPath), false); + // Declare this file + File nestedArchive = new File(frguiDirPath + "/" + file.getName()); + // Extract the new one (run this method with it) and return the extracted file + return extractFutureRestore(nestedArchive, frguiDirPath, operatingSystem); + } - File futureRestoreExecutable = files[0]; + // file is not an archive at this point, and is either futurerestore itself or a script. + // It is in the /FRGUI/extracted directory. - if (futureRestoreExecutable == null) { - System.out.println("Unable to decompress " + fileToExtract); - messageToLog("Unable to decompress " + fileToExtract); - return null; - } + // If it is a script + if (unzippedExtension.equals("sh")) { + Object[] choices = {"Run as root", "Skip"}; + Object defaultChoice = choices[0]; - // Only run on macOS and Linux - if (operatingSystem.contains("mac") || operatingSystem.contains("linux")) { - // Make FutureRestore executable - Process process; - try { - process = Runtime.getRuntime().exec("chmod +x " + futureRestoreExecutable); - process.waitFor(); - } catch (IOException | InterruptedException e) { - System.out.println("Unable to make FutureRestore executable."); - messageToLog("Unable to make FutureRestore executable."); - e.printStackTrace(); + int response = JOptionPane.showOptionDialog(mainMenuView, + "There's a shell script included with your download, called \"" + file.getName() + "\".\n" + + "Do you want to execute it?", + "Script Detected", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, defaultChoice); + if (response == JOptionPane.YES_OPTION) { + // If the script is ".sh", we don't need to check for Windows + // sh is for Linux (unless Cryptic adds a macOS script). Therefore, we can run pkexec without worry + ProcessBuilder processBuilder = new ProcessBuilder("/usr/bin/pkexec", "bash", file.getAbsolutePath()); + try { + Process process = processBuilder.start(); + SwingUtilities.invokeLater(() -> currentTaskTextField.setText("Running " + file.getName() + "...")); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + messageToLog(line); + } + if (process.waitFor() != 0) { + JOptionPane.showMessageDialog(mainMenuView, + "Unable to run the script. Continuing with download + extraction.", + "Script Error", JOptionPane.ERROR_MESSAGE); + } + } catch (InterruptedException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(mainMenuView, + "Unable to run the script. Continuing with download + extraction.", + "Script Error", JOptionPane.ERROR_MESSAGE); + } + } + continue; } - } - return futureRestoreExecutable; + // Only run on macOS and Linux + if (operatingSystem.contains("mac") || operatingSystem.contains("linux")) { + // Make FutureRestore executable + Process process; + try { + process = Runtime.getRuntime().exec("chmod +x " + file); + process.waitFor(); + } catch (IOException | InterruptedException e) { + System.out.println("Unable to make FutureRestore executable."); + messageToLog("Unable to make FutureRestore executable."); + e.printStackTrace(); + } + } + // We don't immediately return here in case there's a script later in the loop + futureRestoreBinary = file; + } + if (futureRestoreBinary != null) + return futureRestoreBinary; + // We should never reach here unless there's no FutureRestore executable/binary in the zip + // Show an error to the user + Object[] choices = {"Open link", "Ok"}; + Object defaultChoice = choices[0]; + int response = JOptionPane.showOptionDialog(mainMenuView, "Could not find FutureRestore in the downloaded archive. Please download FutureRestore.\n" + + "https://github.com/futurerestore/futurerestore/releases/latest/", "Download FutureRestore", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, defaultChoice); + if (response == JOptionPane.YES_OPTION) { + FRUtils.openWebpage("https://github.com/futurerestore/futurerestore/releases/latest/", this); + } + return null; } static void initializePreferences() { @@ -1214,30 +1330,38 @@ boolean previewCommand(ArrayList allArgs) { return true; } - static void alertIfNewerFRGUIAvailable(MainMenu mainMenuInstance, String currentFRGUIVersion) { + static void alertIfNewerFRGUIAvailable(MainMenu mainMenuInstance) { new Thread(() -> { try { - String content = getRequestUrl("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases/latest"); + final Gson gson = new Gson(); - Gson gson = new Gson(); - Map newestRelease = gson.fromJson(content, Map.class); + Map newestRelease; + if (Main.futureRestoreGUIPrerelease) { + String content = getRequestUrl("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases"); + ArrayList> result = gson.fromJson(content, ArrayList.class); + newestRelease = result.get(0); + } else { + String content = getRequestUrl("https://api.github.com/repos/CoocooFroggy/FutureRestore-GUI/releases/latest"); + newestRelease = gson.fromJson(content, Map.class); + } + String newestTag = (String) newestRelease.get("tag_name"); System.out.println("Newest FRGUI version: " + newestTag); // If user is not on latest version - String currentFRGUITag = "v" + currentFRGUIVersion; + String currentFRGUITag = "v" + Main.futureRestoreGUIVersion; if (!newestTag.equals(currentFRGUITag)) { System.out.println("A newer version of FutureRestore GUI is available."); mainMenuInstance.messageToLog("A newer version of FutureRestore GUI is available."); // Label on top of release notes JLabel label = new JLabel("A newer version of FutureRestore GUI is available.\n" + - "You're on version " + currentFRGUIVersion + " and the latest version is " + newestTag + "."); + "You're on version " + Main.futureRestoreGUIVersion + " and the latest version is " + newestTag + "."); Border padding = BorderFactory.createEmptyBorder(0, 0, 10, 10); label.setBorder(padding); // Fetch release notes - String mdReleaseBody = getLatestFrguiReleaseBody(); + String mdReleaseBody = FRUtils.getLatestFrguiReleaseBody(); String htmlReleaseBody = "" + "" + "