|
|
@ -14,7 +14,8 @@ config = loaded.config |
|
|
keycodes = loaded.keycodes |
|
|
keycodes = loaded.keycodes |
|
|
exe = config.defaults.exe |
|
|
exe = config.defaults.exe |
|
|
|
|
|
|
|
|
def merge(src, dst,log = False): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def merge(src, dst, log=False): |
|
|
if not os.path.exists(dst): |
|
|
if not os.path.exists(dst): |
|
|
return False |
|
|
return False |
|
|
ok = True |
|
|
ok = True |
|
|
@ -27,7 +28,8 @@ def merge(src, dst,log = False): |
|
|
destFile = os.path.join(destPath, file) |
|
|
destFile = os.path.join(destPath, file) |
|
|
if os.path.isfile(destFile): |
|
|
if os.path.isfile(destFile): |
|
|
if log: |
|
|
if log: |
|
|
print("Skipping existing file: " + os.path.join(relPath, file)) |
|
|
|
|
|
|
|
|
print("Skipping existing file: " + |
|
|
|
|
|
os.path.join(relPath, file)) |
|
|
ok = False |
|
|
ok = False |
|
|
continue |
|
|
continue |
|
|
srcFile = os.path.join(path, file) |
|
|
srcFile = os.path.join(path, file) |
|
|
@ -37,65 +39,71 @@ def merge(src, dst,log = False): |
|
|
os.rmdir(path) |
|
|
os.rmdir(path) |
|
|
return ok |
|
|
return ok |
|
|
|
|
|
|
|
|
def _adb(*args,output = "shell"): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _adb(*args, output="shell"): |
|
|
'''Output modes: |
|
|
'''Output modes: |
|
|
"out": return output |
|
|
"out": return output |
|
|
"shell": print to shell |
|
|
"shell": print to shell |
|
|
"buffered": read line by line''' |
|
|
"buffered": read line by line''' |
|
|
args = [exe] + list(args) |
|
|
args = [exe] + list(args) |
|
|
if output == "out": |
|
|
if output == "out": |
|
|
return subprocess.check_output(args,shell = False).decode().replace('\r\n','\n').rstrip() |
|
|
|
|
|
|
|
|
return subprocess.check_output(args, shell=False).decode().replace('\r\n', '\n').rstrip() |
|
|
elif output == "shell": |
|
|
elif output == "shell": |
|
|
ret = subprocess.call(args,shell = False) |
|
|
|
|
|
|
|
|
ret = subprocess.call(args, shell=False) |
|
|
if ret: |
|
|
if ret: |
|
|
raise subprocess.CalledProcessError(ret,args) |
|
|
|
|
|
|
|
|
raise subprocess.CalledProcessError(ret, args) |
|
|
elif output == "buffered": |
|
|
elif output == "buffered": |
|
|
p = subprocess.Popen(args,stdout = subprocess.PIPE) |
|
|
|
|
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE) |
|
|
return p.stdout |
|
|
return p.stdout |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def kill_server(): |
|
|
def kill_server(): |
|
|
_adb('kill-server') |
|
|
_adb('kill-server') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_server(): |
|
|
def start_server(): |
|
|
_adb('start-server') |
|
|
_adb('start-server') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_info(): |
|
|
def get_info(): |
|
|
start_server() |
|
|
start_server() |
|
|
thing = _adb("devices","-l",output="out") |
|
|
|
|
|
formed = list(filter(bool,thing.split("\n")))[1:] |
|
|
|
|
|
|
|
|
thing = _adb("devices", "-l", output="out") |
|
|
|
|
|
formed = list(filter(bool, thing.split("\n")))[1:] |
|
|
main = {} |
|
|
main = {} |
|
|
for device in formed: |
|
|
for device in formed: |
|
|
categories = re.split(" +",device) |
|
|
|
|
|
|
|
|
categories = re.split(" +", device) |
|
|
device_dict = { |
|
|
device_dict = { |
|
|
"serial":categories[0], |
|
|
|
|
|
"mode":categories[1] |
|
|
|
|
|
} |
|
|
|
|
|
device_dict.update(dict(category.split(":") for category in categories[2:])) |
|
|
|
|
|
|
|
|
"serial": categories[0], |
|
|
|
|
|
"mode": categories[1] |
|
|
|
|
|
} |
|
|
|
|
|
device_dict.update(dict(category.split(":") |
|
|
|
|
|
for category in categories[2:])) |
|
|
main[categories[0]] = device_dict |
|
|
main[categories[0]] = device_dict |
|
|
return main |
|
|
return main |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ADBWrapper: |
|
|
class ADBWrapper: |
|
|
root_mode = False |
|
|
root_mode = False |
|
|
|
|
|
|
|
|
def connect(ip,port=5555): |
|
|
|
|
|
if not re.match(r'(\d{1,3}\.){3}\d{1,3}',ip): |
|
|
|
|
|
|
|
|
def connect(ip, port=5555): |
|
|
|
|
|
if not re.match(r'(\d{1,3}\.){3}\d{1,3}', ip): |
|
|
raise TypeError("Invalid ip") |
|
|
raise TypeError("Invalid ip") |
|
|
if not all(int(n) <= 255 and int(n) >= 0 for n in ip.split('.')): |
|
|
if not all(int(n) <= 255 and int(n) >= 0 for n in ip.split('.')): |
|
|
raise TypeError("Invalid ip") |
|
|
raise TypeError("Invalid ip") |
|
|
if not (port >= 0 and port <= 2**16-1): |
|
|
if not (port >= 0 and port <= 2**16-1): |
|
|
raise TyperError("Port must be in the range 0-65536") |
|
|
raise TyperError("Port must be in the range 0-65536") |
|
|
id = '{}:{}'.format(ip,port) |
|
|
|
|
|
_adb('connect','{}:{}'.format(ip,port)) |
|
|
|
|
|
|
|
|
id = '{}:{}'.format(ip, port) |
|
|
|
|
|
_adb('connect', '{}:{}'.format(ip, port)) |
|
|
dev = Device(id) |
|
|
dev = Device(id) |
|
|
dev.tcip = True |
|
|
dev.tcip = True |
|
|
return dev |
|
|
return dev |
|
|
|
|
|
|
|
|
def disconnect(self): |
|
|
def disconnect(self): |
|
|
if self.tcip: |
|
|
if self.tcip: |
|
|
_adb('disconnect',self.serial) |
|
|
|
|
|
|
|
|
_adb('disconnect', self.serial) |
|
|
|
|
|
|
|
|
def db_connect(self,filepath): |
|
|
|
|
|
return AndroidSQLConn(self,filepath) |
|
|
|
|
|
|
|
|
def db_connect(self, filepath): |
|
|
|
|
|
return AndroidSQLConn(self, filepath) |
|
|
|
|
|
|
|
|
def prim_device(): |
|
|
def prim_device(): |
|
|
cont = True |
|
|
cont = True |
|
|
@ -107,28 +115,28 @@ class ADBWrapper: |
|
|
time.sleep(1) |
|
|
time.sleep(1) |
|
|
return d |
|
|
return d |
|
|
|
|
|
|
|
|
def __init__(self,serial=None): |
|
|
|
|
|
|
|
|
def __init__(self, serial=None): |
|
|
self.tcip = False |
|
|
self.tcip = False |
|
|
if serial: |
|
|
if serial: |
|
|
self.serial = serial |
|
|
self.serial = serial |
|
|
info = get_info()[serial] |
|
|
info = get_info()[serial] |
|
|
else: |
|
|
else: |
|
|
serial,info = list(get_info().items())[0] |
|
|
|
|
|
|
|
|
serial, info = list(get_info().items())[0] |
|
|
self.__dict__.update(info) |
|
|
self.__dict__.update(info) |
|
|
|
|
|
|
|
|
def adb(self,*args,output="shell"): |
|
|
|
|
|
args = ['-s',self.serial]+ list(args) |
|
|
|
|
|
return _adb(*args,output = output) |
|
|
|
|
|
|
|
|
def adb(self, *args, output="shell"): |
|
|
|
|
|
args = ['-s', self.serial] + list(args) |
|
|
|
|
|
return _adb(*args, output=output) |
|
|
|
|
|
|
|
|
def shell(self,*args,output="shell"): |
|
|
|
|
|
|
|
|
def shell(self, *args, output="shell"): |
|
|
args = ('shell',)+args |
|
|
args = ('shell',)+args |
|
|
return self.adb(*args,output=output) |
|
|
|
|
|
|
|
|
return self.adb(*args, output=output) |
|
|
|
|
|
|
|
|
def sudo(self,*args,output="shell"): |
|
|
|
|
|
|
|
|
def sudo(self, *args, output="shell"): |
|
|
if self.mode == 'recovery' or self.root_mode: |
|
|
if self.mode == 'recovery' or self.root_mode: |
|
|
return self.shell(*args,output=output) |
|
|
|
|
|
|
|
|
return self.shell(*args, output=output) |
|
|
else: |
|
|
else: |
|
|
return self.shell('su','--','--',*args,output=output) |
|
|
|
|
|
|
|
|
return self.shell('su', '--', '--', *args, output=output) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def root(cls): |
|
|
def root(cls): |
|
|
@ -140,16 +148,16 @@ class ADBWrapper: |
|
|
cls.root_mode = False |
|
|
cls.root_mode = False |
|
|
_adb('unroot') |
|
|
_adb('unroot') |
|
|
|
|
|
|
|
|
def reboot(self,mode = None): |
|
|
|
|
|
|
|
|
def reboot(self, mode=None): |
|
|
if mode: |
|
|
if mode: |
|
|
if mode == "soft": |
|
|
if mode == "soft": |
|
|
if self.mode != 'recovery': |
|
|
if self.mode != 'recovery': |
|
|
pid = self.shell("pidof","zygote",output="out") |
|
|
|
|
|
return self.sudo("kill",pid,output="shell") |
|
|
|
|
|
|
|
|
pid = self.shell("pidof", "zygote", output="out") |
|
|
|
|
|
return self.sudo("kill", pid, output="shell") |
|
|
else: |
|
|
else: |
|
|
return self.reboot() |
|
|
return self.reboot() |
|
|
else: |
|
|
else: |
|
|
self.adb("reboot",mode) |
|
|
|
|
|
|
|
|
self.adb("reboot", mode) |
|
|
else: |
|
|
else: |
|
|
self.adb("reboot") |
|
|
self.adb("reboot") |
|
|
while True: |
|
|
while True: |
|
|
@ -159,9 +167,10 @@ class ADBWrapper: |
|
|
break |
|
|
break |
|
|
time.sleep(1) |
|
|
time.sleep(1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FSActionWrapper(ADBWrapper): |
|
|
class FSActionWrapper(ADBWrapper): |
|
|
|
|
|
|
|
|
def stat(self,file): |
|
|
|
|
|
|
|
|
def stat(self, file): |
|
|
'''\ |
|
|
'''\ |
|
|
%a Access bits (octal) |%A Access bits (flags)|%b Size/512 |
|
|
%a Access bits (octal) |%A Access bits (flags)|%b Size/512 |
|
|
%B Bytes per %b (512) |%d Device ID (dec) |%D Device ID (hex) |
|
|
%B Bytes per %b (512) |%d Device ID (dec) |%D Device ID (hex) |
|
|
@ -179,27 +188,27 @@ The valid format escape sequences for filesystems: |
|
|
%l Max filename length |%n File name |%s Fragment size |
|
|
%l Max filename length |%n File name |%s Fragment size |
|
|
%S Best transfer size |%t FS type (hex) |%T FS type (driver name)''' |
|
|
%S Best transfer size |%t FS type (hex) |%T FS type (driver name)''' |
|
|
command = 'stat -c "%A;%F;%U;%G" {};echo $?'.format(file) |
|
|
command = 'stat -c "%A;%F;%U;%G" {};echo $?'.format(file) |
|
|
res = self.sudo(command,output = "out") |
|
|
|
|
|
output,res = res.split('\n') |
|
|
|
|
|
|
|
|
res = self.sudo(command, output="out") |
|
|
|
|
|
output, res = res.split('\n') |
|
|
if res == '0': |
|
|
if res == '0': |
|
|
return output.split(';') |
|
|
return output.split(';') |
|
|
|
|
|
|
|
|
def exists(self,file): |
|
|
|
|
|
|
|
|
def exists(self, file): |
|
|
return self.stat(file) is not None |
|
|
return self.stat(file) is not None |
|
|
|
|
|
|
|
|
def isfile(self,file): |
|
|
|
|
|
|
|
|
def isfile(self, file): |
|
|
return self.stat(file)[1] == 'file' |
|
|
return self.stat(file)[1] == 'file' |
|
|
|
|
|
|
|
|
def isdir(self,file): |
|
|
|
|
|
|
|
|
def isdir(self, file): |
|
|
return self.stat(file)[1] == 'directory' |
|
|
return self.stat(file)[1] == 'directory' |
|
|
|
|
|
|
|
|
def islink(self,file): |
|
|
|
|
|
|
|
|
def islink(self, file): |
|
|
return self.stat(file)[1] == 'symbolic link' |
|
|
return self.stat(file)[1] == 'symbolic link' |
|
|
|
|
|
|
|
|
def delete(self,path): |
|
|
|
|
|
return self.sudo("rm","-rf",path,output="out") |
|
|
|
|
|
|
|
|
def delete(self, path): |
|
|
|
|
|
return self.sudo("rm", "-rf", path, output="out") |
|
|
|
|
|
|
|
|
def copy(self,remote,local,del_duplicates = True,ignore_error=True): |
|
|
|
|
|
|
|
|
def copy(self, remote, local, del_duplicates=True, ignore_error=True): |
|
|
remote_stat = self.stat(remote) |
|
|
remote_stat = self.stat(remote) |
|
|
if remote_stat is not None: |
|
|
if remote_stat is not None: |
|
|
if remote_stat[1] == "directory" and not remote.endswith('/'): |
|
|
if remote_stat[1] == "directory" and not remote.endswith('/'): |
|
|
@ -208,54 +217,57 @@ The valid format escape sequences for filesystems: |
|
|
if os.path.exists(local): |
|
|
if os.path.exists(local): |
|
|
last = os.path.split(local)[-1] |
|
|
last = os.path.split(local)[-1] |
|
|
real_dir = local |
|
|
real_dir = local |
|
|
local = os.path.join(config['local']['temp'],last) |
|
|
|
|
|
|
|
|
local = os.path.join(config['local']['temp'], last) |
|
|
merge_flag = True |
|
|
merge_flag = True |
|
|
try: |
|
|
try: |
|
|
self.adb("pull","-a",remote,local) |
|
|
|
|
|
|
|
|
self.adb("pull", "-a", remote, local) |
|
|
except subprocess.CalledProcessError as e: |
|
|
except subprocess.CalledProcessError as e: |
|
|
if ignore_error: |
|
|
if ignore_error: |
|
|
pass |
|
|
pass |
|
|
else: |
|
|
else: |
|
|
raise e |
|
|
raise e |
|
|
if merge_flag: |
|
|
if merge_flag: |
|
|
merge(local,real_dir) |
|
|
|
|
|
|
|
|
merge(local, real_dir) |
|
|
if os.path.exists(local) and del_duplicates: |
|
|
if os.path.exists(local) and del_duplicates: |
|
|
shutil.rmtree(local) |
|
|
shutil.rmtree(local) |
|
|
else: |
|
|
else: |
|
|
print("File not found: {}".format(remote)) |
|
|
print("File not found: {}".format(remote)) |
|
|
|
|
|
|
|
|
def move(self,remote,local,del_duplicates = True,ignore_error=False): |
|
|
|
|
|
|
|
|
def move(self, remote, local, del_duplicates=True, ignore_error=False): |
|
|
if self.exists(remote): |
|
|
if self.exists(remote): |
|
|
self.copy(remote,local,del_duplicates = del_duplicates,ignore_error=ignore_error) |
|
|
|
|
|
|
|
|
self.copy(remote, local, del_duplicates=del_duplicates, |
|
|
|
|
|
ignore_error=ignore_error) |
|
|
self.delete(remote) |
|
|
self.delete(remote) |
|
|
else: |
|
|
else: |
|
|
print("File not found: {}".format(remote)) |
|
|
print("File not found: {}".format(remote)) |
|
|
|
|
|
|
|
|
def push(self,local,remote): |
|
|
|
|
|
self.adb('push',local,remote) |
|
|
|
|
|
|
|
|
def push(self, local, remote): |
|
|
|
|
|
self.adb('push', local, remote) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Input(ADBWrapper): |
|
|
class Input(ADBWrapper): |
|
|
|
|
|
|
|
|
def send_keycode(self,code): |
|
|
|
|
|
|
|
|
def send_keycode(self, code): |
|
|
try: |
|
|
try: |
|
|
keycode = keycodes[code] |
|
|
keycode = keycodes[code] |
|
|
except KeyError: |
|
|
except KeyError: |
|
|
keycode = str(code) |
|
|
keycode = str(code) |
|
|
self.shell("input","keyevent",keycode) |
|
|
|
|
|
|
|
|
self.shell("input", "keyevent", keycode) |
|
|
|
|
|
|
|
|
def unlock_phone(self,password): |
|
|
|
|
|
|
|
|
def unlock_phone(self, password): |
|
|
if self.mode == 'recovery': |
|
|
if self.mode == 'recovery': |
|
|
return |
|
|
return |
|
|
if not decode_parcel(self.shell('service','call', 'power','12',output="out"),'int'): |
|
|
|
|
|
|
|
|
if not decode_parcel(self.shell('service', 'call', 'power', '12', output="out"), 'int'): |
|
|
self.send_keycode('power') |
|
|
self.send_keycode('power') |
|
|
if decode_parcel(self.shell('service','call','trust','7',output="out"),'int'): |
|
|
|
|
|
|
|
|
if decode_parcel(self.shell('service', 'call', 'trust', '7', output="out"), 'int'): |
|
|
self.send_keycode('space') |
|
|
self.send_keycode('space') |
|
|
self.shell("input","text",str(password)) |
|
|
|
|
|
|
|
|
self.shell("input", "text", str(password)) |
|
|
self.send_keycode('enter') |
|
|
self.send_keycode('enter') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TWRP(FSActionWrapper): |
|
|
class TWRP(FSActionWrapper): |
|
|
|
|
|
|
|
|
def backup(self,*partitions,name = None,backupdir = None): |
|
|
|
|
|
|
|
|
def backup(self, *partitions, name=None, backupdir=None): |
|
|
if self.mode != 'recovery': |
|
|
if self.mode != 'recovery': |
|
|
self.reboot('recovery') |
|
|
self.reboot('recovery') |
|
|
if backupdir is None: |
|
|
if backupdir is None: |
|
|
@ -275,33 +287,38 @@ class TWRP(FSActionWrapper): |
|
|
} |
|
|
} |
|
|
options = "".join(options_dict[option] for option in partitions) |
|
|
options = "".join(options_dict[option] for option in partitions) |
|
|
if not name: |
|
|
if not name: |
|
|
name = "backup_"+datetime.datetime.today().strftime(defaults['date_format']) |
|
|
|
|
|
filename = os.path.join(backupdir,name) |
|
|
|
|
|
self.shell("twrp","backup",options,name) |
|
|
|
|
|
phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format(serial = self.serial,name = name) |
|
|
|
|
|
self.move(phone_dir,filename) |
|
|
|
|
|
|
|
|
|
|
|
def wipe(self,partition): |
|
|
|
|
|
|
|
|
name = "backup_" + \ |
|
|
|
|
|
datetime.datetime.today().strftime(defaults['date_format']) |
|
|
|
|
|
filename = os.path.join(backupdir, name) |
|
|
|
|
|
self.shell("twrp", "backup", options, name) |
|
|
|
|
|
phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format( |
|
|
|
|
|
serial=self.serial, name=name) |
|
|
|
|
|
self.move(phone_dir, filename) |
|
|
|
|
|
|
|
|
|
|
|
def wipe(self, partition): |
|
|
if self.mode != 'recovery': |
|
|
if self.mode != 'recovery': |
|
|
self.reboot('recovery') |
|
|
self.reboot('recovery') |
|
|
self.shell("twrp","wipe",partition) |
|
|
|
|
|
|
|
|
self.shell("twrp", "wipe", partition) |
|
|
|
|
|
|
|
|
def install(self,name): |
|
|
|
|
|
|
|
|
def install(self, name): |
|
|
if self.mode != 'recovery': |
|
|
if self.mode != 'recovery': |
|
|
self.reboot('recovery') |
|
|
self.reboot('recovery') |
|
|
if os.path.exists(name): |
|
|
if os.path.exists(name): |
|
|
local_name = name |
|
|
local_name = name |
|
|
name = os.path.split(name)[-1] |
|
|
name = os.path.split(name)[-1] |
|
|
update_path = '{}/{}'.format(config['remote']['updates'],name) |
|
|
|
|
|
|
|
|
update_path = '{}/{}'.format(config['remote']['updates'], name) |
|
|
if not self.exists(config['remote']['updates']): |
|
|
if not self.exists(config['remote']['updates']): |
|
|
self.sudo('mkdir',config['remote']['updates']) |
|
|
|
|
|
|
|
|
self.sudo('mkdir', config['remote']['updates']) |
|
|
if not self.exists(update_path): |
|
|
if not self.exists(update_path): |
|
|
self.push(local_name,config['remote']['updates']) |
|
|
|
|
|
|
|
|
self.push(local_name, config['remote']['updates']) |
|
|
else: |
|
|
else: |
|
|
update_path = '{}/{}'.format(config['remote']['updates'],name) |
|
|
|
|
|
self.shell("twrp","install",update_path) |
|
|
|
|
|
|
|
|
update_path = '{}/{}'.format(config['remote']['updates'], name) |
|
|
|
|
|
self.shell("twrp", "install", update_path) |
|
|
|
|
|
|
|
|
class Device(TWRP,Input): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Device(TWRP, Input): |
|
|
pass |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__" and debug: |
|
|
if __name__ == "__main__" and debug: |
|
|
d = Device.prim_device() |
|
|
|
|
|
|
|
|
d = Device.prim_device() |