rn-update 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (880) hide show
  1. package/LICENSE +22 -0
  2. package/README-CN.md +97 -0
  3. package/README.md +97 -0
  4. package/THIRD_PARTY_NOTICES.md +8 -0
  5. package/android/build.gradle +253 -0
  6. package/android/jni/Android.mk +41 -0
  7. package/android/jni/Application.mk +20 -0
  8. package/android/jni/HDiffPatch/.travis.yml +33 -0
  9. package/android/jni/HDiffPatch/CHANGELOG.md +153 -0
  10. package/android/jni/HDiffPatch/LICENSE +48 -0
  11. package/android/jni/HDiffPatch/Makefile +157 -0
  12. package/android/jni/HDiffPatch/README.md +307 -0
  13. package/android/jni/HDiffPatch/_atosize.h +83 -0
  14. package/android/jni/HDiffPatch/_clock_for_demo.h +47 -0
  15. package/android/jni/HDiffPatch/_dir_ignore.h +191 -0
  16. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/Android.mk +37 -0
  17. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/Application.mk +7 -0
  18. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/build_libs.bat +1 -0
  19. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/build_libs.sh +1 -0
  20. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/hpatch.c +18 -0
  21. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/hpatch.h +20 -0
  22. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/hpatch_jni.c +28 -0
  23. package/android/jni/HDiffPatch/builds/android_ndk_jni_mk/java/com/github/sisong/HPatch.java +8 -0
  24. package/android/jni/HDiffPatch/builds/codeblocks/HDiffPatch.workspace +8 -0
  25. package/android/jni/HDiffPatch/builds/codeblocks/HDiffZ.cbp +219 -0
  26. package/android/jni/HDiffPatch/builds/codeblocks/HPatchZ.cbp +156 -0
  27. package/android/jni/HDiffPatch/builds/codeblocks/unitTest.cbp +65 -0
  28. package/android/jni/HDiffPatch/builds/vc/HDiffPatch.sln +105 -0
  29. package/android/jni/HDiffPatch/builds/vc/HDiffZ.vcxproj +237 -0
  30. package/android/jni/HDiffPatch/builds/vc/HDiffZ.vcxproj.user +3 -0
  31. package/android/jni/HDiffPatch/builds/vc/HPatchZ.vcxproj +318 -0
  32. package/android/jni/HDiffPatch/builds/vc/HPatchZ.vcxproj.user +3 -0
  33. package/android/jni/HDiffPatch/builds/vc/unitTest.vcxproj +182 -0
  34. package/android/jni/HDiffPatch/builds/vc/unitTest.vcxproj.user +3 -0
  35. package/android/jni/HDiffPatch/builds/xcode/HDiffPatch.xcworkspace/contents.xcworkspacedata +30 -0
  36. package/android/jni/HDiffPatch/builds/xcode/HDiffPatch.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  37. package/android/jni/HDiffPatch/builds/xcode/bestParams.xcodeproj/project.pbxproj +408 -0
  38. package/android/jni/HDiffPatch/builds/xcode/bestParams.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  39. package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.pbxproj +573 -0
  40. package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  41. package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  42. package/android/jni/HDiffPatch/builds/xcode/hpatchz.xcodeproj/project.pbxproj +396 -0
  43. package/android/jni/HDiffPatch/builds/xcode/hpatchz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  44. package/android/jni/HDiffPatch/builds/xcode/lz4.xcodeproj/project.pbxproj +268 -0
  45. package/android/jni/HDiffPatch/builds/xcode/lz4.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  46. package/android/jni/HDiffPatch/builds/xcode/lzma.xcodeproj/project.pbxproj +361 -0
  47. package/android/jni/HDiffPatch/builds/xcode/lzma.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  48. package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.pbxproj +301 -0
  49. package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  50. package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  51. package/android/jni/HDiffPatch/builds/xcode/unitTest.xcodeproj/project.pbxproj +411 -0
  52. package/android/jni/HDiffPatch/builds/xcode/unitTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  53. package/android/jni/HDiffPatch/builds/xcode/zstd.xcodeproj/project.pbxproj +504 -0
  54. package/android/jni/HDiffPatch/builds/xcode/zstd.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  55. package/android/jni/HDiffPatch/checksum_plugin_demo.h +344 -0
  56. package/android/jni/HDiffPatch/compress_parallel.cpp +166 -0
  57. package/android/jni/HDiffPatch/compress_parallel.h +59 -0
  58. package/android/jni/HDiffPatch/compress_plugin_demo.h +1152 -0
  59. package/android/jni/HDiffPatch/decompress_plugin_demo.h +995 -0
  60. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_diff.cpp +797 -0
  61. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_diff.h +65 -0
  62. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_diff_tools.cpp +200 -0
  63. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_diff_tools.h +178 -0
  64. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_manifest.cpp +357 -0
  65. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/dir_manifest.h +87 -0
  66. package/android/jni/HDiffPatch/dirDiffPatch/dir_diff/file_for_dirDiff.h +151 -0
  67. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch.c +770 -0
  68. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch.h +236 -0
  69. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch_private.h +47 -0
  70. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch_tools.c +112 -0
  71. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch_tools.h +53 -0
  72. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/dir_patch_types.h +61 -0
  73. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/new_dir_output.c +331 -0
  74. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/new_dir_output.h +144 -0
  75. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/new_stream.c +176 -0
  76. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/new_stream.h +84 -0
  77. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/ref_stream.c +146 -0
  78. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/ref_stream.h +61 -0
  79. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/res_handle_limit.c +152 -0
  80. package/android/jni/HDiffPatch/dirDiffPatch/dir_patch/res_handle_limit.h +76 -0
  81. package/android/jni/HDiffPatch/file_for_patch.c +524 -0
  82. package/android/jni/HDiffPatch/file_for_patch.h +259 -0
  83. package/android/jni/HDiffPatch/hdiffz.cpp +1570 -0
  84. package/android/jni/HDiffPatch/hpatch_dir_listener.h +290 -0
  85. package/android/jni/HDiffPatch/hpatchz.c +1221 -0
  86. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/diff.cpp +1025 -0
  87. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/diff.h +116 -0
  88. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/diff_types.h +72 -0
  89. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/bytes_rle.cpp +213 -0
  90. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/bytes_rle.h +63 -0
  91. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/compress_detect.cpp +170 -0
  92. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/compress_detect.h +86 -0
  93. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/config.h +89 -0
  94. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort.c +13 -0
  95. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort.c.inc.h +398 -0
  96. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort.h +194 -0
  97. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort64.c +13 -0
  98. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort64.h +204 -0
  99. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/divsufsort_private.h +207 -0
  100. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/sssort.c.inc.h +815 -0
  101. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/trsort.c.inc.h +586 -0
  102. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/libdivsufsort/utils.c.inc.h +381 -0
  103. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/adler_roll.c +352 -0
  104. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/adler_roll.h +137 -0
  105. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/bloom_filter.h +137 -0
  106. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/covers.h +70 -0
  107. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/digest_matcher.cpp +628 -0
  108. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/digest_matcher.h +87 -0
  109. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/stream_serialize.cpp +363 -0
  110. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/limit_mem_diff/stream_serialize.h +139 -0
  111. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/mem_buf.h +68 -0
  112. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/pack_uint.h +115 -0
  113. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/suffix_string.cpp +342 -0
  114. package/android/jni/HDiffPatch/libHDiffPatch/HDiff/private_diff/suffix_string.h +94 -0
  115. package/android/jni/HDiffPatch/libHDiffPatch/HPatch/checksum_plugin.h +52 -0
  116. package/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch.c +2292 -0
  117. package/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch.h +215 -0
  118. package/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch_private.h +165 -0
  119. package/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch_types.h +251 -0
  120. package/android/jni/HDiffPatch/libParallel/parallel_channel.cpp +174 -0
  121. package/android/jni/HDiffPatch/libParallel/parallel_channel.h +73 -0
  122. package/android/jni/HDiffPatch/libParallel/parallel_import.cpp +367 -0
  123. package/android/jni/HDiffPatch/libParallel/parallel_import.h +92 -0
  124. package/android/jni/hpatch.c +232 -0
  125. package/android/jni/hpatch.h +43 -0
  126. package/android/jni/lzma/Asm/arm/7zCrcOpt.asm +100 -0
  127. package/android/jni/lzma/Asm/x86/7zAsm.asm +147 -0
  128. package/android/jni/lzma/Asm/x86/7zCrcOpt.asm +147 -0
  129. package/android/jni/lzma/Asm/x86/AesOpt.asm +237 -0
  130. package/android/jni/lzma/Asm/x86/LzmaDecOpt.asm +1258 -0
  131. package/android/jni/lzma/Asm/x86/XzCrc64Opt.asm +205 -0
  132. package/android/jni/lzma/C/7z.h +202 -0
  133. package/android/jni/lzma/C/7zAlloc.c +80 -0
  134. package/android/jni/lzma/C/7zAlloc.h +19 -0
  135. package/android/jni/lzma/C/7zArcIn.c +1771 -0
  136. package/android/jni/lzma/C/7zBuf.c +36 -0
  137. package/android/jni/lzma/C/7zBuf.h +35 -0
  138. package/android/jni/lzma/C/7zBuf2.c +52 -0
  139. package/android/jni/lzma/C/7zCrc.c +128 -0
  140. package/android/jni/lzma/C/7zCrc.h +25 -0
  141. package/android/jni/lzma/C/7zCrcOpt.c +115 -0
  142. package/android/jni/lzma/C/7zDec.c +591 -0
  143. package/android/jni/lzma/C/7zFile.c +286 -0
  144. package/android/jni/lzma/C/7zFile.h +83 -0
  145. package/android/jni/lzma/C/7zStream.c +176 -0
  146. package/android/jni/lzma/C/7zTypes.h +375 -0
  147. package/android/jni/lzma/C/7zVersion.h +27 -0
  148. package/android/jni/lzma/C/7zVersion.rc +55 -0
  149. package/android/jni/lzma/C/Aes.c +306 -0
  150. package/android/jni/lzma/C/Aes.h +38 -0
  151. package/android/jni/lzma/C/AesOpt.c +184 -0
  152. package/android/jni/lzma/C/Alloc.c +455 -0
  153. package/android/jni/lzma/C/Alloc.h +51 -0
  154. package/android/jni/lzma/C/Bcj2.c +257 -0
  155. package/android/jni/lzma/C/Bcj2.h +146 -0
  156. package/android/jni/lzma/C/Bcj2Enc.c +311 -0
  157. package/android/jni/lzma/C/Bra.c +230 -0
  158. package/android/jni/lzma/C/Bra.h +64 -0
  159. package/android/jni/lzma/C/Bra86.c +82 -0
  160. package/android/jni/lzma/C/BraIA64.c +53 -0
  161. package/android/jni/lzma/C/Compiler.h +33 -0
  162. package/android/jni/lzma/C/CpuArch.c +218 -0
  163. package/android/jni/lzma/C/CpuArch.h +336 -0
  164. package/android/jni/lzma/C/Delta.c +64 -0
  165. package/android/jni/lzma/C/Delta.h +19 -0
  166. package/android/jni/lzma/C/DllSecur.c +108 -0
  167. package/android/jni/lzma/C/DllSecur.h +20 -0
  168. package/android/jni/lzma/C/LzFind.c +1127 -0
  169. package/android/jni/lzma/C/LzFind.h +121 -0
  170. package/android/jni/lzma/C/LzFindMt.c +853 -0
  171. package/android/jni/lzma/C/LzFindMt.h +101 -0
  172. package/android/jni/lzma/C/LzHash.h +57 -0
  173. package/android/jni/lzma/C/Lzma2Dec.c +488 -0
  174. package/android/jni/lzma/C/Lzma2Dec.h +120 -0
  175. package/android/jni/lzma/C/Lzma2DecMt.c +1082 -0
  176. package/android/jni/lzma/C/Lzma2DecMt.h +79 -0
  177. package/android/jni/lzma/C/Lzma2Enc.c +803 -0
  178. package/android/jni/lzma/C/Lzma2Enc.h +55 -0
  179. package/android/jni/lzma/C/Lzma86.h +111 -0
  180. package/android/jni/lzma/C/Lzma86Dec.c +54 -0
  181. package/android/jni/lzma/C/Lzma86Enc.c +106 -0
  182. package/android/jni/lzma/C/LzmaDec.c +1185 -0
  183. package/android/jni/lzma/C/LzmaDec.h +234 -0
  184. package/android/jni/lzma/C/LzmaEnc.c +2976 -0
  185. package/android/jni/lzma/C/LzmaEnc.h +76 -0
  186. package/android/jni/lzma/C/LzmaLib.c +40 -0
  187. package/android/jni/lzma/C/LzmaLib.h +131 -0
  188. package/android/jni/lzma/C/MtCoder.c +601 -0
  189. package/android/jni/lzma/C/MtCoder.h +141 -0
  190. package/android/jni/lzma/C/MtDec.c +1138 -0
  191. package/android/jni/lzma/C/MtDec.h +201 -0
  192. package/android/jni/lzma/C/Ppmd.h +85 -0
  193. package/android/jni/lzma/C/Ppmd7.c +712 -0
  194. package/android/jni/lzma/C/Ppmd7.h +142 -0
  195. package/android/jni/lzma/C/Ppmd7Dec.c +191 -0
  196. package/android/jni/lzma/C/Ppmd7Enc.c +187 -0
  197. package/android/jni/lzma/C/Precomp.h +10 -0
  198. package/android/jni/lzma/C/RotateDefs.h +30 -0
  199. package/android/jni/lzma/C/Sha256.c +248 -0
  200. package/android/jni/lzma/C/Sha256.h +26 -0
  201. package/android/jni/lzma/C/Sort.c +141 -0
  202. package/android/jni/lzma/C/Sort.h +18 -0
  203. package/android/jni/lzma/C/Threads.c +95 -0
  204. package/android/jni/lzma/C/Threads.h +70 -0
  205. package/android/jni/lzma/C/ThreadsP.c +396 -0
  206. package/android/jni/lzma/C/ThreadsP.h +31 -0
  207. package/android/jni/lzma/C/Util/7z/7z.dsp +241 -0
  208. package/android/jni/lzma/C/Util/7z/7z.dsw +29 -0
  209. package/android/jni/lzma/C/Util/7z/7zMain.c +686 -0
  210. package/android/jni/lzma/C/Util/7z/Precomp.c +4 -0
  211. package/android/jni/lzma/C/Util/7z/Precomp.h +10 -0
  212. package/android/jni/lzma/C/Util/7z/makefile +40 -0
  213. package/android/jni/lzma/C/Util/7z/makefile.gcc +75 -0
  214. package/android/jni/lzma/C/Util/Lzma/LzmaUtil.c +258 -0
  215. package/android/jni/lzma/C/Util/Lzma/LzmaUtil.dsp +168 -0
  216. package/android/jni/lzma/C/Util/Lzma/LzmaUtil.dsw +29 -0
  217. package/android/jni/lzma/C/Util/Lzma/makefile +28 -0
  218. package/android/jni/lzma/C/Util/Lzma/makefile.gcc +44 -0
  219. package/android/jni/lzma/C/Util/LzmaLib/LzmaLib.def +4 -0
  220. package/android/jni/lzma/C/Util/LzmaLib/LzmaLib.dsp +178 -0
  221. package/android/jni/lzma/C/Util/LzmaLib/LzmaLib.dsw +29 -0
  222. package/android/jni/lzma/C/Util/LzmaLib/LzmaLibExports.c +14 -0
  223. package/android/jni/lzma/C/Util/LzmaLib/makefile +34 -0
  224. package/android/jni/lzma/C/Util/LzmaLib/resource.rc +3 -0
  225. package/android/jni/lzma/C/Util/SfxSetup/Precomp.c +4 -0
  226. package/android/jni/lzma/C/Util/SfxSetup/Precomp.h +10 -0
  227. package/android/jni/lzma/C/Util/SfxSetup/SfxSetup.c +640 -0
  228. package/android/jni/lzma/C/Util/SfxSetup/SfxSetup.dsp +231 -0
  229. package/android/jni/lzma/C/Util/SfxSetup/SfxSetup.dsw +29 -0
  230. package/android/jni/lzma/C/Util/SfxSetup/makefile +37 -0
  231. package/android/jni/lzma/C/Util/SfxSetup/makefile_con +38 -0
  232. package/android/jni/lzma/C/Util/SfxSetup/resource.rc +5 -0
  233. package/android/jni/lzma/C/Util/SfxSetup/setup.ico +0 -0
  234. package/android/jni/lzma/C/Xz.c +90 -0
  235. package/android/jni/lzma/C/Xz.h +460 -0
  236. package/android/jni/lzma/C/XzCrc64.c +86 -0
  237. package/android/jni/lzma/C/XzCrc64.h +26 -0
  238. package/android/jni/lzma/C/XzCrc64Opt.c +69 -0
  239. package/android/jni/lzma/C/XzDec.c +2766 -0
  240. package/android/jni/lzma/C/XzEnc.c +1329 -0
  241. package/android/jni/lzma/C/XzEnc.h +60 -0
  242. package/android/jni/lzma/C/XzIn.c +319 -0
  243. package/android/jni/lzma/CPP/7zip/7zip.mak +240 -0
  244. package/android/jni/lzma/CPP/7zip/Aes.mak +7 -0
  245. package/android/jni/lzma/CPP/7zip/Archive/7z/7zCompressionMode.cpp +3 -0
  246. package/android/jni/lzma/CPP/7zip/Archive/7z/7zCompressionMode.h +76 -0
  247. package/android/jni/lzma/CPP/7zip/Archive/7z/7zDecode.cpp +569 -0
  248. package/android/jni/lzma/CPP/7zip/Archive/7z/7zDecode.h +70 -0
  249. package/android/jni/lzma/CPP/7zip/Archive/7z/7zEncode.cpp +678 -0
  250. package/android/jni/lzma/CPP/7zip/Archive/7z/7zEncode.h +92 -0
  251. package/android/jni/lzma/CPP/7zip/Archive/7z/7zExtract.cpp +423 -0
  252. package/android/jni/lzma/CPP/7zip/Archive/7z/7zFolderInStream.cpp +139 -0
  253. package/android/jni/lzma/CPP/7zip/Archive/7z/7zFolderInStream.h +61 -0
  254. package/android/jni/lzma/CPP/7zip/Archive/7z/7zHandler.cpp +763 -0
  255. package/android/jni/lzma/CPP/7zip/Archive/7z/7zHandler.h +181 -0
  256. package/android/jni/lzma/CPP/7zip/Archive/7z/7zHandlerOut.cpp +942 -0
  257. package/android/jni/lzma/CPP/7zip/Archive/7z/7zHeader.cpp +19 -0
  258. package/android/jni/lzma/CPP/7zip/Archive/7z/7zHeader.h +148 -0
  259. package/android/jni/lzma/CPP/7zip/Archive/7z/7zIn.cpp +1666 -0
  260. package/android/jni/lzma/CPP/7zip/Archive/7z/7zIn.h +445 -0
  261. package/android/jni/lzma/CPP/7zip/Archive/7z/7zItem.h +202 -0
  262. package/android/jni/lzma/CPP/7zip/Archive/7z/7zOut.cpp +901 -0
  263. package/android/jni/lzma/CPP/7zip/Archive/7z/7zOut.h +335 -0
  264. package/android/jni/lzma/CPP/7zip/Archive/7z/7zProperties.cpp +174 -0
  265. package/android/jni/lzma/CPP/7zip/Archive/7z/7zProperties.h +22 -0
  266. package/android/jni/lzma/CPP/7zip/Archive/7z/7zRegister.cpp +21 -0
  267. package/android/jni/lzma/CPP/7zip/Archive/7z/7zSpecStream.cpp +22 -0
  268. package/android/jni/lzma/CPP/7zip/Archive/7z/7zSpecStream.h +35 -0
  269. package/android/jni/lzma/CPP/7zip/Archive/7z/7zUpdate.cpp +2500 -0
  270. package/android/jni/lzma/CPP/7zip/Archive/7z/7zUpdate.h +139 -0
  271. package/android/jni/lzma/CPP/7zip/Archive/7z/StdAfx.cpp +3 -0
  272. package/android/jni/lzma/CPP/7zip/Archive/7z/StdAfx.h +8 -0
  273. package/android/jni/lzma/CPP/7zip/Archive/Archive.def +12 -0
  274. package/android/jni/lzma/CPP/7zip/Archive/Archive2.def +19 -0
  275. package/android/jni/lzma/CPP/7zip/Archive/ArchiveExports.cpp +151 -0
  276. package/android/jni/lzma/CPP/7zip/Archive/Common/CoderMixer2.cpp +1125 -0
  277. package/android/jni/lzma/CPP/7zip/Archive/Common/CoderMixer2.h +447 -0
  278. package/android/jni/lzma/CPP/7zip/Archive/Common/DummyOutStream.cpp +17 -0
  279. package/android/jni/lzma/CPP/7zip/Archive/Common/DummyOutStream.h +25 -0
  280. package/android/jni/lzma/CPP/7zip/Archive/Common/HandlerOut.cpp +232 -0
  281. package/android/jni/lzma/CPP/7zip/Archive/Common/HandlerOut.h +110 -0
  282. package/android/jni/lzma/CPP/7zip/Archive/Common/InStreamWithCRC.cpp +46 -0
  283. package/android/jni/lzma/CPP/7zip/Archive/Common/InStreamWithCRC.h +67 -0
  284. package/android/jni/lzma/CPP/7zip/Archive/Common/ItemNameUtils.cpp +88 -0
  285. package/android/jni/lzma/CPP/7zip/Archive/Common/ItemNameUtils.h +28 -0
  286. package/android/jni/lzma/CPP/7zip/Archive/Common/MultiStream.cpp +191 -0
  287. package/android/jni/lzma/CPP/7zip/Archive/Common/MultiStream.h +89 -0
  288. package/android/jni/lzma/CPP/7zip/Archive/Common/OutStreamWithCRC.cpp +18 -0
  289. package/android/jni/lzma/CPP/7zip/Archive/Common/OutStreamWithCRC.h +37 -0
  290. package/android/jni/lzma/CPP/7zip/Archive/Common/ParseProperties.cpp +3 -0
  291. package/android/jni/lzma/CPP/7zip/Archive/Common/ParseProperties.h +6 -0
  292. package/android/jni/lzma/CPP/7zip/Archive/Common/StdAfx.h +8 -0
  293. package/android/jni/lzma/CPP/7zip/Archive/DllExports2.cpp +122 -0
  294. package/android/jni/lzma/CPP/7zip/Archive/IArchive.h +608 -0
  295. package/android/jni/lzma/CPP/7zip/Archive/Icons/7z.ico +0 -0
  296. package/android/jni/lzma/CPP/7zip/Archive/LzmaHandler.cpp +629 -0
  297. package/android/jni/lzma/CPP/7zip/Archive/SplitHandler.cpp +359 -0
  298. package/android/jni/lzma/CPP/7zip/Archive/StdAfx.h +8 -0
  299. package/android/jni/lzma/CPP/7zip/Archive/XzHandler.cpp +1308 -0
  300. package/android/jni/lzma/CPP/7zip/Archive/XzHandler.h +11 -0
  301. package/android/jni/lzma/CPP/7zip/Asm.mak +9 -0
  302. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/Alone.dsp +1910 -0
  303. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/Alone.dsw +29 -0
  304. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/StdAfx.cpp +3 -0
  305. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/StdAfx.h +8 -0
  306. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/makefile +154 -0
  307. package/android/jni/lzma/CPP/7zip/Bundles/Alone7z/resource.rc +7 -0
  308. package/android/jni/lzma/CPP/7zip/Bundles/Format7zExtractR/StdAfx.cpp +3 -0
  309. package/android/jni/lzma/CPP/7zip/Bundles/Format7zExtractR/StdAfx.h +8 -0
  310. package/android/jni/lzma/CPP/7zip/Bundles/Format7zExtractR/makefile +96 -0
  311. package/android/jni/lzma/CPP/7zip/Bundles/Format7zExtractR/resource.rc +5 -0
  312. package/android/jni/lzma/CPP/7zip/Bundles/Format7zR/StdAfx.cpp +3 -0
  313. package/android/jni/lzma/CPP/7zip/Bundles/Format7zR/StdAfx.h +8 -0
  314. package/android/jni/lzma/CPP/7zip/Bundles/Format7zR/makefile +116 -0
  315. package/android/jni/lzma/CPP/7zip/Bundles/Format7zR/resource.rc +5 -0
  316. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/LzmaAlone.cpp +799 -0
  317. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/LzmaCon.dsp +477 -0
  318. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/LzmaCon.dsw +29 -0
  319. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/StdAfx.cpp +3 -0
  320. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/StdAfx.h +8 -0
  321. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/makefile +59 -0
  322. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/makefile.gcc +195 -0
  323. package/android/jni/lzma/CPP/7zip/Bundles/LzmaCon/resource.rc +3 -0
  324. package/android/jni/lzma/CPP/7zip/Bundles/LzmaSpec/LzmaSpec.cpp +715 -0
  325. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/7z.ico +0 -0
  326. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/SFXCon.dsp +912 -0
  327. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/SFXCon.dsw +29 -0
  328. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/SfxCon.cpp +482 -0
  329. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/StdAfx.cpp +3 -0
  330. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/StdAfx.h +8 -0
  331. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/makefile +134 -0
  332. package/android/jni/lzma/CPP/7zip/Bundles/SFXCon/resource.rc +5 -0
  333. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/ExtractCallbackSfx.cpp +246 -0
  334. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/ExtractCallbackSfx.h +86 -0
  335. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/ExtractEngine.cpp +137 -0
  336. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/ExtractEngine.h +11 -0
  337. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/SFXSetup.dsp +803 -0
  338. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/SFXSetup.dsw +29 -0
  339. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/SfxSetup.cpp +364 -0
  340. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/StdAfx.cpp +3 -0
  341. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/StdAfx.h +13 -0
  342. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/makefile +117 -0
  343. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/resource.h +6 -0
  344. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/resource.rc +16 -0
  345. package/android/jni/lzma/CPP/7zip/Bundles/SFXSetup/setup.ico +0 -0
  346. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/7z.ico +0 -0
  347. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/SFXWin.dsp +988 -0
  348. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/SFXWin.dsw +29 -0
  349. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/SfxWin.cpp +241 -0
  350. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/StdAfx.cpp +3 -0
  351. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/StdAfx.h +14 -0
  352. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/makefile +153 -0
  353. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/resource.h +1 -0
  354. package/android/jni/lzma/CPP/7zip/Bundles/SFXWin/resource.rc +50 -0
  355. package/android/jni/lzma/CPP/7zip/Common/CWrappers.cpp +250 -0
  356. package/android/jni/lzma/CPP/7zip/Common/CWrappers.h +120 -0
  357. package/android/jni/lzma/CPP/7zip/Common/CreateCoder.cpp +536 -0
  358. package/android/jni/lzma/CPP/7zip/Common/CreateCoder.h +192 -0
  359. package/android/jni/lzma/CPP/7zip/Common/FilePathAutoRename.cpp +46 -0
  360. package/android/jni/lzma/CPP/7zip/Common/FilePathAutoRename.h +10 -0
  361. package/android/jni/lzma/CPP/7zip/Common/FileStreams.cpp +475 -0
  362. package/android/jni/lzma/CPP/7zip/Common/FileStreams.h +166 -0
  363. package/android/jni/lzma/CPP/7zip/Common/FilterCoder.cpp +435 -0
  364. package/android/jni/lzma/CPP/7zip/Common/FilterCoder.h +205 -0
  365. package/android/jni/lzma/CPP/7zip/Common/InBuffer.cpp +163 -0
  366. package/android/jni/lzma/CPP/7zip/Common/InBuffer.h +92 -0
  367. package/android/jni/lzma/CPP/7zip/Common/InOutTempBuffer.cpp +127 -0
  368. package/android/jni/lzma/CPP/7zip/Common/InOutTempBuffer.h +48 -0
  369. package/android/jni/lzma/CPP/7zip/Common/LimitedStreams.cpp +367 -0
  370. package/android/jni/lzma/CPP/7zip/Common/LimitedStreams.h +252 -0
  371. package/android/jni/lzma/CPP/7zip/Common/LockedStream.cpp +3 -0
  372. package/android/jni/lzma/CPP/7zip/Common/LockedStream.h +6 -0
  373. package/android/jni/lzma/CPP/7zip/Common/MethodId.cpp +3 -0
  374. package/android/jni/lzma/CPP/7zip/Common/MethodId.h +10 -0
  375. package/android/jni/lzma/CPP/7zip/Common/MethodProps.cpp +509 -0
  376. package/android/jni/lzma/CPP/7zip/Common/MethodProps.h +264 -0
  377. package/android/jni/lzma/CPP/7zip/Common/OffsetStream.cpp +39 -0
  378. package/android/jni/lzma/CPP/7zip/Common/OffsetStream.h +26 -0
  379. package/android/jni/lzma/CPP/7zip/Common/OutBuffer.cpp +111 -0
  380. package/android/jni/lzma/CPP/7zip/Common/OutBuffer.h +66 -0
  381. package/android/jni/lzma/CPP/7zip/Common/ProgressUtils.cpp +51 -0
  382. package/android/jni/lzma/CPP/7zip/Common/ProgressUtils.h +35 -0
  383. package/android/jni/lzma/CPP/7zip/Common/PropId.cpp +108 -0
  384. package/android/jni/lzma/CPP/7zip/Common/RegisterArc.h +78 -0
  385. package/android/jni/lzma/CPP/7zip/Common/RegisterCodec.h +106 -0
  386. package/android/jni/lzma/CPP/7zip/Common/StdAfx.h +8 -0
  387. package/android/jni/lzma/CPP/7zip/Common/StreamBinder.cpp +156 -0
  388. package/android/jni/lzma/CPP/7zip/Common/StreamBinder.h +60 -0
  389. package/android/jni/lzma/CPP/7zip/Common/StreamObjects.cpp +285 -0
  390. package/android/jni/lzma/CPP/7zip/Common/StreamObjects.h +157 -0
  391. package/android/jni/lzma/CPP/7zip/Common/StreamUtils.cpp +56 -0
  392. package/android/jni/lzma/CPP/7zip/Common/StreamUtils.h +13 -0
  393. package/android/jni/lzma/CPP/7zip/Common/UniqBlocks.cpp +57 -0
  394. package/android/jni/lzma/CPP/7zip/Common/UniqBlocks.h +26 -0
  395. package/android/jni/lzma/CPP/7zip/Common/VirtThread.cpp +48 -0
  396. package/android/jni/lzma/CPP/7zip/Common/VirtThread.h +24 -0
  397. package/android/jni/lzma/CPP/7zip/Compress/Bcj2Coder.cpp +666 -0
  398. package/android/jni/lzma/CPP/7zip/Compress/Bcj2Coder.h +120 -0
  399. package/android/jni/lzma/CPP/7zip/Compress/Bcj2Register.cpp +24 -0
  400. package/android/jni/lzma/CPP/7zip/Compress/BcjCoder.cpp +24 -0
  401. package/android/jni/lzma/CPP/7zip/Compress/BcjCoder.h +31 -0
  402. package/android/jni/lzma/CPP/7zip/Compress/BcjRegister.cpp +17 -0
  403. package/android/jni/lzma/CPP/7zip/Compress/BranchMisc.cpp +23 -0
  404. package/android/jni/lzma/CPP/7zip/Compress/BranchMisc.h +35 -0
  405. package/android/jni/lzma/CPP/7zip/Compress/BranchRegister.cpp +41 -0
  406. package/android/jni/lzma/CPP/7zip/Compress/ByteSwap.cpp +92 -0
  407. package/android/jni/lzma/CPP/7zip/Compress/CodecExports.cpp +344 -0
  408. package/android/jni/lzma/CPP/7zip/Compress/CopyCoder.cpp +120 -0
  409. package/android/jni/lzma/CPP/7zip/Compress/CopyCoder.h +49 -0
  410. package/android/jni/lzma/CPP/7zip/Compress/CopyRegister.cpp +15 -0
  411. package/android/jni/lzma/CPP/7zip/Compress/DeltaFilter.cpp +128 -0
  412. package/android/jni/lzma/CPP/7zip/Compress/Lzma2Decoder.cpp +265 -0
  413. package/android/jni/lzma/CPP/7zip/Compress/Lzma2Decoder.h +96 -0
  414. package/android/jni/lzma/CPP/7zip/Compress/Lzma2Encoder.cpp +122 -0
  415. package/android/jni/lzma/CPP/7zip/Compress/Lzma2Encoder.h +42 -0
  416. package/android/jni/lzma/CPP/7zip/Compress/Lzma2Register.cpp +22 -0
  417. package/android/jni/lzma/CPP/7zip/Compress/LzmaDecoder.cpp +343 -0
  418. package/android/jni/lzma/CPP/7zip/Compress/LzmaDecoder.h +113 -0
  419. package/android/jni/lzma/CPP/7zip/Compress/LzmaEncoder.cpp +182 -0
  420. package/android/jni/lzma/CPP/7zip/Compress/LzmaEncoder.h +46 -0
  421. package/android/jni/lzma/CPP/7zip/Compress/LzmaRegister.cpp +22 -0
  422. package/android/jni/lzma/CPP/7zip/Compress/PpmdDecoder.cpp +170 -0
  423. package/android/jni/lzma/CPP/7zip/Compress/PpmdDecoder.h +86 -0
  424. package/android/jni/lzma/CPP/7zip/Compress/PpmdEncoder.cpp +152 -0
  425. package/android/jni/lzma/CPP/7zip/Compress/PpmdEncoder.h +58 -0
  426. package/android/jni/lzma/CPP/7zip/Compress/PpmdRegister.cpp +22 -0
  427. package/android/jni/lzma/CPP/7zip/Compress/StdAfx.h +8 -0
  428. package/android/jni/lzma/CPP/7zip/Compress/XzDecoder.cpp +150 -0
  429. package/android/jni/lzma/CPP/7zip/Compress/XzDecoder.h +92 -0
  430. package/android/jni/lzma/CPP/7zip/Compress/XzEncoder.cpp +245 -0
  431. package/android/jni/lzma/CPP/7zip/Compress/XzEncoder.h +46 -0
  432. package/android/jni/lzma/CPP/7zip/Crc.mak +8 -0
  433. package/android/jni/lzma/CPP/7zip/Crc64.mak +8 -0
  434. package/android/jni/lzma/CPP/7zip/Crypto/7zAes.cpp +280 -0
  435. package/android/jni/lzma/CPP/7zip/Crypto/7zAes.h +118 -0
  436. package/android/jni/lzma/CPP/7zip/Crypto/7zAesRegister.cpp +17 -0
  437. package/android/jni/lzma/CPP/7zip/Crypto/MyAes.cpp +112 -0
  438. package/android/jni/lzma/CPP/7zip/Crypto/MyAes.h +57 -0
  439. package/android/jni/lzma/CPP/7zip/Crypto/MyAesReg.cpp +16 -0
  440. package/android/jni/lzma/CPP/7zip/Crypto/RandGen.cpp +233 -0
  441. package/android/jni/lzma/CPP/7zip/Crypto/RandGen.h +40 -0
  442. package/android/jni/lzma/CPP/7zip/Crypto/StdAfx.h +8 -0
  443. package/android/jni/lzma/CPP/7zip/GuiCommon.rc +84 -0
  444. package/android/jni/lzma/CPP/7zip/Guid.txt +220 -0
  445. package/android/jni/lzma/CPP/7zip/ICoder.h +399 -0
  446. package/android/jni/lzma/CPP/7zip/IDecl.h +28 -0
  447. package/android/jni/lzma/CPP/7zip/IPassword.h +23 -0
  448. package/android/jni/lzma/CPP/7zip/IProgress.h +19 -0
  449. package/android/jni/lzma/CPP/7zip/IStream.h +127 -0
  450. package/android/jni/lzma/CPP/7zip/LzmaDec.mak +5 -0
  451. package/android/jni/lzma/CPP/7zip/MyVersion.h +2 -0
  452. package/android/jni/lzma/CPP/7zip/MyVersionInfo.rc +2 -0
  453. package/android/jni/lzma/CPP/7zip/PropID.h +127 -0
  454. package/android/jni/lzma/CPP/7zip/SubBuild.mak +3 -0
  455. package/android/jni/lzma/CPP/7zip/UI/Client7z/Client7z.cpp +993 -0
  456. package/android/jni/lzma/CPP/7zip/UI/Client7z/Client7z.dsp +235 -0
  457. package/android/jni/lzma/CPP/7zip/UI/Client7z/Client7z.dsw +29 -0
  458. package/android/jni/lzma/CPP/7zip/UI/Client7z/StdAfx.cpp +3 -0
  459. package/android/jni/lzma/CPP/7zip/UI/Client7z/StdAfx.h +8 -0
  460. package/android/jni/lzma/CPP/7zip/UI/Client7z/makefile +28 -0
  461. package/android/jni/lzma/CPP/7zip/UI/Client7z/resource.rc +3 -0
  462. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveCommandLine.cpp +1295 -0
  463. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveCommandLine.h +136 -0
  464. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp +1715 -0
  465. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveExtractCallback.h +403 -0
  466. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveName.cpp +155 -0
  467. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveName.h +10 -0
  468. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveOpenCallback.cpp +161 -0
  469. package/android/jni/lzma/CPP/7zip/UI/Common/ArchiveOpenCallback.h +112 -0
  470. package/android/jni/lzma/CPP/7zip/UI/Common/Bench.cpp +3618 -0
  471. package/android/jni/lzma/CPP/7zip/UI/Common/Bench.h +77 -0
  472. package/android/jni/lzma/CPP/7zip/UI/Common/DefaultName.cpp +37 -0
  473. package/android/jni/lzma/CPP/7zip/UI/Common/DefaultName.h +11 -0
  474. package/android/jni/lzma/CPP/7zip/UI/Common/DirItem.h +190 -0
  475. package/android/jni/lzma/CPP/7zip/UI/Common/EnumDirItems.cpp +1096 -0
  476. package/android/jni/lzma/CPP/7zip/UI/Common/EnumDirItems.h +42 -0
  477. package/android/jni/lzma/CPP/7zip/UI/Common/ExitCode.h +27 -0
  478. package/android/jni/lzma/CPP/7zip/UI/Common/Extract.cpp +482 -0
  479. package/android/jni/lzma/CPP/7zip/UI/Common/Extract.h +94 -0
  480. package/android/jni/lzma/CPP/7zip/UI/Common/ExtractMode.h +34 -0
  481. package/android/jni/lzma/CPP/7zip/UI/Common/ExtractingFilePath.cpp +280 -0
  482. package/android/jni/lzma/CPP/7zip/UI/Common/ExtractingFilePath.h +31 -0
  483. package/android/jni/lzma/CPP/7zip/UI/Common/HashCalc.cpp +347 -0
  484. package/android/jni/lzma/CPP/7zip/UI/Common/HashCalc.h +110 -0
  485. package/android/jni/lzma/CPP/7zip/UI/Common/IFileExtractCallback.h +114 -0
  486. package/android/jni/lzma/CPP/7zip/UI/Common/LoadCodecs.cpp +1074 -0
  487. package/android/jni/lzma/CPP/7zip/UI/Common/LoadCodecs.h +424 -0
  488. package/android/jni/lzma/CPP/7zip/UI/Common/OpenArchive.cpp +3553 -0
  489. package/android/jni/lzma/CPP/7zip/UI/Common/OpenArchive.h +436 -0
  490. package/android/jni/lzma/CPP/7zip/UI/Common/PropIDUtils.cpp +668 -0
  491. package/android/jni/lzma/CPP/7zip/UI/Common/PropIDUtils.h +18 -0
  492. package/android/jni/lzma/CPP/7zip/UI/Common/Property.h +14 -0
  493. package/android/jni/lzma/CPP/7zip/UI/Common/SetProperties.cpp +80 -0
  494. package/android/jni/lzma/CPP/7zip/UI/Common/SetProperties.h +10 -0
  495. package/android/jni/lzma/CPP/7zip/UI/Common/SortUtils.cpp +25 -0
  496. package/android/jni/lzma/CPP/7zip/UI/Common/SortUtils.h +10 -0
  497. package/android/jni/lzma/CPP/7zip/UI/Common/StdAfx.h +8 -0
  498. package/android/jni/lzma/CPP/7zip/UI/Common/TempFiles.cpp +19 -0
  499. package/android/jni/lzma/CPP/7zip/UI/Common/TempFiles.h +16 -0
  500. package/android/jni/lzma/CPP/7zip/UI/Common/Update.cpp +1704 -0
  501. package/android/jni/lzma/CPP/7zip/UI/Common/Update.h +200 -0
  502. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateAction.cpp +64 -0
  503. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateAction.h +66 -0
  504. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateCallback.cpp +771 -0
  505. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateCallback.h +162 -0
  506. package/android/jni/lzma/CPP/7zip/UI/Common/UpdatePair.cpp +233 -0
  507. package/android/jni/lzma/CPP/7zip/UI/Common/UpdatePair.h +27 -0
  508. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateProduce.cpp +70 -0
  509. package/android/jni/lzma/CPP/7zip/UI/Common/UpdateProduce.h +55 -0
  510. package/android/jni/lzma/CPP/7zip/UI/Common/WorkDir.cpp +94 -0
  511. package/android/jni/lzma/CPP/7zip/UI/Common/WorkDir.h +26 -0
  512. package/android/jni/lzma/CPP/7zip/UI/Common/ZipRegistry.h +130 -0
  513. package/android/jni/lzma/CPP/7zip/UI/Console/BenchCon.cpp +41 -0
  514. package/android/jni/lzma/CPP/7zip/UI/Console/BenchCon.h +14 -0
  515. package/android/jni/lzma/CPP/7zip/UI/Console/Console.mak +43 -0
  516. package/android/jni/lzma/CPP/7zip/UI/Console/Console.manifest +13 -0
  517. package/android/jni/lzma/CPP/7zip/UI/Console/ConsoleClose.cpp +69 -0
  518. package/android/jni/lzma/CPP/7zip/UI/Console/ConsoleClose.h +33 -0
  519. package/android/jni/lzma/CPP/7zip/UI/Console/ExtractCallbackConsole.cpp +825 -0
  520. package/android/jni/lzma/CPP/7zip/UI/Console/ExtractCallbackConsole.h +164 -0
  521. package/android/jni/lzma/CPP/7zip/UI/Console/HashCon.cpp +367 -0
  522. package/android/jni/lzma/CPP/7zip/UI/Console/HashCon.h +48 -0
  523. package/android/jni/lzma/CPP/7zip/UI/Console/List.cpp +1359 -0
  524. package/android/jni/lzma/CPP/7zip/UI/Console/List.h +27 -0
  525. package/android/jni/lzma/CPP/7zip/UI/Console/Main.cpp +1154 -0
  526. package/android/jni/lzma/CPP/7zip/UI/Console/MainAr.cpp +175 -0
  527. package/android/jni/lzma/CPP/7zip/UI/Console/OpenCallbackConsole.cpp +115 -0
  528. package/android/jni/lzma/CPP/7zip/UI/Console/OpenCallbackConsole.h +66 -0
  529. package/android/jni/lzma/CPP/7zip/UI/Console/PercentPrinter.cpp +183 -0
  530. package/android/jni/lzma/CPP/7zip/UI/Console/PercentPrinter.h +62 -0
  531. package/android/jni/lzma/CPP/7zip/UI/Console/StdAfx.cpp +3 -0
  532. package/android/jni/lzma/CPP/7zip/UI/Console/StdAfx.h +8 -0
  533. package/android/jni/lzma/CPP/7zip/UI/Console/UpdateCallbackConsole.cpp +702 -0
  534. package/android/jni/lzma/CPP/7zip/UI/Console/UpdateCallbackConsole.h +124 -0
  535. package/android/jni/lzma/CPP/7zip/UI/Console/UserInputUtils.cpp +110 -0
  536. package/android/jni/lzma/CPP/7zip/UI/Console/UserInputUtils.h +27 -0
  537. package/android/jni/lzma/CPP/7zip/UI/Console/makefile +64 -0
  538. package/android/jni/lzma/CPP/7zip/UI/Console/resource.rc +7 -0
  539. package/android/jni/lzma/CPP/7zip/UI/Explorer/MyMessages.cpp +37 -0
  540. package/android/jni/lzma/CPP/7zip/UI/Explorer/MyMessages.h +16 -0
  541. package/android/jni/lzma/CPP/7zip/UI/FileManager/BrowseDialog.cpp +1025 -0
  542. package/android/jni/lzma/CPP/7zip/UI/FileManager/BrowseDialog.h +21 -0
  543. package/android/jni/lzma/CPP/7zip/UI/FileManager/BrowseDialogRes.h +9 -0
  544. package/android/jni/lzma/CPP/7zip/UI/FileManager/ComboDialog.cpp +64 -0
  545. package/android/jni/lzma/CPP/7zip/UI/FileManager/ComboDialog.h +28 -0
  546. package/android/jni/lzma/CPP/7zip/UI/FileManager/ComboDialogRes.h +4 -0
  547. package/android/jni/lzma/CPP/7zip/UI/FileManager/DialogSize.h +16 -0
  548. package/android/jni/lzma/CPP/7zip/UI/FileManager/ExtractCallback.cpp +1037 -0
  549. package/android/jni/lzma/CPP/7zip/UI/FileManager/ExtractCallback.h +328 -0
  550. package/android/jni/lzma/CPP/7zip/UI/FileManager/FormatUtils.cpp +28 -0
  551. package/android/jni/lzma/CPP/7zip/UI/FileManager/FormatUtils.h +14 -0
  552. package/android/jni/lzma/CPP/7zip/UI/FileManager/LangUtils.h +40 -0
  553. package/android/jni/lzma/CPP/7zip/UI/FileManager/MyWindowsNew.h +76 -0
  554. package/android/jni/lzma/CPP/7zip/UI/FileManager/OverwriteDialog.cpp +122 -0
  555. package/android/jni/lzma/CPP/7zip/UI/FileManager/OverwriteDialog.h +69 -0
  556. package/android/jni/lzma/CPP/7zip/UI/FileManager/OverwriteDialog.rc +91 -0
  557. package/android/jni/lzma/CPP/7zip/UI/FileManager/OverwriteDialogRes.h +17 -0
  558. package/android/jni/lzma/CPP/7zip/UI/FileManager/PasswordDialog.cpp +58 -0
  559. package/android/jni/lzma/CPP/7zip/UI/FileManager/PasswordDialog.h +28 -0
  560. package/android/jni/lzma/CPP/7zip/UI/FileManager/PasswordDialog.rc +14 -0
  561. package/android/jni/lzma/CPP/7zip/UI/FileManager/PasswordDialogRes.h +5 -0
  562. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog.cpp +196 -0
  563. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog.h +170 -0
  564. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog.rc +12 -0
  565. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog2.cpp +1337 -0
  566. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog2.h +351 -0
  567. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog2.rc +40 -0
  568. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog2Res.h +48 -0
  569. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialog2a.rc +80 -0
  570. package/android/jni/lzma/CPP/7zip/UI/FileManager/ProgressDialogRes.h +3 -0
  571. package/android/jni/lzma/CPP/7zip/UI/FileManager/PropertyName.cpp +23 -0
  572. package/android/jni/lzma/CPP/7zip/UI/FileManager/PropertyName.h +10 -0
  573. package/android/jni/lzma/CPP/7zip/UI/FileManager/PropertyNameRes.h +95 -0
  574. package/android/jni/lzma/CPP/7zip/UI/FileManager/SysIconUtils.cpp +255 -0
  575. package/android/jni/lzma/CPP/7zip/UI/FileManager/SysIconUtils.h +62 -0
  576. package/android/jni/lzma/CPP/7zip/UI/FileManager/resource.h +177 -0
  577. package/android/jni/lzma/CPP/7zip/UI/FileManager/resourceGui.h +15 -0
  578. package/android/jni/lzma/CPP/7zip/UI/GUI/Extract.rc +59 -0
  579. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractDialog.cpp +418 -0
  580. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractDialog.h +113 -0
  581. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractDialog.rc +98 -0
  582. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractDialogRes.h +24 -0
  583. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractGUI.cpp +280 -0
  584. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractGUI.h +38 -0
  585. package/android/jni/lzma/CPP/7zip/UI/GUI/ExtractRes.h +51 -0
  586. package/android/jni/lzma/CPP/7zip/UI/GUI/HashGUI.h +27 -0
  587. package/android/jni/lzma/CPP/7zip/UI/GUI/resource2.h +2 -0
  588. package/android/jni/lzma/CPP/Build.mak +156 -0
  589. package/android/jni/lzma/CPP/Common/AutoPtr.h +35 -0
  590. package/android/jni/lzma/CPP/Common/CRC.cpp +7 -0
  591. package/android/jni/lzma/CPP/Common/C_FileIO.cpp +92 -0
  592. package/android/jni/lzma/CPP/Common/C_FileIO.h +53 -0
  593. package/android/jni/lzma/CPP/Common/ComTry.h +21 -0
  594. package/android/jni/lzma/CPP/Common/CommandLineParser.cpp +197 -0
  595. package/android/jni/lzma/CPP/Common/CommandLineParser.h +63 -0
  596. package/android/jni/lzma/CPP/Common/Common.h +43 -0
  597. package/android/jni/lzma/CPP/Common/CrcReg.cpp +98 -0
  598. package/android/jni/lzma/CPP/Common/Defs.h +15 -0
  599. package/android/jni/lzma/CPP/Common/DynamicBuffer.h +64 -0
  600. package/android/jni/lzma/CPP/Common/IntToString.cpp +193 -0
  601. package/android/jni/lzma/CPP/Common/IntToString.h +28 -0
  602. package/android/jni/lzma/CPP/Common/Lang.h +23 -0
  603. package/android/jni/lzma/CPP/Common/ListFileUtils.cpp +132 -0
  604. package/android/jni/lzma/CPP/Common/ListFileUtils.h +18 -0
  605. package/android/jni/lzma/CPP/Common/MyBuffer.h +259 -0
  606. package/android/jni/lzma/CPP/Common/MyBuffer2.h +100 -0
  607. package/android/jni/lzma/CPP/Common/MyCom.h +277 -0
  608. package/android/jni/lzma/CPP/Common/MyException.h +14 -0
  609. package/android/jni/lzma/CPP/Common/MyGuidDef.h +54 -0
  610. package/android/jni/lzma/CPP/Common/MyInitGuid.h +45 -0
  611. package/android/jni/lzma/CPP/Common/MyLinux.h +42 -0
  612. package/android/jni/lzma/CPP/Common/MyString.cpp +1659 -0
  613. package/android/jni/lzma/CPP/Common/MyString.h +868 -0
  614. package/android/jni/lzma/CPP/Common/MyTypes.h +35 -0
  615. package/android/jni/lzma/CPP/Common/MyUnknown.h +17 -0
  616. package/android/jni/lzma/CPP/Common/MyVector.cpp +3 -0
  617. package/android/jni/lzma/CPP/Common/MyVector.h +634 -0
  618. package/android/jni/lzma/CPP/Common/MyWindows.cpp +145 -0
  619. package/android/jni/lzma/CPP/Common/MyWindows.h +231 -0
  620. package/android/jni/lzma/CPP/Common/NewHandler.cpp +163 -0
  621. package/android/jni/lzma/CPP/Common/NewHandler.h +88 -0
  622. package/android/jni/lzma/CPP/Common/Sha256Reg.cpp +40 -0
  623. package/android/jni/lzma/CPP/Common/StdAfx.h +8 -0
  624. package/android/jni/lzma/CPP/Common/StdInStream.cpp +89 -0
  625. package/android/jni/lzma/CPP/Common/StdInStream.h +38 -0
  626. package/android/jni/lzma/CPP/Common/StdOutStream.cpp +163 -0
  627. package/android/jni/lzma/CPP/Common/StdOutStream.h +71 -0
  628. package/android/jni/lzma/CPP/Common/StringConvert.cpp +319 -0
  629. package/android/jni/lzma/CPP/Common/StringConvert.h +88 -0
  630. package/android/jni/lzma/CPP/Common/StringToInt.cpp +144 -0
  631. package/android/jni/lzma/CPP/Common/StringToInt.h +21 -0
  632. package/android/jni/lzma/CPP/Common/TextConfig.cpp +124 -0
  633. package/android/jni/lzma/CPP/Common/TextConfig.h +19 -0
  634. package/android/jni/lzma/CPP/Common/UTFConvert.cpp +288 -0
  635. package/android/jni/lzma/CPP/Common/UTFConvert.h +12 -0
  636. package/android/jni/lzma/CPP/Common/Wildcard.cpp +676 -0
  637. package/android/jni/lzma/CPP/Common/Wildcard.h +149 -0
  638. package/android/jni/lzma/CPP/Common/XzCrc64Init.cpp +7 -0
  639. package/android/jni/lzma/CPP/Common/XzCrc64Reg.cpp +42 -0
  640. package/android/jni/lzma/CPP/Windows/COM.h +70 -0
  641. package/android/jni/lzma/CPP/Windows/CommonDialog.cpp +185 -0
  642. package/android/jni/lzma/CPP/Windows/CommonDialog.h +23 -0
  643. package/android/jni/lzma/CPP/Windows/Control/ComboBox.cpp +66 -0
  644. package/android/jni/lzma/CPP/Windows/Control/ComboBox.h +65 -0
  645. package/android/jni/lzma/CPP/Windows/Control/CommandBar.h +52 -0
  646. package/android/jni/lzma/CPP/Windows/Control/Dialog.cpp +251 -0
  647. package/android/jni/lzma/CPP/Windows/Control/Dialog.h +170 -0
  648. package/android/jni/lzma/CPP/Windows/Control/Edit.h +19 -0
  649. package/android/jni/lzma/CPP/Windows/Control/ImageList.cpp +10 -0
  650. package/android/jni/lzma/CPP/Windows/Control/ImageList.h +87 -0
  651. package/android/jni/lzma/CPP/Windows/Control/ListView.cpp +155 -0
  652. package/android/jni/lzma/CPP/Windows/Control/ListView.h +146 -0
  653. package/android/jni/lzma/CPP/Windows/Control/ProgressBar.h +35 -0
  654. package/android/jni/lzma/CPP/Windows/Control/PropertyPage.cpp +143 -0
  655. package/android/jni/lzma/CPP/Windows/Control/PropertyPage.h +50 -0
  656. package/android/jni/lzma/CPP/Windows/Control/ReBar.h +34 -0
  657. package/android/jni/lzma/CPP/Windows/Control/Static.h +28 -0
  658. package/android/jni/lzma/CPP/Windows/Control/StatusBar.h +42 -0
  659. package/android/jni/lzma/CPP/Windows/Control/StdAfx.h +8 -0
  660. package/android/jni/lzma/CPP/Windows/Control/ToolBar.h +43 -0
  661. package/android/jni/lzma/CPP/Windows/Control/Trackbar.h +27 -0
  662. package/android/jni/lzma/CPP/Windows/Control/Window2.cpp +200 -0
  663. package/android/jni/lzma/CPP/Windows/Control/Window2.h +51 -0
  664. package/android/jni/lzma/CPP/Windows/DLL.cpp +109 -0
  665. package/android/jni/lzma/CPP/Windows/DLL.h +58 -0
  666. package/android/jni/lzma/CPP/Windows/Defs.h +17 -0
  667. package/android/jni/lzma/CPP/Windows/ErrorMsg.cpp +66 -0
  668. package/android/jni/lzma/CPP/Windows/ErrorMsg.h +15 -0
  669. package/android/jni/lzma/CPP/Windows/FileDir.cpp +714 -0
  670. package/android/jni/lzma/CPP/Windows/FileDir.h +117 -0
  671. package/android/jni/lzma/CPP/Windows/FileFind.cpp +749 -0
  672. package/android/jni/lzma/CPP/Windows/FileFind.h +161 -0
  673. package/android/jni/lzma/CPP/Windows/FileIO.cpp +432 -0
  674. package/android/jni/lzma/CPP/Windows/FileIO.h +212 -0
  675. package/android/jni/lzma/CPP/Windows/FileLink.cpp +440 -0
  676. package/android/jni/lzma/CPP/Windows/FileMapping.cpp +12 -0
  677. package/android/jni/lzma/CPP/Windows/FileMapping.h +66 -0
  678. package/android/jni/lzma/CPP/Windows/FileName.cpp +839 -0
  679. package/android/jni/lzma/CPP/Windows/FileName.h +115 -0
  680. package/android/jni/lzma/CPP/Windows/FileSystem.cpp +131 -0
  681. package/android/jni/lzma/CPP/Windows/FileSystem.h +27 -0
  682. package/android/jni/lzma/CPP/Windows/Handle.h +37 -0
  683. package/android/jni/lzma/CPP/Windows/MemoryLock.cpp +112 -0
  684. package/android/jni/lzma/CPP/Windows/MemoryLock.h +40 -0
  685. package/android/jni/lzma/CPP/Windows/NtCheck.h +46 -0
  686. package/android/jni/lzma/CPP/Windows/PropVariant.cpp +347 -0
  687. package/android/jni/lzma/CPP/Windows/PropVariant.h +114 -0
  688. package/android/jni/lzma/CPP/Windows/PropVariantConv.cpp +138 -0
  689. package/android/jni/lzma/CPP/Windows/PropVariantConv.h +37 -0
  690. package/android/jni/lzma/CPP/Windows/Registry.cpp +390 -0
  691. package/android/jni/lzma/CPP/Windows/Registry.h +84 -0
  692. package/android/jni/lzma/CPP/Windows/ResourceString.cpp +103 -0
  693. package/android/jni/lzma/CPP/Windows/ResourceString.h +16 -0
  694. package/android/jni/lzma/CPP/Windows/SecurityUtils.cpp +181 -0
  695. package/android/jni/lzma/CPP/Windows/SecurityUtils.h +167 -0
  696. package/android/jni/lzma/CPP/Windows/Shell.cpp +358 -0
  697. package/android/jni/lzma/CPP/Windows/Shell.h +94 -0
  698. package/android/jni/lzma/CPP/Windows/StdAfx.h +8 -0
  699. package/android/jni/lzma/CPP/Windows/Synchronization.cpp +10 -0
  700. package/android/jni/lzma/CPP/Windows/Synchronization.h +164 -0
  701. package/android/jni/lzma/CPP/Windows/System.cpp +142 -0
  702. package/android/jni/lzma/CPP/Windows/System.h +40 -0
  703. package/android/jni/lzma/CPP/Windows/Thread.h +38 -0
  704. package/android/jni/lzma/CPP/Windows/TimeUtils.cpp +213 -0
  705. package/android/jni/lzma/CPP/Windows/TimeUtils.h +32 -0
  706. package/android/jni/lzma/CPP/Windows/Window.cpp +179 -0
  707. package/android/jni/lzma/CPP/Windows/Window.h +284 -0
  708. package/android/jni/lzma/CS/7zip/Common/CRC.cs +55 -0
  709. package/android/jni/lzma/CS/7zip/Common/CommandLineParser.cs +274 -0
  710. package/android/jni/lzma/CS/7zip/Common/InBuffer.cs +72 -0
  711. package/android/jni/lzma/CS/7zip/Common/OutBuffer.cs +47 -0
  712. package/android/jni/lzma/CS/7zip/Compress/LZ/IMatchFinder.cs +24 -0
  713. package/android/jni/lzma/CS/7zip/Compress/LZ/LzBinTree.cs +367 -0
  714. package/android/jni/lzma/CS/7zip/Compress/LZ/LzInWindow.cs +132 -0
  715. package/android/jni/lzma/CS/7zip/Compress/LZ/LzOutWindow.cs +110 -0
  716. package/android/jni/lzma/CS/7zip/Compress/LZMA/LzmaBase.cs +76 -0
  717. package/android/jni/lzma/CS/7zip/Compress/LZMA/LzmaDecoder.cs +398 -0
  718. package/android/jni/lzma/CS/7zip/Compress/LZMA/LzmaEncoder.cs +1480 -0
  719. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/LzmaAlone.cs +364 -0
  720. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/LzmaAlone.csproj +90 -0
  721. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/LzmaAlone.sln +20 -0
  722. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/LzmaBench.cs +340 -0
  723. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/Properties/AssemblyInfo.cs +29 -0
  724. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/Properties/Resources.cs +70 -0
  725. package/android/jni/lzma/CS/7zip/Compress/LzmaAlone/Properties/Settings.cs +42 -0
  726. package/android/jni/lzma/CS/7zip/Compress/RangeCoder/RangeCoder.cs +234 -0
  727. package/android/jni/lzma/CS/7zip/Compress/RangeCoder/RangeCoderBit.cs +117 -0
  728. package/android/jni/lzma/CS/7zip/Compress/RangeCoder/RangeCoderBitTree.cs +157 -0
  729. package/android/jni/lzma/CS/7zip/ICoder.cs +157 -0
  730. package/android/jni/lzma/DOC/7zC.txt +187 -0
  731. package/android/jni/lzma/DOC/7zFormat.txt +469 -0
  732. package/android/jni/lzma/DOC/Methods.txt +173 -0
  733. package/android/jni/lzma/DOC/installer.txt +166 -0
  734. package/android/jni/lzma/DOC/lzma-history.txt +446 -0
  735. package/android/jni/lzma/DOC/lzma-sdk.txt +357 -0
  736. package/android/jni/lzma/DOC/lzma-specification.txt +1176 -0
  737. package/android/jni/lzma/DOC/lzma.txt +328 -0
  738. package/android/jni/lzma/Java/SevenZip/CRC.java +52 -0
  739. package/android/jni/lzma/Java/SevenZip/Compression/LZ/BinTree.java +382 -0
  740. package/android/jni/lzma/Java/SevenZip/Compression/LZ/InWindow.java +131 -0
  741. package/android/jni/lzma/Java/SevenZip/Compression/LZ/OutWindow.java +85 -0
  742. package/android/jni/lzma/Java/SevenZip/Compression/LZMA/Base.java +88 -0
  743. package/android/jni/lzma/Java/SevenZip/Compression/LZMA/Decoder.java +329 -0
  744. package/android/jni/lzma/Java/SevenZip/Compression/LZMA/Encoder.java +1416 -0
  745. package/android/jni/lzma/Java/SevenZip/Compression/RangeCoder/BitTreeDecoder.java +55 -0
  746. package/android/jni/lzma/Java/SevenZip/Compression/RangeCoder/BitTreeEncoder.java +99 -0
  747. package/android/jni/lzma/Java/SevenZip/Compression/RangeCoder/Decoder.java +88 -0
  748. package/android/jni/lzma/Java/SevenZip/Compression/RangeCoder/Encoder.java +151 -0
  749. package/android/jni/lzma/Java/SevenZip/ICodeProgress.java +6 -0
  750. package/android/jni/lzma/Java/SevenZip/LzmaAlone.java +253 -0
  751. package/android/jni/lzma/Java/SevenZip/LzmaBench.java +392 -0
  752. package/android/jni/lzma/bin/7zS2.sfx +0 -0
  753. package/android/jni/lzma/bin/7zS2con.sfx +0 -0
  754. package/android/jni/lzma/bin/7zSD.sfx +0 -0
  755. package/android/jni/lzma/bin/installer/config.txt +5 -0
  756. package/android/jni/lzma/bin/installer/cr.bat +5 -0
  757. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  758. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  759. package/android/lib/x86/librnupdate.so +0 -0
  760. package/android/lib/x86_64/librnupdate.so +0 -0
  761. package/android/proguard.pro +83 -0
  762. package/android/src/main/AndroidManifest.xml +14 -0
  763. package/android/src/main/AndroidManifestNew.xml +13 -0
  764. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +292 -0
  765. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +50 -0
  766. package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +11 -0
  767. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +394 -0
  768. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +30 -0
  769. package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +11 -0
  770. package/android/src/main/java/cn/reactnative/modules/update/CrashRescue.java +120 -0
  771. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +954 -0
  772. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +64 -0
  773. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +32 -0
  774. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +20 -0
  775. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +64 -0
  776. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +156 -0
  777. package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +725 -0
  778. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateCore.java +43 -0
  779. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +30 -0
  780. package/android/src/main/java/cn/reactnative/modules/update/PackageInstallerStatusReceiver.java +17 -0
  781. package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +21 -0
  782. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +274 -0
  783. package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +187 -0
  784. package/android/src/main/java/cn/reactnative/modules/update/StateCoreResult.java +22 -0
  785. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +70 -0
  786. package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +42 -0
  787. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +952 -0
  788. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +70 -0
  789. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +110 -0
  790. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +403 -0
  791. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +51 -0
  792. package/android/src/main/java/expo/modules/pakta/ExpoPaktaModule.kt +15 -0
  793. package/android/src/main/java/expo/modules/pakta/ExpoPaktaPackage.java +29 -0
  794. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +126 -0
  795. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +130 -0
  796. package/cpp/patch_core/archive_limits.h +49 -0
  797. package/cpp/patch_core/archive_patch_core.cpp +146 -0
  798. package/cpp/patch_core/archive_patch_core.h +69 -0
  799. package/cpp/patch_core/digest.cpp +235 -0
  800. package/cpp/patch_core/digest.h +66 -0
  801. package/cpp/patch_core/error_codes.h +57 -0
  802. package/cpp/patch_core/hbc_transform.cpp +152 -0
  803. package/cpp/patch_core/hbc_transform.h +58 -0
  804. package/cpp/patch_core/hbc_transform_wire.cpp +332 -0
  805. package/cpp/patch_core/hbc_transform_wire.h +57 -0
  806. package/cpp/patch_core/install_record.h +41 -0
  807. package/cpp/patch_core/jni_util.h +65 -0
  808. package/cpp/patch_core/patch_core.cpp +849 -0
  809. package/cpp/patch_core/patch_core.h +103 -0
  810. package/cpp/patch_core/patch_core_android.cpp +102 -0
  811. package/cpp/patch_core/state_core.cpp +122 -0
  812. package/cpp/patch_core/state_core.h +66 -0
  813. package/cpp/patch_core/state_ops.h +29 -0
  814. package/cpp/patch_core/update_core_android.cpp +502 -0
  815. package/cpp/update_flow_core/flow_json.cpp +660 -0
  816. package/cpp/update_flow_core/flow_json.h +121 -0
  817. package/cpp/update_flow_core/update_flow_core.cpp +418 -0
  818. package/cpp/update_flow_core/update_flow_core.h +100 -0
  819. package/cpp/update_flow_core/update_flow_jni.cpp +157 -0
  820. package/expo-module.config.json +13 -0
  821. package/harmony/hvigor-plugin.ts +212 -0
  822. package/harmony/pakta/src/README.md +74 -0
  823. package/harmony/pakta/src/main/cpp/CMakeLists.txt +92 -0
  824. package/harmony/pakta/src/main/cpp/PaktaPackage.h +60 -0
  825. package/harmony/pakta/src/main/cpp/PaktaTurboModule.cpp +123 -0
  826. package/harmony/pakta/src/main/cpp/PaktaTurboModule.h +43 -0
  827. package/harmony/pakta/src/main/cpp/pakta.cpp +1243 -0
  828. package/harmony/pakta/src/main/ets/ArchiveLimits.ts +144 -0
  829. package/harmony/pakta/src/main/ets/DownloadTask.ts +1375 -0
  830. package/harmony/pakta/src/main/ets/DownloadTaskParams.ts +27 -0
  831. package/harmony/pakta/src/main/ets/ErrorCodes.ts +80 -0
  832. package/harmony/pakta/src/main/ets/EventHub.ts +46 -0
  833. package/harmony/pakta/src/main/ets/InstallRecord.ts +192 -0
  834. package/harmony/pakta/src/main/ets/Logger.ts +83 -0
  835. package/harmony/pakta/src/main/ets/MonotonicClock.ts +18 -0
  836. package/harmony/pakta/src/main/ets/NativeCheckOrchestrator.ts +640 -0
  837. package/harmony/pakta/src/main/ets/NativePatchCore.ts +123 -0
  838. package/harmony/pakta/src/main/ets/PaktaFileJSBundleProvider.ets +54 -0
  839. package/harmony/pakta/src/main/ets/PaktaPackage.ets +15 -0
  840. package/harmony/pakta/src/main/ets/PaktaPackageCompat.ts +15 -0
  841. package/harmony/pakta/src/main/ets/PaktaPackageFactory.ts +19 -0
  842. package/harmony/pakta/src/main/ets/PaktaTurboModule.ts +433 -0
  843. package/harmony/pakta/src/main/ets/PathUtils.ts +32 -0
  844. package/harmony/pakta/src/main/ets/SaveFile.ts +39 -0
  845. package/harmony/pakta/src/main/ets/UpdateContext.ts +1059 -0
  846. package/harmony/pakta/src/main/module.json5 +14 -0
  847. package/harmony/pakta/src/main/resources/base/element/string.json +8 -0
  848. package/harmony/pakta/src/main/resources/en_US/element/string.json +8 -0
  849. package/harmony/pakta/src/main/resources/zh_CN/element/string.json +8 -0
  850. package/harmony/pakta.har +0 -0
  851. package/ios/Expo/ExpoPaktaModule.swift +12 -0
  852. package/ios/Expo/ExpoPaktaReactDelegateHandler.swift +25 -0
  853. package/ios/ImportReact.h +7 -0
  854. package/ios/PrivacyInfo.xcprivacy +84 -0
  855. package/ios/RCTPakta/RCTPakta.h +14 -0
  856. package/ios/RCTPakta/RCTPakta.mm +2775 -0
  857. package/ios/RCTPakta/RCTPaktaDownloader.h +29 -0
  858. package/ios/RCTPakta/RCTPaktaDownloader.mm +625 -0
  859. package/ios/pakta_build_time.txt +1 -0
  860. package/package.json +88 -0
  861. package/react-native.config.js +6 -0
  862. package/rn-update.podspec +221 -0
  863. package/src/NativePakta.ts +85 -0
  864. package/src/client.ts +1839 -0
  865. package/src/context.ts +102 -0
  866. package/src/core.ts +190 -0
  867. package/src/endpoint.ts +204 -0
  868. package/src/error.ts +168 -0
  869. package/src/errorReporting.ts +141 -0
  870. package/src/globals.d.ts +6 -0
  871. package/src/i18n.ts +113 -0
  872. package/src/index.ts +46 -0
  873. package/src/locales/en.ts +77 -0
  874. package/src/locales/zh.ts +71 -0
  875. package/src/metadata.ts +175 -0
  876. package/src/provider.tsx +629 -0
  877. package/src/telemetry.ts +104 -0
  878. package/src/type.ts +243 -0
  879. package/src/updateFlowCore.ts +428 -0
  880. package/src/utils.ts +268 -0
@@ -0,0 +1,2775 @@
1
+ /**
2
+ * [INPUT]: 依赖 React Native 桥/事件与重载 API、RCTPaktaDownloader、SSZipArchive、UIKit/NSUserDefaults、JS 冻结的启动版本身份及 patch/state/update_flow 共享 C++ 核心
3
+ * [OUTPUT]: 对外实现 RCTPakta 模块、bundleURL、下载/切包/回滚/重置/配置 API、版本绑定成功确认、进度事件和无 JS 冷启动检查/崩溃救援
4
+ * [POS]: iOS 更新聚合核心,统一状态锁、文件队列和原生救砖提交;Swift Expo 层与 JS 桥均复用这一事实源
5
+ */
6
+ #import "RCTPakta.h"
7
+ #import "RCTPaktaDownloader.h"
8
+ #import "ZipArchive.h"
9
+ #include "../../cpp/patch_core/archive_limits.h"
10
+ #include "../../cpp/patch_core/archive_patch_core.h"
11
+ #include "../../cpp/patch_core/digest.h"
12
+ #include "../../cpp/patch_core/hbc_transform_wire.h"
13
+ #include "../../cpp/patch_core/install_record.h"
14
+ #include "../../cpp/patch_core/error_codes.h"
15
+ #include "../../cpp/patch_core/patch_core.h"
16
+ #include "../../cpp/patch_core/state_core.h"
17
+ #include "../../cpp/update_flow_core/flow_json.h"
18
+ #include "../../cpp/update_flow_core/update_flow_core.h"
19
+
20
+ #import <UIKit/UIKit.h>
21
+
22
+ // use_frameworks! 构建里 React 头文件只有 <React/…> 框架形式可见,引号形式的
23
+ // __has_include 会探测失败;漏掉这里会静默编入 [self.bridge reload] 兜底,而
24
+ // bridgeless 下 bridge 是 nil/代理,reload 变成无声 no-op(切版本后不重启)。
25
+ #if __has_include("RCTReloadCommand.h")
26
+ #import "RCTReloadCommand.h"
27
+ #define PAKTA_HAS_RELOAD_COMMAND 1
28
+ #elif __has_include(<React/RCTReloadCommand.h>)
29
+ #import <React/RCTReloadCommand.h>
30
+ #define PAKTA_HAS_RELOAD_COMMAND 1
31
+ #endif
32
+ #ifdef RCT_NEW_ARCH_ENABLED
33
+ #import "RCTPaktaSpec.h"
34
+ #endif
35
+
36
+ #import <React/RCTConvert.h>
37
+ #import <React/RCTLog.h>
38
+ #import <objc/runtime.h>
39
+ #import <os/lock.h>
40
+
41
+ #include <atomic>
42
+ #include <sys/stat.h>
43
+
44
+ static NSString *const keyPaktaInfo = @"REACTNATIVECN_PAKTA_INFO_KEY";
45
+ // Binary identity (SyncBinaryVersion input). Prefixed like every other key:
46
+ // the unprefixed names collided with generic host-app/SDK defaults, and a
47
+ // foreign value there made every launch look like a rebuilt binary and wipe
48
+ // the installed update.
49
+ static NSString *const paramPackageVersion = @"REACTNATIVECN_PAKTA_PACKAGEVERSION";
50
+ static NSString *const paramBuildTime = @"REACTNATIVECN_PAKTA_BUILDTIME";
51
+ static NSString *const keyBinaryChannel = @"REACTNATIVECN_PAKTA_BINARY_CHANNEL";
52
+ // The unprefixed names earlier SDKs wrote. Read only as a fallback until the
53
+ // first state write migrates them (PaktaStateFromDefaults /
54
+ // PaktaApplyStateToDefaults).
55
+ static NSString *const legacyParamPackageVersion = @"packageVersion";
56
+ static NSString *const legacyParamBuildTime = @"buildTime";
57
+ static NSString *const paramLastVersion = @"lastVersion";
58
+ static NSString *const paramCurrentVersion = @"currentVersion";
59
+ static NSString *const paramIsFirstTime = @"isFirstTime";
60
+ static NSString *const paramIsFirstLoadOk = @"isFirstLoadOK";
61
+ static NSString *const keyUuid = @"REACTNATIVECN_PAKTA_UUID";
62
+ static NSString *const keyHashInfo = @"REACTNATIVECN_PAKTA_HASH_";
63
+ static NSString *const keyFirstLoadMarked = @"REACTNATIVECN_PAKTA_FIRSTLOADMARKED_KEY";
64
+ static NSString *const keyRolledBackMarked = @"REACTNATIVECN_PAKTA_ROLLEDBACKMARKED_KEY";
65
+ static NSString *const KeyPackageUpdatedMarked = @"REACTNATIVECN_PAKTA_ISPACKAGEUPDATEDMARKED_KEY";
66
+ // bundleHash cache: "<cacheKey>|<sha256hex>" where cacheKey identifies the
67
+ // installed binary (packageVersion + embedded bundle size + mtime). Recomputed
68
+ // only when the key changes, i.e. once per install.
69
+ static NSString *const keyBundleHashCache = @"REACTNATIVECN_PAKTA_BUNDLEHASH_KEY";
70
+ // Raw JSON persisted by JS (syncNativeConfig) for the native cold-start
71
+ // update check; parsed on read by the orchestrator. Absent = check disabled.
72
+ static NSString *const keyNativeConfig = @"REACTNATIVECN_PAKTA_NATIVE_CONFIG_KEY";
73
+ // Raw response cache written by the native cold-start check for the JS side
74
+ // to reuse (§10.3), scoped to the request and config that produced it.
75
+ static NSString *const keyNativeCheckCache = @"REACTNATIVECN_PAKTA_NATIVE_CHECK_RESP_KEY";
76
+ // Set when a native check round starts, cleared when it ends (§11.4). Residue
77
+ // on the next launch means the previous process died mid-round (a crash
78
+ // rescue was truncated): that launch resumes immediately instead of waiting.
79
+ static NSString *const keyNativeCheckIncomplete = @"REACTNATIVECN_PAKTA_NATIVE_CHECK_INCOMPLETE_KEY";
80
+ static NSString *const PaktaErrorDomain = @"cn.reactnative.pakta";
81
+
82
+ // file def
83
+ static NSString * const BUNDLE_FILE_NAME = @"index.bundlejs";
84
+ static NSString * const SOURCE_PATCH_NAME = @"__diff.json";
85
+ static NSString * const BUNDLE_PATCH_NAME = @"index.bundlejs.patch";
86
+ #define VERSION_COMPLETE_FILE_NAME_LITERAL ".pakta-complete"
87
+ static NSString * const VERSION_COMPLETE_FILE_NAME = @VERSION_COMPLETE_FILE_NAME_LITERAL;
88
+
89
+ // error def — messages are human-readable; the stable cross-platform codes
90
+ // live in cpp/patch_core/error_codes.h and travel in PaktaErrorCodeKey.
91
+ static NSString * const ERROR_OPTIONS = @"options error";
92
+ static NSString * const ERROR_FILE_OPERATION = @"file operation error";
93
+ static NSString * const PaktaErrorCodeKey = @"PaktaErrorCode";
94
+
95
+ static NSString *PaktaCode(const char *code) {
96
+ return [NSString stringWithUTF8String:code];
97
+ }
98
+
99
+ // event def
100
+ static NSString * const EVENT_PROGRESS_DOWNLOAD = @"RCTPaktaDownloadProgress";
101
+ static NSString * const PARAM_PROGRESS_HASH = @"hash";
102
+ static NSString * const PARAM_PROGRESS_RECEIVED = @"received";
103
+ static NSString * const PARAM_PROGRESS_TOTAL = @"total";
104
+
105
+ static NSTimeInterval PaktaMonotonicNow(void) {
106
+ return [NSProcessInfo processInfo].systemUptime;
107
+ }
108
+
109
+ static std::string PaktaToStdString(NSString *value);
110
+
111
+ static NSString *PaktaStagingDirForVersionDir(NSString *versionDir) {
112
+ return [versionDir stringByAppendingString:@(pakta::install_record::kStagingSuffix)];
113
+ }
114
+
115
+ // Parsed completion record (cpp/patch_core/install_record.h): nil when the
116
+ // file is absent or malformed; an empty dictionary for the legacy empty
117
+ // marker written by SDK < 10.53.
118
+ static NSDictionary *PaktaReadInstallRecord(NSString *versionDir) {
119
+ NSString *path = [versionDir stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME];
120
+ NSData *data = [NSData dataWithContentsOfFile:path];
121
+ if (data == nil) {
122
+ return nil;
123
+ }
124
+ if (data.length == 0) {
125
+ return @{};
126
+ }
127
+ if (data.length > 64 * 1024) {
128
+ return nil;
129
+ }
130
+ id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
131
+ return [object isKindOfClass:[NSDictionary class]] ? (NSDictionary *)object : nil;
132
+ }
133
+
134
+ // Presence check (launch / dedup paths, no digest work): bundle present and
135
+ // the record exists and, unless legacy-empty, names this version.
136
+ static BOOL PaktaHasCompletedVersionAtPath(NSString *versionDir, NSString *hash) {
137
+ NSString *bundlePath = [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
138
+ if (![[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
139
+ return NO;
140
+ }
141
+ NSDictionary *record = PaktaReadInstallRecord(versionDir);
142
+ if (record == nil) {
143
+ return NO;
144
+ }
145
+ if (record.count == 0) {
146
+ return YES;
147
+ }
148
+ return [record[@"schema"] isKindOfClass:[NSNumber class]]
149
+ && [record[@"schema"] intValue] == pakta::install_record::kSchema
150
+ && [record[@"versionHash"] isKindOfClass:[NSString class]]
151
+ && [record[@"versionHash"] isEqualToString:hash];
152
+ }
153
+
154
+ // Activation check: re-hashes the bundle when the record carries a digest.
155
+ // Returns nil when the directory may be activated, the reason otherwise.
156
+ static NSString *PaktaVerifyInstallForActivation(NSString *versionDir, NSString *hash) {
157
+ NSDictionary *record = PaktaReadInstallRecord(versionDir);
158
+ if (record == nil) {
159
+ return [NSString stringWithFormat:@"Bundle version %@ has no valid completion record.", hash];
160
+ }
161
+ if (record.count == 0) {
162
+ return nil;
163
+ }
164
+ if (![record[@"schema"] isKindOfClass:[NSNumber class]]
165
+ || [record[@"schema"] intValue] != pakta::install_record::kSchema
166
+ || ![record[@"versionHash"] isKindOfClass:[NSString class]]
167
+ || ![record[@"versionHash"] isEqualToString:hash]) {
168
+ return [NSString stringWithFormat:@"Bundle version %@ completion record mismatch.", hash];
169
+ }
170
+ NSString *expected = record[@"bundleSha256"];
171
+ if (![expected isKindOfClass:[NSString class]] || expected.length == 0) {
172
+ return nil;
173
+ }
174
+ std::string actual = pakta::digest::Sha256File(
175
+ PaktaToStdString([versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]));
176
+ if ([[expected lowercaseString] isEqualToString:[NSString stringWithUTF8String:actual.c_str()]]) {
177
+ return nil;
178
+ }
179
+ return [NSString stringWithFormat:@"Bundle version %@ bundle digest mismatch.", hash];
180
+ }
181
+
182
+ // SHA-256 of each in-flight download's archive, keyed by hash; consumed by
183
+ // the completion record write. Guarded by @synchronized on the table.
184
+ static NSMutableDictionary<NSString *, NSString *> *PaktaArtifactDigests(void) {
185
+ static NSMutableDictionary *table;
186
+ static dispatch_once_t once;
187
+ dispatch_once(&once, ^{
188
+ table = [NSMutableDictionary dictionary];
189
+ });
190
+ return table;
191
+ }
192
+
193
+ static NSError *PaktaErrorWithCode(const char *code, NSString *message);
194
+
195
+ // nil when the volume holding `path` can take `bytesToWrite` plus the safety
196
+ // margin (or the free space is unknown); a PATCH_FAILED error otherwise. The
197
+ // probe is the downloader's (RCTPaktaFreeSpaceShortfall), which applies the
198
+ // same rule to the announced Content-Length before the first byte lands.
199
+ static NSError *PaktaEnsureFreeSpace(NSString *path, long long bytesToWrite) {
200
+ NSString *shortfall = RCTPaktaFreeSpaceShortfall(path, bytesToWrite);
201
+ if (shortfall == nil) {
202
+ return nil;
203
+ }
204
+ return PaktaErrorWithCode(pakta::error_codes::kPatchFailed, shortfall);
205
+ }
206
+
207
+ // SSZipArchive delegate enforcing cpp/patch_core/archive_limits.h while the
208
+ // archive is walked (entry count, per-entry size, total size, compression
209
+ // ratio) plus two entry-shape rules Android's java.util.zip gets for free:
210
+ // - no symlinks: SSZipArchive materialises them with any target regardless
211
+ // of preserveAttributes, so `assets -> ../../Documents` followed by
212
+ // `assets/x` would write outside the version directory;
213
+ // - no ".pakta-" reserved names at any depth: the completion marker is
214
+ // written by this SDK only, after the whole install succeeded. An archive
215
+ // shipping its own would be trusted as a finished install even if the
216
+ // patch step that follows fails.
217
+ // A NO from zipArchiveShouldUnzipFileAtIndex: cancels the whole unzip (the
218
+ // 2.x contract the podspec pins), and the completion handler turns the
219
+ // recorded violation into a hard PATCH_FAILED; the caller then discards the
220
+ // staging directory.
221
+ @interface PaktaUnzipGuard : NSObject <SSZipArchiveDelegate>
222
+ @property (nonatomic, copy) NSString *violation;
223
+ @property (nonatomic, assign) long long totalUncompressed;
224
+ @end
225
+
226
+ @implementation PaktaUnzipGuard
227
+
228
+ - (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo {
229
+ if ((long long)zipInfo.number_entry > pakta::archive_limits::kMaxEntries) {
230
+ self.violation = [NSString stringWithFormat:@"archive has too many entries (%lu)",
231
+ (unsigned long)zipInfo.number_entry];
232
+ }
233
+ }
234
+
235
+ - (BOOL)zipArchiveShouldUnzipFileAtIndex:(NSInteger)fileIndex
236
+ totalFiles:(NSInteger)totalFiles
237
+ archivePath:(NSString *)archivePath
238
+ fileInfo:(unz_file_info)fileInfo {
239
+ if (self.violation != nil) {
240
+ return NO;
241
+ }
242
+ // Unix mode bits travel in the high 16 bits of the external attributes
243
+ // (the same field SSZipArchive keys its own symlink branch on).
244
+ if (((fileInfo.external_fa >> 16) & S_IFMT) == S_IFLNK) {
245
+ self.violation = [NSString stringWithFormat:@"archive entry #%ld is a symlink",
246
+ (long)fileIndex];
247
+ return NO;
248
+ }
249
+ long long size = (long long)fileInfo.uncompressed_size;
250
+ long long compressed = (long long)fileInfo.compressed_size;
251
+ if (size > pakta::archive_limits::kMaxEntryBytes) {
252
+ self.violation = [NSString stringWithFormat:@"archive entry #%ld too large (%lld bytes)",
253
+ (long)fileIndex, size];
254
+ return NO;
255
+ }
256
+ self.totalUncompressed += size;
257
+ if (self.totalUncompressed > pakta::archive_limits::kMaxTotalUncompressedBytes) {
258
+ self.violation = [NSString stringWithFormat:@"archive expands beyond %lld bytes",
259
+ pakta::archive_limits::kMaxTotalUncompressedBytes];
260
+ return NO;
261
+ }
262
+ if (compressed > 0 && size > pakta::archive_limits::kRatioCheckMinBytes
263
+ && size / compressed > pakta::archive_limits::kMaxCompressionRatio) {
264
+ self.violation = [NSString stringWithFormat:@"archive entry #%ld compression ratio too high",
265
+ (long)fileIndex];
266
+ return NO;
267
+ }
268
+ return YES;
269
+ }
270
+
271
+ // The entry name is only exposed once the entry is on disk (shouldUnzip
272
+ // runs before the name is read). A reserved name is flagged here; the next
273
+ // shouldUnzip refuses (cancelling the unzip) or, for the last entry, the
274
+ // completion handler picks the violation up. Either way the caller drops
275
+ // the whole staging directory, so the written file never survives.
276
+ - (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex
277
+ totalFiles:(NSInteger)totalFiles
278
+ archivePath:(NSString *)archivePath
279
+ unzippedFilePath:(NSString *)unzippedFilePath {
280
+ NSString *name = unzippedFilePath.lastPathComponent;
281
+ if (self.violation == nil && [name hasPrefix:@".pakta-"]) {
282
+ self.violation = [NSString stringWithFormat:@"archive contains reserved entry %@", name];
283
+ }
284
+ }
285
+
286
+ @end
287
+
288
+ static NSError *PaktaDownloadDeadlineExpiredError(void) {
289
+ return [NSError errorWithDomain:PaktaErrorDomain
290
+ code:-1
291
+ userInfo:@{
292
+ NSLocalizedDescriptionKey: @"download deadline expired before start",
293
+ PaktaErrorCodeKey: PaktaCode(pakta::error_codes::kDownloadFailed),
294
+ }];
295
+ }
296
+
297
+
298
+ typedef NS_ENUM(NSInteger, PaktaType) {
299
+ PaktaTypeFullDownload = 1,
300
+ PaktaTypePatchFromPackage = 2,
301
+ PaktaTypePatchFromPpk = 3,
302
+ };
303
+
304
+ static std::atomic<bool> ignoreRollback{false};
305
+ // Whether the host asked +bundleURL for the bundle to load (the
306
+ // isUsingBundleUrl constant, mirroring Android/Harmony). NO means the app
307
+ // runs a bundle this SDK never resolved, so updates cannot take effect.
308
+ static std::atomic<bool> paktaIsUsingBundleUrl{false};
309
+ // Bumped by resetToPackagedBundle. The cold-start check runs for minutes and
310
+ // may already hold a decision when the app resets to the packaged bundle; it
311
+ // samples this counter and abandons activation (and its response cache) when
312
+ // the value moved, so an in-flight rescue can never resurrect the version the
313
+ // app just reset away from.
314
+ static std::atomic<uint64_t> paktaResetGeneration{0};
315
+ // The version whose bundle this process actually loaded (resolved in
316
+ // +bundleURL). resetToPackagedBundle must not delete its directory: update
317
+ // assets (images/fonts) are read from it on demand at runtime, so wiping it
318
+ // under a silent (no-restart) reset would break every image the running app
319
+ // has not loaded yet. Guarded by the state lock.
320
+ static NSString *paktaLaunchVersion = nil;
321
+
322
+ // JS and the bridge-free cold-start engine use different RCTPakta instances,
323
+ // but they must still share one download per target hash. Without this
324
+ // process-wide registry two NSURLSessionDownloadTasks race over the same
325
+ // archive path and the later unzip can delete the first task's valid output.
326
+ static NSMutableDictionary<NSString *, NSMutableDictionary *> *PaktaInFlightDownloads(void) {
327
+ static NSMutableDictionary<NSString *, NSMutableDictionary *> *downloads;
328
+ static dispatch_once_t onceToken;
329
+ dispatch_once(&onceToken, ^{
330
+ downloads = [NSMutableDictionary dictionary];
331
+ });
332
+ return downloads;
333
+ }
334
+
335
+ typedef NS_ENUM(NSInteger, PaktaDownloadRegistration) {
336
+ PaktaDownloadRegistrationOwner,
337
+ PaktaDownloadRegistrationJoined,
338
+ PaktaDownloadRegistrationDeferred,
339
+ };
340
+
341
+ static PaktaDownloadRegistration PaktaRegisterDownload(
342
+ NSString *hash,
343
+ NSInteger type,
344
+ NSTimeInterval deadlineUptime,
345
+ void (^callback)(NSError *),
346
+ void (^progress)(long long, long long),
347
+ void (^deferredStart)(void)
348
+ ) {
349
+ NSMutableDictionary *downloads = PaktaInFlightDownloads();
350
+ @synchronized (downloads) {
351
+ NSMutableDictionary *entry = downloads[hash];
352
+ if (entry != nil) {
353
+ if ([entry[@"type"] integerValue] == type) {
354
+ NSTimeInterval ownerDeadline = [entry[@"deadlineUptime"] doubleValue];
355
+ // A caller with substantially more time must not inherit an
356
+ // owner's nearly-exhausted timeout: it observes the current
357
+ // transfer and restarts after the owner settles (the
358
+ // completion-marker preflight makes a successful owner free).
359
+ // The comparison is on remaining budget, not on the absolute
360
+ // deadline: a JS caller always computes now+600 a few seconds
361
+ // after the owner did, so a strict `>` would defer every
362
+ // second caller and turn the shared download back into
363
+ // serialized re-downloads. Only a genuinely starved owner
364
+ // (less than half the newcomer's budget left) defers.
365
+ const NSTimeInterval now = PaktaMonotonicNow();
366
+ if (2 * (ownerDeadline - now) < (deadlineUptime - now)) {
367
+ if (progress != nil) {
368
+ [entry[@"progress"] addObject:[progress copy]];
369
+ }
370
+ [entry[@"deferred"] addObject:[deferredStart copy]];
371
+ return PaktaDownloadRegistrationDeferred;
372
+ }
373
+ [entry[@"callbacks"] addObject:[callback copy]];
374
+ if (progress != nil) {
375
+ [entry[@"progress"] addObject:[progress copy]];
376
+ }
377
+ return PaktaDownloadRegistrationJoined;
378
+ }
379
+ // A diff failure must not settle a joined full request. Queue the
380
+ // different artifact type behind the owner; once restarted it
381
+ // registers normally (and re-checks the completion marker).
382
+ if (progress != nil) {
383
+ // The artifact type differs, but it still installs the same
384
+ // target hash. Keep the waiting JS UI moving while its own
385
+ // transfer is queued behind the current owner.
386
+ [entry[@"progress"] addObject:[progress copy]];
387
+ }
388
+ [entry[@"deferred"] addObject:[deferredStart copy]];
389
+ return PaktaDownloadRegistrationDeferred;
390
+ }
391
+ NSMutableArray *progressHandlers = [NSMutableArray array];
392
+ if (progress != nil) {
393
+ [progressHandlers addObject:[progress copy]];
394
+ }
395
+ downloads[hash] = [@{
396
+ @"type": @(type),
397
+ @"deadlineUptime": @(deadlineUptime),
398
+ @"callbacks": [NSMutableArray arrayWithObject:[callback copy]],
399
+ @"progress": progressHandlers,
400
+ @"deferred": [NSMutableArray array],
401
+ } mutableCopy];
402
+ return PaktaDownloadRegistrationOwner;
403
+ }
404
+ }
405
+
406
+ static void PaktaReportDownloadProgress(
407
+ NSString *hash, long long received, long long total
408
+ ) {
409
+ NSMutableDictionary *downloads = PaktaInFlightDownloads();
410
+ NSArray *handlers = nil;
411
+ @synchronized (downloads) {
412
+ handlers = [downloads[hash][@"progress"] copy];
413
+ }
414
+ for (id value in handlers) {
415
+ void (^handler)(long long, long long) =
416
+ (void (^)(long long, long long))value;
417
+ handler(received, total);
418
+ }
419
+ }
420
+
421
+ static void PaktaFinishDownload(NSString *hash, NSError *error) {
422
+ NSMutableDictionary *downloads = PaktaInFlightDownloads();
423
+ NSArray *callbacks = nil;
424
+ NSArray *deferred = nil;
425
+ @synchronized (downloads) {
426
+ callbacks = [downloads[hash][@"callbacks"] copy];
427
+ deferred = [downloads[hash][@"deferred"] copy];
428
+ [downloads removeObjectForKey:hash];
429
+ }
430
+ // Establish the next (different-type) owner before waking the completed
431
+ // owner's callbacks; otherwise its strategy loop could race the waiter.
432
+ for (id value in deferred) {
433
+ void (^start)(void) = (void (^)(void))value;
434
+ start();
435
+ }
436
+ for (id value in callbacks) {
437
+ void (^callback)(NSError *) = (void (^)(NSError *))value;
438
+ callback(error);
439
+ }
440
+ }
441
+
442
+ // Serializes every read-modify-write of the persisted update state. The state
443
+ // machine itself is a pure function (state_core), but callers run on different
444
+ // threads (main thread bundleURL, module method queue, _fileQueue), so the
445
+ // read→transform→write sequence must be atomic to avoid e.g. markSuccess being
446
+ // overwritten by a concurrent bundleURL and the version being rolled back.
447
+ static os_unfair_lock paktaStateLock = OS_UNFAIR_LOCK_INIT;
448
+
449
+ static void PaktaWithStateLock(void (NS_NOESCAPE ^block)(void)) {
450
+ #if DEBUG
451
+ // Not re-entrant: a nested acquisition deadlocks in release, so catch it
452
+ // where it is written.
453
+ os_unfair_lock_assert_not_owner(&paktaStateLock);
454
+ #endif
455
+ os_unfair_lock_lock(&paktaStateLock);
456
+ @try {
457
+ block();
458
+ } @finally {
459
+ os_unfair_lock_unlock(&paktaStateLock);
460
+ }
461
+ }
462
+
463
+ static std::string PaktaToStdString(NSString *value) {
464
+ if (value == nil) {
465
+ return std::string();
466
+ }
467
+ return std::string([value UTF8String]);
468
+ }
469
+
470
+ static NSError *PaktaNSErrorFromStatus(const pakta::patch::Status &status) {
471
+ return [NSError errorWithDomain:PaktaErrorDomain
472
+ code:-1
473
+ userInfo:@{
474
+ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:status.message.c_str()],
475
+ PaktaErrorCodeKey: PaktaCode(pakta::error_codes::kPatchFailed),
476
+ }];
477
+ }
478
+
479
+ static NSUserDefaults *PaktaDefaults(void) {
480
+ return [NSUserDefaults standardUserDefaults];
481
+ }
482
+
483
+ static NSString *PaktaFromStdString(const std::string &value) {
484
+ if (value.empty()) {
485
+ return nil;
486
+ }
487
+ return [NSString stringWithUTF8String:value.c_str()];
488
+ }
489
+
490
+ static void PaktaSetNullableString(NSUserDefaults *defaults, NSString *key, NSString *value) {
491
+ if (value != nil) {
492
+ [defaults setObject:value forKey:key];
493
+ } else {
494
+ [defaults removeObjectForKey:key];
495
+ }
496
+ }
497
+
498
+ static NSString *PaktaHashInfoKey(NSString *hash) {
499
+ return [keyHashInfo stringByAppendingString:hash ?: @""];
500
+ }
501
+
502
+ static NSString *PaktaOptionString(NSDictionary *options, NSString *key) {
503
+ return [RCTConvert NSString:options[key]];
504
+ }
505
+
506
+ static BOOL PaktaStringIsBlank(NSString *value) {
507
+ if (value == nil || [value isKindOfClass:[NSNull class]]) {
508
+ return YES;
509
+ }
510
+ return [[value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0;
511
+ }
512
+
513
+ // Server-provided identifiers (hash/originHash) are used as child names under
514
+ // the download root; anything that could resolve outside of it (path
515
+ // separators, "..", ".") must be rejected before touching the filesystem.
516
+ static BOOL PaktaIsSafePathComponent(NSString *value) {
517
+ if (PaktaStringIsBlank(value)) {
518
+ return NO;
519
+ }
520
+ if ([value isEqualToString:@"."] || [value isEqualToString:@".."]) {
521
+ return NO;
522
+ }
523
+ if ([value containsString:@"/"] || [value containsString:@"\\"]) {
524
+ return NO;
525
+ }
526
+ if ([value rangeOfString:@"\0"].location != NSNotFound) {
527
+ return NO;
528
+ }
529
+ return YES;
530
+ }
531
+
532
+ static void PaktaRejectError(RCTPromiseRejectBlock reject, NSError *error) {
533
+ // Prefer the stable cross-platform code (error_codes.h); fall back to the
534
+ // numeric NSError code for system errors that were not classified.
535
+ NSString *code = error.userInfo[PaktaErrorCodeKey];
536
+ if (code == nil) {
537
+ code = [NSString stringWithFormat:@"%ld", (long)error.code];
538
+ }
539
+ reject(code, error.localizedDescription, error);
540
+ }
541
+
542
+ static NSError *PaktaErrorWithCode(const char *code, NSString *message) {
543
+ return [NSError errorWithDomain:PaktaErrorDomain
544
+ code:-1
545
+ userInfo:@{
546
+ NSLocalizedDescriptionKey: message ?: @"unknown error",
547
+ PaktaErrorCodeKey: PaktaCode(code),
548
+ }];
549
+ }
550
+
551
+ static BOOL PaktaJsonIsAbsent(id value) {
552
+ return value == nil || [value isKindOfClass:[NSNull class]];
553
+ }
554
+
555
+ // Builds the patch-core manifest from a parsed __diff.json. Every field is
556
+ // type-checked: this runs inside a GCD block on _fileQueue, where the
557
+ // unrecognized-selector exception a `copies` array or a numeric `from` would
558
+ // raise takes the whole app down (and trips the crash rescue) instead of
559
+ // failing the patch. NO with `reason` set for a malformed manifest.
560
+ static BOOL PaktaPatchManifestFromJson(NSDictionary *json,
561
+ pakta::patch::PatchManifest *manifest,
562
+ NSString **reason) {
563
+ id copies = json[@"copies"];
564
+ if (!PaktaJsonIsAbsent(copies)) {
565
+ if (![copies isKindOfClass:[NSDictionary class]]) {
566
+ *reason = @"patch manifest: copies is not an object";
567
+ return NO;
568
+ }
569
+ // Content checksum per copy target ("copiesCrc", pdiff manifests
570
+ // from CLI >= 1.0.0). The patch core verifies the copy source
571
+ // against it and fails the patch on mismatch, so the JS strategy
572
+ // chain falls back to the full package instead of copying drifted
573
+ // bytes from a rebuilt binary.
574
+ NSDictionary *copiesCrc = json[@"copiesCrc"];
575
+ if (![copiesCrc isKindOfClass:[NSDictionary class]]) {
576
+ copiesCrc = nil;
577
+ }
578
+ for (id to in (NSDictionary *)copies) {
579
+ id from = ((NSDictionary *)copies)[to];
580
+ if (![to isKindOfClass:[NSString class]] || ![from isKindOfClass:[NSString class]]) {
581
+ *reason = @"patch manifest: copies entry is not a string";
582
+ return NO;
583
+ }
584
+ if ([from length] == 0) {
585
+ from = to;
586
+ }
587
+ pakta::patch::CopyOperation operation;
588
+ operation.from = PaktaToStdString(from);
589
+ operation.to = PaktaToStdString(to);
590
+ NSNumber *expectedCrc = copiesCrc[to];
591
+ if ([expectedCrc isKindOfClass:[NSNumber class]]) {
592
+ operation.has_expected_crc = true;
593
+ operation.expected_crc = (uint32_t)[expectedCrc unsignedLongLongValue];
594
+ }
595
+ manifest->copies.push_back(operation);
596
+ }
597
+ }
598
+
599
+ // The CLI emits `deletes` as an object keyed by path (Android reads its
600
+ // keys); a plain array of paths is accepted as well, like Harmony.
601
+ id deletes = json[@"deletes"];
602
+ if (!PaktaJsonIsAbsent(deletes)) {
603
+ if (![deletes isKindOfClass:[NSDictionary class]] && ![deletes isKindOfClass:[NSArray class]]) {
604
+ *reason = @"patch manifest: deletes is not an object or array";
605
+ return NO;
606
+ }
607
+ for (id path in deletes) {
608
+ if (![path isKindOfClass:[NSString class]]) {
609
+ *reason = @"patch manifest: deletes entry is not a string";
610
+ return NO;
611
+ }
612
+ manifest->deletes.push_back(PaktaToStdString(path));
613
+ }
614
+ }
615
+ return YES;
616
+ }
617
+
618
+ // Binary identity under the prefixed key, or — until the first state write
619
+ // migrates it — the value an earlier SDK left under the unprefixed one, so an
620
+ // installed update survives the SDK upgrade instead of reading as a rebuilt
621
+ // binary.
622
+ static NSString *PaktaBinaryIdentityValue(NSUserDefaults *defaults, NSString *key, NSString *legacyKey) {
623
+ return [defaults stringForKey:key] ?: [defaults stringForKey:legacyKey];
624
+ }
625
+
626
+ static pakta::state::State PaktaStateFromDefaults(NSUserDefaults *defaults) {
627
+ pakta::state::State state;
628
+ state.package_version = PaktaToStdString(
629
+ PaktaBinaryIdentityValue(defaults, paramPackageVersion, legacyParamPackageVersion));
630
+ state.build_time = PaktaToStdString(
631
+ PaktaBinaryIdentityValue(defaults, paramBuildTime, legacyParamBuildTime));
632
+ NSDictionary *paktaInfo = [defaults dictionaryForKey:keyPaktaInfo];
633
+ if (paktaInfo != nil) {
634
+ state.current_version = PaktaToStdString(paktaInfo[paramCurrentVersion]);
635
+ state.last_version = PaktaToStdString(paktaInfo[paramLastVersion]);
636
+ state.first_time = [paktaInfo[paramIsFirstTime] boolValue];
637
+ id firstLoadOk = paktaInfo[paramIsFirstLoadOk];
638
+ state.first_time_ok = firstLoadOk == nil ? true : [firstLoadOk boolValue];
639
+ }
640
+ state.rolled_back_version = PaktaToStdString([defaults stringForKey:keyRolledBackMarked]);
641
+ return state;
642
+ }
643
+
644
+ static void PaktaApplyStateToDefaults(NSUserDefaults *defaults, const pakta::state::State &state) {
645
+ PaktaSetNullableString(defaults, paramPackageVersion, PaktaFromStdString(state.package_version));
646
+ PaktaSetNullableString(defaults, paramBuildTime, PaktaFromStdString(state.build_time));
647
+ // One-time migration: once the prefixed keys hold the identity, the
648
+ // legacy ones are retired so a host-app write there is never read again.
649
+ [defaults removeObjectForKey:legacyParamPackageVersion];
650
+ [defaults removeObjectForKey:legacyParamBuildTime];
651
+
652
+ BOOL hasPaktaInfo = !state.current_version.empty() || !state.last_version.empty() || state.first_time || !state.first_time_ok;
653
+ if (hasPaktaInfo) {
654
+ NSMutableDictionary *newInfo = [[NSMutableDictionary alloc] init];
655
+ if (!state.current_version.empty()) {
656
+ newInfo[paramCurrentVersion] = PaktaFromStdString(state.current_version);
657
+ }
658
+ if (!state.last_version.empty()) {
659
+ newInfo[paramLastVersion] = PaktaFromStdString(state.last_version);
660
+ }
661
+ newInfo[paramIsFirstTime] = @(state.first_time);
662
+ newInfo[paramIsFirstLoadOk] = @(state.first_time_ok);
663
+ [defaults setObject:newInfo forKey:keyPaktaInfo];
664
+ } else {
665
+ [defaults removeObjectForKey:keyPaktaInfo];
666
+ }
667
+
668
+ PaktaSetNullableString(
669
+ defaults,
670
+ keyRolledBackMarked,
671
+ PaktaFromStdString(state.rolled_back_version));
672
+ }
673
+
674
+ // Version switch without acquiring the state lock: the caller must already
675
+ // hold it. Lets the cold-start check commit its whole result (version info,
676
+ // switch, response cache) inside one lock acquisition, so resetToPackagedBundle
677
+ // can never interleave between the generation check and the writes.
678
+ static void PaktaSwitchVersionLocked(NSString *hash) {
679
+ NSUserDefaults *defaults = PaktaDefaults();
680
+ pakta::state::State next = pakta::state::SwitchVersion(
681
+ PaktaStateFromDefaults(defaults),
682
+ PaktaToStdString(hash)
683
+ );
684
+ PaktaApplyStateToDefaults(defaults, next);
685
+ // Re-enable first-load consumption and rollback checks for the newly selected bundle.
686
+ ignoreRollback = false;
687
+ }
688
+
689
+ @interface RCTPakta ()
690
+ - (void)downloadUpdate:(PaktaType)type
691
+ options:(NSDictionary *)options
692
+ resolver:(RCTPromiseResolveBlock)resolve
693
+ rejecter:(RCTPromiseRejectBlock)reject;
694
+ - (void)performUpdate:(PaktaType)type
695
+ options:(NSDictionary *)options
696
+ callback:(void (^)(NSError *error))callback;
697
+ - (NSError *)reloadBridgeWithReason:(NSString *)reason;
698
+ - (void)unzipDownloadedPackage:(NSString *)zipFilePath
699
+ hash:(NSString *)hash
700
+ type:(PaktaType)type
701
+ originHash:(NSString *)originHash
702
+ callback:(void (^)(NSError *error))callback;
703
+ - (void)finishDownloadedPackage:(NSString *)hash
704
+ type:(PaktaType)type
705
+ originHash:(NSString *)originHash
706
+ callback:(void (^)(NSError *error))callback;
707
+ - (void)applyPatchForHash:(NSString *)hash
708
+ type:(PaktaType)type
709
+ fromBundle:(NSString *)bundleOrigin
710
+ source:(NSString *)sourceOrigin
711
+ callback:(void (^)(NSError *error))callback;
712
+ - (BOOL)switchVersion:(NSString *)hash error:(NSError **)error;
713
+ - (BOOL)ensureDirectoryExistsAtPath:(NSString *)path;
714
+ + (void)excludeFromBackup:(NSString *)path;
715
+ - (void)unzipFileAtPath:(NSString *)path
716
+ toDestination:(NSString *)destination
717
+ completionHandler:(void (^)(NSError *error))completionHandler;
718
+ + (NSString *)downloadDir;
719
+ + (NSURL *)binaryBundleURL;
720
+ + (NSString *)packageVersion;
721
+ + (NSString *)buildTime;
722
+ + (NSString *)channel;
723
+ @end
724
+
725
+ // Native cold-start update check (NATIVE_CHECKUPDATE_DESIGN §10): runs once
726
+ // per process, a few seconds after launch, entirely independent of the app
727
+ // bundle — this is what lets a bricked hot update be replaced on the next
728
+ // launch. Decisions come from cpp/update_flow_core; this class is IO glue.
729
+ @interface RCTPaktaOrchestrator : NSObject
730
+ + (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion;
731
+ + (void)markJsCheckCompleted:(NSString *)config;
732
+ + (void)startRoundWithDeadline:(NSTimeInterval)deadlineUptime;
733
+ + (void)runOnce:(NSString *)launchRolledBackVersion deadline:(NSTimeInterval)deadlineUptime;
734
+ + (void)runRescueWithDeadline:(NSTimeInterval)deadlineUptime;
735
+ + (BOOL)commitRoundWithGeneration:(uint64_t)generation
736
+ hashInfo:(NSDictionary *)hashInfoEntry
737
+ activate:(NSString *)hashToActivate
738
+ responseText:(NSString *)responseText
739
+ request:(NSString *)requestBody
740
+ config:(NSString *)configJson
741
+ responseAt:(long long)responseAtSeconds;
742
+ @end
743
+
744
+ // One round per process, whoever starts it first — the delayed cold-start
745
+ // path or the crash-rescue thread (§11.3). The semaphore lets the rescue
746
+ // wait out an in-flight round instead of racing it.
747
+ static std::atomic<bool> paktaRoundStarted{false};
748
+ static std::atomic<bool> paktaRoundCompleted{false};
749
+ // Flipped the moment a crash is being held. JS is dead from that point on,
750
+ // so there is no second decision maker: the round force-activates whatever
751
+ // it downloads (§11.3).
752
+ static std::atomic<bool> paktaCrashRescueActive{false};
753
+ static std::atomic<bool> paktaRescueAttempted{false};
754
+ static dispatch_semaphore_t paktaRoundDone;
755
+ static NSString *paktaLaunchRolledBackForRescue = nil;
756
+ // A version this process downloaded but left for JS to activate. If the
757
+ // process then crashes, JS will never activate it — the crash handler
758
+ // activates it directly (bounded local work, no network). The generation is
759
+ // the one the round committed under: a reset that lands afterwards bumps it,
760
+ // and the late activation must lose to that reset exactly like the round
761
+ // itself would. Guarded by @synchronized (RCTPaktaOrchestrator class).
762
+ static NSString *paktaUnactivatedHash = nil;
763
+ static uint64_t paktaUnactivatedGeneration = 0;
764
+ static NSUncaughtExceptionHandler *paktaPreviousExceptionHandler = NULL;
765
+ // ≈ process start: scheduleFromColdStart runs during the first bundleURL.
766
+ static NSTimeInterval paktaProcessAnchorUptime = 0;
767
+ // Config JSON for which JS reported a completed check in this process
768
+ // (markJsCheckCompleted). Process-scoped by design. Guarded by
769
+ // @synchronized (RCTPaktaOrchestrator class).
770
+ static NSString *paktaJsCompletedConfig = nil;
771
+
772
+ static const NSTimeInterval kPaktaRescueTriggerUptime = 60;
773
+ static const NSTimeInterval kPaktaRescueBudgetBackgroundThread = 10;
774
+ // A held main thread freezes UI teardown; stay well under the watchdog.
775
+ static const NSTimeInterval kPaktaRescueBudgetMainThread = 3.5;
776
+
777
+ static void PaktaMaybeHoldForRescue(void) {
778
+ // Once per process; a second crashing thread passes straight through
779
+ // instead of waiting behind the first (§11.3: prefer under-rescuing over
780
+ // wedging the teardown).
781
+ bool expected = false;
782
+ if (!paktaRescueAttempted.compare_exchange_strong(expected, true)) {
783
+ return;
784
+ }
785
+ NSTimeInterval uptime = PaktaMonotonicNow() - paktaProcessAnchorUptime;
786
+ bool roundInFlight = paktaRoundStarted.load() && !paktaRoundCompleted.load();
787
+ // Early crashes are the brick signature; a crash with an in-flight round
788
+ // is worth finishing regardless of uptime. Everything else is an ordinary
789
+ // crash whose UX must not be delayed.
790
+ if (uptime >= kPaktaRescueTriggerUptime && !roundInFlight) {
791
+ return;
792
+ }
793
+ NSTimeInterval budget = [NSThread isMainThread]
794
+ ? kPaktaRescueBudgetMainThread
795
+ : kPaktaRescueBudgetBackgroundThread;
796
+ NSTimeInterval deadline = PaktaMonotonicNow() + budget;
797
+ NSLog(@"RCTPakta -- crash rescue: holding process for up to %.1fs "
798
+ @"(uptime %.1fs)", budget, uptime);
799
+
800
+ // The rescue runs on its own queue and the dying thread only waits with a
801
+ // hard timeout: even a deadlocked rescue (a thread that died holding a
802
+ // lock — uncaught ObjC exceptions skip @finally) can only delay the
803
+ // process death, never prevent it.
804
+ dispatch_semaphore_t done = dispatch_semaphore_create(0);
805
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
806
+ @try {
807
+ [RCTPaktaOrchestrator runRescueWithDeadline:deadline];
808
+ } @catch (NSException *exception) {
809
+ NSLog(@"RCTPakta -- crash rescue failed: %@", exception.reason);
810
+ }
811
+ dispatch_semaphore_signal(done);
812
+ });
813
+ if (dispatch_semaphore_wait(done, dispatch_time(DISPATCH_TIME_NOW,
814
+ (int64_t)(budget * NSEC_PER_SEC))) != 0) {
815
+ NSLog(@"RCTPakta -- crash rescue: budget exhausted, letting go");
816
+ }
817
+ }
818
+
819
+ // Crash-moment brick rescue (§11): when the app is dying of an uncaught
820
+ // exception during startup (RCTFatal raises NSException in release), the
821
+ // process is still alive and JS will never run again — a natural,
822
+ // false-positive-free window to finish the cold-start check. The previous
823
+ // handler (crash reporters chain the same way) always runs afterwards.
824
+ static void PaktaCrashRescueExceptionHandler(NSException *exception) {
825
+ @try {
826
+ PaktaMaybeHoldForRescue();
827
+ } @catch (NSException *inner) {
828
+ // The dying process owes the previous handler its turn no matter
829
+ // what the rescue did.
830
+ }
831
+ if (paktaPreviousExceptionHandler != NULL) {
832
+ paktaPreviousExceptionHandler(exception);
833
+ }
834
+ }
835
+
836
+ static void PaktaInstallCrashRescueHandler(void) {
837
+ static dispatch_once_t onceToken;
838
+ dispatch_once(&onceToken, ^{
839
+ paktaPreviousExceptionHandler = NSGetUncaughtExceptionHandler();
840
+ NSSetUncaughtExceptionHandler(&PaktaCrashRescueExceptionHandler);
841
+ });
842
+ }
843
+
844
+ // Single-flight for the digest in PaktaBundleHashSync: the delayed cold-start
845
+ // round (utility queue) and JS getBundleHash (_fileQueue) can both miss the
846
+ // cache right after an install; the second caller waits for the first and
847
+ // then reads its cache entry instead of hashing the same multi-MB file twice.
848
+ static NSLock *PaktaBundleHashLock(void) {
849
+ static NSLock *lock;
850
+ static dispatch_once_t once;
851
+ dispatch_once(&once, ^{
852
+ lock = [NSLock new];
853
+ });
854
+ return lock;
855
+ }
856
+
857
+ static NSString *PaktaCachedBundleHash(NSUserDefaults *defaults, NSString *cachedPrefix) {
858
+ NSString *cached = [defaults stringForKey:keyBundleHashCache];
859
+ if ([cached hasPrefix:cachedPrefix]) {
860
+ return [cached substringFromIndex:cachedPrefix.length];
861
+ }
862
+ return nil;
863
+ }
864
+
865
+ // Shared by the getBundleHash RCT method and the native cold-start check.
866
+ // Returns @"" when unknown. With computeIfMissing the sha256 of the embedded
867
+ // bundle is taken on a cache miss (once per install; blocking — call off the
868
+ // main thread). Without it only the cache is consulted: a crash rescue on
869
+ // the main thread has a 3.5s budget in total, and hashing a large bundle on
870
+ // a fresh install would spend all of it before the first request.
871
+ static NSString *PaktaBundleHashSync(BOOL computeIfMissing) {
872
+ NSString *path = [[RCTPakta binaryBundleURL] path];
873
+ if (path == nil) {
874
+ return @"";
875
+ }
876
+ NSDictionary *attributes =
877
+ [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
878
+ if (attributes == nil) {
879
+ return @"";
880
+ }
881
+ NSString *cacheKey = [NSString stringWithFormat:@"%@|%llu|%.0f",
882
+ [RCTPakta packageVersion],
883
+ attributes.fileSize,
884
+ [attributes.fileModificationDate timeIntervalSince1970]];
885
+
886
+ NSUserDefaults *defaults = PaktaDefaults();
887
+ NSString *cachedPrefix = [cacheKey stringByAppendingString:@"|"];
888
+ NSString *cached = PaktaCachedBundleHash(defaults, cachedPrefix);
889
+ if (cached != nil) {
890
+ return cached;
891
+ }
892
+ if (!computeIfMissing) {
893
+ return @"";
894
+ }
895
+
896
+ NSLock *lock = PaktaBundleHashLock();
897
+ [lock lock];
898
+ @try {
899
+ // The previous holder may have filled the cache while we waited.
900
+ cached = PaktaCachedBundleHash(defaults, cachedPrefix);
901
+ if (cached != nil) {
902
+ return cached;
903
+ }
904
+ NSString *hash = PaktaFromStdString(
905
+ pakta::digest::Sha256File(PaktaToStdString(path))) ?: @"";
906
+ if (hash.length > 0) {
907
+ [defaults setObject:[cachedPrefix stringByAppendingString:hash]
908
+ forKey:keyBundleHashCache];
909
+ }
910
+ return hash;
911
+ } @finally {
912
+ [lock unlock];
913
+ }
914
+ }
915
+
916
+ @implementation RCTPakta {
917
+ dispatch_queue_t _fileQueue;
918
+ bool hasListeners;
919
+ }
920
+
921
+ RCT_EXPORT_MODULE(RCTPakta);
922
+
923
+ + (NSURL *)bundleURL
924
+ {
925
+ paktaIsUsingBundleUrl = true;
926
+ __block NSURL *resolvedURL = nil;
927
+ __block NSString *launchRolledBackVersion = nil;
928
+ @try {
929
+ PaktaWithStateLock(^{
930
+ NSUserDefaults *defaults = PaktaDefaults();
931
+
932
+ NSString *curPackageVersion = [RCTPakta packageVersion];
933
+ NSString *curBuildTime = [RCTPakta buildTime];
934
+ NSString *curChannel = [RCTPakta channel];
935
+ NSString *previousChannel = [defaults stringForKey:keyBinaryChannel];
936
+
937
+ pakta::state::State state = PaktaStateFromDefaults(defaults);
938
+ pakta::state::BinaryVersionSyncResult sync = pakta::state::SyncBinaryVersion(
939
+ state,
940
+ PaktaToStdString(curPackageVersion),
941
+ PaktaToStdString(curBuildTime)
942
+ );
943
+ BOOL channelChanged = previousChannel != nil && ![previousChannel isEqualToString:curChannel];
944
+ if (channelChanged) {
945
+ // 渠道是安装包身份的一部分。覆盖安装另一渠道时撤销热更状态、
946
+ // 回滚标记和响应缓存,但保留 uuid、nativeConfig 等安装级数据。
947
+ state.package_version = PaktaToStdString(curPackageVersion);
948
+ state.build_time = PaktaToStdString(curBuildTime);
949
+ state.current_version.clear();
950
+ state.last_version.clear();
951
+ state.first_time = false;
952
+ state.first_time_ok = true;
953
+ state.rolled_back_version.clear();
954
+ paktaResetGeneration.fetch_add(1);
955
+ for (NSString *key in [defaults dictionaryRepresentation].allKeys) {
956
+ if ([key hasPrefix:keyHashInfo]) {
957
+ [defaults removeObjectForKey:key];
958
+ }
959
+ }
960
+ [defaults removeObjectForKey:keyNativeCheckCache];
961
+ [defaults removeObjectForKey:keyNativeCheckIncomplete];
962
+ } else if (sync.changed) {
963
+ [defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
964
+ state = sync.state;
965
+ PaktaApplyStateToDefaults(defaults, state);
966
+ [defaults setObject:curChannel forKey:keyBinaryChannel];
967
+ } else if (previousChannel == nil) {
968
+ [defaults setObject:curChannel forKey:keyBinaryChannel];
969
+ }
970
+ if (channelChanged) {
971
+ [defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
972
+ PaktaApplyStateToDefaults(defaults, state);
973
+ [defaults setObject:curChannel forKey:keyBinaryChannel];
974
+ }
975
+
976
+ if (!state.current_version.empty()) {
977
+ std::string const versionBeforeLaunch = state.current_version;
978
+ pakta::state::LaunchDecision decision = pakta::state::ResolveLaunchState(
979
+ state,
980
+ ignoreRollback.load(),
981
+ true
982
+ );
983
+ state = decision.state;
984
+
985
+ if (decision.did_rollback) {
986
+ // The crash-protection rollback: the new version never called
987
+ // markSuccess. Keep this visible in release logs.
988
+ RCTLogWarn(@"RCTPakta -- version %@ was not marked as successful, rolled back to %@",
989
+ PaktaFromStdString(versionBeforeLaunch),
990
+ PaktaFromStdString(state.current_version));
991
+ }
992
+ if (decision.did_rollback || decision.consumed_first_time) {
993
+ PaktaApplyStateToDefaults(defaults, state);
994
+ }
995
+ if (decision.consumed_first_time) {
996
+ // bundleURL may be called many times, ignore rollbacks before process restarted again.
997
+ ignoreRollback = true;
998
+ [defaults setObject:@(YES) forKey:keyFirstLoadMarked];
999
+ }
1000
+
1001
+ NSString *loadVersion = PaktaFromStdString(decision.load_version);
1002
+ NSString *downloadDir = [RCTPakta downloadDir];
1003
+ // Guard the rollback chain against cycles: a corrupted state
1004
+ // returning an already-visited version would otherwise spin this
1005
+ // loop forever during startup (Android has the same guard).
1006
+ NSMutableSet<NSString *> *visitedVersions = [NSMutableSet set];
1007
+ while (loadVersion.length && ![visitedVersions containsObject:loadVersion]) {
1008
+ [visitedVersions addObject:loadVersion];
1009
+ NSString *bundlePath = [[downloadDir stringByAppendingPathComponent:loadVersion] stringByAppendingPathComponent:BUNDLE_FILE_NAME];
1010
+ if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) {
1011
+ paktaLaunchVersion = loadVersion;
1012
+ resolvedURL = [NSURL fileURLWithPath:bundlePath];
1013
+ break;
1014
+ } else {
1015
+ RCTLogError(@"RCTPakta -- bundle version %@ not found, rolling back", loadVersion);
1016
+ state = pakta::state::Rollback(state);
1017
+ PaktaApplyStateToDefaults(defaults, state);
1018
+ loadVersion = PaktaFromStdString(state.current_version);
1019
+ }
1020
+ }
1021
+ }
1022
+ // Capture before constantsToExport consumes this one-shot marker.
1023
+ // The delayed native check must never forceBoot the version that this
1024
+ // launch just rolled back.
1025
+ launchRolledBackVersion = PaktaFromStdString(state.rolled_back_version);
1026
+ });
1027
+ return resolvedURL ?: [RCTPakta binaryBundleURL];
1028
+ } @finally {
1029
+ // State corruption is exactly when the rescue path matters most. If
1030
+ // resolution throws before a snapshot exists, nil safely omits only
1031
+ // this launch's rollback guard instead of disabling the check.
1032
+ [RCTPaktaOrchestrator scheduleFromColdStart:launchRolledBackVersion];
1033
+ }
1034
+ }
1035
+
1036
+ + (NSString *) rollback {
1037
+ __block NSString *currentVersion = nil;
1038
+ PaktaWithStateLock(^{
1039
+ NSUserDefaults *defaults = PaktaDefaults();
1040
+ pakta::state::State state = pakta::state::Rollback(PaktaStateFromDefaults(defaults));
1041
+ PaktaApplyStateToDefaults(defaults, state);
1042
+ currentVersion = PaktaFromStdString(state.current_version);
1043
+ });
1044
+ return currentVersion;
1045
+ }
1046
+
1047
+ + (BOOL)requiresMainQueueSetup
1048
+ {
1049
+ return NO;
1050
+ }
1051
+
1052
+ - (NSDictionary *)constantsToExport
1053
+ {
1054
+ NSMutableDictionary *ret = [NSMutableDictionary new];
1055
+ PaktaWithStateLock(^{
1056
+ NSUserDefaults *defaults = PaktaDefaults();
1057
+
1058
+ ret[@"downloadRootDir"] = [RCTPakta downloadDir];
1059
+ ret[@"packageVersion"] = [RCTPakta packageVersion];
1060
+ ret[@"buildTime"] = [RCTPakta buildTime];
1061
+ ret[@"channel"] = [RCTPakta channel];
1062
+ ret[@"rolledBackVersion"] = [defaults objectForKey:keyRolledBackMarked];
1063
+ ret[@"isFirstTime"] = [defaults objectForKey:keyFirstLoadMarked];
1064
+ ret[@"uuid"] = [defaults objectForKey:keyUuid];
1065
+ ret[@"isUsingBundleUrl"] = @(paktaIsUsingBundleUrl.load());
1066
+ // 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道),
1067
+ // JS 随 checkUpdate 以 diffV 上报,服务端按能力门控下发
1068
+ ret[@"supportedDiffVersion"] = @(pakta::hbc::kSupportedDiffVersion);
1069
+ NSDictionary *paktaInfo = [defaults dictionaryForKey:keyPaktaInfo];
1070
+ NSString *currentVersion = [paktaInfo objectForKey:paramCurrentVersion];
1071
+ ret[@"currentVersion"] = currentVersion;
1072
+ if (currentVersion != nil) {
1073
+ ret[@"currentVersionInfo"] = [defaults objectForKey:PaktaHashInfoKey(currentVersion)];
1074
+ // bundleSha256 from the install record, for crash-report attribution.
1075
+ NSString *bundleSha256 = @"";
1076
+ if (PaktaIsSafePathComponent(currentVersion)) {
1077
+ NSDictionary *record = PaktaReadInstallRecord(
1078
+ [[RCTPakta downloadDir] stringByAppendingPathComponent:currentVersion]);
1079
+ if ([record[@"bundleSha256"] isKindOfClass:[NSString class]]) {
1080
+ bundleSha256 = record[@"bundleSha256"];
1081
+ }
1082
+ }
1083
+ ret[@"currentBundleSha256"] = bundleSha256;
1084
+ }
1085
+
1086
+ if (ret[@"isFirstTime"]) {
1087
+ [defaults removeObjectForKey:keyFirstLoadMarked];
1088
+ }
1089
+
1090
+ if (ret[@"rolledBackVersion"] != nil) {
1091
+ [defaults removeObjectForKey:keyRolledBackMarked];
1092
+ [self clearInvalidFiles];
1093
+ }
1094
+
1095
+ if ([[defaults objectForKey:KeyPackageUpdatedMarked] boolValue]) {
1096
+ [defaults removeObjectForKey:KeyPackageUpdatedMarked];
1097
+ [self clearInvalidFiles];
1098
+ }
1099
+ });
1100
+
1101
+ return ret;
1102
+ }
1103
+
1104
+ - (instancetype)init
1105
+ {
1106
+ self = [super init];
1107
+ if (self) {
1108
+ // One process-wide serial queue, not per-instance: a bridge reload can
1109
+ // briefly keep two RCTPakta instances alive, and destructive file work
1110
+ // (resetToPackagedBundle's full cleanup) must stay serialized with the
1111
+ // other instance's unzip/patch jobs.
1112
+ static dispatch_queue_t sharedFileQueue;
1113
+ static dispatch_once_t onceToken;
1114
+ dispatch_once(&onceToken, ^{
1115
+ sharedFileQueue = dispatch_queue_create("cn.reactnative.pakta.file", DISPATCH_QUEUE_SERIAL);
1116
+ });
1117
+ _fileQueue = sharedFileQueue;
1118
+ }
1119
+ return self;
1120
+ }
1121
+
1122
+ RCT_EXPORT_METHOD(setUuid:(NSString *)uuid resolver:(RCTPromiseResolveBlock)resolve
1123
+ rejecter:(RCTPromiseRejectBlock)reject)
1124
+ {
1125
+ if (PaktaStringIsBlank(uuid)) {
1126
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1127
+ return;
1128
+ }
1129
+
1130
+ NSUserDefaults *defaults = PaktaDefaults();
1131
+ [defaults setObject:uuid forKey:keyUuid];
1132
+ resolve(@true);
1133
+ }
1134
+
1135
+ RCT_EXPORT_METHOD(syncNativeConfig:(NSString *)config
1136
+ resolver:(RCTPromiseResolveBlock)resolve
1137
+ rejecter:(RCTPromiseRejectBlock)reject)
1138
+ {
1139
+ // Provisioning for the native cold-start check (NATIVE_CHECKUPDATE_DESIGN
1140
+ // §10.1). Validate at write time: a corrupt config would otherwise
1141
+ // silently disable the native check forever with no signal.
1142
+ if (PaktaStringIsBlank(config)) {
1143
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1144
+ return;
1145
+ }
1146
+ NSData *data = [config dataUsingEncoding:NSUTF8StringEncoding];
1147
+ NSError *error = nil;
1148
+ id object = data == nil ? nil : [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
1149
+ if (![object isKindOfClass:[NSDictionary class]]) {
1150
+ PaktaRejectError(reject, PaktaErrorWithCode(
1151
+ pakta::error_codes::kInvalidOptions,
1152
+ error != nil ? error.localizedDescription : ERROR_OPTIONS));
1153
+ return;
1154
+ }
1155
+ [PaktaDefaults() setObject:config forKey:keyNativeConfig];
1156
+ resolve(@true);
1157
+ }
1158
+
1159
+ RCT_EXPORT_METHOD(getNativeCheckCache:(RCTPromiseResolveBlock)resolve
1160
+ rejecter:(RCTPromiseRejectBlock)reject)
1161
+ {
1162
+ resolve([PaktaDefaults() stringForKey:keyNativeCheckCache] ?: @"");
1163
+ }
1164
+
1165
+ RCT_EXPORT_METHOD(markJsCheckCompleted:(NSString *)config
1166
+ resolver:(RCTPromiseResolveBlock)resolve
1167
+ rejecter:(RCTPromiseRejectBlock)reject)
1168
+ {
1169
+ if (PaktaStringIsBlank(config)) {
1170
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1171
+ return;
1172
+ }
1173
+ [RCTPaktaOrchestrator markJsCheckCompleted:config];
1174
+ resolve(@true);
1175
+ }
1176
+
1177
+ RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash
1178
+ value:(NSString *)value resolver:(RCTPromiseResolveBlock)resolve
1179
+ rejecter:(RCTPromiseRejectBlock)reject)
1180
+ {
1181
+ if (PaktaStringIsBlank(hash) || PaktaStringIsBlank(value)) {
1182
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1183
+ return;
1184
+ }
1185
+
1186
+ NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
1187
+ NSError *error = nil;
1188
+ id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
1189
+ if (object && [object isKindOfClass:[NSDictionary class]]) {
1190
+ NSUserDefaults *defaults = PaktaDefaults();
1191
+ [defaults setObject:value forKey:PaktaHashInfoKey(hash)];
1192
+
1193
+ resolve(@true);
1194
+ } else {
1195
+ PaktaRejectError(reject, PaktaErrorWithCode(
1196
+ pakta::error_codes::kInvalidHashInfo,
1197
+ error != nil ? error.localizedDescription : @"invalid json string"));
1198
+ }
1199
+ }
1200
+
1201
+
1202
+ RCT_EXPORT_METHOD(getLocalHashInfo:(NSString *)hash
1203
+ resolver:(RCTPromiseResolveBlock)resolve
1204
+ rejecter:(RCTPromiseRejectBlock)reject)
1205
+ {
1206
+
1207
+ NSUserDefaults *defaults = PaktaDefaults();
1208
+ resolve([defaults stringForKey:PaktaHashInfoKey(hash)]);
1209
+ }
1210
+
1211
+ RCT_EXPORT_METHOD(downloadFullUpdate:(NSDictionary *)options
1212
+ resolver:(RCTPromiseResolveBlock)resolve
1213
+ rejecter:(RCTPromiseRejectBlock)reject)
1214
+ {
1215
+ [self downloadUpdate:PaktaTypeFullDownload options:options resolver:resolve rejecter:reject];
1216
+ }
1217
+
1218
+ RCT_EXPORT_METHOD(downloadPatchFromPackage:(NSDictionary *)options
1219
+ resolver:(RCTPromiseResolveBlock)resolve
1220
+ rejecter:(RCTPromiseRejectBlock)reject)
1221
+ {
1222
+ [self downloadUpdate:PaktaTypePatchFromPackage options:options resolver:resolve rejecter:reject];
1223
+ }
1224
+
1225
+ RCT_EXPORT_METHOD(downloadPatchFromPpk:(NSDictionary *)options
1226
+ resolver:(RCTPromiseResolveBlock)resolve
1227
+ rejecter:(RCTPromiseRejectBlock)reject)
1228
+ {
1229
+ [self downloadUpdate:PaktaTypePatchFromPpk options:options resolver:resolve rejecter:reject];
1230
+ }
1231
+
1232
+ RCT_EXPORT_METHOD(downloadAndInstallApk:(NSDictionary *)options
1233
+ resolver:(RCTPromiseResolveBlock)resolve
1234
+ rejecter:(RCTPromiseRejectBlock)reject)
1235
+ {
1236
+ PaktaRejectError(reject, PaktaErrorWithCode(
1237
+ pakta::error_codes::kUnsupportedPlatform,
1238
+ @"downloadAndInstallApk is only supported on Android"));
1239
+ }
1240
+
1241
+ RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
1242
+ resolver:(RCTPromiseResolveBlock)resolve
1243
+ rejecter:(RCTPromiseRejectBlock)reject)
1244
+ {
1245
+ NSError *error = nil;
1246
+ if (![self switchVersion:PaktaOptionString(options, @"hash") error:&error]) {
1247
+ PaktaRejectError(reject, error);
1248
+ return;
1249
+ }
1250
+
1251
+ resolve(@true);
1252
+ }
1253
+
1254
+ RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
1255
+ resolver:(RCTPromiseResolveBlock)resolve
1256
+ rejecter:(RCTPromiseRejectBlock)reject)
1257
+ {
1258
+ NSError *error = nil;
1259
+ if (![self switchVersion:PaktaOptionString(options, @"hash") error:&error]) {
1260
+ PaktaRejectError(reject, error);
1261
+ return;
1262
+ }
1263
+
1264
+ NSError *reloadError = [self reloadBridgeWithReason:@"pakta reloadUpdate"];
1265
+ if (reloadError != nil) {
1266
+ PaktaRejectError(reject, reloadError);
1267
+ return;
1268
+ }
1269
+ resolve(@true);
1270
+ }
1271
+
1272
+ RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
1273
+ rejecter:(RCTPromiseRejectBlock)reject)
1274
+ {
1275
+ NSError *reloadError = [self reloadBridgeWithReason:@"pakta restartApp"];
1276
+ if (reloadError != nil) {
1277
+ PaktaRejectError(reject, reloadError);
1278
+ return;
1279
+ }
1280
+ resolve(@true);
1281
+ }
1282
+
1283
+ RCT_EXPORT_METHOD(getBundleHash:(RCTPromiseResolveBlock)resolve
1284
+ rejecter:(RCTPromiseRejectBlock)reject)
1285
+ {
1286
+ // bundleHash = sha256 of the bundle embedded in the binary — the identity
1287
+ // of the binary itself, not of whatever hot update is currently running.
1288
+ // It must hash exactly the bytes pdiff patches from (binaryBundleURL, the
1289
+ // pdiff fromBundle). Lazily computed once per install, cached in defaults.
1290
+ // Never rejects: an empty string means "unknown" and the server falls back
1291
+ // to the buildTime heuristic.
1292
+ #if DEBUG
1293
+ // Metro serves the bundle in debug; the embedded file (if any) is not what
1294
+ // is running. Mirrors the dev behaviour of buildTime.
1295
+ resolve(@"");
1296
+ #else
1297
+ dispatch_async(_fileQueue, ^{
1298
+ resolve(PaktaBundleHashSync(YES));
1299
+ });
1300
+ #endif
1301
+ }
1302
+
1303
+ RCT_EXPORT_METHOD(markSuccess:(NSString *)expectedHash
1304
+ resolver:(RCTPromiseResolveBlock)resolve
1305
+ rejecter:(RCTPromiseRejectBlock)reject)
1306
+ {
1307
+ #if DEBUG
1308
+ resolve(@true);
1309
+ #else
1310
+
1311
+ __block BOOL accepted = NO;
1312
+ // Failures surface as MARK_SUCCESS_FAILED like on Android, so per-code
1313
+ // telemetry is not blind to this transition on iOS.
1314
+ @try {
1315
+ PaktaWithStateLock(^{
1316
+ // expectedHash 必须同时对应本进程实际解析并加载的 bundle;只看持久化
1317
+ // currentVersion 会把冷启动检查在 JS 初始化前切换的版本误报成功。
1318
+ if (paktaLaunchVersion == nil || ![expectedHash isEqualToString:paktaLaunchVersion]) {
1319
+ return;
1320
+ }
1321
+ NSUserDefaults *defaults = PaktaDefaults();
1322
+ pakta::state::MarkSuccessResult result =
1323
+ pakta::state::MarkSuccess(
1324
+ PaktaStateFromDefaults(defaults),
1325
+ PaktaToStdString(expectedHash));
1326
+ accepted = result.accepted;
1327
+ if (!accepted) {
1328
+ return;
1329
+ }
1330
+ if (!result.stale_version_to_delete.empty()) {
1331
+ [defaults removeObjectForKey:PaktaHashInfoKey(PaktaFromStdString(result.stale_version_to_delete))];
1332
+ }
1333
+ PaktaApplyStateToDefaults(defaults, result.state);
1334
+ });
1335
+ } @catch (NSException *exception) {
1336
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kMarkSuccessFailed,
1337
+ exception.reason ?: @"markSuccess failed"));
1338
+ return;
1339
+ }
1340
+
1341
+ if (!accepted) {
1342
+ resolve(@false);
1343
+ return;
1344
+ }
1345
+ [self clearInvalidFiles];
1346
+ resolve(@true);
1347
+ #endif
1348
+ }
1349
+
1350
+ RCT_EXPORT_METHOD(resetToPackagedBundle:(RCTPromiseResolveBlock)resolve
1351
+ rejecter:(RCTPromiseRejectBlock)reject)
1352
+ {
1353
+ // Reset to the bundle packaged in the binary: wipe the whole update state
1354
+ // (so the next launch resolves to the built-in bundle) and delete the
1355
+ // downloaded versions, keeping only the directory of the version this
1356
+ // process is running from (a silent reset must not break its on-demand
1357
+ // asset loads). Only the client uuid survives — it identifies the install
1358
+ // for gray release bucketing and must not change on reset.
1359
+ __block NSString *keepVersion = nil;
1360
+ @try {
1361
+ PaktaWithStateLock(^{
1362
+ // Invalidate any in-flight cold-start round before clearing state, so
1363
+ // a round that commits under this same lock afterwards always sees the
1364
+ // new generation and drops its result.
1365
+ paktaResetGeneration.fetch_add(1);
1366
+ NSUserDefaults *defaults = PaktaDefaults();
1367
+ keepVersion = paktaLaunchVersion;
1368
+
1369
+ // A default-constructed State is exactly the reset state (no current /
1370
+ // last version, first_time=false, first_time_ok=true); keep the binary
1371
+ // identity so the next launch does not re-trigger the package-updated
1372
+ // sync path.
1373
+ pakta::state::State state;
1374
+ state.package_version = PaktaToStdString([RCTPakta packageVersion]);
1375
+ state.build_time = PaktaToStdString([RCTPakta buildTime]);
1376
+ PaktaApplyStateToDefaults(defaults, state);
1377
+
1378
+ for (NSString *key in [defaults dictionaryRepresentation].allKeys) {
1379
+ if ([key hasPrefix:keyHashInfo]) {
1380
+ [defaults removeObjectForKey:key];
1381
+ }
1382
+ }
1383
+ [defaults removeObjectForKey:keyFirstLoadMarked];
1384
+ [defaults removeObjectForKey:KeyPackageUpdatedMarked];
1385
+ // A cached response still advertises the version this reset just
1386
+ // removed; dropping it stops the JS side from reusing that answer.
1387
+ [defaults removeObjectForKey:keyNativeCheckCache];
1388
+ ignoreRollback = false;
1389
+ });
1390
+ } @catch (NSException *exception) {
1391
+ PaktaRejectError(reject, PaktaErrorWithCode(pakta::error_codes::kResetFailed,
1392
+ exception.reason ?: @"reset failed"));
1393
+ return;
1394
+ }
1395
+
1396
+ dispatch_async(_fileQueue, ^{
1397
+ // maxAgeDays=0: remove every downloaded entry except the running
1398
+ // version's directory (cleaned up by the next regular cleanup). The
1399
+ // state was just wiped, so that is the only name left to keep. The
1400
+ // promise settles here, after the cleanup, so a failed wipe reaches
1401
+ // JS as RESET_FAILED (Android parity) instead of a warning nobody
1402
+ // aggregates.
1403
+ pakta::patch::Status status = pakta::patch::CleanupOldEntries(
1404
+ PaktaToStdString([RCTPakta downloadDir]),
1405
+ std::vector<std::string>{PaktaToStdString(keepVersion)},
1406
+ 0
1407
+ );
1408
+ if (!status.ok) {
1409
+ RCTLogWarn(@"Pakta reset cleanup error: %s", status.message.c_str());
1410
+ PaktaRejectError(reject, PaktaErrorWithCode(
1411
+ pakta::error_codes::kResetFailed,
1412
+ [NSString stringWithUTF8String:status.message.c_str()] ?: @"reset cleanup failed"));
1413
+ return;
1414
+ }
1415
+ resolve(@true);
1416
+ });
1417
+ }
1418
+
1419
+
1420
+
1421
+ #pragma mark - private
1422
+ - (NSArray<NSString *> *)supportedEvents
1423
+ {
1424
+ return @[
1425
+ EVENT_PROGRESS_DOWNLOAD,
1426
+ ];
1427
+ }
1428
+
1429
+ -(void)startObserving {
1430
+ hasListeners = YES;
1431
+ }
1432
+
1433
+ -(void)stopObserving {
1434
+ hasListeners = NO;
1435
+ }
1436
+
1437
+ - (void)downloadUpdate:(PaktaType)type
1438
+ options:(NSDictionary *)options
1439
+ resolver:(RCTPromiseResolveBlock)resolve
1440
+ rejecter:(RCTPromiseRejectBlock)reject
1441
+ {
1442
+ [self performUpdate:type options:options callback:^(NSError *error) {
1443
+ if (error != nil) {
1444
+ if (error.userInfo[PaktaErrorCodeKey] == nil) {
1445
+ // Unclassified (system/network) errors from the download
1446
+ // pipeline; keep the original message.
1447
+ error = PaktaErrorWithCode(pakta::error_codes::kDownloadFailed,
1448
+ error.localizedDescription);
1449
+ }
1450
+ PaktaRejectError(reject, error);
1451
+ return;
1452
+ }
1453
+ resolve(nil);
1454
+ }];
1455
+ }
1456
+
1457
+ // Triggers a JS reload; returns nil when one was scheduled, a RESTART_FAILED
1458
+ // error when this host cannot restart. React Native re-resolves the bundle
1459
+ // URL itself on reload (RCTHost asks its bundleURLProvider, RCTBridge.setUp
1460
+ // re-asks sourceURLForBridge:), so +bundleURL is deliberately NOT called
1461
+ // here: it consumes one-shot launch state (first_time, the first-load mark,
1462
+ // ignoreRollback), and consuming it for a reload that never happens would
1463
+ // make the next cold start roll the freshly switched version back.
1464
+ - (NSError *)reloadBridgeWithReason:(NSString *)reason
1465
+ {
1466
+ #if PAKTA_HAS_RELOAD_COMMAND
1467
+ dispatch_async(dispatch_get_main_queue(), ^{
1468
+ RCTTriggerReloadCommandListeners(reason);
1469
+ });
1470
+ return nil;
1471
+ #else
1472
+ // No RCTReloadCommand in this React Native: only the legacy bridge can
1473
+ // reload. Under bridgeless the module's bridge is nil or an RCTBridgeProxy
1474
+ // whose -reload is a logged no-op (object_getClass avoids messaging the
1475
+ // proxy) — report that instead of resolving a restart that never happens.
1476
+ RCTBridge *bridge = self.bridge;
1477
+ if (bridge == nil || object_getClass(bridge) == NSClassFromString(@"RCTBridgeProxy")) {
1478
+ return PaktaErrorWithCode(pakta::error_codes::kRestartFailed,
1479
+ @"no bridge available to reload (bridgeless host without RCTReloadCommand)");
1480
+ }
1481
+ dispatch_async(dispatch_get_main_queue(), ^{
1482
+ [bridge reload];
1483
+ });
1484
+ return nil;
1485
+ #endif
1486
+ }
1487
+
1488
+ - (void)performUpdate:(PaktaType)type options:(NSDictionary *)options callback:(void (^)(NSError *error))callback
1489
+ {
1490
+ NSString *updateUrl = PaktaOptionString(options, @"updateUrl");
1491
+ NSString *hash = PaktaOptionString(options, @"hash");
1492
+
1493
+ if (PaktaStringIsBlank(updateUrl) || !PaktaIsSafePathComponent(hash)) {
1494
+ callback(PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1495
+ return;
1496
+ }
1497
+ NSString *originHash = PaktaOptionString(options, @"originHash");
1498
+ if (type == PaktaTypePatchFromPpk && !PaktaIsSafePathComponent(originHash)) {
1499
+ callback(PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS));
1500
+ return;
1501
+ }
1502
+
1503
+ NSString *dir = [RCTPakta downloadDir];
1504
+ BOOL success = [self ensureDirectoryExistsAtPath:dir];
1505
+ if (!success) {
1506
+ callback(PaktaErrorWithCode(pakta::error_codes::kFileOperationFailed, ERROR_FILE_OPERATION));
1507
+ return;
1508
+ }
1509
+
1510
+ NSString *unzipDir = [dir stringByAppendingPathComponent:hash];
1511
+ if (PaktaHasCompletedVersionAtPath(unzipDir, hash)) {
1512
+ callback(nil);
1513
+ return;
1514
+ }
1515
+
1516
+ NSTimeInterval deadlineUptime = PaktaMonotonicNow() + 600;
1517
+ NSNumber *configuredDeadline = options[@"deadlineUptime"];
1518
+ if ([configuredDeadline isKindOfClass:[NSNumber class]]) {
1519
+ deadlineUptime = configuredDeadline.doubleValue;
1520
+ }
1521
+ if (deadlineUptime <= PaktaMonotonicNow()) {
1522
+ callback(PaktaDownloadDeadlineExpiredError());
1523
+ return;
1524
+ }
1525
+
1526
+ void (^progress)(long long, long long) = ^(long long receivedBytes, long long totalBytes) {
1527
+ if (self->hasListeners) {
1528
+ [self sendEventWithName:EVENT_PROGRESS_DOWNLOAD body:@{
1529
+ PARAM_PROGRESS_HASH:hash,
1530
+ PARAM_PROGRESS_RECEIVED:@(receivedBytes),
1531
+ PARAM_PROGRESS_TOTAL:@(totalBytes),
1532
+ }];
1533
+ }
1534
+ };
1535
+ void (^deferredStart)(void) = ^{
1536
+ [self performUpdate:type options:options callback:callback];
1537
+ };
1538
+ PaktaDownloadRegistration registration = PaktaRegisterDownload(
1539
+ hash, type, deadlineUptime, callback, progress, deferredStart);
1540
+ if (registration != PaktaDownloadRegistrationOwner) {
1541
+ RCTLogInfo(
1542
+ @"RCTPakta -- %@ in-flight download for %@",
1543
+ registration == PaktaDownloadRegistrationJoined ? @"join" : @"defer",
1544
+ hash);
1545
+ return;
1546
+ }
1547
+
1548
+ NSString *zipFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%@",hash, [self zipExtension:type]]];
1549
+
1550
+ // On failure, remove the partial version directory like Android/Harmony
1551
+ // do: a half-unzipped/half-patched dir leaks disk and could later be
1552
+ // mistaken for a complete version. hash is validated non-blank above, so
1553
+ // this can never resolve to the download root itself.
1554
+ void (^completion)(NSError *) = ^(NSError *error) {
1555
+ // Settle every JS/native waiter only after cleanup or the atomic
1556
+ // completion marker write has run on the process-wide file queue.
1557
+ dispatch_async(self->_fileQueue, ^{
1558
+ NSError *finalError = error;
1559
+ NSFileManager *fileManager = [NSFileManager defaultManager];
1560
+ NSString *staging = PaktaStagingDirForVersionDir(unzipDir);
1561
+ NSString *artifactSha256 = nil;
1562
+ @synchronized (PaktaArtifactDigests()) {
1563
+ artifactSha256 = PaktaArtifactDigests()[hash];
1564
+ [PaktaArtifactDigests() removeObjectForKey:hash];
1565
+ }
1566
+ if (finalError == nil) {
1567
+ // Two-phase install (cpp/patch_core/install_record.h): the
1568
+ // completion record with the final bundle's digest goes into
1569
+ // the staging directory, which is then renamed over the
1570
+ // version directory in one atomic step.
1571
+ NSString *bundlePath = [staging stringByAppendingPathComponent:BUNDLE_FILE_NAME];
1572
+ if (![fileManager fileExistsAtPath:bundlePath]) {
1573
+ finalError = PaktaErrorWithCode(pakta::error_codes::kPatchFailed,
1574
+ @"bundle missing after install");
1575
+ } else {
1576
+ std::string bundleSha256 = pakta::digest::Sha256File(PaktaToStdString(bundlePath));
1577
+ NSMutableDictionary *record = [NSMutableDictionary dictionary];
1578
+ record[@"schema"] = @(pakta::install_record::kSchema);
1579
+ record[@"versionHash"] = hash;
1580
+ if (!bundleSha256.empty()) {
1581
+ record[@"bundleSha256"] = [NSString stringWithUTF8String:bundleSha256.c_str()];
1582
+ }
1583
+ if (artifactSha256.length > 0) {
1584
+ record[@"artifactSha256"] = artifactSha256;
1585
+ }
1586
+ NSError *recordError = nil;
1587
+ NSData *recordData = [NSJSONSerialization dataWithJSONObject:record options:0 error:&recordError];
1588
+ NSString *marker = [staging stringByAppendingPathComponent:VERSION_COMPLETE_FILE_NAME];
1589
+ if (recordData == nil || ![recordData writeToFile:marker
1590
+ options:NSDataWritingAtomic
1591
+ error:&recordError]) {
1592
+ finalError = recordError ?: PaktaErrorWithCode(
1593
+ pakta::error_codes::kFileOperationFailed,
1594
+ @"failed to write completion record");
1595
+ } else {
1596
+ if ([fileManager fileExistsAtPath:unzipDir]) {
1597
+ [fileManager removeItemAtPath:unzipDir error:nil];
1598
+ }
1599
+ NSError *moveError = nil;
1600
+ if (![fileManager moveItemAtPath:staging toPath:unzipDir error:&moveError]) {
1601
+ finalError = moveError ?: PaktaErrorWithCode(
1602
+ pakta::error_codes::kFileOperationFailed,
1603
+ @"failed to promote staging directory");
1604
+ }
1605
+ }
1606
+ }
1607
+ }
1608
+ if (finalError != nil) {
1609
+ // Only the staging directory is ours to drop: the final
1610
+ // version directory is never touched by a failed install.
1611
+ [fileManager removeItemAtPath:staging error:nil];
1612
+ }
1613
+ PaktaFinishDownload(hash, finalError);
1614
+ });
1615
+ };
1616
+
1617
+ RCTLogInfo(@"RCTPakta -- download file %@", updateUrl);
1618
+ NSTimeInterval timeoutSeconds = deadlineUptime - PaktaMonotonicNow();
1619
+ if (timeoutSeconds <= 0) {
1620
+ completion(PaktaDownloadDeadlineExpiredError());
1621
+ return;
1622
+ }
1623
+ [RCTPaktaDownloader download:updateUrl
1624
+ savePath:zipFilePath
1625
+ timeoutInterval:timeoutSeconds
1626
+ progressHandler:^(long long receivedBytes, long long totalBytes) {
1627
+ PaktaReportDownloadProgress(hash, receivedBytes, totalBytes);
1628
+ } completionHandler:^(NSString *path, NSError *error) {
1629
+ if (error != nil) {
1630
+ completion(error);
1631
+ return;
1632
+ }
1633
+ [self unzipDownloadedPackage:zipFilePath
1634
+ hash:hash
1635
+ type:type
1636
+ originHash:originHash
1637
+ callback:completion];
1638
+ }];
1639
+ }
1640
+
1641
+ - (void)unzipDownloadedPackage:(NSString *)zipFilePath
1642
+ hash:(NSString *)hash
1643
+ type:(PaktaType)type
1644
+ originHash:(NSString *)originHash
1645
+ callback:(void (^)(NSError *error))callback
1646
+ {
1647
+ RCTLogInfo(@"RCTPakta -- unzip file %@", zipFilePath);
1648
+ // Everything lands in <hash>.staging; the completion block promotes it.
1649
+ NSString *unzipFilePath = PaktaStagingDirForVersionDir(
1650
+ [[RCTPakta downloadDir] stringByAppendingPathComponent:hash]);
1651
+ dispatch_async(_fileQueue, ^{
1652
+ // Archive digest for the completion record, taken before the unzip
1653
+ // consumes the file (same serial queue, so it runs first).
1654
+ std::string digest = pakta::digest::Sha256File(PaktaToStdString(zipFilePath));
1655
+ @synchronized (PaktaArtifactDigests()) {
1656
+ PaktaArtifactDigests()[hash] = [NSString stringWithUTF8String:digest.c_str()];
1657
+ }
1658
+ });
1659
+ [self unzipFileAtPath:zipFilePath
1660
+ toDestination:unzipFilePath
1661
+ completionHandler:^(NSError *error) {
1662
+ dispatch_async(self->_fileQueue, ^{
1663
+ if (error != nil) {
1664
+ callback(error);
1665
+ return;
1666
+ }
1667
+ @try {
1668
+ [self finishDownloadedPackage:hash type:type originHash:originHash callback:callback];
1669
+ } @catch (NSException *exception) {
1670
+ // An uncaught exception in a GCD block is fatal (and would
1671
+ // trip the crash rescue); a bad package is a PATCH_FAILED.
1672
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed,
1673
+ exception.reason ?: @"patch failed"));
1674
+ }
1675
+ });
1676
+ }];
1677
+ }
1678
+
1679
+ - (void)finishDownloadedPackage:(NSString *)hash
1680
+ type:(PaktaType)type
1681
+ originHash:(NSString *)originHash
1682
+ callback:(void (^)(NSError *error))callback
1683
+ {
1684
+ switch (type) {
1685
+ case PaktaTypePatchFromPackage:
1686
+ [self applyPatchForHash:hash
1687
+ type:type
1688
+ fromBundle:[[RCTPakta binaryBundleURL] path]
1689
+ source:[[NSBundle mainBundle] resourcePath]
1690
+ callback:callback];
1691
+ return;
1692
+ case PaktaTypePatchFromPpk: {
1693
+ NSString *lastVersionDir = [[RCTPakta downloadDir] stringByAppendingPathComponent:originHash];
1694
+ [self applyPatchForHash:hash
1695
+ type:type
1696
+ fromBundle:[lastVersionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]
1697
+ source:lastVersionDir
1698
+ callback:callback];
1699
+ return;
1700
+ }
1701
+ case PaktaTypeFullDownload:
1702
+ callback(nil);
1703
+ return;
1704
+ }
1705
+ }
1706
+
1707
+ - (void)applyPatchForHash:(NSString *)hash
1708
+ type:(PaktaType)type
1709
+ fromBundle:(NSString *)bundleOrigin
1710
+ source:(NSString *)sourceOrigin
1711
+ callback:(void (^)(NSError *error))callback
1712
+ {
1713
+ // Patch work happens in the staging directory (two-phase install).
1714
+ NSString *unzipDir = PaktaStagingDirForVersionDir(
1715
+ [[RCTPakta downloadDir] stringByAppendingPathComponent:hash]);
1716
+ NSString *sourcePatch = [unzipDir stringByAppendingPathComponent:SOURCE_PATCH_NAME];
1717
+ NSString *bundlePatch = [unzipDir stringByAppendingPathComponent:BUNDLE_PATCH_NAME];
1718
+
1719
+ NSString *destination = [unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME];
1720
+ long long manifestBytes = RCTPaktaFileSize(sourcePatch);
1721
+ if (manifestBytes > pakta::archive_limits::kMaxManifestBytes) {
1722
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed,
1723
+ [NSString stringWithFormat:@"patch manifest too large: %lld bytes", manifestBytes]));
1724
+ return;
1725
+ }
1726
+ NSData *data = [NSData dataWithContentsOfFile:sourcePatch];
1727
+ if (data == nil) {
1728
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed, @"missing patch manifest"));
1729
+ return;
1730
+ }
1731
+
1732
+ NSError *error = nil;
1733
+ id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
1734
+ if (error != nil) {
1735
+ // Classify as a patch failure like the sibling manifest branches;
1736
+ // unclassified errors would otherwise be tagged DOWNLOAD_FAILED by the
1737
+ // downloadUpdate fallback even though the download itself succeeded.
1738
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed, error.localizedDescription));
1739
+ return;
1740
+ }
1741
+ if (![jsonObject isKindOfClass:[NSDictionary class]]) {
1742
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed, @"invalid patch manifest"));
1743
+ return;
1744
+ }
1745
+ NSDictionary *json = (NSDictionary *)jsonObject;
1746
+ pakta::patch::PatchManifest manifest;
1747
+ NSString *manifestReason = nil;
1748
+ if (!PaktaPatchManifestFromJson(json, &manifest, &manifestReason)) {
1749
+ callback(PaktaErrorWithCode(pakta::error_codes::kPatchFailed, manifestReason));
1750
+ return;
1751
+ }
1752
+
1753
+ std::vector<std::string> entryNames;
1754
+ if ([[NSFileManager defaultManager] fileExistsAtPath:sourcePatch isDirectory:NULL]) {
1755
+ entryNames.push_back(PaktaToStdString(SOURCE_PATCH_NAME));
1756
+ }
1757
+ if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePatch isDirectory:NULL]) {
1758
+ entryNames.push_back(PaktaToStdString(BUNDLE_PATCH_NAME));
1759
+ }
1760
+
1761
+ pakta::archive_patch::ArchivePatchPlan plan;
1762
+ pakta::patch::Status planStatus = pakta::archive_patch::BuildArchivePatchPlan(
1763
+ type == PaktaTypePatchFromPackage
1764
+ ? pakta::archive_patch::ArchivePatchType::kPatchFromPackage
1765
+ : pakta::archive_patch::ArchivePatchType::kPatchFromPpk,
1766
+ manifest,
1767
+ entryNames,
1768
+ &plan
1769
+ );
1770
+ if (!planStatus.ok) {
1771
+ callback(PaktaNSErrorFromStatus(planStatus));
1772
+ return;
1773
+ }
1774
+
1775
+ pakta::patch::FileSourcePatchOptions options;
1776
+ pakta::patch::Status optionStatus = pakta::archive_patch::BuildFileSourcePatchOptions(
1777
+ plan,
1778
+ PaktaToStdString(sourceOrigin),
1779
+ PaktaToStdString(unzipDir),
1780
+ PaktaToStdString(bundleOrigin),
1781
+ PaktaToStdString(bundlePatch),
1782
+ PaktaToStdString(destination),
1783
+ &options
1784
+ );
1785
+ if (!optionStatus.ok) {
1786
+ callback(PaktaNSErrorFromStatus(optionStatus));
1787
+ return;
1788
+ }
1789
+
1790
+ // __diff.json 的 hbcTransform 元数据(HBC 变换域 patch,hdiffv2 轨道):
1791
+ // 存在时透传给 patch 内核执行 T(origin) → hpatch → T⁻¹;缺失走现状路径。
1792
+ NSDictionary *hbcTransform = json[@"hbcTransform"];
1793
+ if ([hbcTransform isKindOfClass:[NSDictionary class]]) {
1794
+ NSDictionary *meta = hbcTransform[BUNDLE_PATCH_NAME];
1795
+ if ([meta isKindOfClass:[NSDictionary class]]) {
1796
+ NSError *metaError = nil;
1797
+ NSData *metaData = [NSJSONSerialization dataWithJSONObject:meta options:0 error:&metaError];
1798
+ if (metaData != nil && metaError == nil) {
1799
+ NSString *metaString = [[NSString alloc] initWithData:metaData encoding:NSUTF8StringEncoding];
1800
+ options.bundle_hbc_transform_meta = PaktaToStdString(metaString);
1801
+ }
1802
+ }
1803
+ }
1804
+
1805
+ pakta::patch::Status status = pakta::patch::ApplyPatchFromFileSource(options);
1806
+ if (!status.ok) {
1807
+ callback(PaktaNSErrorFromStatus(status));
1808
+ return;
1809
+ }
1810
+
1811
+ callback(nil);
1812
+ }
1813
+
1814
+ - (BOOL)switchVersion:(NSString *)hash error:(NSError **)error
1815
+ {
1816
+ if (!PaktaIsSafePathComponent(hash)) {
1817
+ if (error != NULL) {
1818
+ *error = PaktaErrorWithCode(pakta::error_codes::kInvalidOptions, ERROR_OPTIONS);
1819
+ }
1820
+ return NO;
1821
+ }
1822
+
1823
+ __block NSError *switchError = nil;
1824
+ PaktaWithStateLock(^{
1825
+ // Same rule as Android: only a version this SDK recorded as completely
1826
+ // installed (bundle + marker) may be activated. Versions activated
1827
+ // before markers existed are grandfathered through current/last
1828
+ // state; any other markerless directory may be a crash-left partial
1829
+ // install or a directory something else put there.
1830
+ NSString *versionDir = [[RCTPakta downloadDir] stringByAppendingPathComponent:hash];
1831
+ pakta::state::State state = PaktaStateFromDefaults(PaktaDefaults());
1832
+ std::string hashStd = PaktaToStdString(hash);
1833
+ BOOL legacyActivated = state.current_version == hashStd || state.last_version == hashStd;
1834
+ if (!PaktaHasCompletedVersionAtPath(versionDir, hash) && !legacyActivated) {
1835
+ BOOL hasBundle = [[NSFileManager defaultManager] fileExistsAtPath:
1836
+ [versionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]];
1837
+ switchError = PaktaErrorWithCode(
1838
+ pakta::error_codes::kSwitchVersionFailed,
1839
+ [NSString stringWithFormat:@"Bundle version %@ %@", hash,
1840
+ hasBundle ? @"is incomplete." : @"not found."]);
1841
+ return;
1842
+ }
1843
+ if (!legacyActivated) {
1844
+ // The record's bundle digest must match the bytes on disk before
1845
+ // the next launch is pointed at them.
1846
+ NSString *reason = PaktaVerifyInstallForActivation(versionDir, hash);
1847
+ if (reason != nil) {
1848
+ switchError = PaktaErrorWithCode(pakta::error_codes::kSwitchVersionFailed, reason);
1849
+ return;
1850
+ }
1851
+ }
1852
+ PaktaSwitchVersionLocked(hash);
1853
+ });
1854
+ if (switchError != nil) {
1855
+ if (error != NULL) {
1856
+ *error = switchError;
1857
+ }
1858
+ return NO;
1859
+ }
1860
+ return YES;
1861
+ }
1862
+
1863
+ - (BOOL)ensureDirectoryExistsAtPath:(NSString *)path
1864
+ {
1865
+ // No _fileQueue hop here: that queue also runs multi-second unzip/patch
1866
+ // work, and a dispatch_sync onto it would block the whole module method
1867
+ // queue for the duration. createDirectoryAtPath is idempotent and
1868
+ // thread-safe, so checking inline is fine.
1869
+ NSFileManager *fileManager = [NSFileManager defaultManager];
1870
+ BOOL isDirectory = NO;
1871
+ if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory]) {
1872
+ if (isDirectory) {
1873
+ // Directories created by older versions never got the flag.
1874
+ [RCTPakta excludeFromBackup:path];
1875
+ }
1876
+ return isDirectory;
1877
+ }
1878
+
1879
+ NSError *error = nil;
1880
+ BOOL success = [fileManager createDirectoryAtPath:path
1881
+ withIntermediateDirectories:YES
1882
+ attributes:nil
1883
+ error:&error];
1884
+ if (!success && error != nil) {
1885
+ RCTLogWarn(@"Pakta create directory error: %@", error.localizedDescription);
1886
+ }
1887
+ if (success) {
1888
+ [RCTPakta excludeFromBackup:path];
1889
+ }
1890
+
1891
+ return success;
1892
+ }
1893
+
1894
+ // Everything under rctpakta is re-downloadable, and Application Support is
1895
+ // backed up to iCloud by default — Apple requires such content to be
1896
+ // excluded from backups.
1897
+ + (void)excludeFromBackup:(NSString *)path
1898
+ {
1899
+ NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
1900
+ NSError *error = nil;
1901
+ if (![url setResourceValue:@YES
1902
+ forKey:NSURLIsExcludedFromBackupKey
1903
+ error:&error]) {
1904
+ RCTLogWarn(@"Pakta exclude from backup error: %@", error.localizedDescription);
1905
+ }
1906
+ }
1907
+
1908
+ - (void)unzipFileAtPath:(NSString *)path
1909
+ toDestination:(NSString *)destination
1910
+ completionHandler:(void (^)(NSError *error))completionHandler
1911
+ {
1912
+ dispatch_async(_fileQueue, ^{
1913
+ NSFileManager *fileManager = [NSFileManager defaultManager];
1914
+ if ([fileManager fileExistsAtPath:destination]) {
1915
+ [fileManager removeItemAtPath:destination error:nil];
1916
+ }
1917
+
1918
+ // Resource caps before the first byte is extracted
1919
+ // (cpp/patch_core/archive_limits.h). SSZipArchive only exposes the
1920
+ // entry count up front (zipArchiveWillUnzipArchiveAtPath:zipInfo:),
1921
+ // not the uncompressed total, so the disk check uses a 2x-archive
1922
+ // heuristic; the delegate below enforces the exact caps entry by
1923
+ // entry.
1924
+ long long archiveBytes = RCTPaktaFileSize(path);
1925
+ NSError *preflight = nil;
1926
+ if (archiveBytes > pakta::archive_limits::kMaxArchiveBytes) {
1927
+ preflight = PaktaErrorWithCode(pakta::error_codes::kPatchFailed,
1928
+ [NSString stringWithFormat:@"archive too large: %lld bytes", archiveBytes]);
1929
+ } else {
1930
+ preflight = PaktaEnsureFreeSpace(destination, MAX(0LL, archiveBytes) * 2);
1931
+ }
1932
+ if (preflight != nil) {
1933
+ [fileManager removeItemAtPath:path error:nil];
1934
+ [fileManager removeItemAtPath:RCTPaktaResumeSidecarPath(path) error:nil];
1935
+ if (completionHandler != nil) {
1936
+ completionHandler(preflight);
1937
+ }
1938
+ return;
1939
+ }
1940
+
1941
+ PaktaUnzipGuard *guard = [PaktaUnzipGuard new];
1942
+ // preserveAttributes:NO — the archive's mode/mtime bits have no use
1943
+ // here and are one less thing a hostile package controls.
1944
+ [SSZipArchive unzipFileAtPath:path
1945
+ toDestination:destination
1946
+ preserveAttributes:NO
1947
+ overwrite:YES
1948
+ nestedZipLevel:0
1949
+ password:nil
1950
+ error:nil
1951
+ delegate:guard
1952
+ progressHandler:nil
1953
+ completionHandler:^(NSString *archivePath, BOOL succeeded, NSError *error) {
1954
+ [fileManager removeItemAtPath:archivePath error:nil];
1955
+ // The resume sidecar dies with its archive (§11.4): whether the
1956
+ // unzip consumed it or classified it as poisoned, nothing must
1957
+ // survive to vouch for bytes that are gone.
1958
+ [fileManager removeItemAtPath:RCTPaktaResumeSidecarPath(archivePath) error:nil];
1959
+ if (completionHandler == nil) {
1960
+ return;
1961
+ }
1962
+
1963
+ NSError *unzipError = error;
1964
+ if (guard.violation != nil) {
1965
+ unzipError = PaktaErrorWithCode(pakta::error_codes::kPatchFailed, guard.violation);
1966
+ } else if (!succeeded && unzipError == nil) {
1967
+ unzipError = PaktaErrorWithCode(pakta::error_codes::kPatchFailed, @"unzip failed");
1968
+ } else if (unzipError != nil && unzipError.userInfo[PaktaErrorCodeKey] == nil) {
1969
+ // SSZipArchive's own NSError (corrupt zip, bad magic, ...) has
1970
+ // no stable code; without one, downloadUpdate's fallback would
1971
+ // classify it as DOWNLOAD_FAILED even though the download
1972
+ // succeeded — keep the classification deterministic.
1973
+ unzipError = PaktaErrorWithCode(pakta::error_codes::kPatchFailed,
1974
+ unzipError.localizedDescription ?: @"unzip failed");
1975
+ }
1976
+ completionHandler(unzipError);
1977
+ }];
1978
+ });
1979
+ }
1980
+
1981
+ - (void)clearInvalidFiles
1982
+ {
1983
+ dispatch_async(_fileQueue, ^{
1984
+ // Snapshot the state under the lock, but run the (slow) filesystem
1985
+ // cleanup outside of it so state operations are not blocked.
1986
+ __block pakta::state::State state;
1987
+ __block NSString *launchVersion = nil;
1988
+ PaktaWithStateLock(^{
1989
+ state = PaktaStateFromDefaults(PaktaDefaults());
1990
+ launchVersion = paktaLaunchVersion;
1991
+ });
1992
+ NSString *downloadDir = [RCTPakta downloadDir];
1993
+ // Keep the persisted current/last versions AND the version this
1994
+ // process booted from: two switches without a restart would otherwise
1995
+ // evict the running bundle while its on-demand assets are still
1996
+ // being served.
1997
+ pakta::patch::Status status = pakta::patch::CleanupOldEntries(
1998
+ PaktaToStdString(downloadDir),
1999
+ std::vector<std::string>{
2000
+ state.current_version,
2001
+ state.last_version,
2002
+ PaktaToStdString(launchVersion),
2003
+ },
2004
+ 3
2005
+ );
2006
+ if (!status.ok) {
2007
+ RCTLogWarn(@"Pakta cleanup error: %s", status.message.c_str());
2008
+ }
2009
+ });
2010
+ }
2011
+
2012
+ - (NSString *)zipExtension:(PaktaType)type
2013
+ {
2014
+ switch (type) {
2015
+ case PaktaTypePatchFromPackage:
2016
+ return @".ipa.patch";
2017
+ case PaktaTypePatchFromPpk:
2018
+ return @".ppk.patch";
2019
+ case PaktaTypeFullDownload:
2020
+ break;
2021
+ }
2022
+ return @".ppk";
2023
+ }
2024
+
2025
+ + (NSString *)downloadDir
2026
+ {
2027
+ NSString *directory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
2028
+ return [directory stringByAppendingPathComponent:@"rctpakta"];
2029
+ }
2030
+
2031
+ + (NSURL *)binaryBundleURL
2032
+ {
2033
+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
2034
+ }
2035
+
2036
+ + (NSString *)packageVersion
2037
+ {
2038
+ static NSString *version = nil;
2039
+
2040
+ static dispatch_once_t onceToken;
2041
+ dispatch_once(&onceToken, ^{
2042
+ NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
2043
+ version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
2044
+ });
2045
+ return version;
2046
+ }
2047
+
2048
+ + (NSString *)buildTime
2049
+ {
2050
+ #if DEBUG
2051
+ return @"0";
2052
+ #else
2053
+ static NSString *buildTime;
2054
+ static dispatch_once_t onceToken;
2055
+ dispatch_once(&onceToken, ^{
2056
+ // The podspec's s.resource lands in the app bundle for a static
2057
+ // library but inside this pod's own framework bundle under
2058
+ // use_frameworks!; look in both.
2059
+ NSString *stampPath =
2060
+ [[NSBundle mainBundle] pathForResource:@"pakta_build_time" ofType:@"txt"]
2061
+ ?: [[NSBundle bundleForClass:[RCTPakta class]] pathForResource:@"pakta_build_time"
2062
+ ofType:@"txt"];
2063
+ NSString *stamp = stampPath == nil ? nil
2064
+ : [[NSString stringWithContentsOfFile:stampPath encoding:NSUTF8StringEncoding error:nil]
2065
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
2066
+ if (stamp.length == 0) {
2067
+ // Never empty: with "" SyncBinaryVersion would miss a rebuilt
2068
+ // binary that kept its CFBundleShortVersionString, and a stale
2069
+ // hot update would outlive an App Store update. A correctly
2070
+ // packaged binary always carries the UUID resource; this
2071
+ // last-resort value stays opaque rather than exposing a timestamp.
2072
+ RCTLogWarn(@"RCTPakta -- pakta_build_time.txt %@; using a generated UUID as buildTime",
2073
+ stampPath == nil ? @"not found in the main or framework bundle" : @"is empty");
2074
+ stamp = [[[NSUUID UUID] UUIDString] lowercaseString];
2075
+ }
2076
+ buildTime = stamp;
2077
+ });
2078
+ return buildTime;
2079
+ #endif
2080
+ }
2081
+
2082
+ + (NSString *)channel
2083
+ {
2084
+ static NSString *channel;
2085
+ static dispatch_once_t onceToken;
2086
+ dispatch_once(&onceToken, ^{
2087
+ id value = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"channel"];
2088
+ channel = [value isKindOfClass:[NSString class]] && [value length] > 0
2089
+ ? [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
2090
+ : @"default";
2091
+ if (channel.length == 0) channel = @"default";
2092
+ });
2093
+ return channel;
2094
+ }
2095
+
2096
+ #ifdef RCT_NEW_ARCH_ENABLED
2097
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
2098
+ (const facebook::react::ObjCTurboModule::InitParams &)params
2099
+ {
2100
+ return std::make_shared<facebook::react::NativePaktaSpecJSI>(params);
2101
+ }
2102
+ #endif
2103
+
2104
+ @end
2105
+
2106
+ #pragma mark - native cold-start check orchestration
2107
+
2108
+ // Refuses an https→http downgrade before the redirected request — and with
2109
+ // it the POST body (appKey, uuid, versions) — leaves the device. The shared
2110
+ // session would follow first and only the response could be rejected; ATS
2111
+ // blocks plaintext by default, this covers apps that opted out with
2112
+ // NSAllowsArbitraryLoads. Same rule as RCTPaktaDownloader. Returning nil
2113
+ // delivers the 3xx itself as the final response, which the status check in
2114
+ // PaktaHttpRequest then treats as a failed endpoint.
2115
+ @interface PaktaCheckRequestRedirectGuard : NSObject <NSURLSessionTaskDelegate>
2116
+ @end
2117
+
2118
+ @implementation PaktaCheckRequestRedirectGuard
2119
+
2120
+ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
2121
+ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
2122
+ newRequest:(NSURLRequest *)request
2123
+ completionHandler:(void (^)(NSURLRequest *))completionHandler
2124
+ {
2125
+ NSString *originalScheme = [task.originalRequest.URL.scheme lowercaseString];
2126
+ NSString *nextScheme = [request.URL.scheme lowercaseString];
2127
+ if ([originalScheme isEqualToString:@"https"] && [nextScheme isEqualToString:@"http"]) {
2128
+ completionHandler(nil);
2129
+ return;
2130
+ }
2131
+ completionHandler(request);
2132
+ }
2133
+
2134
+ @end
2135
+
2136
+ // Blocking JSON HTTP round-trip on the orchestrator's utility thread. Returns
2137
+ // the response body on 2xx, nil on any failure. The semaphore timeout is a
2138
+ // backstop over the request's own timeoutInterval.
2139
+ static NSString *PaktaHttpRequest(NSString *urlString, NSString *method,
2140
+ NSString *body, NSTimeInterval timeout) {
2141
+ NSURL *url = [NSURL URLWithString:urlString];
2142
+ if (url == nil) {
2143
+ return nil;
2144
+ }
2145
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
2146
+ request.HTTPMethod = method;
2147
+ request.timeoutInterval = timeout;
2148
+ [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
2149
+ if (body != nil) {
2150
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
2151
+ request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
2152
+ }
2153
+ dispatch_semaphore_t sem = dispatch_semaphore_create(0);
2154
+ __block NSString *result = nil;
2155
+ // A delegate session (not the shared one) so the redirect guard sees the
2156
+ // 3xx before it is followed; the completion handler still receives the
2157
+ // final response.
2158
+ NSURLSession *session = [NSURLSession
2159
+ sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
2160
+ delegate:[PaktaCheckRequestRedirectGuard new]
2161
+ delegateQueue:nil];
2162
+ NSURLSessionDataTask *task = [session dataTaskWithRequest:request
2163
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
2164
+ NSHTTPURLResponse *httpResponse =
2165
+ [response isKindOfClass:[NSHTTPURLResponse class]]
2166
+ ? (NSHTTPURLResponse *)response
2167
+ : nil;
2168
+ NSInteger status = httpResponse.statusCode;
2169
+ if (error == nil && httpResponse != nil && status >= 200 && status < 300
2170
+ && data != nil) {
2171
+ result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2172
+ }
2173
+ dispatch_semaphore_signal(sem);
2174
+ }];
2175
+ [task resume];
2176
+ // The session retains its delegate until invalidated: let this one task
2177
+ // settle, then release everything.
2178
+ [session finishTasksAndInvalidate];
2179
+ if (dispatch_semaphore_wait(
2180
+ sem, dispatch_time(DISPATCH_TIME_NOW,
2181
+ (int64_t)((timeout + 5) * NSEC_PER_SEC))) != 0) {
2182
+ [task cancel];
2183
+ return nil;
2184
+ }
2185
+ return result;
2186
+ }
2187
+
2188
+ static NSString *PaktaNormalizeEndpointBase(NSString *base) {
2189
+ while ([base hasSuffix:@"/"]) {
2190
+ base = [base substringToIndex:base.length - 1];
2191
+ }
2192
+ return base;
2193
+ }
2194
+
2195
+ static BOOL PaktaIsValidCheckResponse(NSString *responseText) {
2196
+ if (responseText == nil) {
2197
+ return NO;
2198
+ }
2199
+ // Shared schema rule (update_flow_core::IsValidCheckResponse): a 200 with
2200
+ // `{"error": ...}` is a failed endpoint, not a verdict.
2201
+ return updateflow::IsValidCheckResponse(PaktaToStdString(responseText)) ? YES : NO;
2202
+ }
2203
+
2204
+ @implementation RCTPaktaOrchestrator
2205
+
2206
+ + (void)scheduleFromColdStart:(NSString *)launchRolledBackVersion {
2207
+ #if !DEBUG
2208
+ // Once per process; a few seconds of delay keeps the check away from the
2209
+ // cold-start critical path (§7 R5) — its result targets the NEXT launch.
2210
+ // Unless the previous process died mid-round (residual incomplete
2211
+ // marker), in which case every launch second counts (§11.4).
2212
+ static dispatch_once_t onceToken;
2213
+ dispatch_once(&onceToken, ^{
2214
+ paktaRoundDone = dispatch_semaphore_create(0);
2215
+ paktaProcessAnchorUptime = PaktaMonotonicNow();
2216
+ paktaLaunchRolledBackForRescue = [launchRolledBackVersion copy];
2217
+ NSUserDefaults *defaults = PaktaDefaults();
2218
+ // The crash-hold rescue shares the orchestrator's rollout gate: no
2219
+ // persisted config, no handler (§11.3).
2220
+ if ([defaults stringForKey:keyNativeConfig].length > 0) {
2221
+ PaktaInstallCrashRescueHandler();
2222
+ }
2223
+ int64_t delaySeconds =
2224
+ [defaults objectForKey:keyNativeCheckIncomplete] != nil ? 0 : 5;
2225
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delaySeconds * NSEC_PER_SEC),
2226
+ dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
2227
+ if ([self isJsCheckCompleted]) {
2228
+ // Not consuming the round: a later crash rescue may still
2229
+ // need it.
2230
+ NSLog(@"RCTPakta -- native check skipped: JS check completed in this process");
2231
+ return;
2232
+ }
2233
+ [self startRoundWithDeadline:0];
2234
+ });
2235
+ });
2236
+ #endif
2237
+ }
2238
+
2239
+ + (void)markJsCheckCompleted:(NSString *)config {
2240
+ @synchronized (RCTPaktaOrchestrator.class) {
2241
+ paktaJsCompletedConfig = [config copy];
2242
+ }
2243
+ }
2244
+
2245
+ // True when JS already obtained a valid response in this process for the
2246
+ // exact config the native round would use (§10.3): the delayed round is
2247
+ // then a duplicate request. Only the scheduled round asks — the crash-rescue
2248
+ // path still runs, JS is dead by then.
2249
+ + (BOOL)isJsCheckCompleted {
2250
+ NSString *jsConfig;
2251
+ @synchronized (RCTPaktaOrchestrator.class) {
2252
+ jsConfig = paktaJsCompletedConfig;
2253
+ }
2254
+ if (jsConfig == nil) {
2255
+ return NO;
2256
+ }
2257
+ NSString *persisted = [PaktaDefaults() stringForKey:keyNativeConfig];
2258
+ return persisted != nil && [persisted isEqualToString:jsConfig];
2259
+ }
2260
+
2261
+ // Runs the process's single round on the calling thread if nobody has
2262
+ // started it yet. deadlineUptime > 0 (crash rescue) caps every HTTP call and
2263
+ // download phase to the remaining budget.
2264
+ + (void)startRoundWithDeadline:(NSTimeInterval)deadlineUptime {
2265
+ bool expected = false;
2266
+ if (!paktaRoundStarted.compare_exchange_strong(expected, true)) {
2267
+ return;
2268
+ }
2269
+ @try {
2270
+ [self runOnce:paktaLaunchRolledBackForRescue deadline:deadlineUptime];
2271
+ } @catch (NSException *exception) {
2272
+ // The rescue path must never take the app down with it.
2273
+ RCTLogWarn(@"RCTPakta -- native check crashed: %@", exception.reason);
2274
+ } @finally {
2275
+ paktaRoundCompleted.store(true);
2276
+ dispatch_semaphore_signal(paktaRoundDone);
2277
+ }
2278
+ }
2279
+
2280
+ // Crash-rescue entry (§11.3), called from the handler's worker queue while
2281
+ // the uncaught-exception handler holds the dying process. Ensures this
2282
+ // process's round runs to completion within the budget, then activates a
2283
+ // downloaded-but-unactivated version if one exists — the last chance before
2284
+ // the process is gone.
2285
+ + (void)runRescueWithDeadline:(NSTimeInterval)deadlineUptime {
2286
+ paktaCrashRescueActive.store(true);
2287
+ [self startRoundWithDeadline:deadlineUptime];
2288
+ if (!paktaRoundCompleted.load()) {
2289
+ NSTimeInterval remaining = deadlineUptime - PaktaMonotonicNow();
2290
+ if (remaining > 0) {
2291
+ dispatch_semaphore_wait(paktaRoundDone, dispatch_time(DISPATCH_TIME_NOW,
2292
+ (int64_t)(remaining * NSEC_PER_SEC)));
2293
+ }
2294
+ }
2295
+ [self activatePendingVersion];
2296
+ }
2297
+
2298
+ // The alert-strategy variant of the §10.7 hole: the round downloaded a fix
2299
+ // but deferred activation to JS, and JS is now dead. Activation is local and
2300
+ // bounded (a state switch under the commit lock).
2301
+ + (void)activatePendingVersion {
2302
+ NSString *hash = nil;
2303
+ uint64_t generation = 0;
2304
+ @synchronized (self) {
2305
+ hash = paktaUnactivatedHash;
2306
+ generation = paktaUnactivatedGeneration;
2307
+ }
2308
+ if (hash == nil) {
2309
+ return;
2310
+ }
2311
+ // Unlike Android's switchVersion, PaktaSwitchVersionLocked does not
2312
+ // validate the version directory; never point the next launch at bytes
2313
+ // that are gone (e.g. wiped by a reset whose generation bump we would
2314
+ // catch below anyway, or by cleanup).
2315
+ NSString *versionDir = [[RCTPakta downloadDir] stringByAppendingPathComponent:hash];
2316
+ if (!PaktaHasCompletedVersionAtPath(versionDir, hash)) {
2317
+ NSLog(@"RCTPakta -- crash rescue: version %@ no longer on disk, dropping activation", hash);
2318
+ return;
2319
+ }
2320
+ NSDictionary *hashInfoEntry = nil;
2321
+ NSString *existingInfo = [PaktaDefaults() stringForKey:PaktaHashInfoKey(hash)];
2322
+ NSData *existingData = [existingInfo dataUsingEncoding:NSUTF8StringEncoding];
2323
+ id parsed = existingData == nil ? nil :
2324
+ [NSJSONSerialization JSONObjectWithData:existingData options:0 error:nil];
2325
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
2326
+ NSMutableDictionary *merged = [parsed mutableCopy];
2327
+ merged[@"crashRescue"] = @YES;
2328
+ hashInfoEntry = @{@"hash": hash, @"info": merged};
2329
+ }
2330
+ BOOL committed = [self commitRoundWithGeneration:generation
2331
+ hashInfo:hashInfoEntry
2332
+ activate:hash
2333
+ responseText:nil
2334
+ request:nil
2335
+ config:nil
2336
+ responseAt:0];
2337
+ if (committed) {
2338
+ @synchronized (self) {
2339
+ paktaUnactivatedHash = nil;
2340
+ }
2341
+ NSLog(@"RCTPakta -- crash rescue: activated downloaded version %@", hash);
2342
+ } else {
2343
+ NSLog(@"RCTPakta -- crash rescue: reset since download, dropping activation");
2344
+ }
2345
+ }
2346
+
2347
+ // A bare module instance drives the existing download/patch pipeline without
2348
+ // a bridge: the file queue is process-global and progress events are gated on
2349
+ // hasListeners (never set without a bridge).
2350
+ + (RCTPakta *)engine {
2351
+ static RCTPakta *engine;
2352
+ static dispatch_once_t onceToken;
2353
+ dispatch_once(&onceToken, ^{
2354
+ engine = [[RCTPakta alloc] init];
2355
+ });
2356
+ return engine;
2357
+ }
2358
+
2359
+ + (void)runOnce:(NSString *)launchRolledBackVersion deadline:(NSTimeInterval)deadlineUptime {
2360
+ // Sampled before any IO: resetToPackagedBundle bumps it, and a reset that
2361
+ // lands while this round is running must win over the round's decision.
2362
+ const uint64_t resetGeneration = paktaResetGeneration.load();
2363
+ NSUserDefaults *defaults = PaktaDefaults();
2364
+ NSString *configJson = [defaults stringForKey:keyNativeConfig];
2365
+ if (configJson.length == 0) {
2366
+ // No persisted config (old integration / first ever launch): the
2367
+ // native check silently does not run — this is the rollout gate.
2368
+ return;
2369
+ }
2370
+ bool ok = false;
2371
+ flowjson::Value config = flowjson::Parse(PaktaToStdString(configJson), &ok);
2372
+ if (!ok || !config.IsObject() || config.Get("disabled").Truthy()) {
2373
+ return;
2374
+ }
2375
+ NSString *appKey = PaktaFromStdString(config.Get("appKey").AsString());
2376
+ if (appKey.length == 0) {
2377
+ return;
2378
+ }
2379
+ // From here on the round does real work: leave the breadcrumb that the
2380
+ // next launch reads to skip its 5s delay if we die mid-round (§11.4).
2381
+ [defaults setObject:@YES forKey:keyNativeCheckIncomplete];
2382
+ @try {
2383
+ [self runConfiguredRound:config
2384
+ configJson:configJson
2385
+ appKey:appKey
2386
+ launchRolledBackVersion:launchRolledBackVersion
2387
+ resetGeneration:resetGeneration
2388
+ deadline:deadlineUptime];
2389
+ } @finally {
2390
+ [defaults removeObjectForKey:keyNativeCheckIncomplete];
2391
+ }
2392
+ }
2393
+
2394
+ + (void)runConfiguredRound:(const flowjson::Value &)config
2395
+ configJson:(NSString *)configJson
2396
+ appKey:(NSString *)appKey
2397
+ launchRolledBackVersion:(NSString *)launchRolledBackVersion
2398
+ resetGeneration:(uint64_t)resetGeneration
2399
+ deadline:(NSTimeInterval)deadlineUptime {
2400
+ NSUserDefaults *defaults = PaktaDefaults();
2401
+ NSString *packageVersion =
2402
+ PaktaFromStdString(config.Get("packageVersion").AsString());
2403
+ if (packageVersion.length == 0) {
2404
+ packageVersion = [RCTPakta packageVersion];
2405
+ }
2406
+
2407
+ __block NSString *currentVersion = nil;
2408
+ PaktaWithStateLock(^{
2409
+ pakta::state::State state = PaktaStateFromDefaults(PaktaDefaults());
2410
+ currentVersion = PaktaFromStdString(state.current_version);
2411
+ });
2412
+ NSString *rolledBackVersion = launchRolledBackVersion;
2413
+ NSString *uuid = [defaults stringForKey:keyUuid] ?: @"";
2414
+
2415
+ flowjson::Value identity = flowjson::Value::Object();
2416
+ identity.Set("packageVersion",
2417
+ flowjson::Value::String(PaktaToStdString(packageVersion)));
2418
+ if (currentVersion != nil) {
2419
+ identity.Set("currentVersion",
2420
+ flowjson::Value::String(PaktaToStdString(currentVersion)));
2421
+ }
2422
+ identity.Set("uuid", flowjson::Value::String(PaktaToStdString(uuid)));
2423
+ if (rolledBackVersion != nil) {
2424
+ identity.Set("rolledBackVersion",
2425
+ flowjson::Value::String(PaktaToStdString(rolledBackVersion)));
2426
+ }
2427
+
2428
+ flowjson::Value cInfo = flowjson::Value::Object();
2429
+ cInfo.Set("rnu", config.Get("rnu"));
2430
+ cInfo.Set("rn", config.Get("rn"));
2431
+ cInfo.Set("os", flowjson::Value::String(PaktaToStdString([NSString
2432
+ stringWithFormat:@"ios %@", [[UIDevice currentDevice] systemVersion]])));
2433
+ cInfo.Set("uuid", flowjson::Value::String(PaktaToStdString(uuid)));
2434
+
2435
+ flowjson::Value input = flowjson::Value::Object();
2436
+ input.Set("packageVersion", identity.Get("packageVersion"));
2437
+ // 渠道由宿主 Info.plist 固化,不能被 JS/native config 覆盖。
2438
+ input.Set("channel", flowjson::Value::String(PaktaToStdString([RCTPakta channel])));
2439
+ if (currentVersion != nil) {
2440
+ input.Set("currentVersion", identity.Get("currentVersion"));
2441
+ }
2442
+ input.Set("buildTime",
2443
+ flowjson::Value::String(PaktaToStdString([RCTPakta buildTime])));
2444
+ input.Set("cInfo", cInfo);
2445
+ input.Set("supportedDiffVersion",
2446
+ flowjson::Value::Number(pakta::hbc::kSupportedDiffVersion));
2447
+ // Cache-only while a crash is being held (§11.3 budget); computed (and
2448
+ // shared with JS getBundleHash) on the ordinary delayed round.
2449
+ input.Set("bundleHash",
2450
+ flowjson::Value::String(PaktaToStdString(
2451
+ PaktaBundleHashSync(!paktaCrashRescueActive.load()))));
2452
+
2453
+ std::string bodyJson =
2454
+ flowjson::Stringify(updateflow::BuildCheckRequestBody(input));
2455
+ NSString *body = [NSString stringWithUTF8String:bodyJson.c_str()];
2456
+ if (body == nil) {
2457
+ RCTLogWarn(@"RCTPakta -- native check: request body is not valid UTF-8");
2458
+ return;
2459
+ }
2460
+
2461
+ NSString *responseText = [self runCheckRequest:config
2462
+ appKey:appKey
2463
+ body:body
2464
+ deadline:deadlineUptime];
2465
+ if (responseText == nil) {
2466
+ RCTLogInfo(@"RCTPakta -- native check: no endpoint reachable, giving up until next launch");
2467
+ return;
2468
+ }
2469
+ long long responseAtSeconds = (long long)[[NSDate date] timeIntervalSince1970];
2470
+
2471
+ flowjson::Value decision = updateflow::HandleCheckResponse(
2472
+ PaktaToStdString(responseText), identity, false,
2473
+ config.Get("afterDownload").AsString());
2474
+ if (decision.Get("action").AsString() != "download") {
2475
+ [self commitRoundWithGeneration:resetGeneration
2476
+ hashInfo:nil
2477
+ activate:nil
2478
+ responseText:responseText
2479
+ request:body
2480
+ config:configJson
2481
+ responseAt:responseAtSeconds];
2482
+ RCTLogInfo(@"RCTPakta -- native check: nothing to do (%s)",
2483
+ decision.Get("reason").AsString().c_str());
2484
+ return;
2485
+ }
2486
+ NSString *hash = PaktaFromStdString(decision.Get("hash").AsString());
2487
+ if (!PaktaIsSafePathComponent(hash)) {
2488
+ return;
2489
+ }
2490
+
2491
+ NSString *versionDir = [[RCTPakta downloadDir] stringByAppendingPathComponent:hash];
2492
+ BOOL downloaded = PaktaHasCompletedVersionAtPath(versionDir, hash);
2493
+ if (!downloaded) {
2494
+ downloaded = [self performAttempts:decision.Get("attempts")
2495
+ hash:hash
2496
+ originHash:currentVersion
2497
+ deadline:deadlineUptime];
2498
+ }
2499
+ if (!downloaded) {
2500
+ [self commitRoundWithGeneration:resetGeneration
2501
+ hashInfo:nil
2502
+ activate:nil
2503
+ responseText:responseText
2504
+ request:body
2505
+ config:configJson
2506
+ responseAt:responseAtSeconds];
2507
+ return;
2508
+ }
2509
+
2510
+ // Persist name/description/metaInfo alongside the version, mirroring the
2511
+ // JS side's setLocalHashInfo after a successful download.
2512
+ const flowjson::Value &info = decision.Get("info");
2513
+ NSMutableDictionary *versionInfo = [NSMutableDictionary dictionary];
2514
+ for (const char *key : {"name", "description", "metaInfo"}) {
2515
+ if (info.Get(key).IsString()) {
2516
+ versionInfo[@(key)] = PaktaFromStdString(info.Get(key).AsString()) ?: @"";
2517
+ }
2518
+ }
2519
+ // A forceBoot activation is the brick-rescue path: mark it in the
2520
+ // persisted info so JS can report force_boot_rescue when this version
2521
+ // survives to markSuccess. Only the server-sent directive counts — a
2522
+ // silent-strategy activation is ordinary delivery.
2523
+ if (info.Get("config").Get("forceBoot").Truthy()) {
2524
+ versionInfo[@"forceBootRescue"] = @YES;
2525
+ }
2526
+ if (paktaCrashRescueActive.load()) {
2527
+ versionInfo[@"crashRescue"] = @YES;
2528
+ }
2529
+ // Silent strategies or a server-marked forceBoot version (per-version
2530
+ // remote override — the brick rescue) activate for the next launch;
2531
+ // otherwise activation stays with the JS side (§6/§10.1). Unless a crash
2532
+ // is being held: JS is dead, deferring to it would leave the fix on disk
2533
+ // forever (§11.3).
2534
+ BOOL activate = decision.Get("activate").Truthy() || paktaCrashRescueActive.load();
2535
+ BOOL committed = [self commitRoundWithGeneration:resetGeneration
2536
+ hashInfo:@{@"hash": hash, @"info": versionInfo}
2537
+ activate:activate ? hash : nil
2538
+ responseText:responseText
2539
+ request:body
2540
+ config:configJson
2541
+ responseAt:responseAtSeconds];
2542
+ if (!committed) {
2543
+ RCTLogInfo(@"RCTPakta -- native check: reset during round, dropping result");
2544
+ } else if (activate) {
2545
+ @synchronized (self) {
2546
+ paktaUnactivatedHash = nil;
2547
+ }
2548
+ RCTLogInfo(@"RCTPakta -- native check: downloaded %@ and set for next launch", hash);
2549
+ } else {
2550
+ // Remembered so a crash later in this process can still activate it
2551
+ // (activatePendingVersion) — JS never will.
2552
+ @synchronized (self) {
2553
+ paktaUnactivatedHash = [hash copy];
2554
+ paktaUnactivatedGeneration = resetGeneration;
2555
+ }
2556
+ RCTLogInfo(@"RCTPakta -- native check: downloaded %@, activation left to JS", hash);
2557
+ }
2558
+ }
2559
+
2560
+ // Everything a round persists — version info, the activation, the response
2561
+ // cache — is written inside ONE state-lock acquisition that first re-checks the
2562
+ // reset generation. resetToPackagedBundle bumps that generation under the same
2563
+ // lock, so there is no compare-and-act window: either the whole round commits,
2564
+ // or the reset wins and none of it does.
2565
+ + (BOOL)commitRoundWithGeneration:(uint64_t)generation
2566
+ hashInfo:(NSDictionary *)hashInfoEntry
2567
+ activate:(NSString *)hashToActivate
2568
+ responseText:(NSString *)responseText
2569
+ request:(NSString *)requestBody
2570
+ config:(NSString *)configJson
2571
+ responseAt:(long long)responseAtSeconds {
2572
+ // responseText is nil for the crash handler's activation-only commit
2573
+ // (activatePendingVersion): no round ran, so there is no cache to write.
2574
+ NSData *cacheData = nil;
2575
+ if (responseText != nil) {
2576
+ NSDictionary *cacheEntry = @{
2577
+ @"ts": @(responseAtSeconds),
2578
+ @"body": responseText,
2579
+ @"request": requestBody,
2580
+ @"config": configJson,
2581
+ };
2582
+ cacheData = [NSJSONSerialization dataWithJSONObject:cacheEntry options:0 error:nil];
2583
+ }
2584
+ __block BOOL committed = NO;
2585
+ PaktaWithStateLock(^{
2586
+ if (paktaResetGeneration.load() != generation) {
2587
+ return;
2588
+ }
2589
+ NSUserDefaults *defaults = PaktaDefaults();
2590
+ if (hashInfoEntry != nil) {
2591
+ NSData *infoData = [NSJSONSerialization dataWithJSONObject:hashInfoEntry[@"info"]
2592
+ options:0
2593
+ error:nil];
2594
+ if (infoData != nil) {
2595
+ [defaults setObject:[[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding]
2596
+ forKey:PaktaHashInfoKey(hashInfoEntry[@"hash"])];
2597
+ }
2598
+ }
2599
+ if (hashToActivate != nil) {
2600
+ PaktaSwitchVersionLocked(hashToActivate);
2601
+ }
2602
+ if (cacheData != nil) {
2603
+ [defaults setObject:[[NSString alloc] initWithData:cacheData encoding:NSUTF8StringEncoding]
2604
+ forKey:keyNativeCheckCache];
2605
+ }
2606
+ committed = YES;
2607
+ });
2608
+ return committed;
2609
+ }
2610
+
2611
+ // Sequential fallback over the ordered candidates (§5.1): one request at a
2612
+ // time with its own timeout; after the configured round fails, queryUrls
2613
+ // discovery merges remote candidates (excluding the already-tried) for one
2614
+ // more round. No hedged race on purpose — this path is latency-insensitive.
2615
+ // Per-request timeout, capped to the crash-rescue budget when one is active.
2616
+ // <= 0 means the budget is gone and the round must stop issuing requests.
2617
+ static NSTimeInterval PaktaCheckRequestTimeout(NSTimeInterval deadlineUptime) {
2618
+ if (deadlineUptime <= 0) {
2619
+ return 10;
2620
+ }
2621
+ return MIN(10, deadlineUptime - PaktaMonotonicNow());
2622
+ }
2623
+
2624
+ + (NSString *)runCheckRequest:(const flowjson::Value &)config
2625
+ appKey:(NSString *)appKey
2626
+ body:(NSString *)body
2627
+ deadline:(NSTimeInterval)deadlineUptime {
2628
+ double sample = arc4random() / 4294967296.0;
2629
+ flowjson::Value ordered =
2630
+ updateflow::OrderEndpointCandidates(config.Get("endpoints"), sample);
2631
+ NSMutableSet<NSString *> *tried = [NSMutableSet set];
2632
+ const NSUInteger maxHttpAttempts = 8;
2633
+ NSUInteger httpAttempts = 0;
2634
+ for (const auto &endpoint : ordered.elements()) {
2635
+ NSString *base = PaktaNormalizeEndpointBase(
2636
+ PaktaFromStdString(endpoint.AsString()));
2637
+ if (base.length == 0 || [tried containsObject:base]) {
2638
+ continue;
2639
+ }
2640
+ if (httpAttempts++ >= maxHttpAttempts) {
2641
+ return nil;
2642
+ }
2643
+ [tried addObject:base];
2644
+ NSTimeInterval timeout = PaktaCheckRequestTimeout(deadlineUptime);
2645
+ if (timeout <= 0) {
2646
+ return nil;
2647
+ }
2648
+ NSString *response = PaktaHttpRequest(
2649
+ [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey],
2650
+ @"POST", body, timeout);
2651
+ if (PaktaIsValidCheckResponse(response)) {
2652
+ return response;
2653
+ }
2654
+ }
2655
+ for (const auto &queryUrl : config.Get("queryUrls").elements()) {
2656
+ NSString *listUrl = PaktaFromStdString(queryUrl.AsString());
2657
+ if (listUrl == nil) {
2658
+ continue;
2659
+ }
2660
+ if (httpAttempts++ >= maxHttpAttempts) {
2661
+ return nil;
2662
+ }
2663
+ NSTimeInterval listTimeout = PaktaCheckRequestTimeout(deadlineUptime);
2664
+ if (listTimeout <= 0) {
2665
+ return nil;
2666
+ }
2667
+ NSString *listText = PaktaHttpRequest(listUrl, @"GET", nil, listTimeout);
2668
+ if (listText == nil) {
2669
+ continue;
2670
+ }
2671
+ bool ok = false;
2672
+ flowjson::Value remote = flowjson::Parse(PaktaToStdString(listText), &ok);
2673
+ if (!ok || !remote.IsArray()) {
2674
+ continue;
2675
+ }
2676
+ for (const auto &endpoint : remote.elements()) {
2677
+ NSString *base = PaktaNormalizeEndpointBase(
2678
+ PaktaFromStdString(endpoint.AsString()));
2679
+ if (base.length == 0 || [tried containsObject:base]) {
2680
+ continue;
2681
+ }
2682
+ if (httpAttempts++ >= maxHttpAttempts) {
2683
+ return nil;
2684
+ }
2685
+ [tried addObject:base];
2686
+ NSTimeInterval timeout = PaktaCheckRequestTimeout(deadlineUptime);
2687
+ if (timeout <= 0) {
2688
+ return nil;
2689
+ }
2690
+ NSString *response = PaktaHttpRequest(
2691
+ [NSString stringWithFormat:@"%@/checkUpdate/%@", base, appKey],
2692
+ @"POST", body, timeout);
2693
+ if (PaktaIsValidCheckResponse(response)) {
2694
+ return response;
2695
+ }
2696
+ }
2697
+ break; // one successfully fetched remote list is enough
2698
+ }
2699
+ return nil;
2700
+ }
2701
+
2702
+ + (BOOL)performAttempts:(const flowjson::Value &)attempts
2703
+ hash:(NSString *)hash
2704
+ originHash:(NSString *)originHash
2705
+ deadline:(NSTimeInterval)rescueDeadline {
2706
+ RCTPakta *engine = [self engine];
2707
+ // Crash-rescue budget caps every phase; 0 keeps the normal 10min windows.
2708
+ NSTimeInterval incrementalDeadline = PaktaMonotonicNow() + 600;
2709
+ if (rescueDeadline > 0) {
2710
+ incrementalDeadline = MIN(incrementalDeadline, rescueDeadline);
2711
+ }
2712
+ NSTimeInterval fullDeadline = 0;
2713
+ for (const auto &attempt : attempts.elements()) {
2714
+ const std::string &type = attempt.Get("type").AsString();
2715
+ PaktaType paktaType = type == "diff" ? PaktaTypePatchFromPpk
2716
+ : type == "pdiff" ? PaktaTypePatchFromPackage
2717
+ : PaktaTypeFullDownload;
2718
+ if (paktaType == PaktaTypePatchFromPpk && originHash.length == 0) {
2719
+ continue; // diff patches from the running version; none running
2720
+ }
2721
+ BOOL isFullAttempt = paktaType == PaktaTypeFullDownload;
2722
+ if (isFullAttempt && fullDeadline == 0) {
2723
+ // diff/pdiff cannot starve the last-resort full download.
2724
+ fullDeadline = PaktaMonotonicNow() + 600;
2725
+ if (rescueDeadline > 0) {
2726
+ fullDeadline = MIN(fullDeadline, rescueDeadline);
2727
+ }
2728
+ }
2729
+ NSTimeInterval deadline = isFullAttempt ? fullDeadline : incrementalDeadline;
2730
+ for (const auto &urlValue : attempt.Get("urls").elements()) {
2731
+ NSString *url = PaktaFromStdString(urlValue.AsString());
2732
+ if (url == nil) {
2733
+ continue;
2734
+ }
2735
+ NSTimeInterval remaining = deadline - PaktaMonotonicNow();
2736
+ if (remaining <= 0) {
2737
+ if (isFullAttempt) {
2738
+ return NO;
2739
+ }
2740
+ break;
2741
+ }
2742
+ NSMutableDictionary *options =
2743
+ [@{
2744
+ @"updateUrl": url,
2745
+ @"hash": hash,
2746
+ @"deadlineUptime": @(deadline),
2747
+ } mutableCopy];
2748
+ if (paktaType == PaktaTypePatchFromPpk) {
2749
+ options[@"originHash"] = originHash;
2750
+ }
2751
+ dispatch_semaphore_t sem = dispatch_semaphore_create(0);
2752
+ __block NSError *resultError = nil;
2753
+ [engine performUpdate:paktaType options:options callback:^(NSError *error) {
2754
+ resultError = error;
2755
+ dispatch_semaphore_signal(sem);
2756
+ }];
2757
+ if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW,
2758
+ (int64_t)(remaining * NSEC_PER_SEC))) != 0) {
2759
+ RCTLogWarn(@"RCTPakta -- native check: %s attempt timed out", type.c_str());
2760
+ if (isFullAttempt) {
2761
+ return NO;
2762
+ }
2763
+ break;
2764
+ }
2765
+ if (resultError == nil) {
2766
+ return YES;
2767
+ }
2768
+ RCTLogInfo(@"RCTPakta -- native check: %s attempt failed: %@",
2769
+ type.c_str(), resultError.localizedDescription);
2770
+ }
2771
+ }
2772
+ return NO;
2773
+ }
2774
+
2775
+ @end