-
Notifications
You must be signed in to change notification settings - Fork 16
/
autotss.py
executable file
·265 lines (204 loc) · 9.06 KB
/
autotss.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import requests as r
import configparser
import subprocess
import argparse
import dataset
import json
import sys
import os
import io
class autotss:
def __init__(self, userPath = None):
self.scriptPath = self.getScriptPath(userPath)
self.liveFirmwareAPI = self.getFirmwareAPI()
self.database = dataset.connect('sqlite:///autotss.db')
self.importNewDevices()
self.checkAllDevices()
self.pushToDatabase()
def importNewDevices(self):
""" Checks devices.txt for new entries. Parses entries and
inserts them into the devices table in our database """
print('\nChecking devices.ini for new devices...')
db = self.database['devices']
newDevices = []
numNew = 0
# Check to make sure devices.ini exists, otherwise warn and continue without new devices
if os.path.isfile('devices.ini'):
config = configparser.ConfigParser()
config.read('devices.ini')
for section in config.sections():
name = section
identifier = config.get(section, 'identifier').replace(' ','')
ecid = config.get(section, 'ecid')
try:
boardconfig = config.get(section, 'boardconfig').lower()
except:
boardconfig = ''
if not boardconfig:
boardconfig = self.getBoardConfig(identifier)
newDevices.append({'deviceName': name, 'deviceID': identifier, 'boardConfig': boardconfig, 'deviceECID': ecid, 'blobsSaved': '[]'})
else:
print('Unable to find devices.ini')
# Add only new devices to database
for newDevice in newDevices:
if not db.find_one(deviceECID=newDevice['deviceECID']):
print('Device: [{deviceName}] ECID: [{deviceECID}] Board Config: [{boardConfig}]'.format(**newDevice))
numNew += 1
db.insert(newDevice)
print('Added {} new devices to the database'.format(str(numNew)))
return
def getBoardConfig(self, deviceID):
""" Using the IPSW.me API, when supplied a device identifier
the relevant board config will be returned."""
return self.liveFirmwareAPI[deviceID]['BoardConfig']
def checkForBlobs(self, deviceECID, buildID):
""" Checks against our database to see if blobs for a
device have already been saved for a specific iOS version.
The device is identified by a deviceECID, iOS version is
identified by a buildID. """
deviceInfo = self.database['devices'].find_one(deviceECID=deviceECID)
for entry in json.loads(deviceInfo['blobsSaved']):
if entry['buildID'] == buildID:
return True
return False
def getFirmwareAPI(self):
""" Taking the raw response from the IPSW.me API, process
the response as a JSON object and remove unsigned firmware
entries. Returns a freshly processed devices JSON containing
only signed firmware versions. """
headers = {'User-Agent': 'Script to automatically save shsh blobs (https://github.com/codsane/autotss)'}
rawResponse = r.get('https://api.ipsw.me/v2.1/firmwares.json/condensed', headers=headers)
deviceAPI = rawResponse.json()['devices']
''' Rather than messing around with copies, we can loop
through all firmware dictionary objects and append the
signed firmware objects to a list. The original firmware
list is then replaced with the new (signed firmware only) list.'''
for deviceID in deviceAPI:
signedFirmwares = []
for firmware in deviceAPI[deviceID]['firmwares']:
if firmware['signed']:
signedFirmwares.append(firmware)
deviceAPI[deviceID]['firmwares'] = signedFirmwares
return deviceAPI
def checkAllDevices(self):
""" Loop through all of our devices and grab matching
device firmwares from the firmwareAPI. Device and
firmware info is sent to saveBlobs(). """
print('\nGrabbing devices from the database...')
self.devices = [row for row in self.database['devices']]
for device in self.devices:
print('Device: [{deviceName}] ECID: [{deviceECID}] Board Config: [{boardConfig}]'.format(**device))
print('Grabbed {} devices from the database'.format(len(self.devices)))
print('\nSaving unsaved blobs for {} devices...'.format(str(len(self.devices))))
for device in self.devices:
for firmware in self.liveFirmwareAPI[device['deviceID']]['firmwares']:
self.saveBlobs(device, firmware['buildid'], firmware['version'])
print('Done saving blobs')
def saveBlobs(self, device, buildID, versionNumber):
""" First, check to see if blobs have already been
saved. If blobs have not been saved, use subprocess
to call the tsschecker script and save blobs. After
saving blobs, logSavedBlobs() is called to log that
we saved the device/firmware blobs. """
if self.checkForBlobs(device['deviceECID'], buildID):
# print('[{0}] [{1}] {2}'.format(device['deviceID'], versionNumber, 'Blobs already saved!'))
return
savePath = 'blobs/' + device['deviceID'] + '/' + device['deviceECID'] + '/' + versionNumber + '/' + buildID
if not os.path.exists(savePath):
os.makedirs(savePath)
scriptArguments = [self.scriptPath,
'-d', device['deviceID'],
'-e', device['deviceECID'],
'--boardconfig', device['boardConfig'],
'--buildid', buildID,
'--save-path', savePath,
'-s']
tssCall = subprocess.Popen(scriptArguments, stdout=subprocess.PIPE)
tssOutput = []
for line in io.TextIOWrapper(tssCall.stdout, encoding='utf-8'):
tssOutput.append(line.strip())
''' Checks console output for the `Saved shsh blobs!`
string. While this works for now, tsschecker updates
may break the check. It may be possible to check to
see if the .shsh file was created and also check for
the right file format. '''
if 'Saved shsh blobs!' in tssOutput:
self.logBlobsSaved(device, buildID, versionNumber)
print('[{0}] [{1} - {2}] {3}'.format(device['deviceName'], versionNumber, buildID, 'Saved shsh blobs!'))
else:
self.logBlobsFailed(scriptArguments, savePath, tssOutput)
print('[{0}] [{1} - {2}] {3}'.format(device['deviceName'], versionNumber, buildID, 'Error, see log file: ' + savePath + '/tsschecker_log.txt'))
return
def logBlobsSaved(self, device, buildID, versionNumber):
""" Taking a reference to a device dictionary, we can
load the string `blobsSaved` from the database into
a JSON object, append a newly saved version, and
turn the JSON object back into a string and
replace `blobsSaved` """
oldBlobsSaved = json.loads(device['blobsSaved'])
newBlobsSaved = {'releaseType': 'release', 'versionNumber': versionNumber, 'buildID': buildID}
oldBlobsSaved.append(newBlobsSaved)
device['blobsSaved'] = json.dumps(oldBlobsSaved)
return
def logBlobsFailed(self, scriptArguments, savePath, tssOutput):
""" When blobs are unable to be saved, we save
a log of tsschecker's output in the blobs folder. """
with open(savePath + '/tsschecker_log.txt', 'w') as file:
file.write(' '.join(scriptArguments) + '\n\n')
file.write('\n'.join(tssOutput))
return
def pushToDatabase(self):
""" Loop through all of our devices and update their
entries into the database. ECID is used as the value
to update by, as it is the only unique device identifier."""
print('\nUpdating database with newly saved blobs...')
for device in self.devices:
self.database['devices'].update(device, ['deviceECID'])
print('Done updating database')
return
def getScriptPath(self, userPath):
""" Determines if the user provided a path to the tsschecker
binary, whether command line argument or passed to autotss().
If the user did not provide a path, try to find it within
/tsschecker or /tsschecker-latest and select the proper binary
Also verifies that these files exist. """
scriptPath = None
argParser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
argParser.add_argument("-p", "--path", help='Supply the path to your tsschecker binary.\nExample: -p /Users/codsane/tsschecker/tsschecker_macos', required=False, default='')
argument = argParser.parse_args()
# Check to see if the user provided the command line argument -p or --path
if argument.path:
scriptPath = argument.path
# Check to make sure this file exists
if os.path.isfile(argument.path):
print('Using manually specified tsschecker binary: ' + argument.path)
else:
print('Unable to find tsschecker at specificed path: ' + argument.path)
sys.exit()
# No command line argument provided, check to see if a path was passed to autotss()
else:
scriptPath = "tsschecker"
try:
tssCall = subprocess.Popen(scriptPath, stdout=subprocess.PIPE)
except subprocess.CalledProcessError:
pass
except OSError:
print('tsschecker not found. Install or point to with -p')
print('Get tsschecker here: https://github.com/encounter/tsschecker/releases')
sys.exit()
# Check to make sure user has the right tsschecker version
tssOutput = []
for line in io.TextIOWrapper(tssCall.stdout, encoding='utf-8'):
tssOutput.append(line.strip())
versionNumber = int(tssOutput[0].split('-')[-1].strip())
if versionNumber < 247:
print('Your version of tss checker is too old')
print('Get the latest version here: http://api.tihmstar.net/builds/tsschecker/tsschecker-latest.zip')
print('Unzip into the same folder as autotss')
sys.exit()
return scriptPath
def main():
# autotss('/Users/codsane/tsschecker/tsschecker_macos')
autotss()
if __name__ == "__main__":
main()