Skip to content

Commit 965c49b

Browse files
Add ability to save blobs to SHSH Host and TSS Saver (#402)
Co-authored-by: airsquared <36649395+airsquared@users.noreply.github.com>
1 parent 7bfb296 commit 965c49b

File tree

5 files changed

+202
-12
lines changed

5 files changed

+202
-12
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ 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;
60+
@FXML private CheckBox apnonceCheckBox, allSignedVersionsCheckBox, identifierCheckBox, betaCheckBox, saveToTSSSaverCheckBox, saveToSHSHHostCheckBox;
6161

6262
@FXML private Label versionLabel, savedDevicesLabel;
6363

@@ -78,6 +78,12 @@ public void initialize() {
7878
backgroundSettingsMenu.textProperty().bind(Bindings.when(backgroundSettingsButton.selectedProperty())
7979
.then("Hide Background Settings").otherwise("Show Background Settings"));
8080
backgroundSettingsMenu.setOnAction(e -> backgroundSettingsButton.fire());
81+
allSignedVersionsCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
82+
if (!newValue) {
83+
saveToTSSSaverCheckBox.setSelected(false);
84+
saveToSHSHHostCheckBox.setSelected(false);
85+
}
86+
});
8187
switch (Prefs.getDarkMode()) {
8288
case DISABLED -> darkDisabled.setSelected(true);
8389
case SYNC_WITH_OS -> darkSync.setSelected(true);
@@ -632,7 +638,9 @@ private TSS createTSS(String runningAlertTitle) {
632638
TSS.Builder builder = new TSS.Builder()
633639
.setDevice(identifierCheckBox.isSelected() ?
634640
identifierField.getText() : Devices.modelToIdentifier(deviceModelChoiceBox.getValue()))
635-
.setEcid(ecidField.getText()).setSavePath(pathField.getText());
641+
.setEcid(ecidField.getText()).setSavePath(pathField.getText())
642+
.saveToTSSSaver(saveToTSSSaverCheckBox.isSelected())
643+
.saveToSHSHHost(saveToSHSHHostCheckBox.isSelected());
636644
if (!boardConfigField.isDisabled()) {
637645
builder.setBoardConfig(boardConfigField.getText());
638646
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package airsquared.blobsaver.app;
2+
3+
import com.google.gson.Gson;
4+
5+
import java.io.IOException;
6+
import java.net.URI;
7+
import java.net.URLEncoder;
8+
import java.net.http.HttpClient;
9+
import java.net.http.HttpRequest;
10+
import java.net.http.HttpRequest.BodyPublishers;
11+
import java.net.http.HttpResponse;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.Map;
14+
15+
public class Network {
16+
17+
// one instance, reuse
18+
private static final HttpClient httpClient = HttpClient.newBuilder()
19+
.version(HttpClient.Version.HTTP_2)
20+
.build();
21+
22+
// Performs a POST Request with the specified URL and Parameters
23+
public static HttpResponse<String> makePOSTRequest(String url, Map<Object, Object> parameters, Map<String, String> headers, boolean convertParamtersToJSON) throws IOException, InterruptedException {
24+
25+
// convert Arguments to JSON (and use them if convertParametersToJSON is true)
26+
Gson gson = new Gson();
27+
String JSONParameters = gson.toJson(parameters);
28+
29+
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
30+
.POST(convertParamtersToJSON ? BodyPublishers.ofString(JSONParameters) : buildFormDataFromMap(parameters))
31+
.uri(URI.create(url));
32+
33+
for (Map.Entry<String, String> entry : headers.entrySet())
34+
requestBuilder.header(entry.getKey(), entry.getValue());
35+
36+
HttpRequest request = requestBuilder.build();
37+
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
38+
}
39+
40+
41+
private static HttpRequest.BodyPublisher buildFormDataFromMap(Map<Object, Object> data) {
42+
var builder = new StringBuilder();
43+
for (Map.Entry<Object, Object> entry : data.entrySet()) {
44+
if (builder.length() > 0) {
45+
builder.append("&");
46+
}
47+
builder.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8));
48+
builder.append("=");
49+
builder.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8));
50+
}
51+
return HttpRequest.BodyPublishers.ofString(builder.toString());
52+
}
53+
}

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

Lines changed: 126 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package airsquared.blobsaver.app;
2020

21+
import com.google.gson.Gson;
2122
import javafx.concurrent.Task;
2223

2324
import java.io.File;
@@ -27,10 +28,13 @@
2728
import java.net.URI;
2829
import java.net.URISyntaxException;
2930
import java.net.URL;
31+
import java.net.http.HttpResponse;
3032
import java.nio.file.Path;
3133
import java.util.ArrayList;
3234
import java.util.Collections;
35+
import java.util.HashMap;
3336
import java.util.List;
37+
import java.util.Map;
3438
import java.util.Objects;
3539
import java.util.regex.Matcher;
3640
import java.util.regex.Pattern;
@@ -58,10 +62,12 @@ public class TSS extends Task<String> {
5862

5963
private final String apnonce, generator;
6064

65+
private final boolean saveToTSSSaver, saveToSHSHHost;
66+
6167
/**
6268
* Private constructor; use {@link TSS.Builder} instead
6369
*/
64-
private TSS(String deviceIdentifier, String ecid, String savePath, String boardConfig, String manualVersion, String manualIpswURL, String apnonce, String generator) {
70+
private TSS(String deviceIdentifier, String ecid, String savePath, String boardConfig, String manualVersion, String manualIpswURL, String apnonce, String generator, boolean saveToTSSSaver, boolean saveToSHSHHost) {
6571
this.deviceIdentifier = deviceIdentifier;
6672
this.ecid = ecid;
6773
this.savePath = savePath;
@@ -70,6 +76,8 @@ private TSS(String deviceIdentifier, String ecid, String savePath, String boardC
7076
this.manualIpswURL = manualIpswURL;
7177
this.apnonce = apnonce;
7278
this.generator = generator;
79+
this.saveToTSSSaver = saveToTSSSaver;
80+
this.saveToSHSHHost = saveToSHSHHost;
7381
}
7482

7583
/**
@@ -85,9 +93,9 @@ protected String call() throws TSSException {
8593
ArrayList<String> args = constructArgs();
8694
final int urlIndex = args.size() - 1;
8795

88-
StringBuilder sb = new StringBuilder("Successfully saved blobs in\n").append(savePath);
96+
StringBuilder responseBuilder = new StringBuilder("Successfully saved blobs in\n").append(savePath);
8997
if (manualIpswURL == null) {
90-
sb.append(iosVersions.size() == 1 ? "\n\nFor version " : "\n\nFor versions ");
98+
responseBuilder.append(iosVersions.size() == 1 ? "\n\nFor version " : "\n\nFor versions ");
9199
}
92100

93101
// can't use forEach() because exception won't be caught
@@ -106,14 +114,24 @@ protected String call() throws TSSException {
106114
}
107115

108116
if (iosVersion.versionString() != null) {
109-
sb.append(iosVersion.versionString());
117+
responseBuilder.append(iosVersion.versionString());
110118
if (iosVersion != iosVersions.get(iosVersions.size() - 1))
111-
sb.append(", ");
119+
responseBuilder.append(", ");
112120
}
113121
}
114122

123+
if (saveToTSSSaver || saveToSHSHHost) {
124+
responseBuilder.append("\n\n");
125+
}
126+
if (saveToTSSSaver) {
127+
saveBlobsTSSSaver(responseBuilder);
128+
}
129+
if (saveToSHSHHost) {
130+
saveBlobsSHSHHost(responseBuilder);
131+
}
132+
115133
Analytics.saveBlobs();
116-
return sb.toString();
134+
return responseBuilder.toString();
117135
}
118136

119137
private void checkInputs() throws TSSException {
@@ -122,6 +140,9 @@ private void checkInputs() throws TSSException {
122140
if (!deviceIdentifier.contains(",") || !hasCorrectIdentifierPrefix) {
123141
throw new TSSException("\"" + deviceIdentifier + "\" is not a valid identifier", false);
124142
}
143+
if (boardConfig == null && Devices.doesRequireBoardConfig(deviceIdentifier)) {
144+
throw new TSSException("A board configuration is required for this device.", false);
145+
}
125146
if (manualIpswURL != null) { // check URL
126147
try {
127148
if (!ipswURLMatcher.reset(manualIpswURL).matches()) {
@@ -152,7 +173,7 @@ private List<Utils.IOSVersion> getIOSVersions() throws TSSException {
152173
try {
153174
if (manualVersion != null) {
154175
return Collections.singletonList(getFirmwareList(deviceIdentifier).filter(iosVersion ->
155-
manualVersion.equals(iosVersion.versionString())).findFirst()
176+
manualVersion.equals(iosVersion.versionString())).findFirst()
156177
.orElseThrow(() -> new TSSException("No versions found.", false)));
157178
} else if (manualIpswURL != null) {
158179
return Collections.singletonList(new Utils.IOSVersion(null, manualIpswURL, null));
@@ -172,8 +193,7 @@ private ArrayList<String> constructArgs() {
172193
//noinspection ResultOfMethodCallIgnored
173194
new File(savePath).mkdirs();
174195
Collections.addAll(args, tsscheckerPath, "--nocache", "--save", "--device", deviceIdentifier, "--ecid", ecid, "--save-path", savePath);
175-
Collections.addAll(args, "--boardconfig",
176-
Objects.requireNonNullElse(boardConfig, Devices.getBoardConfig(deviceIdentifier)));
196+
Collections.addAll(args, "--boardconfig", getBoardConfig());
177197
if (apnonce != null) {
178198
Collections.addAll(args, "--apnonce", apnonce);
179199
if (generator != null) {
@@ -187,6 +207,10 @@ private ArrayList<String> constructArgs() {
187207
return args;
188208
}
189209

210+
private String getBoardConfig() {
211+
return Objects.requireNonNullElse(boardConfig, Devices.getBoardConfig(deviceIdentifier));
212+
}
213+
190214
@SuppressWarnings("TextBlockMigration")
191215
private void parseTSSLog(String tsscheckerLog) throws TSSException {
192216
if (containsIgnoreCase(tsscheckerLog, "Saved shsh blobs")) {
@@ -228,6 +252,7 @@ && containsIgnoreCase(tsscheckerLog, "checking tss status failed")) {
228252
@SuppressWarnings("UnusedReturnValue")
229253
public static class Builder {
230254
private String device, ecid, savePath, boardConfig, manualVersion, manualIpswURL, apnonce, generator;
255+
private boolean saveToTSSSaver, saveToSHSHHost;
231256

232257
public Builder setDevice(String device) {
233258
this.device = device;
@@ -271,11 +296,21 @@ public Builder setGenerator(String generator) {
271296
return this;
272297
}
273298

299+
public Builder saveToTSSSaver(boolean saveToTSSSaver) {
300+
this.saveToTSSSaver = saveToTSSSaver;
301+
return this;
302+
}
303+
304+
public Builder saveToSHSHHost(boolean saveToSHSHHost) {
305+
this.saveToSHSHHost = saveToSHSHHost;
306+
return this;
307+
}
308+
274309
public TSS build() {
275310
return new TSS(Objects.requireNonNull(device, "Device"),
276311
Objects.requireNonNull(ecid, "ECID"),
277312
Objects.requireNonNull(savePath, "Save Path"),
278-
boardConfig, manualVersion, manualIpswURL, apnonce, generator);
313+
boardConfig, manualVersion, manualIpswURL, apnonce, generator, saveToTSSSaver, saveToSHSHHost);
279314
}
280315
}
281316

@@ -308,4 +343,85 @@ public TSSException(String message, boolean isReportable, Throwable cause) {
308343

309344
}
310345

346+
private void saveBlobsTSSSaver(StringBuilder responseBuilder) {
347+
Map<Object, Object> deviceParameters = new HashMap<>();
348+
349+
deviceParameters.put("ecid", String.valueOf(Long.parseLong(ecid, 16)));
350+
deviceParameters.put("deviceIdentifier", deviceIdentifier);
351+
deviceParameters.put("boardConfig", getBoardConfig());
352+
353+
if (generator != null) {
354+
deviceParameters.put("generator", generator);
355+
}
356+
if (apnonce != null) {
357+
deviceParameters.put("apnonce", apnonce);
358+
}
359+
360+
System.out.println(deviceParameters);
361+
var headers = new HashMap<String, String>();
362+
headers.put("Content-Type", "application/x-www-form-urlencoded");
363+
364+
HttpResponse<String> response;
365+
try {
366+
response = Network.makePOSTRequest("https://tsssaver.1conan.com/v2/api/save.php", deviceParameters, headers, true);
367+
} catch (IOException | InterruptedException e) {
368+
e.printStackTrace();
369+
responseBuilder.append("Error encountered while trying to save blobs to TSSSaver: ").append(e.getMessage());
370+
return;
371+
}
372+
System.out.println(response.body());
373+
374+
@SuppressWarnings("rawtypes") Map responseBody = new Gson().fromJson(response.body(), Map.class);
375+
376+
if (responseBody.containsKey("errors")) {
377+
responseBuilder.append("Error encountered while trying to save blobs to TSSSaver: ").append(responseBody.get("errors"));
378+
} else {
379+
responseBuilder.append("Also saved blobs online to TSS Saver.");
380+
}
381+
}
382+
383+
private void saveBlobsSHSHHost(StringBuilder responseBuilder) {
384+
if (saveToTSSSaver) {
385+
responseBuilder.append("\n");
386+
}
387+
388+
Map<Object, Object> deviceParameters = new HashMap<>();
389+
390+
deviceParameters.put("ecid", ecid);
391+
deviceParameters.put("boardconfig", getBoardConfig());
392+
deviceParameters.put("device", deviceIdentifier);
393+
deviceParameters.put("selected_firmware", "All");
394+
395+
if (generator != null) {
396+
deviceParameters.put("generator", generator);
397+
}
398+
if (apnonce != null) {
399+
deviceParameters.put("apnonce", apnonce);
400+
}
401+
402+
System.out.println(deviceParameters);
403+
String userAgent = "Blobsaver " + Main.appVersion;
404+
var headers = new HashMap<String, String>();
405+
headers.put("Content-Type", "application/x-www-form-urlencoded");
406+
headers.put("User-Agent", userAgent);
407+
headers.put("X-CPU-STATE", "0000000000000000000000000000000000000000");
408+
409+
HttpResponse<String> response;
410+
try {
411+
response = Network.makePOSTRequest("https://api.arx8x.net/shsh3/", deviceParameters, headers, false);
412+
} catch (IOException | InterruptedException e) {
413+
e.printStackTrace();
414+
responseBuilder.append("Error encountered while trying to save blobs to SHSH Host: ").append(e.getMessage());
415+
return;
416+
}
417+
System.out.println(response.body());
418+
419+
@SuppressWarnings("rawtypes") Map responseBody = new Gson().fromJson(response.body(), Map.class);
420+
421+
if (responseBody.get("code").equals((double) 0)) {
422+
responseBuilder.append("Also saved blobs online to SHSH Host.");
423+
} else {
424+
responseBuilder.append("Error encountered while trying to save blobs to SHSH Host: ").append(responseBody.get("message"));
425+
}
426+
}
311427
}

src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
requires nsmenufx;
99
requires com.google.gson;
1010
requires org.apache.commons.compress;
11+
requires java.net.http;
1112
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,18 @@
262262
</Label>
263263
</HBox>
264264
<Region VBox.vgrow="ALWAYS"/>
265+
<CheckBox fx:id="saveToTSSSaverCheckBox" mnemonicParsing="false" text="Also save to TSS Saver"
266+
disable="${!allSignedVersionsCheckBox.selected}">
267+
<VBox.margin>
268+
<Insets left="10.0" top="5.0"/>
269+
</VBox.margin>
270+
</CheckBox>
271+
<CheckBox fx:id="saveToSHSHHostCheckBox" mnemonicParsing="false" text="Also save to SHSH Host"
272+
disable="${!allSignedVersionsCheckBox.selected}">
273+
<VBox.margin>
274+
<Insets left="10.0" top="5.0"/>
275+
</VBox.margin>
276+
</CheckBox>
265277
<Button maxWidth="Infinity" mnemonicParsing="false" onAction="#goButtonHandler" prefWidth="Infinity" text="Go"
266278
defaultButton="true" disable="${backgroundSettingsButton.selected}">
267279
<VBox.margin>

0 commit comments

Comments
 (0)