Skip to content

Commit

Permalink
Apply tweaks by patching backup file
Browse files Browse the repository at this point in the history
  • Loading branch information
ma1co committed Aug 11, 2022
1 parent cfea596 commit d634e6c
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 122 deletions.
71 changes: 24 additions & 47 deletions pmca-gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ def start(self):

def launchShell(self, backend):
backend.start()
tweaks = TweakInterface(backend)

endFlag = threading.Event()
root = self.ui.master
root.run(lambda: root.after(0, lambda: TweakDialog(root, TweakInterface(backend), endFlag)))
endFlag.wait()
if next(tweaks.getTweaks(), None):
endFlag = threading.Event()
root = self.ui.master
root.run(lambda: root.after(0, lambda: TweakDialog(root, tweaks, endFlag)))
endFlag.wait()
else:
print('No tweaks available')

try:
backend.syncBackup()
except:
print('Cannot sync backup')
backend.stop()

def doAfter(self, result):
Expand All @@ -152,42 +152,21 @@ def start(self):
senserShellCommand(complete=lambda dev: self.launchShell(SenserPlatformBackend(dev)))


class TweakStatusTask(BackgroundTask):
"""Task to run TweakInterface.getTweaks()"""
class TweakApplyTask(BackgroundTask):
"""Task to run TweakInterface.apply()"""
def doBefore(self):
self.ui.setState(DISABLED)

def do(self, arg):
try:
return list(self.ui.tweakInterface.getTweaks())
print('Applying tweaks...')
self.ui.tweakInterface.apply()
except Exception:
traceback.print_exc()

def doAfter(self, result):
self.ui.setState(NORMAL)
self.ui.setTweakStatus(result)


class TweakSetTask(BackgroundTask):
"""Task to run TweakInterface.setEnabled()"""
def __init__(self, ui, id, var):
BackgroundTask.__init__(self, ui)
self.id = id
self.var = var

def doBefore(self):
self.ui.setState(DISABLED)
return self.var.get()

def do(self, arg):
try:
self.ui.tweakInterface.setEnabled(self.id, arg)
except Exception:
traceback.print_exc()

def doAfter(self, result):
self.ui.setState(NORMAL)
self.ui.updateStatus()
self.ui.cancel()


class InstallerUi(UiRoot):
Expand Down Expand Up @@ -349,27 +328,25 @@ def body(self, top):
self.boxFrame = Frame(tweakFrame)
self.boxFrame.pack(fill=BOTH, expand=True)

self.doneButton = Button(top, text='Done', command=self.cancel, padding=5)
self.doneButton.pack(fill=X)
self.applyButton = Button(top, text='Apply', command=TweakApplyTask(self).run, padding=5)
self.applyButton.pack(fill=X)

self.updateStatus()

def updateStatus(self):
TweakStatusTask(self).run()

def setTweakStatus(self, tweaks):
for child in self.boxFrame.winfo_children():
child.destroy()
if tweaks:
for id, desc, status, value in tweaks:
var = IntVar(value=status)
c = Checkbutton(self.boxFrame, text=desc + '\n' + value, variable=var, command=TweakSetTask(self, id, var).run)
c.pack(fill=X)
else:
Label(self.boxFrame, text='No tweaks available').pack(fill=X)
for id, desc, status, value in self.tweakInterface.getTweaks():
var = IntVar(value=status)
c = Checkbutton(self.boxFrame, text=desc + '\n' + value, variable=var, command=lambda id=id, var=var: self.setTweak(id, var.get()))
c.pack(fill=X)

def setTweak(self, id, enabled):
self.tweakInterface.setEnabled(id, enabled)
self.updateStatus()

def setState(self, state):
for widget in self.boxFrame.winfo_children() + [self.doneButton]:
for widget in self.boxFrame.winfo_children() + [self.applyButton]:
widget.config(state=state)

def cancel(self, event=None):
Expand Down
22 changes: 6 additions & 16 deletions pmca/platform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def __init__(self, backend):
self.backend = backend

self.addCommand('info', Command(self.info, (), 'Print device info'))
self.addCommand('tweak', Command(self.tweak, (), 'Tweak device settings'))

if isinstance(self.backend, ShellPlatformBackend):
self.addCommand('shell', Command(self.shell, (), 'Start an interactive shell'))
Expand All @@ -37,6 +36,8 @@ def __init__(self, backend):
self.addCommand('install', Command(self.install, (1,), 'Install the specified android app', '<APKFILE>'))

if isinstance(self.backend, BackupPlatformBackend):
self.addCommand('tweak', Command(self.tweak, (), 'Tweak device settings'))

bk = SubCommand()
bk.addCommand('r', Command(self.readBackup, (1,), 'Read backup property', '<ID>'))
bk.addCommand('w', ResidueCommand(self.writeBackup, 1, 'Write backup property', '<ID> <DATA>'))
Expand Down Expand Up @@ -122,14 +123,6 @@ def lockBackup(self):
def unlockBackup(self):
self.backend.setBackupProtection(False)

def exit(self):
if isinstance(self.backend, BackupPlatformBackend):
try:
self.backend.syncBackup()
except:
print('Cannot sync backup')
super(CameraShell, self).exit()

def tweak(self):
tweakInterface = TweakInterface(self.backend)
while True:
Expand All @@ -146,7 +139,7 @@ def tweak(self):
try:
while True:
try:
i = int(input('Enter number of tweak to toggle (0 to finish): '))
i = int(input('Enter number of tweak to toggle (0 to apply): '))
if 0 <= i <= len(tweaks):
break
except ValueError:
Expand All @@ -156,12 +149,9 @@ def tweak(self):
break

if i == 0:
tweakInterface.apply()
break
else:
try:
id, desc, status, value = tweaks[i - 1]
tweakInterface.setEnabled(id, not status)
print('Success')
except Exception as e:
print('Error: %s' % e)
id, desc, status, value = tweaks[i - 1]
tweakInterface.setEnabled(id, not status)
print('')
151 changes: 122 additions & 29 deletions pmca/platform/backup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import abc
from collections import OrderedDict
import io

from ..backup import *
from ..util import *

class BaseBackupProp(abc.ABC):
def __init__(self, backend, size):
self.backend = backend
def __init__(self, dataInterface, size):
self.dataInterface = dataInterface
self.size = size

@abc.abstractmethod
Expand All @@ -18,26 +20,26 @@ def write(self, data):


class BackupProp(BaseBackupProp):
def __init__(self, backend, id, size):
super(BackupProp, self).__init__(backend, size)
def __init__(self, dataInterface, id, size):
super(BackupProp, self).__init__(dataInterface, size)
self.id = id

def read(self):
data = self.backend.readBackup(self.id)
data = self.dataInterface.readProp(self.id)
if len(data) != self.size:
raise Exception('Wrong size')
return data

def write(self, data):
if len(data) != self.size:
raise Exception('Wrong size')
self.backend.writeBackup(self.id, data)
self.dataInterface.writeProp(self.id, data)


class CompoundBackupProp(BaseBackupProp):
def __init__(self, backend, props):
super(CompoundBackupProp, self).__init__(backend, sum(size for id, size in props))
self._props = [BackupProp(backend, id, size) for id, size in props]
def __init__(self, dataInterface, props):
super(CompoundBackupProp, self).__init__(dataInterface, sum(size for id, size in props))
self._props = [BackupProp(dataInterface, id, size) for id, size in props]

def read(self):
return b''.join(prop.read() for prop in self._props)
Expand All @@ -50,23 +52,121 @@ def write(self, data):
data = data[prop.size:]


class BackupInterface:
BACKUP_PRESET_DATA_OFFSET_VERSION = 0x0c
BACKUP_PRESET_DATA_OFFSET_ID1 = 0x28
class BackupDataInterface(abc.ABC):
@abc.abstractmethod
def getRegion(self):
pass

@abc.abstractmethod
def readProp(self, id):
pass

@abc.abstractmethod
def writeProp(self, id, data):
pass


class BackupPlatformDataInterface(BackupDataInterface):
def __init__(self, backend):
self.backend = backend

def getRegion(self):
return self.backend.getBackupStatus()[0x14:].decode('latin1').rstrip('\0')

def readProp(self, id):
return self.backend.readBackup(id)

def writeProp(self, id, data):
self.backend.writeBackup(id, data)


class BackupFileDataInterface(BackupDataInterface):
def __init__(self, file):
self.backup = BackupFile(file)

def getRegion(self):
return self.backup.getRegion()

def readProp(self, id):
return self.backup.getProperty(id).data

def writeProp(self, id, data):
self.backup.setProperty(id, data)

def setProtection(self, enabled):
self.backup.setId1(enabled)

def updateChecksum(self):
self.backup.updateChecksum()

class BackupPlatformFileDataInterface(BackupFileDataInterface):
def __init__(self, backend):
self.backend = backend
self.file = io.BytesIO(self.backend.getBackupData())
super(BackupPlatformFileDataInterface, self).__init__(self.file)

def apply(self):
self.updateChecksum()
data = self.file.getvalue()
self.backend.setBackupData(data)
if self.backend.getBackupData()[0x100:] != data[0x100:]:
raise Exception('Cannot overwrite backup')


class BackupPatchDataInterface(BackupPlatformFileDataInterface):
def __init__(self, backend):
super(BackupPatchDataInterface, self).__init__(backend)
self.patch = {}

def readProp(self, id):
if id in self.patch:
return self.patch[id]
return super(BackupPatchDataInterface, self).readProp(id)

def writeProp(self, id, data):
self.patch[id] = data

def getPatch(self):
return self.patch

def setPatch(self, patch):
self.patch = patch

def apply(self):
if not self.patch:
return

patchAttr = {}
for id, data in self.patch.items():
p = self.backup.getProperty(id)
if p.data != data and p.attr & 1:
patchAttr[id] = p.attr
self.backup.setPropertyAttr(id, p.attr & ~1)
self.backup.setProperty(id, data)

try:
super(BackupPatchDataInterface, self).apply()
finally:
if patchAttr:
for id, attr in patchAttr.items():
self.backup.setPropertyAttr(id, attr)
super(BackupPatchDataInterface, self).apply()


class BackupInterface:
def __init__(self, dataInterface):
self.dataInterface = dataInterface
self._props = OrderedDict()

self.addProp('androidPlatformVersion', BackupProp(backend, 0x01660024, 8))
self.addProp('modelCode', BackupProp(backend, 0x00e70000, 5))
self.addProp('modelName', BackupProp(backend, 0x003e0005, 16))
self.addProp('serialNumber', BackupProp(backend, 0x00e70003, 4))
self.addProp('recLimit', CompoundBackupProp(backend, [(0x003c0373 + i, 1) for i in range(3)]))
self.addProp('recLimit4k', BackupProp(backend, 0x003c04b6, 2))
self.addProp('palNtscSelector', BackupProp(backend, 0x01070148, 1))
self.addProp('language', CompoundBackupProp(backend, [(0x010d008f + i, 1) for i in range(35)]))
self.addProp('usbAppInstaller', BackupProp(backend, 0x01640001, 1))
self.addProp('androidPlatformVersion', BackupProp(dataInterface, 0x01660024, 8))
self.addProp('modelCode', BackupProp(dataInterface, 0x00e70000, 5))
self.addProp('modelName', BackupProp(dataInterface, 0x003e0005, 16))
self.addProp('serialNumber', BackupProp(dataInterface, 0x00e70003, 4))
self.addProp('recLimit', CompoundBackupProp(dataInterface, [(0x003c0373 + i, 1) for i in range(3)]))
self.addProp('recLimit4k', BackupProp(dataInterface, 0x003c04b6, 2))
self.addProp('palNtscSelector', BackupProp(dataInterface, 0x01070148, 1))
self.addProp('language', CompoundBackupProp(dataInterface, [(0x010d008f + i, 1) for i in range(35)]))
self.addProp('usbAppInstaller', BackupProp(dataInterface, 0x01640001, 1))

def addProp(self, name, prop):
self._props[name] = prop
Expand All @@ -78,14 +178,7 @@ def writeProp(self, name, data):
return self._props[name].write(data)

def getRegion(self):
return self.backend.getBackupStatus()[0x14:].decode('latin1').rstrip('\0')

def getProtection(self):
data = self.backend.getBackupData()
version = data[self.BACKUP_PRESET_DATA_OFFSET_VERSION:self.BACKUP_PRESET_DATA_OFFSET_VERSION+4]
if version not in [b'BK%d\0' % i for i in [2, 3, 4]]:
raise Exception('Unsupported backup version')
return parse32le(data[self.BACKUP_PRESET_DATA_OFFSET_ID1:self.BACKUP_PRESET_DATA_OFFSET_ID1+4]) != 0
return self.dataInterface.getRegion()

def getDefaultLanguages(self, region):
return {
Expand Down
4 changes: 2 additions & 2 deletions pmca/platform/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def get(self):
class BackupProperty(Property):
def __init__(self, backend, name):
super(BackupProperty, self).__init__(backend)
self._backup = BackupInterface(backend)
self._backup = BackupInterface(BackupPlatformDataInterface(backend))
self.name = name

def _read(self):
Expand All @@ -37,7 +37,7 @@ def get(self):
class BackupRegionProperty(Property):
def __init__(self, backend):
super(BackupRegionProperty, self).__init__(backend)
self._backup = BackupInterface(backend)
self._backup = BackupInterface(BackupPlatformDataInterface(backend))

def get(self):
return self._backup.getRegion()
Expand Down
Loading

0 comments on commit d634e6c

Please sign in to comment.