patch_icamera.sh 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #!/opt/wz_mini/bin/bash
  2. # Description: Applies the iCamera binary patch to prevent iCamera from restarting the wireless
  3. # network when it is unable to reach the internet or Wyze's cloud. This should only be used in
  4. # self-hosted environments as it may break Wyze App functionality.
  5. # Author: Leo Leung <leo@steamr.com>
  6. # Last modified: September 2022
  7. #
  8. set -e
  9. PATH=$PATH:/opt/wz_mini/tmp/.bin
  10. # Firmware version
  11. Version=""
  12. function main() {
  13. # Handle remove / apply commands or print the usage message.
  14. if [[ "$1" == "remove" ]] ; then
  15. remove_patch
  16. exit
  17. fi
  18. if [[ "$1" == "apply" ]] ; then
  19. apply_patch
  20. exit
  21. fi
  22. echo "Usage: $0 [apply|remove]"
  23. echo " Applies the iCamera patch to make it work nice without Wyze Cloud connectivity"
  24. }
  25. function determine_version() {
  26. # Verify the iCamera version is supported
  27. MD5Sum=$(md5sum $1 | awk '{print $1}')
  28. if [[ "$MD5Sum" == "04b90d6d77be72a4dd8c18da4b31946a" ]] ; then
  29. echo "4.61.0.1"
  30. elif [[ "$MD5Sum" == "b1c96d966226d76db86c96ecdfdd79e9" ]] ; then
  31. echo "4.36.9.139"
  32. elif [[ "$MD5Sum" == "b187239d1881a97d4598798a2035c0f3" ]] ; then
  33. # v2 camera firmware
  34. echo "4.9.8.1002"
  35. else
  36. echo "Error: Unknown iCamera version with md5sum $MD5Sum"
  37. exit 1
  38. fi
  39. }
  40. function apply_patch() {
  41. # Check to see if the patched version is installed and is up to date
  42. if [ -f /opt/wz_mini/usr/bin/iCamera.patched ] ; then
  43. # Check the build date. (this check may be brittle?)
  44. OriginalDate=$(strings /system/bin/iCamera | grep "Build date" -A 1 | tail -n 1)
  45. PatchedDate=$(strings /opt/wz_mini/usr/bin/iCamera.patched | grep "Build date" -A 1 | tail -n 1)
  46. if [[ "$OriginalDate" == "$PatchedDate" ]] ; then
  47. echo "Patch already applied to current iCamera version."
  48. exit 0
  49. fi
  50. echo "Patched iCamera binary differs in build date. ($OriginalDate vs $PatchedDate)."
  51. echo "Patch is now reapplying."
  52. fi
  53. # Ensure our version works. This exits if it is unsupported.
  54. Version=$(determine_version /system/bin/iCamera)
  55. # Working in /tmp
  56. cd /tmp
  57. # Make a copy to patch
  58. cp /system/bin/iCamera iCamera
  59. # For the T20/v2 cameras, we also have to patch the libwyzeUtils.so library
  60. [ -f /opt/wz_mini/tmp/.T20 ] && cp /system/lib/libwyzeUtils.so libwyzeUtils.so
  61. # Apply our patches.
  62. patch_out_calls_to_test_cloud_url
  63. patch_out_jobs_after_connect
  64. patch_out_network_reset_to_idle
  65. patch_out_code_test_enable
  66. # v2 specific
  67. patch_wzutil_testconnectbyurl_skip_check
  68. patch_v2_led_connect_led
  69. echo -e "\n\nPatching done."
  70. md5sum iCamera
  71. [ -f /opt/wz_mini/tmp/.T20 ] && md5sum libwyzeUtils.so
  72. # Place it on the SD card and modify the iCamera script to use it.
  73. cp iCamera /opt/wz_mini/usr/bin/iCamera.patched
  74. sed -i 's/\/system\/bin\/iCamera/\/opt\/wz_mini\/usr\/bin\/iCamera.patched/' /opt/wz_mini/usr/bin/iCamera
  75. # the v2 patched library should be copied to /opt/wz_mini/lib
  76. if [ -f /opt/wz_mini/tmp/.T20 ]; then
  77. cp libwyzeUtils.so /opt/wz_mini/lib/libwyzeUtils.so
  78. # Fix the LD_PRELOAD to use this patched version first.
  79. # The T20 has 'libcallback_t20.so:libtinyalsa.so.2.0.0'
  80. sed -i "s/LD_PRELOAD='libcallback_t20.so:libtinyalsa.so.2.0.0'/LD_PRELOAD='\/opt\/wz_mini\/lib\/libwyzeUtils.so:libcallback_t20.so:libtinyalsa.so.2.0.0'/" /opt/wz_mini/usr/bin/iCamera
  81. fi
  82. echo "Installed."
  83. }
  84. function remove_patch() {
  85. echo "Reverting iCamera patch."
  86. # Remove patched iCamera
  87. if [ -f /opt/wz_mini/usr/bin/iCamera.patched ] ; then
  88. rm -v /opt/wz_mini/usr/bin/iCamera.patched
  89. fi
  90. # Remove patched libwyzeUtils
  91. if [ -f /opt/wz_mini/lib/libwyzeUtils.so ] ; then
  92. rm -v /opt/wz_mini/lib/libwyzeUtils.so
  93. fi
  94. # Ensure iCamera shim script points to /system/bin/iCamera
  95. if grep -q iCamera.patched /opt/wz_mini/usr/bin/iCamera ; then
  96. sed -i 's/\/opt\/wz_mini\/usr\/bin\/iCamera.patched/\/system\/bin\/iCamera/' /opt/wz_mini/usr/bin/iCamera
  97. fi
  98. # If the libwyzeUtils is referenced for the t20, remove it
  99. if grep -q libwyzeUtils.so:libcallback_t20.so /opt/wz_mini/usr/bin/iCamera ; then
  100. sed -i "s/LD_PRELOAD='\/opt\/wz_mini\/lib\/libwyzeUtils.so:libcallback_t20.so:libtinyalsa.so.2.0.0'/LD_PRELOAD='libcallback_t20.so:libtinyalsa.so.2.0.0'/" /opt/wz_mini/usr/bin/iCamera
  101. fi
  102. echo "Removed."
  103. }
  104. # Patch out the calls to test cloud url, which calls the DN check function with NOPs
  105. # This isn't strictly necessary, though it will cause iCamera to constantly retry the tests and spam the iCamera outputs with messages like
  106. # DN:854]err: (getaddrinfo) fail:-2(Name or service not known), (domain: www.google.com
  107. # so we'll comment these calls here to make it hush up
  108. function patch_out_calls_to_test_cloud_url() {
  109. [[ "$Version" == "4.61.0.1" ]] && Address="0x603b0 0x602d4"
  110. [[ "$Version" == "4.36.9.139" ]] && Address="0x89938 0x89858"
  111. [[ "$Version" == "4.9.8.1002" ]] && return; # Not in v2
  112. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  113. for i in $Address ; do
  114. echo -e "\nOriginal at $i"
  115. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  116. echo "Patched"
  117. printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  118. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  119. done
  120. }
  121. # Patch out the threadpool_add_job calls for net-valid and upload-rebootlog, and dongle send calls with NOPs
  122. function patch_out_jobs_after_connect () {
  123. [[ "$Version" == "4.61.0.1" ]] && Address="$(seq 0x070d0 4 0x07114)"
  124. [[ "$Version" == "4.36.9.139" ]] && Address="$(seq 0x7b184 4 0x7b1cc)"
  125. [[ "$Version" == "4.9.8.1002" ]] && Address="$(seq 0x089c8 4 0x08a30)"
  126. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  127. # Everything up until the last branch instruction
  128. for i in $Address ; do
  129. echo -e "\nOriginal at $i"
  130. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  131. echo "Patched"
  132. printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  133. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  134. done
  135. }
  136. # When our calls to DN check cloud url fails, we run into the code that calls funky_network_function(0). We don't want that. So noop all this out
  137. # All the code that sets DAT_005e4d54=0, prints debug message, and calls funky_network_func(0) is nooped out here.
  138. # This fixes the network from going back to the idle state and bouncing everything
  139. # The call to print debug message could probably be left intact... we don't actually prevent 005e4d50=0 though..
  140. function patch_out_network_reset_to_idle () {
  141. [[ "$Version" == "4.61.0.1" ]] && Address="$(seq 0x6041c 4 0x6045c)"
  142. [[ "$Version" == "4.36.9.139" ]] && Address="$(seq 0x899a4 4 0x899e4)"
  143. [[ "$Version" == "4.9.8.1002" ]] && return; # this is in the libwyzeUtils.so library, I think. Can't find similar code
  144. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  145. for i in $Address ; do
  146. echo -e "\nOriginal at $i"
  147. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  148. echo "Patched"
  149. # noop
  150. printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  151. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  152. done
  153. }
  154. # Patch out code_test trigger with NOPs
  155. # For some reason on 4.36.9.139, iCamera starts the code_test section which just constantly lists /tmp over and over every 10 seconds.
  156. # I don't know why this is getting triggered, so I'm going to patch this out from being called.
  157. function patch_out_code_test_enable() {
  158. [[ "$Version" == "4.61.0.1" ]] && return; # No need to do this as it doesn't seem to be a problem
  159. [[ "$Version" == "4.36.9.139" ]] && Address="0x7dfcc"
  160. [[ "$Version" == "4.9.8.1002" ]] && return; # not in the v2 firmware, I think.
  161. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  162. for i in $Address ; do
  163. echo -e "\nOriginal at $i"
  164. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  165. echo "Patched"
  166. # noop
  167. printf '\x00\x00\x00\x00' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  168. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  169. done
  170. }
  171. # Patch libwyzeUtils.so so that the testconnectbyurl function always returns true, regardless of whether
  172. # the cloud is available or not.
  173. function patch_wzutil_testconnectbyurl_skip_check() {
  174. # For the v2 firmware using the libwyzeUtils.so library only.
  175. [ ! -f /opt/wz_mini/tmp/.T20 ] && return # Only on the v2
  176. [[ "$Version" == "4.61.0.1" ]] && return
  177. [[ "$Version" == "4.36.9.139" ]] && return
  178. [[ "$Version" == "4.9.8.1002" ]] || return # Only supports 4.9.8.1002
  179. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  180. # BEQ $4 $0 0x5E branches to the 'URL is null' section of the code which returns -1
  181. # We want to go there and return 0 always instead. So let's blez (always true on an unsigned int)
  182. # and patch the -1 to 0
  183. i="0x203d0" # should have content: 5e 00 80 10, (BEQ $4 $0 0x5E)
  184. echo -e "\nOriginal at $i"
  185. dd if=libwyzeUtils.so bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  186. echo "Patched" # patch with 5e 00 81 04 (BLEZ $4 0x5E)
  187. printf '\x5e\x00\x81\x04' | dd conv=notrunc of=libwyzeUtils.so bs=1 seek=$(($i)) 2> /dev/null
  188. dd if=libwyzeUtils.so bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  189. # At 3056c, we load -1 to s0, which is our return code. fix this to 0
  190. i="0x2056c" # should have content: ff ff 10 24
  191. echo -e "\nOriginal at $i"
  192. dd if=libwyzeUtils.so bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  193. echo "Patched" # patch with 00 00 10 24, should load s0 to 0 before returning
  194. printf '\x00\x00\x10\x24' | dd conv=notrunc of=libwyzeUtils.so bs=1 seek=$(($i)) 2> /dev/null
  195. dd if=libwyzeUtils.so bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  196. }
  197. # Patch LED state in iCamera binary so LED stays off after connecting
  198. # Applies only to the v2
  199. function patch_v2_led_connect_led () {
  200. # Applies only to this particular firmware in the v2.
  201. [[ "$Version" == "4.9.8.1002" ]] || return
  202. echo -e "\n\n====> Calling ${FUNCNAME[0]}\n"
  203. # Use the big NOP space from patch_out_jobs_after_connect to call led_ctrl_run_action_by_state(5)
  204. i="0x089c8"
  205. # li 5 a0
  206. printf '\x05\x00\x04\x24' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  207. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  208. i="0x089cc"
  209. # jal led_ctrl_run_action_by_state
  210. printf '\xcc\xc4\x10\x0c' | dd conv=notrunc of=iCamera bs=1 seek=$(($i)) 2> /dev/null
  211. dd if=iCamera bs=1 count=4 skip=$(($i)) 2>/dev/null | xxd
  212. }
  213. main "$@"