|
|
@ -9,6 +9,7 @@ import shlex |
|
|
from android_db import AndroidSQLConn |
|
|
from android_db import AndroidSQLConn |
|
|
from load_things import loaded |
|
|
from load_things import loaded |
|
|
from decode_parcel import decode_parcel |
|
|
from decode_parcel import decode_parcel |
|
|
|
|
|
|
|
|
debug = True |
|
|
debug = True |
|
|
config = loaded.config |
|
|
config = loaded.config |
|
|
keycodes = loaded.keycodes |
|
|
keycodes = loaded.keycodes |
|
|
@ -28,8 +29,7 @@ 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) |
|
|
@ -41,13 +41,18 @@ def merge(src, dst, log=False): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
@ -58,11 +63,11 @@ def _adb(*args, output="shell"): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(): |
|
|
@ -72,12 +77,8 @@ def get_info(): |
|
|
main = {} |
|
|
main = {} |
|
|
for device in formed: |
|
|
for device in formed: |
|
|
categories = re.split(" +", device) |
|
|
categories = re.split(" +", device) |
|
|
device_dict = { |
|
|
|
|
|
"serial": categories[0], |
|
|
|
|
|
"mode": categories[1] |
|
|
|
|
|
} |
|
|
|
|
|
device_dict.update(dict(category.split(":") |
|
|
|
|
|
for category in categories[2:])) |
|
|
|
|
|
|
|
|
device_dict = {"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 |
|
|
|
|
|
|
|
|
@ -86,21 +87,21 @@ class ADBWrapper: |
|
|
root_mode = False |
|
|
root_mode = False |
|
|
|
|
|
|
|
|
def connect(ip, port=5555): |
|
|
def connect(ip, port=5555): |
|
|
if not re.match(r'(\d{1,3}\.){3}\d{1,3}', ip): |
|
|
|
|
|
|
|
|
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): |
|
|
def db_connect(self, filepath): |
|
|
return AndroidSQLConn(self, filepath) |
|
|
return AndroidSQLConn(self, filepath) |
|
|
@ -125,33 +126,33 @@ class ADBWrapper: |
|
|
self.__dict__.update(info) |
|
|
self.__dict__.update(info) |
|
|
|
|
|
|
|
|
def adb(self, *args, output="shell"): |
|
|
def adb(self, *args, output="shell"): |
|
|
args = ['-s', self.serial] + list(args) |
|
|
|
|
|
|
|
|
args = ["-s", self.serial] + list(args) |
|
|
return _adb(*args, output=output) |
|
|
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): |
|
|
cls.root_mode = True |
|
|
cls.root_mode = True |
|
|
_adb('root') |
|
|
|
|
|
|
|
|
_adb("root") |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def unroot(cls): |
|
|
def unroot(cls): |
|
|
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") |
|
|
pid = self.shell("pidof", "zygote", output="out") |
|
|
return self.sudo("kill", pid, output="shell") |
|
|
return self.sudo("kill", pid, output="shell") |
|
|
else: |
|
|
else: |
|
|
@ -169,9 +170,8 @@ class ADBWrapper: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
%f All mode bits (hex) |%F File type |%g Group ID |
|
|
%f All mode bits (hex) |%F File type |%g Group ID |
|
|
@ -186,24 +186,24 @@ The valid format escape sequences for filesystems: |
|
|
%a Available blocks |%b Total blocks |%c Total inodes |
|
|
%a Available blocks |%b Total blocks |%c Total inodes |
|
|
%d Free inodes |%f Free blocks |%i File system ID |
|
|
%d Free inodes |%f Free blocks |%i File system ID |
|
|
%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") |
|
|
res = self.sudo(command, output="out") |
|
|
output, res = res.split('\n') |
|
|
|
|
|
if res == '0': |
|
|
|
|
|
return output.split(';') |
|
|
|
|
|
|
|
|
output, res = res.split("\n") |
|
|
|
|
|
if res == "0": |
|
|
|
|
|
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): |
|
|
def delete(self, path): |
|
|
return self.sudo("rm", "-rf", path, output="out") |
|
|
return self.sudo("rm", "-rf", path, output="out") |
|
|
@ -211,13 +211,13 @@ The valid format escape sequences for filesystems: |
|
|
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('/'): |
|
|
|
|
|
remote += '/' |
|
|
|
|
|
|
|
|
if remote_stat[1] == "directory" and not remote.endswith("/"): |
|
|
|
|
|
remote += "/" |
|
|
merge_flag = False |
|
|
merge_flag = False |
|
|
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) |
|
|
@ -235,18 +235,18 @@ The valid format escape sequences for filesystems: |
|
|
|
|
|
|
|
|
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): |
|
|
def push(self, local, remote): |
|
|
self.adb('push', 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] |
|
|
@ -255,23 +255,26 @@ class Input(ADBWrapper): |
|
|
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'): |
|
|
|
|
|
self.send_keycode('power') |
|
|
|
|
|
if decode_parcel(self.shell('service', 'call', 'trust', '7', output="out"), 'int'): |
|
|
|
|
|
self.send_keycode('space') |
|
|
|
|
|
|
|
|
if not decode_parcel( |
|
|
|
|
|
self.shell("service", "call", "power", "12", output="out"), "int" |
|
|
|
|
|
): |
|
|
|
|
|
self.send_keycode("power") |
|
|
|
|
|
if decode_parcel( |
|
|
|
|
|
self.shell("service", "call", "trust", "7", output="out"), "int" |
|
|
|
|
|
): |
|
|
|
|
|
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': |
|
|
|
|
|
self.reboot('recovery') |
|
|
|
|
|
|
|
|
if self.mode != "recovery": |
|
|
|
|
|
self.reboot("recovery") |
|
|
if backupdir is None: |
|
|
if backupdir is None: |
|
|
backupdir = config['local']['twrp'] |
|
|
|
|
|
|
|
|
backupdir = config["local"]["twrp"] |
|
|
else: |
|
|
else: |
|
|
backupdir = backupdir |
|
|
backupdir = backupdir |
|
|
options_dict = { |
|
|
options_dict = { |
|
|
@ -283,36 +286,38 @@ class TWRP(FSActionWrapper): |
|
|
"spec_part_2": "2", |
|
|
"spec_part_2": "2", |
|
|
"spec_part_3": "3", |
|
|
"spec_part_3": "3", |
|
|
"boot": "B", |
|
|
"boot": "B", |
|
|
"as": "A" |
|
|
|
|
|
|
|
|
"as": "A", |
|
|
} |
|
|
} |
|
|
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']) |
|
|
|
|
|
|
|
|
name = "backup_" + datetime.datetime.today().strftime( |
|
|
|
|
|
defaults["date_format"] |
|
|
|
|
|
) |
|
|
filename = os.path.join(backupdir, name) |
|
|
filename = os.path.join(backupdir, name) |
|
|
self.shell("twrp", "backup", options, name) |
|
|
self.shell("twrp", "backup", options, name) |
|
|
phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format( |
|
|
phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format( |
|
|
serial=self.serial, name=name) |
|
|
|
|
|
|
|
|
serial=self.serial, name=name |
|
|
|
|
|
) |
|
|
self.move(phone_dir, filename) |
|
|
self.move(phone_dir, filename) |
|
|
|
|
|
|
|
|
def wipe(self, partition): |
|
|
def wipe(self, partition): |
|
|
if self.mode != 'recovery': |
|
|
|
|
|
self.reboot('recovery') |
|
|
|
|
|
|
|
|
if self.mode != "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': |
|
|
|
|
|
self.reboot('recovery') |
|
|
|
|
|
|
|
|
if self.mode != "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) |
|
|
|
|
|
if not self.exists(config['remote']['updates']): |
|
|
|
|
|
self.sudo('mkdir', config['remote']['updates']) |
|
|
|
|
|
|
|
|
update_path = "{}/{}".format(config["remote"]["updates"], name) |
|
|
|
|
|
if not self.exists(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) |
|
|
|
|
|
|
|
|
update_path = "{}/{}".format(config["remote"]["updates"], name) |
|
|
self.shell("twrp", "install", update_path) |
|
|
self.shell("twrp", "install", update_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|