react-native-bspatch 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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +471 -0
  3. package/android/build.gradle +100 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/vn/reactnativebspatch/BsPatchModule.kt +139 -0
  6. package/android/src/main/java/vn/reactnativebspatch/BsPatchPackage.kt +16 -0
  7. package/android/src/main/java/vn/reactnativebspatch/OtaBundleResolver.kt +227 -0
  8. package/cpp/CMakeLists.txt +43 -0
  9. package/cpp/bsdiff/bspatch.c +194 -0
  10. package/cpp/bsdiff/bspatch.h +41 -0
  11. package/cpp/bsdiff/bspatch_wrapper.c +150 -0
  12. package/cpp/bsdiff/bspatch_wrapper.h +6 -0
  13. package/cpp/bspatch_jni.cpp +41 -0
  14. package/cpp/bzip2/LICENSE +42 -0
  15. package/cpp/bzip2/blocksort.c +1094 -0
  16. package/cpp/bzip2/bzlib.c +1572 -0
  17. package/cpp/bzip2/bzlib.h +282 -0
  18. package/cpp/bzip2/bzlib_private.h +509 -0
  19. package/cpp/bzip2/compress.c +672 -0
  20. package/cpp/bzip2/crctable.c +104 -0
  21. package/cpp/bzip2/decompress.c +652 -0
  22. package/cpp/bzip2/huffman.c +205 -0
  23. package/cpp/bzip2/randtable.c +84 -0
  24. package/ios/OtaBundleResolver.swift +241 -0
  25. package/ios/RNBsPatch.h +8 -0
  26. package/ios/RNBsPatch.mm +198 -0
  27. package/lib/commonjs/NativeBsPatch.js +9 -0
  28. package/lib/commonjs/NativeBsPatch.js.map +1 -0
  29. package/lib/commonjs/crypto-js.d.js +2 -0
  30. package/lib/commonjs/crypto-js.d.js.map +1 -0
  31. package/lib/commonjs/index.js +90 -0
  32. package/lib/commonjs/index.js.map +1 -0
  33. package/lib/commonjs/ota/OtaProvider.js +306 -0
  34. package/lib/commonjs/ota/OtaProvider.js.map +1 -0
  35. package/lib/commonjs/ota/otaAssetResolver.js +145 -0
  36. package/lib/commonjs/ota/otaAssetResolver.js.map +1 -0
  37. package/lib/commonjs/ota/otaCore.js +736 -0
  38. package/lib/commonjs/ota/otaCore.js.map +1 -0
  39. package/lib/commonjs/ota/otaLogger.js +64 -0
  40. package/lib/commonjs/ota/otaLogger.js.map +1 -0
  41. package/lib/commonjs/ota/otaMetadata.js +138 -0
  42. package/lib/commonjs/ota/otaMetadata.js.map +1 -0
  43. package/lib/commonjs/ota/storage/CustomStorage.js +140 -0
  44. package/lib/commonjs/ota/storage/CustomStorage.js.map +1 -0
  45. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js +96 -0
  46. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js.map +1 -0
  47. package/lib/commonjs/ota/types.js +39 -0
  48. package/lib/commonjs/ota/types.js.map +1 -0
  49. package/lib/commonjs/package.json +1 -0
  50. package/lib/module/NativeBsPatch.js +5 -0
  51. package/lib/module/NativeBsPatch.js.map +1 -0
  52. package/lib/module/crypto-js.d.js +2 -0
  53. package/lib/module/crypto-js.d.js.map +1 -0
  54. package/lib/module/index.js +23 -0
  55. package/lib/module/index.js.map +1 -0
  56. package/lib/module/ota/OtaProvider.js +299 -0
  57. package/lib/module/ota/OtaProvider.js.map +1 -0
  58. package/lib/module/ota/otaAssetResolver.js +138 -0
  59. package/lib/module/ota/otaAssetResolver.js.map +1 -0
  60. package/lib/module/ota/otaCore.js +725 -0
  61. package/lib/module/ota/otaCore.js.map +1 -0
  62. package/lib/module/ota/otaLogger.js +59 -0
  63. package/lib/module/ota/otaLogger.js.map +1 -0
  64. package/lib/module/ota/otaMetadata.js +127 -0
  65. package/lib/module/ota/otaMetadata.js.map +1 -0
  66. package/lib/module/ota/storage/CustomStorage.js +134 -0
  67. package/lib/module/ota/storage/CustomStorage.js.map +1 -0
  68. package/lib/module/ota/storage/GitHubReleaseStorage.js +90 -0
  69. package/lib/module/ota/storage/GitHubReleaseStorage.js.map +1 -0
  70. package/lib/module/ota/types.js +38 -0
  71. package/lib/module/ota/types.js.map +1 -0
  72. package/lib/typescript/NativeBsPatch.d.ts +39 -0
  73. package/lib/typescript/NativeBsPatch.d.ts.map +1 -0
  74. package/lib/typescript/index.d.ts +15 -0
  75. package/lib/typescript/index.d.ts.map +1 -0
  76. package/lib/typescript/ota/OtaProvider.d.ts +10 -0
  77. package/lib/typescript/ota/OtaProvider.d.ts.map +1 -0
  78. package/lib/typescript/ota/otaAssetResolver.d.ts +15 -0
  79. package/lib/typescript/ota/otaAssetResolver.d.ts.map +1 -0
  80. package/lib/typescript/ota/otaCore.d.ts +109 -0
  81. package/lib/typescript/ota/otaCore.d.ts.map +1 -0
  82. package/lib/typescript/ota/otaLogger.d.ts +44 -0
  83. package/lib/typescript/ota/otaLogger.d.ts.map +1 -0
  84. package/lib/typescript/ota/otaMetadata.d.ts +46 -0
  85. package/lib/typescript/ota/otaMetadata.d.ts.map +1 -0
  86. package/lib/typescript/ota/storage/CustomStorage.d.ts +12 -0
  87. package/lib/typescript/ota/storage/CustomStorage.d.ts.map +1 -0
  88. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts +20 -0
  89. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts.map +1 -0
  90. package/lib/typescript/ota/types.d.ts +191 -0
  91. package/lib/typescript/ota/types.d.ts.map +1 -0
  92. package/package.json +89 -0
  93. package/react-native-bspatch.podspec +46 -0
  94. package/src/NativeBsPatch.ts +52 -0
  95. package/src/crypto-js.d.ts +1 -0
  96. package/src/index.ts +29 -0
  97. package/src/ota/OtaProvider.tsx +399 -0
  98. package/src/ota/otaAssetResolver.ts +182 -0
  99. package/src/ota/otaCore.ts +963 -0
  100. package/src/ota/otaLogger.ts +72 -0
  101. package/src/ota/otaMetadata.ts +149 -0
  102. package/src/ota/storage/CustomStorage.ts +143 -0
  103. package/src/ota/storage/GitHubReleaseStorage.ts +98 -0
  104. package/src/ota/types.ts +211 -0
  105. package/tools/bsdiff +0 -0
  106. package/tools/bsdiff.c +445 -0
  107. package/tools/bsdiff.h +45 -0
  108. package/tools/build_ota.sh +457 -0
  109. package/tools/setup_project.js +273 -0
  110. package/tools/update_registry.js +145 -0
  111. package/tools/upload_ota_custom.sh +138 -0
@@ -0,0 +1,139 @@
1
+ package vn.reactnativebspatch
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReactMethod
7
+ import com.facebook.react.bridge.Callback
8
+ import com.facebook.react.bridge.Promise
9
+ import java.io.File
10
+ import java.io.FileInputStream
11
+ import java.io.FileOutputStream
12
+ import java.io.BufferedInputStream
13
+ import java.io.BufferedOutputStream
14
+ import java.util.zip.ZipInputStream
15
+ import java.util.zip.GZIPInputStream
16
+
17
+
18
+ class BsPatchModule(private val reactContext: ReactApplicationContext) : NativeBsPatchSpec(reactContext) {
19
+
20
+ companion object {
21
+ const val NAME = "BsPatch"
22
+
23
+ init {
24
+ System.loadLibrary("bspatch")
25
+ }
26
+ }
27
+
28
+ override fun getName(): String {
29
+ return NAME
30
+ }
31
+
32
+ private external fun nativeApplyPatch(oldPath: String, newPath: String, patchPath: String): Boolean
33
+
34
+ override fun applyPatch(oldPath: String, newPath: String, patchPath: String, promise: com.facebook.react.bridge.Promise) {
35
+ Thread {
36
+ try {
37
+ val result = nativeApplyPatch(oldPath, newPath, patchPath)
38
+ promise.resolve(result)
39
+ } catch (e: Exception) {
40
+ promise.reject("ERR_BSPATCH", e.message, e)
41
+ }
42
+ }.start()
43
+ }
44
+
45
+ override fun unzip(source: String, targetDir: String, promise: Promise) {
46
+ Thread {
47
+ try {
48
+ val sourceFile = File(source)
49
+ val destDir = File(targetDir)
50
+ if (!destDir.exists()) destDir.mkdirs()
51
+
52
+ ZipInputStream(BufferedInputStream(FileInputStream(sourceFile))).use { zis ->
53
+ var entry = zis.nextEntry
54
+ while (entry != null) {
55
+ val file = File(destDir, entry.name)
56
+ if (entry.isDirectory) {
57
+ file.mkdirs()
58
+ } else {
59
+ file.parentFile?.mkdirs()
60
+ BufferedOutputStream(FileOutputStream(file)).use { bos ->
61
+ zis.copyTo(bos)
62
+ }
63
+ }
64
+ zis.closeEntry()
65
+ entry = zis.nextEntry
66
+ }
67
+ }
68
+ promise.resolve(true)
69
+ } catch (e: Exception) {
70
+ promise.reject("ERR_UNZIP", e.message, e)
71
+ }
72
+ }.start()
73
+ }
74
+
75
+ /**
76
+ * Extracts the JS bundle from APK assets using Android AssetManager.
77
+ * Guarantees byte-exact copy — RNFS.copyFileAssets can produce different
78
+ * bytes in certain APK configurations.
79
+ */
80
+ override fun extractBundleFromAssets(assetName: String, destPath: String, promise: Promise) {
81
+ Thread {
82
+ try {
83
+ val assetManager = reactContext.assets
84
+ assetManager.open(assetName).use { input ->
85
+ val destFile = File(destPath)
86
+ destFile.parentFile?.mkdirs()
87
+ BufferedOutputStream(FileOutputStream(destFile)).use { output ->
88
+ input.copyTo(output, bufferSize = 8192)
89
+ }
90
+ }
91
+ promise.resolve(true)
92
+ } catch (e: Exception) {
93
+ promise.reject("ERR_EXTRACT_ASSET", e.message, e)
94
+ }
95
+ }.start()
96
+ }
97
+
98
+ override fun markAsHealthy() {
99
+ OtaBundleResolver.markAsHealthy(reactContext)
100
+ }
101
+
102
+ override fun reloadBundle() {
103
+ val activity = reactContext.currentActivity
104
+ if (activity != null) {
105
+ Handler(Looper.getMainLooper()).post {
106
+ val intent = activity.packageManager.getLaunchIntentForPackage(activity.packageName)
107
+ if (intent != null) {
108
+ intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP)
109
+ activity.startActivity(intent)
110
+ Runtime.getRuntime().exit(0)
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ override fun logNative(message: String?) {
117
+ android.util.Log.d("JS-OTA", message ?: "")
118
+ }
119
+
120
+ override fun decompressGzip(sourcePath: String, destPath: String, promise: Promise) {
121
+ Thread {
122
+ try {
123
+ val source = File(sourcePath)
124
+ val dest = File(destPath)
125
+ dest.parentFile?.mkdirs()
126
+
127
+ GZIPInputStream(BufferedInputStream(FileInputStream(source))).use { gis ->
128
+ BufferedOutputStream(FileOutputStream(dest)).use { bos ->
129
+ gis.copyTo(bos)
130
+ }
131
+ }
132
+ promise.resolve(true)
133
+ } catch (e: Exception) {
134
+ promise.reject("ERR_GZIP_DECOMPRESS", e.message, e)
135
+ }
136
+ }.start()
137
+ }
138
+
139
+ }
@@ -0,0 +1,16 @@
1
+ package vn.reactnativebspatch
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class BsPatchPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf<NativeModule>(BsPatchModule(reactContext) as NativeModule)
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,227 @@
1
+ package vn.reactnativebspatch
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import org.json.JSONArray
6
+ import org.json.JSONObject
7
+ import java.io.File
8
+
9
+ /**
10
+ * OtaBundleResolver — encapsulates OTA bundle path resolution with
11
+ * crash detection and automatic rollback.
12
+ *
13
+ * IMPORTANT: This runs BEFORE any JS code. If the OTA bundle is corrupted,
14
+ * Hermes crashes before React Native boots. This native resolver handles
15
+ * crash detection so the app can recover without JS ever running.
16
+ *
17
+ * Lifecycle:
18
+ * 1. Read metadata.json
19
+ * 2. If lastLaunchSuccessful == false → rollback to previous version
20
+ * 3. Mark current launch as pending (lastLaunchSuccessful = false)
21
+ * 4. Return the bundle path (or null for APK default)
22
+ * 5. JS calls markAsHealthy() → sets lastLaunchSuccessful = true
23
+ * 6. If crash before markAsHealthy → next boot sees false → rollback
24
+ *
25
+ * Usage in MainApplication.kt:
26
+ * ```
27
+ * jsBundleFilePath = OtaBundleResolver.resolve(applicationContext)
28
+ * ```
29
+ */
30
+ object OtaBundleResolver {
31
+
32
+ private const val TAG = "OtaBundleResolver"
33
+ private const val OTA_DIR_NAME = "ota"
34
+ private const val METADATA_FILE = "metadata.json"
35
+ private const val DEFAULT_BASE_VERSION = 1
36
+ private const val MAX_ROLLBACK = 3
37
+
38
+ /**
39
+ * Resolves the JS bundle path from OTA metadata.
40
+ * Handles crash detection and rollback entirely on the native side.
41
+ *
42
+ * @param context Android Application context
43
+ * @param baseVersion The OTA version baked into the APK (default: 1)
44
+ * @return Absolute path to the OTA bundle, or null to use the APK default.
45
+ */
46
+ @JvmStatic
47
+ fun resolve(context: Context, baseVersion: Int = DEFAULT_BASE_VERSION): String? {
48
+ return try {
49
+ val otaDir = File(context.filesDir, OTA_DIR_NAME)
50
+ val metadataFile = File(otaDir, METADATA_FILE)
51
+
52
+ if (!metadataFile.exists()) {
53
+ Log.d(TAG, "No OTA metadata → APK bundle")
54
+ return null
55
+ }
56
+
57
+ var metadata = JSONObject(metadataFile.readText())
58
+ val currentVersion = metadata.optInt("currentVersion", baseVersion)
59
+ val lastLaunchOk = metadata.optBoolean("lastLaunchSuccessful", true)
60
+ val rollbackCount = metadata.optInt("rollbackCount", 0)
61
+
62
+ // ── Step 1: Crash detection & rollback ──────────────────────────
63
+ if (!lastLaunchOk && currentVersion > baseVersion) {
64
+ Log.w(TAG, "Previous launch failed! Rolling back from V$currentVersion...")
65
+ metadata = performRollback(metadata, baseVersion, rollbackCount, otaDir)
66
+ writeMetadata(metadataFile, metadata)
67
+
68
+ val newVersion = metadata.optInt("currentVersion", baseVersion)
69
+ if (newVersion <= baseVersion) {
70
+ Log.i(TAG, "Rolled back to factory APK bundle")
71
+ return null
72
+ }
73
+
74
+ // Check if the rollback target bundle exists
75
+ val rollbackVDir = File(otaDir, "v${newVersion}")
76
+ val rollbackBundle = File(rollbackVDir, "index.android.bundle")
77
+ if (!rollbackBundle.exists()) {
78
+ Log.w(TAG, "Rollback target index.android.bundle missing in V$newVersion → factory")
79
+ resetToFactory(metadataFile, baseVersion)
80
+ return null
81
+ }
82
+
83
+ // Mark this rollback attempt as pending
84
+ metadata.put("lastLaunchSuccessful", false)
85
+ writeMetadata(metadataFile, metadata)
86
+
87
+ Log.i(TAG, "Trying rollback bundle: ${rollbackBundle.absolutePath}")
88
+ return rollbackBundle.absolutePath
89
+ }
90
+
91
+ // ── Step 2: Normal boot — no OTA bundle needed ──────────────────
92
+ if (currentVersion <= baseVersion) {
93
+ Log.d(TAG, "OTA v$currentVersion ≤ base v$baseVersion → APK bundle")
94
+ return null
95
+ }
96
+
97
+ // ── Step 3: OTA bundle — mark as pending verification ───────────
98
+ val vDir = File(otaDir, "v${currentVersion}")
99
+ val bundleFile = File(vDir, "index.android.bundle")
100
+ if (!bundleFile.exists()) {
101
+ Log.w(TAG, "index.android.bundle missing in V$currentVersion → APK bundle")
102
+ resetToFactory(metadataFile, baseVersion)
103
+ return null
104
+ }
105
+
106
+ // Mark launch as pending — if Hermes crashes loading this bundle,
107
+ // the next boot will see false and trigger rollback
108
+ metadata.put("lastLaunchSuccessful", false)
109
+ writeMetadata(metadataFile, metadata)
110
+
111
+ Log.i(TAG, "Loading OTA bundle: ${bundleFile.absolutePath}")
112
+ bundleFile.absolutePath
113
+ } catch (e: Exception) {
114
+ Log.e(TAG, "Error resolving OTA bundle: ${e.message}")
115
+ null
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Performs rollback by popping from previousVersions.
121
+ * If maxRollback reached, resets to factory.
122
+ */
123
+ private fun performRollback(
124
+ metadata: JSONObject,
125
+ baseVersion: Int,
126
+ currentRollbackCount: Int,
127
+ otaDir: File
128
+ ): JSONObject {
129
+ val newCount = currentRollbackCount + 1
130
+
131
+ // Max rollback reached → factory reset
132
+ if (newCount >= MAX_ROLLBACK) {
133
+ Log.w(TAG, "Max rollback ($MAX_ROLLBACK) reached → factory reset")
134
+ return JSONObject().apply {
135
+ put("currentVersion", baseVersion)
136
+ put("previousVersions", JSONArray())
137
+ put("rollbackCount", 0)
138
+ put("lastLaunchSuccessful", true)
139
+ }
140
+ }
141
+
142
+ val prevVersions = metadata.optJSONArray("previousVersions") ?: JSONArray()
143
+
144
+ if (prevVersions.length() == 0) {
145
+ Log.w(TAG, "No previous versions to rollback to → factory reset")
146
+ return JSONObject().apply {
147
+ put("currentVersion", baseVersion)
148
+ put("previousVersions", JSONArray())
149
+ put("rollbackCount", newCount)
150
+ put("lastLaunchSuccessful", true)
151
+ }
152
+ }
153
+
154
+ // Pop the first previous version
155
+ val rollbackTo = prevVersions.optInt(0, baseVersion)
156
+ val remaining = JSONArray()
157
+ for (i in 1 until prevVersions.length()) {
158
+ remaining.put(prevVersions.optInt(i))
159
+ }
160
+
161
+ Log.i(TAG, "Rolling back: V${metadata.optInt("currentVersion")} → V$rollbackTo (attempt $newCount)")
162
+
163
+ // Check if this rollback target bundle exists
164
+ val vDir = File(otaDir, "v${rollbackTo}")
165
+ val targetBundle = File(vDir, "index.android.bundle")
166
+ if (!targetBundle.exists() && rollbackTo > baseVersion) {
167
+ Log.w(TAG, "index.android.bundle not found in V$rollbackTo, trying deeper rollback...")
168
+ val intermediate = JSONObject().apply {
169
+ put("currentVersion", rollbackTo)
170
+ put("previousVersions", remaining)
171
+ put("rollbackCount", newCount)
172
+ put("lastLaunchSuccessful", false)
173
+ }
174
+ return performRollback(intermediate, baseVersion, newCount, otaDir)
175
+ }
176
+
177
+ return JSONObject().apply {
178
+ put("currentVersion", rollbackTo)
179
+ put("previousVersions", remaining)
180
+ put("rollbackCount", newCount)
181
+ put("lastLaunchSuccessful", true) // Will be set to false again in resolve()
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Resets metadata to factory defaults.
187
+ */
188
+ private fun resetToFactory(metadataFile: File, baseVersion: Int) {
189
+ val factory = JSONObject().apply {
190
+ put("currentVersion", baseVersion)
191
+ put("previousVersions", JSONArray())
192
+ put("rollbackCount", 0)
193
+ put("lastLaunchSuccessful", true)
194
+ }
195
+ writeMetadata(metadataFile, factory)
196
+ }
197
+
198
+ @JvmStatic
199
+ fun markAsHealthy(context: Context) {
200
+ try {
201
+ val otaDir = File(context.filesDir, OTA_DIR_NAME)
202
+ val metadataFile = File(otaDir, METADATA_FILE)
203
+ if (!metadataFile.exists()) return
204
+
205
+ val metadata = JSONObject(metadataFile.readText())
206
+ metadata.put("lastLaunchSuccessful", true)
207
+ metadata.put("rollbackCount", 0)
208
+
209
+ writeMetadata(metadataFile, metadata)
210
+ Log.i(TAG, "App marked as healthy")
211
+ } catch (e: Exception) {
212
+ Log.e(TAG, "Failed to mark as healthy: ${e.message}")
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Writes metadata JSON to disk.
218
+ */
219
+ private fun writeMetadata(file: File, metadata: JSONObject) {
220
+ try {
221
+ file.parentFile?.mkdirs()
222
+ file.writeText(metadata.toString(2))
223
+ } catch (e: Exception) {
224
+ Log.e(TAG, "Failed to write metadata: ${e.message}")
225
+ }
226
+ }
227
+ }
@@ -0,0 +1,43 @@
1
+ cmake_minimum_required(VERSION 3.18.1)
2
+
3
+ project("bspatch")
4
+
5
+ # Define bzip2 sources explicitly to avoid including executables
6
+ set(BZIP2_SOURCES
7
+ "bzip2/blocksort.c"
8
+ "bzip2/huffman.c"
9
+ "bzip2/crctable.c"
10
+ "bzip2/randtable.c"
11
+ "bzip2/compress.c"
12
+ "bzip2/decompress.c"
13
+ "bzip2/bzlib.c"
14
+ )
15
+
16
+ # React Native includes
17
+ set(REACT_NATIVE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../node_modules/react-native)
18
+
19
+ # Add our custom library
20
+ add_library(
21
+ bspatch
22
+ SHARED
23
+
24
+ bspatch_jni.cpp
25
+ bsdiff/bspatch_wrapper.c
26
+ bsdiff/bspatch.c
27
+ ${BZIP2_SOURCES}
28
+ )
29
+
30
+ # Include directories
31
+ target_include_directories(bspatch PRIVATE bzip2 bsdiff)
32
+
33
+ # Link against Android standard libraries
34
+ target_link_libraries(
35
+ bspatch
36
+ log
37
+ )
38
+
39
+ # Set 16KB (2^14) page alignment for Android compatibility
40
+ # Required for arm64-v8a and x86_64 architectures
41
+ if(ANDROID)
42
+ target_link_options(bspatch PRIVATE "LINKER:-z,max-page-size=16384")
43
+ endif()
@@ -0,0 +1,194 @@
1
+ /*-
2
+ * Copyright 2003-2005 Colin Percival
3
+ * Copyright 2012 Matthew Endsley
4
+ * All rights reserved
5
+ *
6
+ * Redistribution and use in source and binary forms, with or without
7
+ * modification, are permitted providing that the following conditions
8
+ * are met:
9
+ * 1. Redistributions of source code must retain the above copyright
10
+ * notice, this list of conditions and the following disclaimer.
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in the
13
+ * documentation and/or other materials provided with the distribution.
14
+ *
15
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
+ * POSSIBILITY OF SUCH DAMAGE.
26
+ */
27
+
28
+ #include <limits.h>
29
+ #include "bspatch.h"
30
+
31
+ static int64_t offtin(uint8_t *buf)
32
+ {
33
+ int64_t y;
34
+
35
+ y=buf[7]&0x7F;
36
+ y=y*256;y+=buf[6];
37
+ y=y*256;y+=buf[5];
38
+ y=y*256;y+=buf[4];
39
+ y=y*256;y+=buf[3];
40
+ y=y*256;y+=buf[2];
41
+ y=y*256;y+=buf[1];
42
+ y=y*256;y+=buf[0];
43
+
44
+ if(buf[7]&0x80) y=-y;
45
+
46
+ return y;
47
+ }
48
+
49
+ int bspatch(const uint8_t* old, int64_t oldsize, uint8_t* new, int64_t newsize, struct bspatch_stream* stream)
50
+ {
51
+ uint8_t buf[8];
52
+ int64_t oldpos,newpos;
53
+ int64_t ctrl[3];
54
+ int64_t i;
55
+
56
+ oldpos=0;newpos=0;
57
+ while(newpos<newsize) {
58
+ /* Read control data */
59
+ for(i=0;i<=2;i++) {
60
+ if (stream->read(stream, buf, 8))
61
+ return -1;
62
+ ctrl[i]=offtin(buf);
63
+ };
64
+
65
+ /* Sanity-check */
66
+ if (ctrl[0]<0 || ctrl[0]>INT_MAX ||
67
+ ctrl[1]<0 || ctrl[1]>INT_MAX ||
68
+ newpos+ctrl[0]>newsize)
69
+ return -1;
70
+
71
+ /* Read diff string */
72
+ if (stream->read(stream, new + newpos, ctrl[0]))
73
+ return -1;
74
+
75
+ /* Add old data to diff string */
76
+ for(i=0;i<ctrl[0];i++)
77
+ if((oldpos+i>=0) && (oldpos+i<oldsize))
78
+ new[newpos+i]+=old[oldpos+i];
79
+
80
+ /* Adjust pointers */
81
+ newpos+=ctrl[0];
82
+ oldpos+=ctrl[0];
83
+
84
+ /* Sanity-check */
85
+ if(newpos+ctrl[1]>newsize)
86
+ return -1;
87
+
88
+ /* Read extra string */
89
+ if (stream->read(stream, new + newpos, ctrl[1]))
90
+ return -1;
91
+
92
+ /* Adjust pointers */
93
+ newpos+=ctrl[1];
94
+ oldpos+=ctrl[2];
95
+ };
96
+
97
+ return 0;
98
+ }
99
+
100
+ #if defined(BSPATCH_EXECUTABLE)
101
+
102
+ #include <bzlib.h>
103
+ #include <stdlib.h>
104
+ #include <stdint.h>
105
+ #include <stdio.h>
106
+ #include <string.h>
107
+ #include <err.h>
108
+ #include <sys/types.h>
109
+ #include <sys/stat.h>
110
+ #include <unistd.h>
111
+ #include <fcntl.h>
112
+
113
+ static int bz2_read(const struct bspatch_stream* stream, void* buffer, int length)
114
+ {
115
+ int n;
116
+ int bz2err;
117
+ BZFILE* bz2;
118
+
119
+ bz2 = (BZFILE*)stream->opaque;
120
+ n = BZ2_bzRead(&bz2err, bz2, buffer, length);
121
+ if (n != length)
122
+ return -1;
123
+
124
+ return 0;
125
+ }
126
+
127
+ int main(int argc,char * argv[])
128
+ {
129
+ FILE * f;
130
+ int fd;
131
+ int bz2err;
132
+ uint8_t header[24];
133
+ uint8_t *old, *new;
134
+ int64_t oldsize, newsize;
135
+ BZFILE* bz2;
136
+ struct bspatch_stream stream;
137
+ struct stat sb;
138
+
139
+ if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
140
+
141
+ /* Open patch file */
142
+ if ((f = fopen(argv[3], "r")) == NULL)
143
+ err(1, "fopen(%s)", argv[3]);
144
+
145
+ /* Read header */
146
+ if (fread(header, 1, 24, f) != 24) {
147
+ if (feof(f))
148
+ errx(1, "Corrupt patch\n");
149
+ err(1, "fread(%s)", argv[3]);
150
+ }
151
+
152
+ /* Check for appropriate magic */
153
+ if (memcmp(header, "ENDSLEY/BSDIFF43", 16) != 0)
154
+ errx(1, "Corrupt patch\n");
155
+
156
+ /* Read lengths from header */
157
+ newsize=offtin(header+16);
158
+ if(newsize<0)
159
+ errx(1,"Corrupt patch\n");
160
+
161
+ /* Close patch file and re-open it via libbzip2 at the right places */
162
+ if(((fd=open(argv[1],O_RDONLY,0))<0) ||
163
+ ((oldsize=lseek(fd,0,SEEK_END))==-1) ||
164
+ ((old=malloc(oldsize+1))==NULL) ||
165
+ (lseek(fd,0,SEEK_SET)!=0) ||
166
+ (read(fd,old,oldsize)!=oldsize) ||
167
+ (fstat(fd, &sb)) ||
168
+ (close(fd)==-1)) err(1,"%s",argv[1]);
169
+ if((new=malloc(newsize+1))==NULL) err(1,NULL);
170
+
171
+ if (NULL == (bz2 = BZ2_bzReadOpen(&bz2err, f, 0, 0, NULL, 0)))
172
+ errx(1, "BZ2_bzReadOpen, bz2err=%d", bz2err);
173
+
174
+ stream.read = bz2_read;
175
+ stream.opaque = bz2;
176
+ if (bspatch(old, oldsize, new, newsize, &stream))
177
+ errx(1, "bspatch");
178
+
179
+ /* Clean up the bzip2 reads */
180
+ BZ2_bzReadClose(&bz2err, bz2);
181
+ fclose(f);
182
+
183
+ /* Write the new file */
184
+ if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,sb.st_mode))<0) ||
185
+ (write(fd,new,newsize)!=newsize) || (close(fd)==-1))
186
+ err(1,"%s",argv[2]);
187
+
188
+ free(new);
189
+ free(old);
190
+
191
+ return 0;
192
+ }
193
+
194
+ #endif
@@ -0,0 +1,41 @@
1
+ /*-
2
+ * Copyright 2003-2005 Colin Percival
3
+ * Copyright 2012 Matthew Endsley
4
+ * All rights reserved
5
+ *
6
+ * Redistribution and use in source and binary forms, with or without
7
+ * modification, are permitted providing that the following conditions
8
+ * are met:
9
+ * 1. Redistributions of source code must retain the above copyright
10
+ * notice, this list of conditions and the following disclaimer.
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in the
13
+ * documentation and/or other materials provided with the distribution.
14
+ *
15
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
+ * POSSIBILITY OF SUCH DAMAGE.
26
+ */
27
+
28
+ #ifndef BSPATCH_H
29
+ # define BSPATCH_H
30
+
31
+ # include <stdint.h>
32
+
33
+ struct bspatch_stream
34
+ {
35
+ void* opaque;
36
+ int (*read)(const struct bspatch_stream* stream, void* buffer, int length);
37
+ };
38
+
39
+ int bspatch(const uint8_t* old, int64_t oldsize, uint8_t* new, int64_t newsize, struct bspatch_stream* stream);
40
+
41
+ #endif