react-native-update 10.47.0 → 10.48.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 (59) hide show
  1. package/android/jni/Android.mk +2 -0
  2. package/android/jni/hpatch.c +57 -2
  3. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  4. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  5. package/android/lib/x86/librnupdate.so +0 -0
  6. package/android/lib/x86_64/librnupdate.so +0 -0
  7. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +19 -4
  8. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +2 -0
  9. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +1 -0
  10. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateCore.java +12 -0
  11. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +30 -0
  12. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +13 -0
  13. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +1 -0
  14. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  15. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  16. package/cpp/patch_core/error_codes.h +3 -0
  17. package/cpp/patch_core/hbc_transform.cpp +143 -0
  18. package/cpp/patch_core/hbc_transform.h +53 -0
  19. package/cpp/patch_core/hbc_transform_wire.cpp +321 -0
  20. package/cpp/patch_core/hbc_transform_wire.h +52 -0
  21. package/cpp/patch_core/patch_core.cpp +126 -4
  22. package/cpp/patch_core/patch_core.h +5 -0
  23. package/cpp/patch_core/patch_core_android.cpp +3 -1
  24. package/cpp/patch_core/tests/fixtures/rawstored.lzma2label.patch.bin +0 -0
  25. package/cpp/patch_core/tests/fixtures/rawstored.new.bin +0 -0
  26. package/cpp/patch_core/tests/fixtures/rawstored.old.bin +0 -0
  27. package/cpp/patch_core/tests/fixtures/v96.hbc +0 -0
  28. package/cpp/patch_core/tests/fixtures/v96.meta.json +1 -0
  29. package/cpp/patch_core/tests/fixtures/v96.streampatch.bin +0 -0
  30. package/cpp/patch_core/tests/fixtures/v96.t.hbc +0 -0
  31. package/cpp/patch_core/tests/fixtures/v96.tpatch.bin +0 -0
  32. package/cpp/patch_core/tests/fixtures/v96.tstreampatch.bin +0 -0
  33. package/cpp/patch_core/tests/fixtures/v96b.hbc +0 -0
  34. package/cpp/patch_core/tests/fixtures/v98.hbc +0 -0
  35. package/cpp/patch_core/tests/fixtures/v98.t.hbc +0 -0
  36. package/cpp/patch_core/tests/fixtures/v98b.hbc +0 -0
  37. package/cpp/patch_core/tests/fixtures/v98b.t.hbc +0 -0
  38. package/cpp/patch_core/tests/hbc_transform_test.cpp +229 -0
  39. package/cpp/patch_core/tests/patch_core_test.cpp +150 -1
  40. package/cpp/patch_core/update_core_android.cpp +10 -0
  41. package/harmony/pushy/src/main/cpp/CMakeLists.txt +2 -0
  42. package/harmony/pushy/src/main/cpp/pushy.cpp +18 -1
  43. package/harmony/pushy/src/main/ets/DownloadTask.ts +21 -0
  44. package/harmony/pushy/src/main/ets/NativePatchCore.ts +4 -0
  45. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +12 -0
  46. package/harmony/pushy/src/main/ets/UpdateContext.ts +46 -0
  47. package/harmony/pushy.har +0 -0
  48. package/ios/RCTPushy/RCTPushy.mm +64 -0
  49. package/package.json +1 -1
  50. package/react-native-update.podspec +2 -0
  51. package/src/NativePushy.ts +1 -0
  52. package/src/client.ts +46 -0
  53. package/src/context.ts +4 -0
  54. package/src/core.ts +4 -0
  55. package/src/error.ts +1 -0
  56. package/src/locales/en.ts +2 -0
  57. package/src/locales/zh.ts +2 -0
  58. package/src/provider.tsx +9 -0
  59. package/src/type.ts +2 -0
@@ -21,6 +21,8 @@ Hdp_Files := \
21
21
 
22
22
  LOCAL_SRC_FILES := \
23
23
  ../../cpp/patch_core/archive_patch_core.cpp \
24
+ ../../cpp/patch_core/hbc_transform.cpp \
25
+ ../../cpp/patch_core/hbc_transform_wire.cpp \
24
26
  ../../cpp/patch_core/patch_core.cpp \
25
27
  ../../cpp/patch_core/patch_core_android.cpp \
26
28
  ../../cpp/patch_core/state_core.cpp \
@@ -63,7 +63,9 @@ static int hpatch_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOld
63
63
  _check(old->streamSize==patInfo->oldDataSize,kHPatch_error_old_size);
64
64
  _check(out_new->streamSize>=patInfo->newDataSize,kHPatch_error_new_size);
65
65
  out_new->streamSize=patInfo->newDataSize;
66
- if (strlen(patInfo->compressType)>0){
66
+ // v5 writer 可能保留 lzma2 标签但以 RAW 存储小 diff;compressedSize==0
67
+ // 才是格式里的未压缩判据,不能按标签强行走解压。
68
+ if ((strlen(patInfo->compressType)>0)&&(patInfo->compressedSize>0)){
67
69
  decompressPlugin=getDecompressPlugin(patInfo->compressType);
68
70
  _check(decompressPlugin,kHPatch_error_compressType);
69
71
  }
@@ -105,10 +107,57 @@ int hpatch_by_mem(const uint8_t* old,size_t oldsize,uint8_t* newBuf,size_t newsi
105
107
  return hpatch_by_stream(&oldStream,hpatch_FALSE,&patStream,&newStream,patInfo);
106
108
  }
107
109
 
110
+ // HDIFF13(diffStream 产物,v2 轨道的大 bundle patch):流式应用,
111
+ // 内存 = 解压缓存 + IO 缓冲(+ old ≤ 8MB 时的整载优化),与 single 路径同级。
112
+ static int hpatch_v13_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOldAllToMem,
113
+ const hpatch_TStreamInput* pat,hpatch_TStreamOutput* out_new){
114
+ int result=kHPatch_ok;
115
+ int _isInClear=hpatch_FALSE;
116
+ hpatch_TDecompress* decompressPlugin=0;
117
+ uint8_t* temp_cache=0;
118
+ size_t temp_cache_size;
119
+ hpatch_compressedDiffInfo patInfo;
120
+ hpatch_TStreamInput _old;
121
+ {// info
122
+ _check(getCompressedDiffInfo(&patInfo,pat),kHPatch_error_info);
123
+ _check(old->streamSize==patInfo.oldDataSize,kHPatch_error_old_size);
124
+ _check(out_new->streamSize>=patInfo.newDataSize,kHPatch_error_new_size);
125
+ out_new->streamSize=patInfo.newDataSize;
126
+ if (strlen(patInfo.compressType)>0){
127
+ decompressPlugin=getDecompressPlugin(patInfo.compressType);
128
+ _check(decompressPlugin,kHPatch_error_compressType);
129
+ }
130
+ }
131
+ {// mem
132
+ size_t mem_size;
133
+ size_t oldSize=(size_t)old->streamSize;
134
+ isLoadOldAllToMem=isLoadOldAllToMem&&(old->streamSize<=kMaxLoadMemOldSize);
135
+ temp_cache_size=hpatch_kStreamCacheSize*8+hpatch_kFileIOBufBetterSize*3;
136
+ mem_size=temp_cache_size+(isLoadOldAllToMem?oldSize:0);
137
+ temp_cache=malloc(mem_size);
138
+ _check(temp_cache,kHPatch_error_malloc);
139
+ if (isLoadOldAllToMem){//load old to mem
140
+ uint8_t* oldMem=temp_cache+temp_cache_size;
141
+ _check(old->read(old,0,oldMem,oldMem+oldSize),kHPatch_error_old_fread);
142
+ mem_as_hStreamInput(&_old,oldMem,oldMem+oldSize);
143
+ old=&_old;
144
+ }
145
+ }
146
+
147
+ _check(patch_decompress_with_cache(out_new,old,pat,decompressPlugin,
148
+ temp_cache,temp_cache+temp_cache_size),kHPatch_error_patch);
149
+
150
+ _clear:
151
+ _isInClear=hpatch_TRUE;
152
+ if (temp_cache){ free(temp_cache); temp_cache=0; }
153
+ return result;
154
+ }
155
+
108
156
  int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile){
109
157
  int result=kHPatch_ok;
110
158
  int _isInClear=hpatch_FALSE;
111
159
  int patch_result;
160
+ hpatch_singleCompressedDiffInfo singleInfo;
112
161
  hpatch_TFileStreamInput oldStream;
113
162
  hpatch_TFileStreamInput patStream;
114
163
  hpatch_TFileStreamOutput newStream;
@@ -120,7 +169,13 @@ int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfi
120
169
  _check(hpatch_TFileStreamInput_open(&patStream,patchfile),kHPatch_error_pat_fopen);
121
170
  _check(hpatch_TFileStreamOutput_open(&newStream,newfile,~(hpatch_StreamPos_t)0),kHPatch_error_new_fopen);
122
171
 
123
- patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,0);
172
+ // 按 patch 头自动分派格式:single(HDIFFSF20,现状)或 stream(HDIFF13,
173
+ // v2 轨道大 bundle)。老客户端只会收到 single;能力门控在服务端。
174
+ if (getSingleCompressedDiffInfo(&singleInfo,&patStream.base,0)){
175
+ patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,&singleInfo);
176
+ }else{
177
+ patch_result=hpatch_v13_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base);
178
+ }
124
179
  if (patch_result!=kHPatch_ok){
125
180
  _check(!oldStream.fileError,kHPatch_error_old_fread);
126
181
  _check(!patStream.fileError,kHPatch_error_pat_fread);
Binary file
Binary file
@@ -53,6 +53,17 @@ class DownloadTask implements Runnable {
53
53
  final ArrayList<String> copyFroms = new ArrayList<String>();
54
54
  final ArrayList<String> copyTos = new ArrayList<String>();
55
55
  final ArrayList<String> deletes = new ArrayList<String>();
56
+ // __diff.json 的 hbcTransform 元数据(按 patch 条目名索引);
57
+ // 为空对象/缺失时返回 ""(native 走现状路径)
58
+ JSONObject hbcTransform;
59
+
60
+ String hbcTransformMetaFor(String patchEntryName) {
61
+ if (hbcTransform == null) {
62
+ return "";
63
+ }
64
+ JSONObject meta = hbcTransform.optJSONObject(patchEntryName);
65
+ return meta != null ? meta.toString() : "";
66
+ }
56
67
  // Maps a copy source path ("from") to the CRC32 of the file content,
57
68
  // when provided by the manifest ("copiesCrc"). Lets the resource
58
69
  // copier locate the file by content if the path is not present on
@@ -251,6 +262,7 @@ class DownloadTask implements Runnable {
251
262
  contents.deletes,
252
263
  contents.copyCrcs
253
264
  );
265
+ contents.hbcTransform = manifest.optJSONObject("hbcTransform");
254
266
  continue;
255
267
  }
256
268
 
@@ -311,7 +323,8 @@ class DownloadTask implements Runnable {
311
323
  false,
312
324
  new String[0],
313
325
  new String[0],
314
- new String[0]
326
+ new String[0],
327
+ contents.hbcTransformMetaFor("index.bundlejs.patch")
315
328
  );
316
329
  } finally {
317
330
  originBundleFile.delete();
@@ -345,7 +358,8 @@ class DownloadTask implements Runnable {
345
358
  plan.enableMerge,
346
359
  contents.copyFroms.toArray(new String[0]),
347
360
  contents.copyTos.toArray(new String[0]),
348
- contents.deletes.toArray(new String[0])
361
+ contents.deletes.toArray(new String[0]),
362
+ contents.hbcTransformMetaFor("index.bundlejs.patch")
349
363
  );
350
364
  if (params.targetFile.exists()) {
351
365
  params.targetFile.delete();
@@ -357,7 +371,7 @@ class DownloadTask implements Runnable {
357
371
  params.unzipDirectory.getAbsolutePath(),
358
372
  params.hash,
359
373
  params.originHash,
360
- 3
374
+ params.maxAgeDays
361
375
  );
362
376
  }
363
377
 
@@ -444,7 +458,8 @@ class DownloadTask implements Runnable {
444
458
  boolean enableMerge,
445
459
  String[] copyFroms,
446
460
  String[] copyTos,
447
- String[] deletes
461
+ String[] deletes,
462
+ String hbcTransformMeta
448
463
  );
449
464
 
450
465
  private static native void cleanupOldEntries(
@@ -18,6 +18,8 @@ class DownloadTaskParams {
18
18
  String url;
19
19
  String hash;
20
20
  String originHash;
21
+ // TASK_TYPE_CLEANUP only: entries younger than this survive; 0 = delete all
22
+ int maxAgeDays = 3;
21
23
  File targetFile;
22
24
  File unzipDirectory;
23
25
  File originDirectory;
@@ -16,6 +16,7 @@ final class ErrorCodes {
16
16
  static final String SWITCH_VERSION_FAILED = "SWITCH_VERSION_FAILED";
17
17
  static final String MARK_SUCCESS_FAILED = "MARK_SUCCESS_FAILED";
18
18
  static final String RESTART_FAILED = "RESTART_FAILED";
19
+ static final String RESET_FAILED = "RESET_FAILED";
19
20
  static final String INVALID_HASH_INFO = "INVALID_HASH_INFO";
20
21
  static final String UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM";
21
22
 
@@ -23,4 +23,16 @@ final class NativeUpdateCore {
23
23
 
24
24
  loaded = true;
25
25
  }
26
+
27
+ /**
28
+ * 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道)。
29
+ * 经 getConstants 暴露给 JS,再随 checkUpdate 以 diffV 上报,
30
+ * 服务端按能力门控下发。
31
+ */
32
+ static int supportedDiffVersion() {
33
+ ensureLoaded();
34
+ return getSupportedDiffVersion();
35
+ }
36
+
37
+ private static native int getSupportedDiffVersion();
26
38
  }
@@ -303,6 +303,36 @@ public class UpdateContext {
303
303
  this.cleanUp();
304
304
  }
305
305
 
306
+ /**
307
+ * Reset to the bundle packaged in the binary: wipe the whole update state
308
+ * (so the next launch resolves to the built-in bundle) and delete every
309
+ * downloaded version. Only the client uuid survives — it identifies the
310
+ * install for gray release bucketing and must not change on reset.
311
+ */
312
+ public void resetToPackagedBundle() {
313
+ StateCoreResult resetState = new StateCoreResult();
314
+ resetState.packageVersion = getPackageVersion();
315
+ resetState.buildTime = getBuildTime();
316
+ resetState.firstTime = false;
317
+ resetState.firstTimeOk = true;
318
+ String uuid = sp.getString("uuid", null);
319
+ SharedPreferences.Editor editor = sp.edit();
320
+ editor.clear();
321
+ applyState(editor, resetState);
322
+ if (uuid != null) {
323
+ editor.putString("uuid", uuid);
324
+ }
325
+ persistEditor(editor, "reset to packaged bundle");
326
+ ignoreRollback = false;
327
+ Log.i(TAG, "Reset to packaged bundle");
328
+
329
+ DownloadTaskParams params = new DownloadTaskParams();
330
+ params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
331
+ params.maxAgeDays = 0;
332
+ params.unzipDirectory = rootDir;
333
+ enqueue(params);
334
+ }
335
+
306
336
  public void clearRollbackMark() {
307
337
  StateCoreResult currentState = getStateSnapshot();
308
338
  StateCoreResult nextState = runStateCore(
@@ -191,6 +191,19 @@ public class UpdateModuleImpl {
191
191
  });
192
192
  }
193
193
 
194
+ public static void resetToPackagedBundle(
195
+ final UpdateContext updateContext,
196
+ final Promise promise
197
+ ) {
198
+ StateSerialRunner.run(promise, ErrorCodes.RESET_FAILED, "resetToPackagedBundle", new StateSerialRunner.Operation() {
199
+ @Override
200
+ public void run() {
201
+ updateContext.resetToPackagedBundle();
202
+ promise.resolve(true);
203
+ }
204
+ });
205
+ }
206
+
194
207
  private static void setUuidInternal(UpdateContext updateContext, String uuid) {
195
208
  updateContext.setKv("uuid", uuid);
196
209
  }
@@ -35,6 +35,7 @@ final class UpdateModuleSupport {
35
35
  }
36
36
 
37
37
  constants.put("uuid", updateContext.getKv("uuid"));
38
+ constants.put("supportedDiffVersion", NativeUpdateCore.supportedDiffVersion());
38
39
  return constants;
39
40
  }
40
41
 
@@ -73,6 +73,11 @@ public class UpdateModule extends NativePushySpec {
73
73
  UpdateModuleImpl.markSuccess(updateContext, promise);
74
74
  }
75
75
 
76
+ @Override
77
+ public void resetToPackagedBundle(Promise promise) {
78
+ UpdateModuleImpl.resetToPackagedBundle(updateContext, promise);
79
+ }
80
+
76
81
  @Override
77
82
  public void setUuid(String uuid, Promise promise) {
78
83
  UpdateModuleImpl.setUuid(updateContext, uuid, promise);
@@ -75,6 +75,11 @@ public class UpdateModule extends ReactContextBaseJavaModule {
75
75
  UpdateModuleImpl.markSuccess(updateContext);
76
76
  }
77
77
 
78
+ @ReactMethod
79
+ public void resetToPackagedBundle(Promise promise) {
80
+ UpdateModuleImpl.resetToPackagedBundle(updateContext, promise);
81
+ }
82
+
78
83
  @ReactMethod
79
84
  public void setUuid(String uuid) {
80
85
  UpdateModuleImpl.setUuid(updateContext, uuid);
@@ -31,6 +31,9 @@ constexpr const char* kSwitchVersionFailed = "SWITCH_VERSION_FAILED";
31
31
  constexpr const char* kMarkSuccessFailed = "MARK_SUCCESS_FAILED";
32
32
  // reloadUpdate / restartApp failed.
33
33
  constexpr const char* kRestartFailed = "RESTART_FAILED";
34
+ // resetToPackagedBundle failed (state wipe or cleanup could not run), or the
35
+ // installed native module predates the method (JS-layer detection).
36
+ constexpr const char* kResetFailed = "RESET_FAILED";
34
37
  // Stored or provided hash info is not a valid JSON object.
35
38
  constexpr const char* kInvalidHashInfo = "INVALID_HASH_INFO";
36
39
  // The method is not supported on this platform (e.g. downloadAndInstallApk
@@ -0,0 +1,143 @@
1
+ #include "hbc_transform.h"
2
+
3
+ #include <cstring>
4
+
5
+ namespace pushy {
6
+ namespace hbc {
7
+
8
+ namespace {
9
+
10
+ constexpr uint8_t kHbcMagic[8] =
11
+ {0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f};
12
+ constexpr size_t kHeaderSize = 128;
13
+ constexpr size_t kCountsOffset = 32; // magic(8) + version(4) + sourceHash(20)
14
+ constexpr uint32_t kMaxEntryCount = 0x0fffffff;
15
+ constexpr uint32_t kMaxEntrySize = 4096;
16
+ constexpr uint32_t kMaxSections = 64;
17
+ constexpr uint32_t kMaxCountFields = (kHeaderSize - kCountsOffset) / 4; // 24
18
+
19
+ inline uint32_t ReadU32(const uint8_t* p) {
20
+ return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) |
21
+ (static_cast<uint32_t>(p[2]) << 16) |
22
+ (static_cast<uint32_t>(p[3]) << 24);
23
+ }
24
+
25
+ inline void WriteU32(uint8_t* p, uint32_t v) {
26
+ p[0] = static_cast<uint8_t>(v);
27
+ p[1] = static_cast<uint8_t>(v >> 8);
28
+ p[2] = static_cast<uint8_t>(v >> 16);
29
+ p[3] = static_cast<uint8_t>(v >> 24);
30
+ }
31
+
32
+ inline uint64_t Align4(uint64_t x) {
33
+ return (x + 3) & ~static_cast<uint64_t>(3);
34
+ }
35
+
36
+ struct ResolvedSection {
37
+ uint64_t start;
38
+ uint64_t size;
39
+ const HbcSectionDesc* desc;
40
+ };
41
+
42
+ } // namespace
43
+
44
+ bool TransformHbcInPlace(
45
+ uint8_t* data,
46
+ size_t size,
47
+ const HbcLayoutDesc& layout,
48
+ bool inverse) {
49
+ // ---- 校验阶段:改写任何字节之前完成全部检查 ----
50
+ if (data == nullptr || size < kHeaderSize) {
51
+ return false;
52
+ }
53
+ if (std::memcmp(data, kHbcMagic, sizeof(kHbcMagic)) != 0) {
54
+ return false;
55
+ }
56
+ if (layout.headerCountFields < 2 ||
57
+ layout.headerCountFields > kMaxCountFields) {
58
+ return false;
59
+ }
60
+ if (layout.sections == nullptr || layout.sectionCount == 0 ||
61
+ layout.sectionCount > kMaxSections) {
62
+ return false;
63
+ }
64
+
65
+ uint32_t counts[kMaxCountFields];
66
+ for (uint32_t i = 0; i < layout.headerCountFields; ++i) {
67
+ counts[i] = ReadU32(data + kCountsOffset + static_cast<size_t>(i) * 4);
68
+ }
69
+ // 位置约定:槽位 0 = fileLength,最后一个槽位 = debugInfoOffset
70
+ const uint64_t fileLength = counts[0];
71
+ const uint64_t debugInfoOffset = counts[layout.headerCountFields - 1];
72
+ if (fileLength != size) {
73
+ return false;
74
+ }
75
+ if (debugInfoOffset < kHeaderSize || debugInfoOffset > size) {
76
+ return false;
77
+ }
78
+
79
+ ResolvedSection resolved[kMaxSections];
80
+ uint64_t off = kHeaderSize;
81
+ for (uint32_t i = 0; i < layout.sectionCount; ++i) {
82
+ const HbcSectionDesc& s = layout.sections[i];
83
+ if (s.countIndex >= layout.headerCountFields) {
84
+ return false;
85
+ }
86
+ if (s.entrySize == 0 || s.entrySize > kMaxEntrySize) {
87
+ return false;
88
+ }
89
+ const uint64_t count = counts[s.countIndex];
90
+ if (count > kMaxEntryCount) {
91
+ return false;
92
+ }
93
+ if (s.deltaFieldCount > 0 && s.deltaFields == nullptr) {
94
+ return false;
95
+ }
96
+ for (uint32_t f = 0; f < s.deltaFieldCount; ++f) {
97
+ const HbcDeltaField& field = s.deltaFields[f];
98
+ if (field.bits < 1 || field.bits > 32 || field.bit + field.bits > 32 ||
99
+ static_cast<uint64_t>(field.byte) + 4 > s.entrySize) {
100
+ return false;
101
+ }
102
+ }
103
+ const uint64_t sectionSize = count * s.entrySize; // ≤ 2^28 × 2^12 < 2^40
104
+ off = Align4(off);
105
+ if (off + sectionSize > debugInfoOffset) {
106
+ return false;
107
+ }
108
+ resolved[i] = {off, sectionSize, &s};
109
+ off += sectionSize;
110
+ }
111
+
112
+ // ---- 改写阶段:校验通过后不再有失败路径 ----
113
+ for (uint32_t i = 0; i < layout.sectionCount; ++i) {
114
+ const ResolvedSection& r = resolved[i];
115
+ const HbcSectionDesc& s = *r.desc;
116
+ for (uint32_t f = 0; f < s.deltaFieldCount; ++f) {
117
+ const HbcDeltaField& field = s.deltaFields[f];
118
+ const uint32_t fieldMask =
119
+ field.bits == 32 ? 0xffffffffu : ((1u << field.bits) - 1u);
120
+ const uint32_t mask = fieldMask << field.bit;
121
+ uint32_t prev = 0;
122
+ const uint64_t end = r.start + r.size;
123
+ for (uint64_t p = r.start + field.byte; p < end; p += s.entrySize) {
124
+ uint8_t* wordPtr = data + p;
125
+ const uint32_t word = ReadU32(wordPtr);
126
+ const uint32_t val = (word >> field.bit) & fieldMask;
127
+ uint32_t enc;
128
+ if (!inverse) {
129
+ enc = (val - prev) & fieldMask;
130
+ prev = val;
131
+ } else {
132
+ enc = (val + prev) & fieldMask;
133
+ prev = enc;
134
+ }
135
+ WriteU32(wordPtr, (word & ~mask) | (enc << field.bit));
136
+ }
137
+ }
138
+ }
139
+ return true;
140
+ }
141
+
142
+ } // namespace hbc
143
+ } // namespace pushy
@@ -0,0 +1,53 @@
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+
6
+ namespace pushy {
7
+ namespace hbc {
8
+
9
+ // Hermes 字节码(HBC)delta-friendly 可逆变换的通用解释器。
10
+ //
11
+ // 布局描述表来自 patch 的 __diff.json 元数据(生成端 CLI 写入),本解释器
12
+ // 不含任何 HBC 版本分支——Hermes 版本演进只需要生成端更新描述表,客户端
13
+ // 零跟进。描述表被视为不可信输入:所有段边界、条目大小、位域范围在改写
14
+ // 任何字节之前完成全量校验;校验失败时 buffer 保持原样。
15
+ //
16
+ // 变换语义与生成端(react-native-update-cli src/utils/hbcTransform.ts)
17
+ // 严格一致:对描述的偏移位域做前项差分(wrapping,模字段位宽)。
18
+ // wrapping 保证与数据单调性无关的严格可逆。
19
+
20
+ struct HbcDeltaField {
21
+ // 条目内字节偏移处按小端读取 u32,取 [bit, bit+bits) 位做差分
22
+ uint32_t byte;
23
+ uint32_t bit;
24
+ uint32_t bits;
25
+ };
26
+
27
+ struct HbcSectionDesc {
28
+ // 段大小 = headerCounts[countIndex] × entrySize(字节段 entrySize 为 1)
29
+ uint32_t countIndex;
30
+ uint32_t entrySize;
31
+ const HbcDeltaField* deltaFields;
32
+ uint32_t deltaFieldCount;
33
+ };
34
+
35
+ // 与 wire 格式的位置约定一致:counts 槽位 0 = fileLength,
36
+ // 最后一个槽位 = debugInfoOffset(结构校验依赖)。
37
+ struct HbcLayoutDesc {
38
+ uint32_t headerCountFields;
39
+ const HbcSectionDesc* sections;
40
+ uint32_t sectionCount;
41
+ };
42
+
43
+ // 对 data 原地执行变换(inverse=false)或逆变换(inverse=true)。
44
+ // 返回 false 表示 data 不是该描述表下结构合法的 HBC(或描述表本身非法),
45
+ // 此时 data 未被修改。
46
+ bool TransformHbcInPlace(
47
+ uint8_t* data,
48
+ size_t size,
49
+ const HbcLayoutDesc& layout,
50
+ bool inverse);
51
+
52
+ } // namespace hbc
53
+ } // namespace pushy