Skip to content

Commit 8625f24

Browse files
committed
Support saving blobs for beta versions without a URL
1 parent e52f340 commit 8625f24

File tree

7 files changed

+94
-31
lines changed

7 files changed

+94
-31
lines changed

src/main/java/airsquared/blobsaver/app/Background.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ private static void saveBlobs(Prefs.SavedDevice savedDevice) {
267267
System.out.println("attempting to save for device " + savedDevice);
268268

269269
TSS.Builder builder = new TSS.Builder().setDevice(savedDevice.getIdentifier())
270-
.setEcid(savedDevice.getEcid()).setSavePath(savedDevice.getSavePath());
270+
.setEcid(savedDevice.getEcid()).setSavePath(savedDevice.getSavePath())
271+
.setIncludeBetas(savedDevice.doesIncludeBetas());
271272
savedDevice.getBoardConfig().ifPresent(builder::setBoardConfig);
272273
savedDevice.getApnonce().ifPresent(builder::setApnonce);
273274
savedDevice.getGenerator().ifPresent(builder::setGenerator);

src/main/java/airsquared/blobsaver/app/Controller.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public class Controller {
5757
@FXML private TextField ecidField, boardConfigField, apnonceField, generatorField, versionField, identifierField,
5858
pathField, ipswField;
5959

60-
@FXML private CheckBox apnonceCheckBox, allSignedVersionsCheckBox, identifierCheckBox, betaCheckBox, saveToTSSSaverCheckBox, saveToSHSHHostCheckBox;
60+
@FXML private CheckBox apnonceCheckBox, allSignedVersionsCheckBox, identifierCheckBox, betaCheckBox,
61+
manualURLCheckBox, saveToTSSSaverCheckBox, saveToSHSHHostCheckBox;
6162

6263
@FXML private Label versionLabel, savedDevicesLabel;
6364

@@ -82,6 +83,7 @@ public void initialize() {
8283
if (!newValue) {
8384
saveToTSSSaverCheckBox.setSelected(false);
8485
saveToSHSHHostCheckBox.setSelected(false);
86+
betaCheckBox.setSelected(false);
8587
}
8688
});
8789
switch (Prefs.getDarkMode()) {
@@ -159,8 +161,8 @@ public void identifierCheckBoxHandler() {
159161
}
160162
}
161163

162-
public void betaCheckBoxHandler() {
163-
if (betaCheckBox.isSelected()) {
164+
public void manualURLCheckBoxHandler() {
165+
if (manualURLCheckBox.isSelected()) {
164166
ipswField.setEffect(Utils.borderGlow);
165167
versionField.setEffect(null);
166168
versionField.setText("");
@@ -188,6 +190,9 @@ private void loadSavedDevice(Prefs.SavedDevice savedDevice) {
188190

189191
ecidField.setText(savedDevice.getEcid());
190192
pathField.setText(savedDevice.getSavePath());
193+
if (!betaCheckBox.isDisabled()) {
194+
betaCheckBox.setSelected(savedDevice.doesIncludeBetas());
195+
}
191196
String identifier = savedDevice.getIdentifier();
192197
if (Devices.containsIdentifier(identifier)) {
193198
Utils.setSelectedFire(identifierCheckBox, false);
@@ -223,7 +228,7 @@ public void saveDeviceHandler() {
223228
if (!Utils.isEmptyOrNull(result)) {
224229
Prefs.SavedDeviceBuilder builder = new Prefs.SavedDeviceBuilder(result);
225230

226-
builder.setEcid(ecidField.getText()).setSavePath(pathField.getText())
231+
builder.setEcid(ecidField.getText()).setSavePath(pathField.getText()).setIncludeBetas(betaCheckBox.isSelected())
227232
.setIdentifier(identifierField.isDisabled() ?
228233
Devices.modelToIdentifier(deviceModelChoiceBox.getValue()) : identifierField.getText());
229234
if (!boardConfigField.isDisable()) {
@@ -415,7 +420,7 @@ public void addBackgroundHandler(Prefs.SavedDevice device, SimpleBooleanProperty
415420
updateBackgroundSettings();
416421
} else if (enable.get()) {
417422
loadSavedDevice(device);
418-
Utils.setSelectedFire(betaCheckBox, false);
423+
Utils.setSelectedFire(manualURLCheckBox, false);
419424
Utils.setSelectedFire(allSignedVersionsCheckBox, true);
420425
TSS tss = createTSS("Testing device...");
421426
EventHandler<WorkerStateEvent> oldSucceeded = tss.getOnSucceeded();
@@ -629,8 +634,8 @@ private boolean checkInputs() {
629634
incorrect |= Utils.isFieldEmpty(!boardConfigField.isDisable(), boardConfigField);
630635
incorrect |= Utils.isFieldEmpty(apnonceCheckBox, apnonceField);
631636
incorrect |= Utils.isFieldEmpty(true, pathField);
632-
incorrect |= Utils.isFieldEmpty(!allSignedVersionsCheckBox.isSelected() && !betaCheckBox.isSelected(), versionField);
633-
incorrect |= Utils.isFieldEmpty(betaCheckBox, ipswField);
637+
incorrect |= Utils.isFieldEmpty(!allSignedVersionsCheckBox.isSelected() && !manualURLCheckBox.isSelected(), versionField);
638+
incorrect |= Utils.isFieldEmpty(manualURLCheckBox, ipswField);
634639
return incorrect;
635640
}
636641

@@ -639,6 +644,7 @@ private TSS createTSS(String runningAlertTitle) {
639644
.setDevice(identifierCheckBox.isSelected() ?
640645
identifierField.getText() : Devices.modelToIdentifier(deviceModelChoiceBox.getValue()))
641646
.setEcid(ecidField.getText()).setSavePath(pathField.getText())
647+
.setIncludeBetas(betaCheckBox.isSelected())
642648
.saveToTSSSaver(saveToTSSSaverCheckBox.isSelected())
643649
.saveToSHSHHost(saveToSHSHHostCheckBox.isSelected());
644650
if (!boardConfigField.isDisabled()) {
@@ -693,7 +699,7 @@ private void parseException(Throwable t) {
693699
apnonceField.setEffect(Utils.errorBorder);
694700
} else if (message.contains("not a valid path")) {
695701
pathField.setEffect(Utils.errorBorder);
696-
} else if (message.contains("not being signed") && betaCheckBox.isSelected()) {
702+
} else if (message.contains("not being signed") && manualURLCheckBox.isSelected()) {
697703
ipswField.setEffect(Utils.errorBorder);
698704
} else if (message.contains("not being signed") && !allSignedVersionsCheckBox.isSelected()) {
699705
versionField.setEffect(Utils.errorBorder);

src/main/java/airsquared/blobsaver/app/Prefs.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,14 @@ public void setGenerator(String generator) {
253253
node.put("Generator", generator);
254254
}
255255

256+
public boolean doesIncludeBetas() {
257+
return node.getBoolean("Include Betas", false);
258+
}
259+
260+
public void setIncludeBetas(boolean includeBetas) {
261+
node.putBoolean("Include Betas", includeBetas);
262+
}
263+
256264
public boolean isBackground() {
257265
return node.getBoolean("Save in background", false);
258266
}
@@ -281,6 +289,7 @@ public int hashCode() {
281289
public static class SavedDeviceBuilder {
282290
private final String name;
283291
private String ecid, savePath, identifier, boardConfig, apnonce, generator;
292+
private boolean includeBetas;
284293

285294
public SavedDeviceBuilder(String name) {
286295
this.name = name;
@@ -316,11 +325,17 @@ public SavedDeviceBuilder setGenerator(String generator) {
316325
return this;
317326
}
318327

328+
public SavedDeviceBuilder setIncludeBetas(boolean includeBetas) {
329+
this.includeBetas = includeBetas;
330+
return this;
331+
}
332+
319333
public SavedDevice save() {
320334
SavedDevice device = new SavedDevice(Objects.requireNonNull(name, "Device Name"));
321335
device.setEcid(Objects.requireNonNull(ecid, "ECID"));
322336
device.setSavePath(Objects.requireNonNull(savePath, "Save Path"));
323337
device.setIdentifier(Objects.requireNonNull(identifier, "Identifier"));
338+
device.setIncludeBetas(includeBetas);
324339
if (boardConfig != null) {
325340
device.setBoardConfig(boardConfig);
326341
}

src/main/java/airsquared/blobsaver/app/TSS.java

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@
3838
import java.util.Objects;
3939
import java.util.regex.Matcher;
4040
import java.util.regex.Pattern;
41+
import java.util.stream.Stream;
4142

4243
import static airsquared.blobsaver.app.Utils.containsIgnoreCase;
4344
import static airsquared.blobsaver.app.Utils.executeProgram;
4445
import static airsquared.blobsaver.app.Utils.extractBuildManifest;
4546
import static airsquared.blobsaver.app.Utils.getFirmwareList;
47+
import static airsquared.blobsaver.app.Utils.getSignedBetas;
4648
import static airsquared.blobsaver.app.Utils.getSignedFirmwares;
4749

4850
public class TSS extends Task<String> {
@@ -57,6 +59,7 @@ public class TSS extends Task<String> {
5759

5860
private final String boardConfig;
5961

62+
private final boolean includeBetas;
6063
private final String manualVersion;
6164
private final String manualIpswURL;
6265

@@ -67,11 +70,12 @@ public class TSS extends Task<String> {
6770
/**
6871
* Private constructor; use {@link TSS.Builder} instead
6972
*/
70-
private TSS(String deviceIdentifier, String ecid, String savePath, String boardConfig, String manualVersion, String manualIpswURL, String apnonce, String generator, boolean saveToTSSSaver, boolean saveToSHSHHost) {
73+
private TSS(String deviceIdentifier, String ecid, String savePath, String boardConfig, boolean includeBetas, String manualVersion, String manualIpswURL, String apnonce, String generator, boolean saveToTSSSaver, boolean saveToSHSHHost) {
7174
this.deviceIdentifier = deviceIdentifier;
7275
this.ecid = ecid;
7376
this.savePath = savePath;
7477
this.boardConfig = boardConfig;
78+
this.includeBetas = includeBetas;
7579
this.manualVersion = manualVersion;
7680
this.manualIpswURL = manualIpswURL;
7781
this.apnonce = apnonce;
@@ -91,7 +95,6 @@ protected String call() throws TSSException {
9195
List<Utils.IOSVersion> iosVersions = getIOSVersions();
9296
System.out.println("iosVersions = " + iosVersions);
9397
ArrayList<String> args = constructArgs();
94-
final int urlIndex = args.size() - 1;
9598

9699
StringBuilder responseBuilder = new StringBuilder("Successfully saved blobs in\n").append(savePath);
97100
if (manualIpswURL == null) {
@@ -100,18 +103,7 @@ protected String call() throws TSSException {
100103

101104
// can't use forEach() because exception won't be caught
102105
for (Utils.IOSVersion iosVersion : iosVersions) {
103-
try {
104-
args.set(urlIndex, extractBuildManifest(iosVersion.ipswURL()).toString());
105-
} catch (IOException e) {
106-
throw new TSSException("Unable to extract BuildManifest.", true, e);
107-
}
108-
try {
109-
System.out.println("Running: " + args);
110-
String tssLog = executeProgram(args);
111-
parseTSSLog(tssLog);
112-
} catch (IOException e) {
113-
throw new TSSException("There was an error starting tsschecker.", true, e);
114-
}
106+
saveFor(iosVersion, args);
115107

116108
if (iosVersion.versionString() != null) {
117109
responseBuilder.append(iosVersion.versionString());
@@ -134,6 +126,29 @@ protected String call() throws TSSException {
134126
return responseBuilder.toString();
135127
}
136128

129+
130+
private void saveFor(Utils.IOSVersion iosVersion, ArrayList<String> args) throws TSSException {
131+
final int urlIndex = args.size() - 1;
132+
try {
133+
args.set(urlIndex, extractBuildManifest(iosVersion.ipswURL()).toString());
134+
} catch (IOException e) {
135+
throw new TSSException("Unable to extract BuildManifest.", true, e);
136+
}
137+
try {
138+
System.out.println("Running: " + args);
139+
String tssLog = executeProgram(args);
140+
parseTSSLog(tssLog);
141+
} catch (IOException e) {
142+
throw new TSSException("There was an error starting tsschecker.", true, e);
143+
} catch (TSSException e) {
144+
if ((manualVersion == null && manualIpswURL == null) && e.getMessage().contains("not being signed")) {
145+
System.out.println("Warning: ignoring unsigned version; API might be out of date");
146+
return; // ignore not being signed (API might not be updated)
147+
}
148+
throw e;
149+
}
150+
}
151+
137152
private void checkInputs() throws TSSException {
138153
boolean hasCorrectIdentifierPrefix = deviceIdentifier.startsWith("iPad") || deviceIdentifier.startsWith("iPod")
139154
|| deviceIdentifier.startsWith("iPhone") || deviceIdentifier.startsWith("AppleTV");
@@ -177,6 +192,8 @@ private List<Utils.IOSVersion> getIOSVersions() throws TSSException {
177192
.orElseThrow(() -> new TSSException("No versions found.", false)));
178193
} else if (manualIpswURL != null) {
179194
return Collections.singletonList(new Utils.IOSVersion(null, manualIpswURL, null));
195+
} else if (includeBetas) {
196+
return Stream.concat(getSignedFirmwares(deviceIdentifier), getSignedBetas(deviceIdentifier)).toList();
180197
} else { // all signed firmwares
181198
return getSignedFirmwares(deviceIdentifier).toList();
182199
}
@@ -252,7 +269,7 @@ && containsIgnoreCase(tsscheckerLog, "checking tss status failed")) {
252269
@SuppressWarnings("UnusedReturnValue")
253270
public static class Builder {
254271
private String device, ecid, savePath, boardConfig, manualVersion, manualIpswURL, apnonce, generator;
255-
private boolean saveToTSSSaver, saveToSHSHHost;
272+
private boolean includeBetas, saveToTSSSaver, saveToSHSHHost;
256273

257274
public Builder setDevice(String device) {
258275
this.device = device;
@@ -296,6 +313,11 @@ public Builder setGenerator(String generator) {
296313
return this;
297314
}
298315

316+
public Builder setIncludeBetas(boolean includeBetas) {
317+
this.includeBetas = includeBetas;
318+
return this;
319+
}
320+
299321
public Builder saveToTSSSaver(boolean saveToTSSSaver) {
300322
this.saveToTSSSaver = saveToTSSSaver;
301323
return this;
@@ -310,7 +332,7 @@ public TSS build() {
310332
return new TSS(Objects.requireNonNull(device, "Device"),
311333
Objects.requireNonNull(ecid, "ECID"),
312334
Objects.requireNonNull(savePath, "Save Path"),
313-
boardConfig, manualVersion, manualIpswURL, apnonce, generator, saveToTSSSaver, saveToSHSHHost);
335+
boardConfig, includeBetas, manualVersion, manualIpswURL, apnonce, generator, saveToTSSSaver, saveToSHSHHost);
314336
}
315337
}
316338

src/main/java/airsquared/blobsaver/app/Utils.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package airsquared.blobsaver.app;
2020

21+
import com.google.gson.JsonArray;
2122
import com.google.gson.JsonElement;
2223
import com.google.gson.JsonParser;
2324
import com.sun.jna.Platform;
@@ -349,7 +350,16 @@ static void runSafe(Runnable runnable) {
349350

350351
static Stream<IOSVersion> getFirmwareList(String deviceIdentifier) throws IOException {
351352
String url = "https://api.ipsw.me/v4/device/" + deviceIdentifier;
352-
return StreamSupport.stream(makeRequest(url).getAsJsonObject().getAsJsonArray("firmwares").spliterator(), false)
353+
return createVersionStream(makeRequest(url).getAsJsonObject().getAsJsonArray("firmwares"));
354+
}
355+
356+
static Stream<IOSVersion> getBetaList(String deviceIdentifier) throws IOException {
357+
String url = "https://api.m1sta.xyz/betas/" + deviceIdentifier;
358+
return createVersionStream(makeRequest(url).getAsJsonArray());
359+
}
360+
361+
private static Stream<IOSVersion> createVersionStream(JsonArray array) {
362+
return StreamSupport.stream(array.spliterator(), false)
353363
.map(JsonElement::getAsJsonObject)
354364
.map(o -> new IOSVersion(o.get("version").getAsString(), o.get("url").getAsString(), o.get("signed").getAsBoolean()));
355365
}
@@ -358,6 +368,10 @@ static Stream<IOSVersion> getSignedFirmwares(String deviceIdentifier) throws IOE
358368
return getFirmwareList(deviceIdentifier).filter(IOSVersion::signed);
359369
}
360370

371+
static Stream<IOSVersion> getSignedBetas(String deviceIdentifier) throws IOException {
372+
return getBetaList(deviceIdentifier).filter(IOSVersion::signed);
373+
}
374+
361375
record IOSVersion(String versionString, String ipswURL, Boolean signed) {
362376
public IOSVersion {
363377
Objects.requireNonNull(ipswURL, "ipsw url cannot be null");

src/main/resources/airsquared/blobsaver/app/blobsaver.fxml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,13 @@
167167
<Insets left="10.0" top="5.0"/>
168168
</VBox.margin>
169169
<CheckBox fx:id="allSignedVersionsCheckBox" mnemonicParsing="false" onAction="#versionCheckBoxHandler"
170-
selected="true" text="All Signed Versions" disable="${betaCheckBox.selected}"/>
171-
<CheckBox fx:id="betaCheckBox" mnemonicParsing="false" onAction="#betaCheckBoxHandler" text="Beta Version">
170+
selected="true" text="All Signed Versions" disable="${manualURLCheckBox.selected}"/>
171+
<CheckBox fx:id="betaCheckBox" mnemonicParsing="false" text="Include Betas" disable="${!allSignedVersionsCheckBox.selected}">
172+
<HBox.margin>
173+
<Insets left="10.0"/>
174+
</HBox.margin>
175+
</CheckBox>
176+
<CheckBox fx:id="manualURLCheckBox" mnemonicParsing="false" onAction="#manualURLCheckBoxHandler" text="Manually Specify URL">
172177
<HBox.margin>
173178
<Insets left="10.0"/>
174179
</HBox.margin>
@@ -178,9 +183,9 @@
178183
<VBox.margin>
179184
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0"/>
180185
</VBox.margin>
181-
<TextField fx:id="versionField" disable="${allSignedVersionsCheckBox.selected || betaCheckBox.selected}"
186+
<TextField fx:id="versionField" disable="${allSignedVersionsCheckBox.selected || manualURLCheckBox.selected}"
182187
promptText="Version" onTextChange="#clearEffectHandler"/>
183-
<TextField fx:id="ipswField" disable="${!betaCheckBox.selected}" HBox.hgrow="ALWAYS"
188+
<TextField fx:id="ipswField" disable="${!manualURLCheckBox.selected}" HBox.hgrow="ALWAYS"
184189
promptText="URL to .ipsw file" onTextChange="#clearEffectHandler">
185190
<HBox.margin>
186191
<Insets left="5.0"/>

src/test/java/airsquared/blobsaver/app/TSSTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class TSSTest extends BlobsaverTest {
2626

2727
@Test
2828
public void call() throws TSS.TSSException {
29-
TSS tss = new TSS.Builder().setDevice("iPhone8,4").setEcid("1")
29+
TSS tss = new TSS.Builder().setDevice("iPhone8,4").setEcid("1").setIncludeBetas(true)
3030
.setSavePath(System.getProperty("user.home") + File.separator + "Blobs").setBoardConfig("n69ap").build();
3131
tss.call(); // don't create another thread
3232
}

0 commit comments

Comments
 (0)