Skip to content

Commit

Permalink
Merge branch 'FUSDownloaders/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
MatrixEditor committed Dec 28, 2023
2 parents 6a56382 + a020eb1 commit 603a808
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 47 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ pip install git+https://github.com/MatrixEditor/samloader3.git

## CLI

The interface procided here is separated into two layers. In The first one, one can set basic options, such as the device's country code, model name or a global timeout value. Next, you will
The interface procided here is separated into two layers. In The first one, one can set basic options, such as the device's country code, model name, IMEI number or a global timeout value. Next, you will
operate on a shell that takes commands with arguments as input.

```console
$ python3 -m samloader3 -M "SM-A336B" -R "SFR" -I "35117439020457"
$ python3 -m samloader3 -M "SM-A336B" -R "SFR" -I "12345678901234"
(sl3)> # type commands here
```

Expand Down
16 changes: 13 additions & 3 deletions samloader3/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,34 @@
from typing import Union


class XMLPathError(Exception):
"""Simple exception for missing XML elements"""

def __init__(self, doc: ElementTree.Element, path: str, *args) -> None:
super().__init__(*args)
self.doc = doc
self.path = path


def xml_find(
doc: ElementTree.Element, path: str, text=False
) -> Union[ElementTree.Element, str]:
"""Utility function to find an XML element and its content in a document.
This method is used to locate an XML element based on a given path, and return
either the text or the entire element depending on the 'text' parameter. If
the element cannot be found, it raises a ValueError exception.
the element cannot be found, it raises XMLPathError.
:param doc: The root ElementTree object containing the XML data.
:type doc: ElementTree.Element
:param path: A string specifying the XPath to locate the desired XML element.
:type path: str
:param text: If True, return only the content (text) of the found element; otherwise, return the entire element object.
:type text: bool
:return: The text or ElementTree object based on 'text' parameter."""
:return: The text or ElementTree object based on 'text' parameter.
"""
element = doc.find(path)
if element is None:
raise ValueError(f"Could not find XML element at '{path}'!")
raise XMLPathError(doc, path, f"Could not find XML element at '{path}'!")

return str(element.text) if text else element
111 changes: 73 additions & 38 deletions samloader3/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import signal
import traceback

from xml.etree import ElementTree
from concurrent.futures import ThreadPoolExecutor
from threading import Event

Expand All @@ -44,7 +45,15 @@
)
from rich.markup import escape

from samloader3.fus import FUSClient, firmware_spec_url, FUS_USER_AGENT, v4_key, v2_key
from samloader3.fus import (
FUSClient,
firmware_spec_url,
FUS_USER_AGENT,
v4_key,
v2_key,
AuthenticationError,
XMLPathError,
)
from samloader3.firmware import FirmwareSpec, FirmwareInfo
from samloader3 import crypto, __version__

Expand Down Expand Up @@ -156,9 +165,13 @@ def update():
self.progress.advance(task, block_size)

try:
crypto.file_decrypt(path, self.argv.out, key, block_size, key_version, update)
crypto.file_decrypt(
path, self.argv.out, key, block_size, key_version, update
)
except ValueError:
_print_error("[bold]Invalid Padding:[/] Most likely due to a wrong input file!")
_print_error(
"[bold]Invalid Padding:[/] Most likely due to a wrong input file!"
)


class Download(Task):
Expand Down Expand Up @@ -266,8 +279,15 @@ def run(self, specs: t.List[FirmwareSpec], model, region, imei) -> None:
:type imei: str
"""
data = []
for spec in specs:
data.append(self.client.fw_info(spec.normalized_version, model, region, imei))
try:
for spec in specs:
data.append(
self.client.fw_info(spec.normalized_version, model, region, imei)
)
except XMLPathError as xml_exc:
_print_error(f"Invalid returned XML document: {xml_exc}")
pprint(ElementTree.tostring(xml_exc.doc).decode())
return

with self.progress:
pool = None
Expand Down Expand Up @@ -497,7 +517,11 @@ def get_candidates(
### IMPLEMENTATION ###
def _connect(self) -> bool:
if not self.client.auth.encrypted_nonce:
self.client.setup()
try:
self.client.setup()
except AuthenticationError:
_print_error("Could not connect to FUS-Server (invalid credentials)")
return False
if not self.client.auth.encrypted_nonce:
_print_error("Could not connect to FUS-Server!")
return False
Expand All @@ -511,7 +535,12 @@ def _verify_device_info(self, argv) -> bool:
_print_error("[bold]Device:[/] No device specified!")
return False

_print_ok(f"[bold]Device:[/] {model}/{region}")
imei = self.get_imei(argv)
if not imei:
_print_error("[bold]Device:[/] IMEI not specified!")
return False

_print_ok(f"[bold]Device:[/] {model}/{region}/{imei}")
return True

def _verify_specs(self, model, region) -> bool:
Expand Down Expand Up @@ -545,38 +574,44 @@ def _list_info(self, argv) -> None:
versions = argv.version
if "" in versions:
versions.remove("")
if len(argv.version) == 0:
table = Table(title="Detailed Version Info")
table.add_column("OS Version", justify="left", no_wrap=True)
table.add_column("Version", justify="center")
table.add_column("Latest", justify="center")

with Live(table, refresh_per_second=4):
info = self.client.fw_info(
specs.latest.normalized_version, model, region, imei
)
table.add_row(info.current_os_version, info.version, "[green]True[/]")
if not argv.latest:
for upgrade in specs.upgrade:
info = self.client.fw_info(
upgrade.normalized_version, model, region, imei
)
table.add_row(
info.current_os_version, info.version, "[red]False[/]"
)
else:
load_all = argv.version[0] == "*"
candidates = self.get_candidates(specs, argv.version, load_all)
if len(candidates) != 1:
return
try:
if len(argv.version) == 0:
table = Table(title="Detailed Version Info")
table.add_column("OS Version", justify="left", no_wrap=True)
table.add_column("Version", justify="center")
table.add_column("Latest", justify="center")

with Live(table, refresh_per_second=4):
info = self.client.fw_info(
specs.latest.normalized_version, model, region, imei
)
table.add_row(
info.current_os_version, info.version, "[green]True[/]"
)
if not argv.latest:
for upgrade in specs.upgrade:
info = self.client.fw_info(
upgrade.normalized_version, model, region, imei
)
table.add_row(
info.current_os_version, info.version, "[red]False[/]"
)
else:
load_all = argv.version[0] == "*"
candidates = self.get_candidates(specs, argv.version, load_all)
if len(candidates) != 1:
return

spec = candidates[0]
info = self.client.fw_info(spec.normalized_version, model, region, imei)
tree = Tree(f"({model}): {info.version}")
for key, value in info.entries.items():
if value is not None:
tree.add(f'[italic]{key}[/] -> "{value}"')
pprint(tree)
spec = candidates[0]
info = self.client.fw_info(spec.normalized_version, model, region, imei)
tree = Tree(f"({model}): {info.version}")
for key, value in info.entries.items():
if value is not None:
tree.add(f'[italic]{key}[/] -> "{value}"')
pprint(tree)
except XMLPathError as xml_exc:
_print_error(f"XML Error: {xml_exc} - Most likely due to an incompatible IMEI!")
pprint(ElementTree.tostring(xml_exc.doc).decode())

def _download(self, argv) -> None:
if not self._verify_device_info(argv) or not self._connect():
Expand Down
7 changes: 3 additions & 4 deletions samloader3/fus.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from samloader3.firmware import FirmwareInfo, BinaryNature, FirmwareSpec, VersionInfo
from samloader3.crypto import get_logic_check, get_nonce, get_signature

from ._util import xml_find
from ._util import xml_find, XMLPathError

# General constants
FUS_CLOUD_DOMAIN = "cloud-neofussvr.samsungmobile.com"
Expand Down Expand Up @@ -334,11 +334,10 @@ def fw_info(
)
payload = ElementTree.tostring(xml, encoding="utf-8")
result = self.request(NF_DownloadBinaryInform, payload=payload)
# TODO: validate response
try:
doc = ElementTree.fromstring(result.text)
except ElementTree.ParseError:
print(result.text)
except ElementTree.ParseError as exc:
raise AuthenticationError from exc
entries = {}
for potential_entry in xml_find(doc, "./FUSBody/Put"):
data = potential_entry.find("./Data")
Expand Down

0 comments on commit 603a808

Please sign in to comment.