Skip to content

Commit 09c8f00

Browse files
committed
Linux support, explain how to get ECID and other information with tooltips, specify url for .ipsw so user doesn't have to download entire .ipsw for betas, actually fix iPad7,5 and iPad7,6, bump to v1.2
1 parent a4a956c commit 09c8f00

File tree

8 files changed

+158
-67
lines changed

8 files changed

+158
-67
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# blobsaver
2-
A GUI for saving SHSH blobs using encounter's fork of tsschecker. Supports both Mac and Windows. Requires [Java](https://java.com/inc/BrowserRedirect1.jsp).
2+
A GUI for saving SHSH blobs using encounter's fork of tsschecker. Supports both Mac, Windows, and Linux. Requires [Java](https://java.com/inc/BrowserRedirect1.jsp).
33

44
If you have an antivirus, select "Always Allow" for anything related to tsschecker or Java. An antivirus may cause blobsaver to crash. If that happens please send feedback.
55

@@ -10,16 +10,17 @@ If you have an antivirus, select "Always Allow" for anything related to tsscheck
1010
- Store up to ten devices with presets
1111
- Choose where to save blobs with file picker
1212
- Save blobs for beta versions
13+
- No need to download entire .ipsw for beta versions(just specify link)
14+
- Explains how to get ECID, Board Config(if needed), and information necessary for beta versions
1315
- Automatically checks for updates and prompts if available
1416
- Optionally specify device identifier instead of using device picker
1517
- Optionally specify apnonce
1618

1719
## Feedback
18-
Please send feedback via [Github Issue](https://github.com/airsquared/blobsaver/issues/new) or [Reddit PM](https://www.reddit.com//message/compose?to=01110101_00101111&subject=Blobsaver+Feedback) if you encounter any bugs/problems or have a feature request.
20+
Please send feedback via [Github Issue](https://github.com/airsquared/blobsaver/issues/new/choose) or [Reddit PM](https://www.reddit.com//message/compose?to=01110101_00101111&subject=Blobsaver+Feedback) if you encounter any bugs/problems or have a feature request.
1921

2022
## TODO:
2123
- Use macOS menu bar
22-
- Explain how to get everything
2324
- Package into .app/.exe [maybe this](https://github.com/Jorl17/jar2app)
2425
- Automatically save blobs for all signed versions
2526
- Daemon to do it automatically in the background

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ plugins {
33
id 'java'
44
}
55

6-
version '1.1.1'
6+
version '1.2'
77

88
sourceCompatibility = 1.8
99

src/main/java/blobsaver/Controller.java

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import javafx.scene.layout.VBox;
1818
import javafx.scene.paint.Color;
1919
import javafx.stage.DirectoryChooser;
20-
import javafx.stage.FileChooser;
2120
import javafx.stage.Modality;
2221
import org.json.JSONException;
2322
import org.json.JSONObject;
@@ -33,6 +32,8 @@
3332
import java.util.Arrays;
3433
import java.util.concurrent.CountDownLatch;
3534
import java.util.prefs.Preferences;
35+
import java.util.zip.ZipEntry;
36+
import java.util.zip.ZipInputStream;
3637

3738
public class Controller {
3839

@@ -46,7 +47,7 @@ public class Controller {
4647
@FXML private TextField versionField;
4748
@FXML private TextField identifierField;
4849
@FXML private TextField pathField;
49-
@FXML private TextField buildManifestField;
50+
@FXML private TextField ipswField;
5051
@FXML private TextField buildIDField;
5152

5253
@FXML private CheckBox apnonceCheckBox;
@@ -72,7 +73,6 @@ public class Controller {
7273
@FXML private VBox presetVBox;
7374

7475
@FXML private Button goButton;
75-
@FXML private Button plistPickerButton;
7676

7777
private boolean boardConfig = false;
7878
private boolean editingPresets = false;
@@ -85,6 +85,9 @@ public class Controller {
8585
private URI githubIssueURI;
8686
private URI redditPMURI;
8787

88+
private File buildManifestPlist;
89+
private File tsschecker;
90+
8891
static void setPresetButtonNames() {
8992
Preferences appPrefs = Preferences.userRoot().node("airsquared/blobsaver/prefs");
9093
for (int i = 1; i < 11; i++) {
@@ -154,7 +157,7 @@ public void initialize() {
154157
return;
155158
}
156159
final String v = (String) newValue;
157-
if (v.equals("iPhone 6s") || v.equals("iPhone 6s+") || v.equals("iPhone SE")) {
160+
if (v.equals("iPhone 6s") || v.equals("iPhone 6s+") || v.equals("iPhone SE") || v.equals("iPad 6 (WiFi)(iPad 7,5)") || v.equals("iPad 6 (Cellular)(iPad7,6)")) {
158161
int depth = 20;
159162
DropShadow borderGlow = new DropShadow();
160163
borderGlow.setOffsetY(0f);
@@ -174,7 +177,7 @@ public void initialize() {
174177
});
175178
identifierField.textProperty().addListener((observable, oldValue, newValue) -> {
176179
identifierField.setEffect(null);
177-
if (newValue.equals("iPhone8,1") || newValue.equals("iPhone8,2") || newValue.equals("iPhone8,4")) {
180+
if (newValue.equals("iPhone8,1") || newValue.equals("iPhone8,2") || newValue.equals("iPhone8,4") || newValue.equals("iPad7,5") || newValue.equals("iPad7,6")) {
178181
final int depth = 20;
179182
DropShadow borderGlow = new DropShadow();
180183
borderGlow.setOffsetY(0f);
@@ -212,7 +215,7 @@ public void initialize() {
212215
presetButtons.forEach((Button btn) -> btn.setOnAction(this::presetButtonHandler));
213216

214217
try {
215-
githubIssueURI = new URI("https://github.com/airsquared/blobsaver/issues/new");
218+
githubIssueURI = new URI("https://github.com/airsquared/blobsaver/issues/new/choose");
216219
redditPMURI = new URI("https://www.reddit.com//message/compose?to=01110101_00101111&subject=Blobsaver+Bug+Report");
217220
} catch (URISyntaxException ignored) {
218221
}
@@ -366,17 +369,19 @@ private void newUnreportableError(String msg) {
366369
}
367370

368371
private void run(String device) {
369-
File file;
370372
try {
371373
InputStream input;
372374
if (PlatformUtil.isWindows()) {
373-
input = getClass().getResourceAsStream("tsschecker.exe");
374-
file = File.createTempFile("tsschecker", ".tmp.exe");
375+
input = getClass().getResourceAsStream("tsschecker_windows.exe");
376+
tsschecker = File.createTempFile("tsschecker_windows", ".tmp.exe");
377+
} else if (PlatformUtil.isMac()) {
378+
input = getClass().getResourceAsStream("tsschecker_macos");
379+
tsschecker = File.createTempFile("tsschecker_macos", ".tmp");
375380
} else {
376-
input = getClass().getResourceAsStream("tsschecker");
377-
file = File.createTempFile("tsschecker", ".tmp");
381+
input = getClass().getResourceAsStream("tsschecker_linux");
382+
tsschecker = File.createTempFile("tsschecker_linux", ".tmp");
378383
}
379-
OutputStream out = new FileOutputStream(file);
384+
OutputStream out = new FileOutputStream(tsschecker);
380385
int read;
381386
byte[] bytes = new byte[1024];
382387

@@ -388,18 +393,19 @@ private void run(String device) {
388393
ex.printStackTrace();
389394
return;
390395
}
391-
file.deleteOnExit();
396+
tsschecker.deleteOnExit();
392397

393-
if (!file.setExecutable(true, false)) {
398+
if (!tsschecker.setExecutable(true, false)) {
394399
newReportableError("There was an error setting tsschecker as executable.");
400+
deleteTempFiles();
395401
return;
396402
}
397403

398404
File locationToSaveBlobs = new File(pathField.getText());
399405
//noinspection ResultOfMethodCallIgnored
400406
locationToSaveBlobs.mkdirs();
401407
ArrayList<String> args;
402-
args = new ArrayList<>(Arrays.asList(file.getPath(), "-d", device, "-s", "-e", ecidField.getText(), "--save-path", pathField.getText()));
408+
args = new ArrayList<>(Arrays.asList(tsschecker.getPath(), "-d", device, "-s", "-e", ecidField.getText(), "--save-path", pathField.getText()));
403409
if (boardConfig) {
404410
args.add("--boardconfig");
405411
args.add(boardConfigField.getText());
@@ -411,26 +417,65 @@ private void run(String device) {
411417
if (versionCheckBox.isSelected()) {
412418
args.add("-l");
413419
} else if (betaCheckBox.isSelected()) {
420+
try {
421+
buildManifestPlist = File.createTempFile("BuildManifest", ".plist");
422+
OutputStream out = new FileOutputStream(buildManifestPlist);
423+
if (!ipswField.getText().matches("https?://.*apple.*\\.ipsw")) {
424+
newUnreportableError("\"" + ipswField.getText() + "\" is not a valid URL.\n\nMake sure it starts with \"http://\" or \"https://\", has \"apple\" in it, and ends with \".ipsw\"");
425+
deleteTempFiles();
426+
return;
427+
}
428+
ZipInputStream zin;
429+
try {
430+
URL url = new URL(ipswField.getText());
431+
zin = new ZipInputStream(url.openStream());
432+
} catch (IOException e) {
433+
newUnreportableError("\"" + ipswField.getText() + "\" is not a valid URL.\n\nMake sure it starts with \"http://\" or \"https://\", has \"apple\" in it, and ends with \".ipsw\"");
434+
deleteTempFiles();
435+
return;
436+
}
437+
ZipEntry ze;
438+
while ((ze = zin.getNextEntry()) != null) {
439+
if (ze.getName().equals("BuildManifest.plist")) {
440+
byte[] buffer = new byte[500_000];
441+
int len;
442+
while ((len = zin.read(buffer)) != -1) {
443+
out.write(buffer, 0, len);
444+
}
445+
out.close();
446+
break;
447+
}
448+
}
449+
zin.close();
450+
buildManifestPlist.deleteOnExit();
451+
} catch (IOException e) {
452+
newReportableError("Unable to get BuildManifest from .ipsw.", e.getMessage());
453+
e.printStackTrace();
454+
deleteTempFiles();
455+
return;
456+
}
414457
args.add("-i");
415458
args.add(versionField.getText());
416459
args.add("--beta");
417460
args.add("--buildid");
418461
args.add(buildIDField.getText());
419462
args.add("-m");
420-
args.add(buildManifestField.getText());
463+
args.add(buildManifestPlist.toString());
421464
} else {
422465
args.add("-i");
423466
args.add(versionField.getText());
424467
}
425-
Process proc = null;
468+
Process proc;
426469
try {
470+
System.out.println("Running: " + args.toString());
427471
proc = new ProcessBuilder(args).start();
428472
} catch (IOException e) {
429473
newReportableError("There was an error starting tsschecker.", e.toString());
430474
e.printStackTrace();
475+
deleteTempFiles();
476+
return;
431477
}
432478
String tsscheckerLog;
433-
//noinspection ConstantConditions
434479
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
435480
StringBuilder logBuilder = new StringBuilder();
436481
String line;
@@ -442,18 +487,20 @@ private void run(String device) {
442487
} catch (IOException e) {
443488
newReportableError("There was an error getting the tsschecker result", e.toString());
444489
e.printStackTrace();
490+
deleteTempFiles();
445491
return;
446492
}
447493

448494
if (tsscheckerLog.contains("Saved shsh blobs")) {
449495
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Successfully saved blobs in\n" + pathField.getText(), ButtonType.OK);
496+
alert.setHeaderText("Success!");
450497
alert.showAndWait();
451498
} else if (tsscheckerLog.contains("[Error] [TSSC] manually specified ecid=" + ecidField.getText() + ", but parsing failed")) {
452499
newUnreportableError("\"" + ecidField.getText() + "\"" + " is not a valid ECID. Try getting it from iTunes");
453500
ecidField.setEffect(errorBorder);
454501
} else if (tsscheckerLog.contains("[Error] [TSSC] device " + device + " could not be found in devicelist")) {
455502
Alert alert = new Alert(Alert.AlertType.ERROR, "tsschecker could not find device: \"" + device +
456-
"\"\n\nPlease create a new Github issue or PM me on Reddit if you used the dropdown menu", githubIssue, redditPM, ButtonType.CANCEL);
503+
"\"\n\nPlease create a new Github issue or PM me on Reddit if you used the dropdown menu.", githubIssue, redditPM, ButtonType.CANCEL);
457504
alert.showAndWait();
458505
reportError(alert);
459506
} else if (tsscheckerLog.contains("[Error] [TSSC] ERROR: could not get url for device " + device + " on iOS " + versionField.getText())) {
@@ -485,7 +532,11 @@ private void run(String device) {
485532
newUnreportableError("iOS/tvOS " + versionField.getText() + " is not being signed for device " + device);
486533
versionField.setEffect(errorBorder);
487534
} else if (tsscheckerLog.contains("[Error] [TSSC] failed to load manifest")) {
488-
newUnreportableError("\'" + buildManifestField.getText() + "\' is not a valid manifest");
535+
Alert alert = new Alert(Alert.AlertType.ERROR,
536+
"Failed to load manifest.\n\n \"" + ipswField.getText() + "\" might not be a valid URL.\n\nMake sure it starts with \"http://\" or \"https://\", has \"apple\" in it, and ends with \".ipsw\"\n\nIf the URL is fine, please create a new issue on Github or PM me on Reddit. The log has been copied to your clipboard",
537+
githubIssue, redditPM, ButtonType.OK);
538+
alert.showAndWait();
539+
reportError(alert, tsscheckerLog);
489540
} else if (tsscheckerLog.contains("[Error]")) {
490541
newReportableError("Saving blobs failed.", tsscheckerLog);
491542
} else {
@@ -497,12 +548,23 @@ private void run(String device) {
497548
newReportableError("The tsschecker process was interrupted.", e.toString());
498549
}
499550

500-
if (!file.delete()) {
501-
newUnreportableError("\"There was an error deleting the temporary file.\"");
502-
}
551+
deleteTempFiles();
503552

504553
}
505554

555+
@SuppressWarnings("ResultOfMethodCallIgnored")
556+
private void deleteTempFiles() {
557+
try {
558+
if (tsschecker.exists()) {
559+
tsschecker.delete();
560+
}
561+
if (buildManifestPlist.exists()) {
562+
buildManifestPlist.delete();
563+
}
564+
} catch (NullPointerException ignored) {
565+
}
566+
}
567+
506568
public void apnonceCheckBoxHandler() {
507569
if (apnonceCheckBox.isSelected()) {
508570
apnonceField.setDisable(false);
@@ -570,40 +632,29 @@ public void identifierCheckBoxHandler() {
570632

571633
public void betaCheckBoxHandler() {
572634
if (betaCheckBox.isSelected()) {
573-
buildManifestField.setDisable(false);
635+
ipswField.setDisable(false);
574636
int depth = 20;
575637
DropShadow borderGlow = new DropShadow();
576638
borderGlow.setOffsetY(0f);
577639
borderGlow.setOffsetX(0f);
578640
borderGlow.setColor(Color.DARKCYAN);
579641
borderGlow.setWidth(depth);
580642
borderGlow.setHeight(depth);
581-
buildManifestField.setEffect(borderGlow);
582-
plistPickerButton.setDisable(false);
643+
ipswField.setEffect(borderGlow);
583644
buildIDField.setDisable(false);
584645
buildIDField.setEffect(borderGlow);
585646
if (versionCheckBox.isSelected()) {
586647
versionCheckBox.fire();
587648
}
649+
versionCheckBox.setDisable(true);
588650
} else {
589-
buildManifestField.setEffect(null);
590-
buildManifestField.setText("");
591-
buildManifestField.setDisable(true);
592-
plistPickerButton.setDisable(true);
651+
ipswField.setEffect(null);
652+
ipswField.setText("");
653+
ipswField.setDisable(true);
593654
buildIDField.setEffect(null);
594655
buildIDField.setText("");
595656
buildIDField.setDisable(true);
596-
}
597-
}
598-
599-
public void plistPickerHandler() {
600-
FileChooser fileChooser = new FileChooser();
601-
fileChooser.setTitle("Select the BuildManifest.plist");
602-
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
603-
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PLIST", "*.plist"));
604-
File result = fileChooser.showOpenDialog(Main.primaryStage);
605-
if (result != null) {
606-
buildManifestField.setText(result.toString());
657+
versionCheckBox.setDisable(false);
607658
}
608659
}
609660

@@ -647,7 +698,6 @@ private void loadPreset(int preset) {
647698
if (!prefs.get("Board Config", "").equals("none")) {
648699
boardConfigField.setText(prefs.get("Board Config", ""));
649700
}
650-
651701
}
652702

653703
private void presetButtonHandler(ActionEvent evt) {
@@ -807,8 +857,8 @@ public void go() {
807857
buildIDField.setEffect(errorBorder);
808858
doReturn = true;
809859
}
810-
if (betaCheckBox.isSelected() && buildManifestField.getText().equals("")) {
811-
buildManifestField.setEffect(errorBorder);
860+
if (betaCheckBox.isSelected() && ipswField.getText().equals("")) {
861+
ipswField.setEffect(errorBorder);
812862
doReturn = true;
813863
}
814864
if (doReturn) {

src/main/java/blobsaver/Main.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@
66
import javafx.scene.Scene;
77
import javafx.stage.Stage;
88

9+
import java.io.IOException;
10+
911
public class Main extends Application {
1012

11-
static final String appVersion = "v1.1.1";
13+
static final String appVersion = "v1.2";
1214
static Stage primaryStage;
1315

1416
public static void main(String[] args) {
1517
launch(args);
1618
}
1719

1820
@Override
19-
public void start(Stage primaryStage) throws Exception {
21+
public void start(Stage primaryStage) throws IOException {
2022
Main.primaryStage = primaryStage;
2123
Parent root = FXMLLoader.load(getClass().getResource("blobsaver.fxml"));
2224
primaryStage.setTitle("SHSH Blob Saver " + appVersion);

0 commit comments

Comments
 (0)