mirror of
https://github.com/tihmstar/futurerestore.git
synced 2025-01-02 19:25:31 +00:00
added optional libipatcher support
This commit is contained in:
parent
de907d3fa8
commit
d1c90d1185
35
configure.ac
35
configure.ac
|
@ -37,9 +37,44 @@ PKG_CHECK_MODULES(libirecovery, libirecovery >= 0.2.0)
|
|||
AC_PROG_CC
|
||||
AC_PROG_CXX
|
||||
|
||||
|
||||
# Optional module libipatcher
|
||||
AC_ARG_WITH([libipatcher],
|
||||
[AS_HELP_STRING([--without-libipatcher],
|
||||
[build with bundle-less odysseus support (default is yes)])],
|
||||
[build_libipatcher=false],
|
||||
[build_libipatcher=true])
|
||||
|
||||
PKG_PROG_PKG_CONFIG
|
||||
LIBIPATCHER_FLAGS=$(pkg-config --cflags libipatcher)
|
||||
if [test -z "$LIBIPATCHER_FLAGS"]; then
|
||||
do_libipatcher=no
|
||||
else
|
||||
AC_MSG_CHECKING([for futurerestore libipatcher])
|
||||
if test "$build_libipatcher" = "true"; then
|
||||
PKG_CHECK_MODULES(libipatcher, libipatcher >= 1.0)
|
||||
do_libipatcher=yes
|
||||
else
|
||||
do_libipatcher=no
|
||||
fi
|
||||
fi
|
||||
AM_CONDITIONAL([HAVE_LIBIPATCHER],[test "x$do_libipatcher" = "xyes"])
|
||||
|
||||
|
||||
LT_INIT
|
||||
|
||||
AC_OUTPUT([
|
||||
Makefile
|
||||
futurerestore/Makefile
|
||||
])
|
||||
|
||||
|
||||
echo "
|
||||
Configuration for $PACKAGE $VERSION:
|
||||
-------------------------------------------
|
||||
|
||||
Install prefix ..........: $prefix
|
||||
With libipatcher ........: $do_libipatcher
|
||||
Now type 'make' to build $PACKAGE $VERSION,
|
||||
and then 'make install' for installation.
|
||||
"
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
AM_CFLAGS = $(libplist_CFLAGS) $(libzip_CFLAGS) $(libimobiledevice_CFLAGS) $(libfragmentzip_CFLAGS) $(libirecovery_CFLAGS) -I$(top_srcdir)/external/tsschecker/tsschecker -I$(top_srcdir)/external/img4tool/img4tool -I$(top_srcdir)/external/idevicerestore/src -std=c++11
|
||||
AM_LDFLAGS = $(libplist_LIBS) $(libzip_LIBS) $(libimobiledevice_LIBS) $(libfragmentzip_LIBS) $(libirecovery_LIBS)
|
||||
|
||||
if HAVE_LIBIPATCHER
|
||||
AM_LDFLAGS += $(libipatcher_LIBS)
|
||||
AM_CFLAGS += $(libipatcher_CFLAGS) -DHAVE_LIBIPATCHER
|
||||
endif
|
||||
|
||||
bin_PROGRAMS = futurerestore
|
||||
futurerestore_CXXFLAGS = $(AM_CFLAGS)
|
||||
futurerestore_LDADD = $(top_srcdir)/external/idevicerestore/src/libidevicerestore.la $(top_srcdir)/external/img4tool/img4tool/libimg4tool.la $(top_srcdir)/external/tsschecker/tsschecker/libtsschecker.la $(AM_LDFLAGS)
|
||||
|
|
|
@ -20,17 +20,22 @@
|
|||
#include <libgen.h>
|
||||
#include <zlib.h>
|
||||
#include "futurerestore.hpp"
|
||||
#ifdef HAVE_LIBIPATCHER
|
||||
#include <libipatcher/libipatcher.hpp>
|
||||
#endif
|
||||
extern "C"{
|
||||
#include "common.h"
|
||||
#include "../external/img4tool/img4tool/img4.h"
|
||||
#include "img4tool.h"
|
||||
#include "normal.h"
|
||||
#include "recovery.h"
|
||||
#include "dfu.h"
|
||||
#include "ipsw.h"
|
||||
#include "locking.h"
|
||||
#include "restore.h"
|
||||
#include "tsschecker.h"
|
||||
#include "all_tsschecker.h"
|
||||
#include <libirecovery.h>
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,16 +69,15 @@ extern "C"{
|
|||
# include <openssl/sha.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
#ifndef HAVE_LIBIPATCHER
|
||||
#define _enterPwnRecoveryRequested false
|
||||
#endif
|
||||
|
||||
#define reterror(code,msg ...) error(msg),throw int(code)
|
||||
#define safeFree(buf) if (buf) free(buf), buf = NULL
|
||||
#define safePlistFree(buf) if (buf) plist_free(buf), buf = NULL
|
||||
|
||||
futurerestore::futurerestore(){
|
||||
futurerestore(false);
|
||||
}
|
||||
|
||||
futurerestore::futurerestore(bool isUpdateInstall) : _isUpdateInstall(isUpdateInstall){
|
||||
futurerestore::futurerestore(bool isUpdateInstall, bool isPwnDfu) : _isUpdateInstall(isUpdateInstall), _isPwnDfu(isPwnDfu){
|
||||
_client = idevicerestore_client_new();
|
||||
if (_client == NULL) throw std::string("could not create idevicerestore client\n");
|
||||
|
||||
|
@ -92,6 +96,7 @@ bool futurerestore::init(){
|
|||
info("[INFO] 32bit device detected\n");
|
||||
}else{
|
||||
info("[INFO] 64bit device detected\n");
|
||||
if (_isPwnDfu) reterror(-90, "isPwnDfu is only allowed for 32bit devices\n");
|
||||
}
|
||||
return _didInit;
|
||||
}
|
||||
|
@ -111,6 +116,7 @@ int futurerestore::getDeviceMode(bool reRequest){
|
|||
return _client->mode->index;
|
||||
}else{
|
||||
normal_client_free(_client);
|
||||
dfu_client_free(_client);
|
||||
recovery_client_free(_client);
|
||||
return check_mode(_client);
|
||||
}
|
||||
|
@ -128,6 +134,14 @@ void futurerestore::putDeviceIntoRecovery(){
|
|||
}
|
||||
}else if (_client->mode->index == MODE_RECOVERY){
|
||||
info("Device already in Recovery mode\n");
|
||||
}else if (_client->mode->index == MODE_DFU && _isPwnDfu &&
|
||||
#ifdef HAVE_LIBIPATCHER
|
||||
(_enterPwnRecoveryRequested = true)
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
){
|
||||
info("requesting to get into pwnRecovery later\n");
|
||||
}else{
|
||||
reterror(-3, "unsupported devicemode, please put device in recovery mode or normal mode\n");
|
||||
}
|
||||
|
@ -137,6 +151,7 @@ void futurerestore::putDeviceIntoRecovery(){
|
|||
|
||||
//these get also freed by destructor
|
||||
normal_client_free(_client);
|
||||
dfu_client_free(_client);
|
||||
recovery_client_free(_client);
|
||||
}
|
||||
|
||||
|
@ -368,6 +383,114 @@ char *futurerestore::getiBootBuild(){
|
|||
}
|
||||
|
||||
|
||||
pair<ptr_smart<char*>, size_t> getIPSWComponent(struct idevicerestore_client_t* client, plist_t build_identity, string component){
|
||||
ptr_smart<char *> path;
|
||||
unsigned char* component_data = NULL;
|
||||
unsigned int component_size = 0;
|
||||
|
||||
if (!(char*)path) {
|
||||
if (build_identity_get_component_path(build_identity, component.c_str(), &path) < 0) {
|
||||
reterror(-95,"ERROR: Unable to get path for component '%s'\n", component.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (extract_component(client->ipsw, (char*)path, &component_data, &component_size) < 0) {
|
||||
reterror(-95,"ERROR: Unable to extract component: %s\n", component.c_str());
|
||||
}
|
||||
|
||||
return {(char*)component_data,component_size};
|
||||
}
|
||||
|
||||
|
||||
void futurerestore::enterPwnRecovery(plist_t build_identity){
|
||||
#ifndef HAVE_LIBIPATCHER
|
||||
reterror(-404, "compiled without libipatcher");
|
||||
#else
|
||||
int mode = 0;
|
||||
|
||||
if (dfu_client_new(_client) < 0)
|
||||
reterror(-91,"Unable to connect to DFU device\n");
|
||||
|
||||
irecv_get_mode(_client->dfu->client, &mode);
|
||||
|
||||
if (mode != IRECV_K_DFU_MODE) {
|
||||
info("NOTE: device is not in DFU mode, assuming recovery mode.\n");
|
||||
_client->mode = &idevicerestore_modes[MODE_RECOVERY];
|
||||
reterror(-91, "Device is in wrong mode\n");
|
||||
}
|
||||
|
||||
auto iBSS = getIPSWComponent(_client, build_identity, "iBSS");
|
||||
iBSS = move(libipatcher::patchiBSS((char*)iBSS.first, iBSS.second, libipatcher::getFirmwareKey(_client->device->product_type, _client->build, "iBSS")));
|
||||
|
||||
|
||||
auto iBEC = getIPSWComponent(_client, build_identity, "iBEC");
|
||||
iBEC = move(libipatcher::patchiBEC((char*)iBEC.first, iBEC.second, libipatcher::getFirmwareKey(_client->device->product_type, _client->build, "iBEC")));
|
||||
|
||||
|
||||
info("Sending %s (%lu bytes)...\n", "iBSS", iBSS.second);
|
||||
// FIXME: Did I do this right????
|
||||
irecv_error_t err = irecv_send_buffer(_client->dfu->client, (unsigned char*)(char*)iBSS.first, (unsigned long)iBSS.second, 1);
|
||||
if (err != IRECV_E_SUCCESS) {
|
||||
reterror(-92,"ERROR: Unable to send %s component: %s\n", "iBSS", irecv_strerror(err));
|
||||
}
|
||||
|
||||
if (_client->build_major > 8) {
|
||||
/* reconnect */
|
||||
dfu_client_free(_client);
|
||||
sleep(3);
|
||||
dfu_client_new(_client);
|
||||
|
||||
if (irecv_usb_set_configuration(_client->dfu->client, 1) < 0) {
|
||||
reterror(-92,"ERROR: set configuration failed\n");
|
||||
}
|
||||
|
||||
/* send iBEC */
|
||||
info("Sending %s (%lu bytes)...\n", "iBEC", iBEC.second);
|
||||
// FIXME: Did I do this right????
|
||||
irecv_error_t err = irecv_send_buffer(_client->dfu->client, (unsigned char*)(char*)iBEC.first, (unsigned long)iBEC.second, 1);
|
||||
if (err != IRECV_E_SUCCESS) {
|
||||
reterror(-92,"ERROR: Unable to send %s component: %s\n", "iBSS", irecv_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
dfu_client_free(_client);
|
||||
|
||||
sleep(7);
|
||||
|
||||
// Reconnect to device, but this time make sure we're not still in DFU mode
|
||||
if (recovery_client_new(_client) < 0) {
|
||||
if (_client->recovery->client) {
|
||||
irecv_close(_client->recovery->client);
|
||||
_client->recovery->client = NULL;
|
||||
}
|
||||
reterror(-93,"ERROR: Unable to connect to recovery device\n");
|
||||
}
|
||||
|
||||
irecv_get_mode(_client->recovery->client, &mode);
|
||||
|
||||
if (mode == IRECV_K_DFU_MODE) {
|
||||
if (_client->recovery->client) {
|
||||
irecv_close(_client->recovery->client);
|
||||
_client->recovery->client = NULL;
|
||||
}
|
||||
reterror(-94,"ERROR: Unable to connect to recovery device\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void get_custom_component(struct idevicerestore_client_t* client, plist_t build_identity, const char* component, unsigned char** data, unsigned int *size){
|
||||
#ifndef HAVE_LIBIPATCHER
|
||||
reterror(-404, "compiled without libipatcher");
|
||||
#else
|
||||
auto comp = getIPSWComponent(client, build_identity, component);
|
||||
comp = move(libipatcher::decryptFile3((char*)comp.first, comp.second, libipatcher::getFirmwareKey(client->device->product_type, client->build, component)));
|
||||
*data = (unsigned char*)(char*)comp.first;
|
||||
*size = comp.second;
|
||||
comp.first = NULL; //don't free on destruction
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int futurerestore::doRestore(const char *ipsw){
|
||||
int err = 0;
|
||||
//some memory might not get freed if this function throws an exception, but you probably don't want to catch that anyway.
|
||||
|
@ -386,7 +509,9 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
|
||||
getDeviceMode(true);
|
||||
info("Found device in %s mode\n", client->mode->string);
|
||||
if (client->mode->index != MODE_RECOVERY) reterror(-6, "device not in recovery mode\n");
|
||||
|
||||
if (client->mode->index != MODE_RECOVERY && client->mode->index != MODE_DFU && !_enterPwnRecoveryRequested)
|
||||
reterror(-6, "device not in recovery mode\n");
|
||||
// discover the device type
|
||||
if (check_hardware_model(client) == NULL || client->device == NULL) {
|
||||
reterror(-2,"ERROR: Unable to discover device model\n");
|
||||
|
@ -415,8 +540,9 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
client->image4supported = is_image4_supported(client);
|
||||
info("Device supports Image4: %s\n", (client->image4supported) ? "true" : "false");
|
||||
|
||||
|
||||
if (!(client->tss = nonceMatchesApTickets()))
|
||||
if (_enterPwnRecoveryRequested) //we are in pwnDFU, so we don't need to check nonces
|
||||
client->tss = _aptickets.at(0);
|
||||
else if (!(client->tss = nonceMatchesApTickets()))
|
||||
reterror(-20, "Devicenonce does not match APTicket nonce\n");
|
||||
|
||||
plist_dict_remove_item(client->tss, "BBTicket");
|
||||
|
@ -432,7 +558,8 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
plist_t manifest = plist_dict_get_item(build_identity, "Manifest");
|
||||
|
||||
printf("checking APTicket to be valid for this restore...\n");
|
||||
const char * im4m = nonceMatchesIM4Ms();
|
||||
//if we are in pwnDFU, just use first apticket. no need to check nonces
|
||||
const char * im4m = (!_enterPwnRecoveryRequested) ? nonceMatchesIM4Ms() : _im4ms.at(0);
|
||||
|
||||
uint64_t deviceEcid = getDeviceEcid();
|
||||
uint64_t im4mEcid = (_client->image4supported) ? getEcidFromIM4M(im4m) : getEcidFromSCAB(im4m);
|
||||
|
@ -484,6 +611,9 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
}
|
||||
printf("Verified APTicket to be valid for this restore\n");
|
||||
}
|
||||
}else if (_enterPwnRecoveryRequested){
|
||||
info("[WARNING] skipping ramdisk hash check, since device is in pwnDFU according to user\n");
|
||||
|
||||
}else{
|
||||
info("[WARNING] full buildidentity check is not implemented, only comparing ramdisk hash.\n");
|
||||
size_t tickethashSize = 0;
|
||||
|
@ -553,6 +683,14 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
/* print information about current build identity */
|
||||
build_identity_print_information(build_identity);
|
||||
|
||||
//check for enterpwnrecovery, because we could be in DFU mode
|
||||
if (_enterPwnRecoveryRequested){
|
||||
if (getDeviceMode(true) != MODE_DFU)
|
||||
reterror(-6, "unexpected device mode");
|
||||
enterPwnRecovery(build_identity);
|
||||
}
|
||||
|
||||
|
||||
// Get filesystem name from build identity
|
||||
char* fsname = NULL;
|
||||
if (build_identity_get_component_path(build_identity, "OS", &fsname) < 0) {
|
||||
|
@ -651,16 +789,19 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
}
|
||||
}
|
||||
|
||||
if (!_enterPwnRecoveryRequested) {
|
||||
/* now we load the iBEC */
|
||||
if (recovery_send_ibec(client, build_identity) < 0) {
|
||||
reterror(-8,"ERROR: Unable to send iBEC\n");
|
||||
}
|
||||
printf("waiting for device to reconnect... ");
|
||||
recovery_client_free(client);
|
||||
|
||||
/* this must be long enough to allow the device to run the iBEC */
|
||||
/* FIXME: Probably better to detect if the device is back then */
|
||||
sleep(7);
|
||||
}else //if pwnrecovery send all components decrypted
|
||||
client->recovery_custom_component_function = get_custom_component;
|
||||
|
||||
for (int i=0;getDeviceMode(true) != MODE_RECOVERY && i<40; i++) putchar('.'),usleep(USEC_PER_SEC*0.5);
|
||||
putchar('\n');
|
||||
|
||||
|
@ -670,7 +811,9 @@ int futurerestore::doRestore(const char *ipsw){
|
|||
//do magic
|
||||
if (_client->image4supported) get_sep_nonce(client, &client->sepnonce, &client->sepnonce_size);
|
||||
get_ap_nonce(client, &client->nonce, &client->nonce_size);
|
||||
|
||||
get_ecid(client, &client->ecid);
|
||||
|
||||
if (client->mode->index == MODE_RECOVERY) {
|
||||
if (client->srnm == NULL) {
|
||||
reterror(-9,"ERROR: could not retrieve device serial number. Can't continue.\n");
|
||||
|
@ -719,7 +862,6 @@ error:
|
|||
return result ? abs(result) : err;
|
||||
}
|
||||
|
||||
|
||||
futurerestore::~futurerestore(){
|
||||
normal_client_free(_client);
|
||||
recovery_client_free(_client);
|
||||
|
@ -752,7 +894,7 @@ const char *futurerestore::getDeviceModelNoCopy(){
|
|||
if (!_client->device || !_client->device->product_type){
|
||||
|
||||
int mode = getDeviceMode(true);
|
||||
if (mode != MODE_NORMAL && mode != MODE_RECOVERY)
|
||||
if (mode == MODE_NORMAL && mode != MODE_RECOVERY && mode != MODE_DFU)
|
||||
reterror(-20, "unexpected device mode=%d\n",mode);
|
||||
|
||||
if (check_hardware_model(_client) == NULL || _client->device == NULL)
|
||||
|
|
|
@ -27,9 +27,12 @@ public:
|
|||
ptr_smart(T p, function<void(T)> ptr_free){static_assert(is_pointer<T>(), "error: this is for pointers only\n"); _p = p;_ptr_free = ptr_free;}
|
||||
ptr_smart(T p){_p = p;}
|
||||
ptr_smart(){_p = NULL;}
|
||||
T operator=(T p){return _p = p;}
|
||||
ptr_smart(ptr_smart &&p){ _p = p._p; _ptr_free = p._ptr_free; p._p = NULL; p._ptr_free = NULL;}
|
||||
ptr_smart& operator =(ptr_smart &&p){_p = p._p; _ptr_free = p._ptr_free; p._p = NULL; p._ptr_free = NULL; return *this;}
|
||||
T operator =(T p){ _p = p; return _p;}
|
||||
T operator =(T &p){_p = p; p = NULL; return _p;}
|
||||
T *operator&(){return &_p;}
|
||||
operator const T() const {return _p;}
|
||||
explicit operator const T() const {return _p;}
|
||||
operator const void*() const {return _p;}
|
||||
~ptr_smart(){if (_p) (_ptr_free) ? _ptr_free(_p) : free((void*)_p);}
|
||||
};
|
||||
|
@ -42,6 +45,7 @@ class futurerestore {
|
|||
vector<char *>_im4ms;
|
||||
int _foundnonce = -1;
|
||||
bool _isUpdateInstall = false;
|
||||
bool _isPwnDfu = false;
|
||||
|
||||
char *_firmwareJson = NULL;
|
||||
jsmntok_t *_firmwareTokens = NULL;;
|
||||
|
@ -55,10 +59,13 @@ class futurerestore {
|
|||
const char *_sepbuildmanifestPath = NULL;
|
||||
const char *_basebandbuildmanifestPath = NULL;
|
||||
|
||||
bool _enterPwnRecoveryRequested = false;
|
||||
//methods
|
||||
void enterPwnRecovery(plist_t build_identity);
|
||||
|
||||
|
||||
public:
|
||||
futurerestore();
|
||||
futurerestore(bool isUpdateInstall);
|
||||
futurerestore(bool isUpdateInstall = false, bool isPwnDfu = false);
|
||||
bool init();
|
||||
int getDeviceMode(bool reRequest);
|
||||
uint64_t getDeviceEcid();
|
||||
|
|
|
@ -32,6 +32,9 @@ static struct option longopts[] = {
|
|||
{ "latest-sep", no_argument, NULL, '0' },
|
||||
{ "latest-baseband", no_argument, NULL, '1' },
|
||||
{ "no-baseband", no_argument, NULL, '2' },
|
||||
#ifdef HAVE_LIBIPATCHER
|
||||
{ "use-pwndfu", no_argument, NULL, '3' },
|
||||
#endif
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
|
@ -40,10 +43,18 @@ static struct option longopts[] = {
|
|||
#define FLAG_LATEST_SEP 1 << 2
|
||||
#define FLAG_LATEST_BASEBAND 1 << 3
|
||||
#define FLAG_NO_BASEBAND 1 << 4
|
||||
#define FLAG_IS_PWN_DFU 1 << 5
|
||||
|
||||
void cmd_help(){
|
||||
printf("Usage: futurerestore [OPTIONS] IPSW\n");
|
||||
printf("Allows restoring nonmatching iOS/Sep/Baseband\n\n");
|
||||
printf("Allows restoring nonmatching iOS/Sep/Baseband\n");
|
||||
printf("Odysseus Support: %s\n\n",
|
||||
#ifdef HAVE_LIBIPATCHER
|
||||
"yes"
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
);
|
||||
|
||||
printf(" -t, --apticket PATH\t\tApticket used for restoring\n");
|
||||
printf(" -b, --baseband PATH\t\tBaseband to be flashed\n");
|
||||
|
@ -55,6 +66,9 @@ void cmd_help(){
|
|||
printf(" --latest-sep\t\tuse latest signed sep instead of manually specifying one(may cause bad restore)\n");
|
||||
printf(" --latest-baseband\t\tse latest signed baseband instead of manually specifying one(may cause bad restore)\n");
|
||||
printf(" --no-baseband\t\tskip checks and don't flash baseband.\n");
|
||||
#ifdef HAVE_LIBIPATCHER
|
||||
printf(" --use-pwndfu\t\tuse this for restoring devices with odysseus method. Device needs to be in kDFU mode already\n");
|
||||
#endif
|
||||
printf(" \t\tWARNING: only use this for device without baseband (eg iPod or some wifi only iPads)\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
@ -89,6 +103,7 @@ int main(int argc, const char * argv[]) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
while ((opt = getopt_long(argc, (char* const *)argv, "ht:b:p:s:m:wud01", longopts, &optindex)) > 0) {
|
||||
switch (opt) {
|
||||
case 't': // long option: "apticket"; can be called as short option
|
||||
|
@ -121,6 +136,9 @@ int main(int argc, const char * argv[]) {
|
|||
case '2': // long option: "no-baseband";
|
||||
flags |= FLAG_NO_BASEBAND;
|
||||
break;
|
||||
case '3': // long option: "no-baseband";
|
||||
flags |= FLAG_IS_PWN_DFU;
|
||||
break;
|
||||
case 'd': // long option: "debug"; can be called as short option
|
||||
idevicerestore_debug = 1;
|
||||
break;
|
||||
|
@ -146,7 +164,7 @@ int main(int argc, const char * argv[]) {
|
|||
return -5;
|
||||
}
|
||||
|
||||
futurerestore client(flags & FLAG_UPDATE);
|
||||
futurerestore client(flags & FLAG_UPDATE, flags & FLAG_IS_PWN_DFU);
|
||||
if (!client.init()) reterror(-3,"can't init, no device found\n");
|
||||
|
||||
printf("futurerestore init done\n");
|
||||
|
|
Loading…
Reference in a new issue