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
@@ -0,0 +1,321 @@
1
+ #include "hbc_transform_wire.h"
2
+
3
+ namespace pushy {
4
+ namespace hbc {
5
+
6
+ namespace {
7
+
8
+ // 面向单一受限格式的手写递归下降解析器。不追求通用 JSON 兼容,
9
+ // 追求:无第三方依赖、输入不可信下的确定性行为、显式上限。
10
+ constexpr size_t kMaxInputBytes = 64 * 1024;
11
+ constexpr int kMaxDepth = 8;
12
+ constexpr uint32_t kMaxSections = 64;
13
+ constexpr uint32_t kMaxDeltaFields = 8;
14
+
15
+ struct Cursor {
16
+ const char* p;
17
+ const char* end;
18
+ };
19
+
20
+ void SkipWs(Cursor* c) {
21
+ while (c->p < c->end &&
22
+ (*c->p == ' ' || *c->p == '\t' || *c->p == '\n' || *c->p == '\r')) {
23
+ ++c->p;
24
+ }
25
+ }
26
+
27
+ bool Consume(Cursor* c, char ch) {
28
+ SkipWs(c);
29
+ if (c->p < c->end && *c->p == ch) {
30
+ ++c->p;
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ bool Peek(Cursor* c, char ch) {
37
+ SkipWs(c);
38
+ return c->p < c->end && *c->p == ch;
39
+ }
40
+
41
+ // 仅接受非负整数(本格式中所有数值都是非负整数)
42
+ bool ParseUInt(Cursor* c, uint32_t* out) {
43
+ SkipWs(c);
44
+ if (c->p >= c->end || *c->p < '0' || *c->p > '9') {
45
+ return false;
46
+ }
47
+ uint64_t value = 0;
48
+ while (c->p < c->end && *c->p >= '0' && *c->p <= '9') {
49
+ value = value * 10 + static_cast<uint64_t>(*c->p - '0');
50
+ if (value > 0xffffffffull) {
51
+ return false;
52
+ }
53
+ ++c->p;
54
+ }
55
+ *out = static_cast<uint32_t>(value);
56
+ return true;
57
+ }
58
+
59
+ bool ParseKey(Cursor* c, std::string* out) {
60
+ SkipWs(c);
61
+ if (!Consume(c, '"')) {
62
+ return false;
63
+ }
64
+ out->clear();
65
+ while (c->p < c->end && *c->p != '"') {
66
+ if (*c->p == '\\') {
67
+ return false; // 本格式的键不含转义
68
+ }
69
+ out->push_back(*c->p);
70
+ ++c->p;
71
+ if (out->size() > 64) {
72
+ return false;
73
+ }
74
+ }
75
+ return Consume(c, '"');
76
+ }
77
+
78
+ // 跳过未知键的值(数字/字符串/数组/对象),深度受限
79
+ bool SkipValue(Cursor* c, int depth) {
80
+ if (depth > kMaxDepth) {
81
+ return false;
82
+ }
83
+ SkipWs(c);
84
+ if (c->p >= c->end) {
85
+ return false;
86
+ }
87
+ const char ch = *c->p;
88
+ if (ch == '"') {
89
+ ++c->p;
90
+ while (c->p < c->end && *c->p != '"') {
91
+ if (*c->p == '\\') {
92
+ ++c->p;
93
+ }
94
+ ++c->p;
95
+ }
96
+ return Consume(c, '"');
97
+ }
98
+ if (ch == '[' || ch == '{') {
99
+ const char close = ch == '[' ? ']' : '}';
100
+ ++c->p;
101
+ SkipWs(c);
102
+ if (Consume(c, close)) {
103
+ return true;
104
+ }
105
+ while (true) {
106
+ if (close == '}') {
107
+ std::string key;
108
+ if (!ParseKey(c, &key) || !Consume(c, ':')) {
109
+ return false;
110
+ }
111
+ }
112
+ if (!SkipValue(c, depth + 1)) {
113
+ return false;
114
+ }
115
+ if (Consume(c, close)) {
116
+ return true;
117
+ }
118
+ if (!Consume(c, ',')) {
119
+ return false;
120
+ }
121
+ }
122
+ }
123
+ if ((ch >= '0' && ch <= '9') || ch == '-') {
124
+ ++c->p;
125
+ while (c->p < c->end &&
126
+ ((*c->p >= '0' && *c->p <= '9') || *c->p == '.' || *c->p == 'e' ||
127
+ *c->p == 'E' || *c->p == '+' || *c->p == '-')) {
128
+ ++c->p;
129
+ }
130
+ return true;
131
+ }
132
+ // true/false/null
133
+ for (const char* lit : {"true", "false", "null"}) {
134
+ const size_t n = std::char_traits<char>::length(lit);
135
+ if (static_cast<size_t>(c->end - c->p) >= n &&
136
+ std::char_traits<char>::compare(c->p, lit, n) == 0) {
137
+ c->p += n;
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+
144
+ // [byte, bit, bits]
145
+ bool ParseDeltaField(Cursor* c, HbcDeltaField* out) {
146
+ if (!Consume(c, '[')) {
147
+ return false;
148
+ }
149
+ if (!ParseUInt(c, &out->byte) || !Consume(c, ',') ||
150
+ !ParseUInt(c, &out->bit) || !Consume(c, ',') ||
151
+ !ParseUInt(c, &out->bits)) {
152
+ return false;
153
+ }
154
+ return Consume(c, ']');
155
+ }
156
+
157
+ // [countIndex, entrySize, [field, ...]]
158
+ bool ParseSection(Cursor* c, HbcTransformMeta::Section* out) {
159
+ if (!Consume(c, '[')) {
160
+ return false;
161
+ }
162
+ if (!ParseUInt(c, &out->countIndex) || !Consume(c, ',') ||
163
+ !ParseUInt(c, &out->entrySize) || !Consume(c, ',')) {
164
+ return false;
165
+ }
166
+ if (!Consume(c, '[')) {
167
+ return false;
168
+ }
169
+ if (!Consume(c, ']')) {
170
+ while (true) {
171
+ HbcDeltaField field{};
172
+ if (out->deltaFields.size() >= kMaxDeltaFields ||
173
+ !ParseDeltaField(c, &field)) {
174
+ return false;
175
+ }
176
+ out->deltaFields.push_back(field);
177
+ if (Consume(c, ']')) {
178
+ break;
179
+ }
180
+ if (!Consume(c, ',')) {
181
+ return false;
182
+ }
183
+ }
184
+ }
185
+ return Consume(c, ']');
186
+ }
187
+
188
+ bool ParseLayout(Cursor* c, HbcTransformMeta* out) {
189
+ if (!Consume(c, '{')) {
190
+ return false;
191
+ }
192
+ bool sawCounts = false;
193
+ bool sawSections = false;
194
+ if (!Consume(c, '}')) {
195
+ while (true) {
196
+ std::string key;
197
+ if (!ParseKey(c, &key) || !Consume(c, ':')) {
198
+ return false;
199
+ }
200
+ if (key == "counts") {
201
+ if (!ParseUInt(c, &out->headerCountFields)) {
202
+ return false;
203
+ }
204
+ sawCounts = true;
205
+ } else if (key == "sections") {
206
+ if (!Consume(c, '[')) {
207
+ return false;
208
+ }
209
+ if (!Consume(c, ']')) {
210
+ while (true) {
211
+ HbcTransformMeta::Section section;
212
+ if (out->sections.size() >= kMaxSections ||
213
+ !ParseSection(c, &section)) {
214
+ return false;
215
+ }
216
+ out->sections.push_back(std::move(section));
217
+ if (Consume(c, ']')) {
218
+ break;
219
+ }
220
+ if (!Consume(c, ',')) {
221
+ return false;
222
+ }
223
+ }
224
+ }
225
+ sawSections = true;
226
+ } else if (!SkipValue(c, 0)) {
227
+ return false;
228
+ }
229
+ if (Consume(c, '}')) {
230
+ break;
231
+ }
232
+ if (!Consume(c, ',')) {
233
+ return false;
234
+ }
235
+ }
236
+ }
237
+ return sawCounts && sawSections;
238
+ }
239
+
240
+ } // namespace
241
+
242
+ bool ParseHbcTransformMeta(const std::string& json, HbcTransformMeta* out) {
243
+ if (out == nullptr || json.empty() || json.size() > kMaxInputBytes) {
244
+ return false;
245
+ }
246
+ *out = HbcTransformMeta{};
247
+ Cursor c{json.data(), json.data() + json.size()};
248
+
249
+ if (!Consume(&c, '{')) {
250
+ return false;
251
+ }
252
+ bool sawV = false;
253
+ bool sawHbcVersion = false;
254
+ bool sawLayout = false;
255
+ if (!Consume(&c, '}')) {
256
+ while (true) {
257
+ std::string key;
258
+ if (!ParseKey(&c, &key) || !Consume(&c, ':')) {
259
+ return false;
260
+ }
261
+ if (key == "v") {
262
+ if (!ParseUInt(&c, &out->v)) {
263
+ return false;
264
+ }
265
+ sawV = true;
266
+ } else if (key == "hbcVersion") {
267
+ if (!ParseUInt(&c, &out->hbcVersion)) {
268
+ return false;
269
+ }
270
+ sawHbcVersion = true;
271
+ } else if (key == "layout") {
272
+ if (!Peek(&c, '{') || !ParseLayout(&c, out)) {
273
+ return false;
274
+ }
275
+ sawLayout = true;
276
+ } else if (!SkipValue(&c, 0)) {
277
+ return false;
278
+ }
279
+ if (Consume(&c, '}')) {
280
+ break;
281
+ }
282
+ if (!Consume(&c, ',')) {
283
+ return false;
284
+ }
285
+ }
286
+ }
287
+ SkipWs(&c);
288
+ if (c.p != c.end) {
289
+ return false; // 尾部不允许有多余内容
290
+ }
291
+ if (!sawV || !sawHbcVersion || !sawLayout) {
292
+ return false;
293
+ }
294
+ if (out->sections.empty() || out->headerCountFields == 0) {
295
+ return false;
296
+ }
297
+ return true;
298
+ }
299
+
300
+ HbcLayoutDesc BuildLayout(
301
+ const HbcTransformMeta& meta,
302
+ std::vector<HbcSectionDesc>* sections_scratch) {
303
+ sections_scratch->clear();
304
+ sections_scratch->reserve(meta.sections.size());
305
+ for (const HbcTransformMeta::Section& s : meta.sections) {
306
+ HbcSectionDesc desc;
307
+ desc.countIndex = s.countIndex;
308
+ desc.entrySize = s.entrySize;
309
+ desc.deltaFields = s.deltaFields.empty() ? nullptr : s.deltaFields.data();
310
+ desc.deltaFieldCount = static_cast<uint32_t>(s.deltaFields.size());
311
+ sections_scratch->push_back(desc);
312
+ }
313
+ HbcLayoutDesc layout;
314
+ layout.headerCountFields = meta.headerCountFields;
315
+ layout.sections = sections_scratch->data();
316
+ layout.sectionCount = static_cast<uint32_t>(sections_scratch->size());
317
+ return layout;
318
+ }
319
+
320
+ } // namespace hbc
321
+ } // namespace pushy
@@ -0,0 +1,52 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+ #include "hbc_transform.h"
8
+
9
+ namespace pushy {
10
+ namespace hbc {
11
+
12
+ // 客户端当前支持的变换规范版本(内部 wire 版本,仅用于校验 __diff.json
13
+ // 中 hbcTransform.v)。v 不在支持范围时调用方必须放弃 diff 应用(回退
14
+ // 整包),不能忽略元数据。
15
+ constexpr uint32_t kHbcTransformSupportedVersion = 1;
16
+
17
+ // 客户端可消费的 diff 轨道版本(客户端↔服务端接口用这个数字):
18
+ // 1 = baseline(普通 hdiff single 格式)
19
+ // 2 = v2 轨道(HBC 变换元数据 spec v1 + HDIFF13 流式容器)
20
+ // 经 getConstants 暴露给 JS,随 checkUpdate 以 diffV 上报,服务端据此
21
+ // 决定下发 v2/ 前缀产物。轨道号与 OSS 前缀/文档中的 "v2" 语义对齐;
22
+ // 变换 spec 版本是其内部实现细节(轨道 v2 ⇒ spec ≤ 1)。
23
+ constexpr uint32_t kSupportedDiffVersion = 2;
24
+
25
+ // __diff.json 中单个 bundle patch 条目的 hbcTransform 元数据:
26
+ // {"v":1,"hbcVersion":96,"layout":{"counts":19,"sections":[[ci,es,[[b,bi,bits],...]],...]}}
27
+ // 解析结果自持有存储;BuildLayout 产出的描述指向本结构,生存期须不短于使用期。
28
+ struct HbcTransformMeta {
29
+ uint32_t v = 0;
30
+ uint32_t hbcVersion = 0;
31
+ uint32_t headerCountFields = 0;
32
+ struct Section {
33
+ uint32_t countIndex = 0;
34
+ uint32_t entrySize = 0;
35
+ std::vector<HbcDeltaField> deltaFields;
36
+ };
37
+ std::vector<Section> sections;
38
+ };
39
+
40
+ // 严格解析(元数据视为不可信输入):结构、类型、数量全部受限,
41
+ // 任何偏离返回 false。允许对象中出现未知键(数字/字符串/嵌套值会被跳过),
42
+ // 为未来元数据扩展留余地。
43
+ bool ParseHbcTransformMeta(const std::string& json, HbcTransformMeta* out);
44
+
45
+ // 由 meta 构建解释器输入。sections_scratch 由调用方持有,生存期须覆盖
46
+ // layout 的使用期(layout 内部指针指向 meta 与 scratch)。
47
+ HbcLayoutDesc BuildLayout(
48
+ const HbcTransformMeta& meta,
49
+ std::vector<HbcSectionDesc>* sections_scratch);
50
+
51
+ } // namespace hbc
52
+ } // namespace pushy
@@ -10,6 +10,9 @@
10
10
 
11
11
  #include <vector>
12
12
 
13
+ #include "hbc_transform.h"
14
+ #include "hbc_transform_wire.h"
15
+
13
16
  extern "C" {
14
17
  #include "hpatch.h"
15
18
  }
@@ -181,6 +184,51 @@ Status RemovePathRecursively(const std::string& path) {
181
184
  return Status::Ok();
182
185
  }
183
186
 
187
+ Status ReadFileBytes(const std::string& path, std::vector<uint8_t>* out) {
188
+ FILE* file = fopen(path.c_str(), "rb");
189
+ if (file == nullptr) {
190
+ return MakeErrnoStatus("Failed to open file for reading " + path);
191
+ }
192
+ if (fseek(file, 0, SEEK_END) != 0) {
193
+ fclose(file);
194
+ return MakeErrnoStatus("Failed to seek file " + path);
195
+ }
196
+ const long size = ftell(file);
197
+ if (size < 0) {
198
+ fclose(file);
199
+ return MakeErrnoStatus("Failed to size file " + path);
200
+ }
201
+ if (fseek(file, 0, SEEK_SET) != 0) {
202
+ fclose(file);
203
+ return MakeErrnoStatus("Failed to rewind file " + path);
204
+ }
205
+ out->resize(static_cast<size_t>(size));
206
+ if (size > 0 &&
207
+ fread(out->data(), 1, out->size(), file) != out->size()) {
208
+ fclose(file);
209
+ return Status::Error("Failed to read file " + path);
210
+ }
211
+ fclose(file);
212
+ return Status::Ok();
213
+ }
214
+
215
+ Status WriteFileBytes(const std::string& path, const std::vector<uint8_t>& data) {
216
+ FILE* file = fopen(path.c_str(), "wb");
217
+ if (file == nullptr) {
218
+ return MakeErrnoStatus("Failed to open file for writing " + path);
219
+ }
220
+ if (!data.empty() && fwrite(data.data(), 1, data.size(), file) != data.size()) {
221
+ fclose(file);
222
+ remove(path.c_str());
223
+ return Status::Error("Failed to write file " + path);
224
+ }
225
+ if (fclose(file) != 0) {
226
+ remove(path.c_str());
227
+ return MakeErrnoStatus("Failed to flush file " + path);
228
+ }
229
+ return Status::Ok();
230
+ }
231
+
184
232
  Status CopyFile(const std::string& from, const std::string& to, bool overwrite) {
185
233
  struct stat st;
186
234
  if (stat(from.c_str(), &st) != 0) {
@@ -393,6 +441,77 @@ const BundlePatcher& DefaultBundlePatcher() {
393
441
  return kPatcher;
394
442
  }
395
443
 
444
+ namespace {
445
+
446
+ // 变换域 bundle patch:T(origin) → hpatch → T⁻¹。
447
+ // 元数据/变换的任何失败都返回错误——调用方沿既有失败路径回退整包;
448
+ // 绝不能忽略元数据直接 hpatch(会产出损坏 bundle,虽然最终 hash 校验
449
+ // 也会拦住,但应在此处快速失败)。
450
+ Status ApplyBundlePatchWithHbcTransform(
451
+ const FileSourcePatchOptions& options,
452
+ const BundlePatcher& bundle_patcher) {
453
+ hbc::HbcTransformMeta meta;
454
+ if (!hbc::ParseHbcTransformMeta(options.bundle_hbc_transform_meta, &meta)) {
455
+ return Status::Error("Invalid hbcTransform metadata");
456
+ }
457
+ if (meta.v != hbc::kHbcTransformSupportedVersion) {
458
+ return Status::Error(
459
+ "Unsupported hbcTransform version " +
460
+ IntToString(static_cast<int>(meta.v)));
461
+ }
462
+ std::vector<hbc::HbcSectionDesc> sections_scratch;
463
+ const hbc::HbcLayoutDesc layout = hbc::BuildLayout(meta, &sections_scratch);
464
+
465
+ std::vector<uint8_t> origin;
466
+ Status status = ReadFileBytes(options.origin_bundle_path, &origin);
467
+ if (!status) {
468
+ return status;
469
+ }
470
+ if (!hbc::TransformHbcInPlace(origin.data(), origin.size(), layout, false)) {
471
+ return Status::Error("hbcTransform failed on origin bundle");
472
+ }
473
+
474
+ Status dir_status = EnsureDirectory(Dirname(options.bundle_output_path));
475
+ if (!dir_status) {
476
+ return dir_status;
477
+ }
478
+ const std::string temp_origin = options.bundle_output_path + ".hbct-origin";
479
+ const std::string temp_patched = options.bundle_output_path + ".hbct-patched";
480
+ status = WriteFileBytes(temp_origin, origin);
481
+ if (!status) {
482
+ return status;
483
+ }
484
+ origin = std::vector<uint8_t>(); // 及早释放,patch 期间只保留文件副本
485
+
486
+ Status patch_status =
487
+ bundle_patcher.Apply(temp_origin, options.bundle_patch_path, temp_patched);
488
+ remove(temp_origin.c_str());
489
+ if (!patch_status) {
490
+ remove(temp_patched.c_str());
491
+ return patch_status;
492
+ }
493
+
494
+ std::vector<uint8_t> patched;
495
+ status = ReadFileBytes(temp_patched, &patched);
496
+ remove(temp_patched.c_str());
497
+ if (!status) {
498
+ return status;
499
+ }
500
+ if (!hbc::TransformHbcInPlace(patched.data(), patched.size(), layout, true)) {
501
+ return Status::Error("hbcTransform inverse failed on patched bundle");
502
+ }
503
+
504
+ if (PathExists(options.bundle_output_path)) {
505
+ Status remove_status = RemovePathRecursively(options.bundle_output_path);
506
+ if (!remove_status) {
507
+ return remove_status;
508
+ }
509
+ }
510
+ return WriteFileBytes(options.bundle_output_path, patched);
511
+ }
512
+
513
+ } // namespace
514
+
396
515
  Status ApplyPatchFromFileSource(
397
516
  const FileSourcePatchOptions& options,
398
517
  const BundlePatcher& bundle_patcher) {
@@ -401,10 +520,13 @@ Status ApplyPatchFromFileSource(
401
520
  return manifest_status;
402
521
  }
403
522
 
404
- Status bundle_status = bundle_patcher.Apply(
405
- options.origin_bundle_path,
406
- options.bundle_patch_path,
407
- options.bundle_output_path);
523
+ Status bundle_status =
524
+ options.bundle_hbc_transform_meta.empty()
525
+ ? bundle_patcher.Apply(
526
+ options.origin_bundle_path,
527
+ options.bundle_patch_path,
528
+ options.bundle_output_path)
529
+ : ApplyBundlePatchWithHbcTransform(options, bundle_patcher);
408
530
  if (!bundle_status) {
409
531
  return bundle_status;
410
532
  }
@@ -36,6 +36,11 @@ struct FileSourcePatchOptions {
36
36
  std::string bundle_output_path;
37
37
  std::string merge_source_subdir;
38
38
  bool enable_merge = true;
39
+ // __diff.json 中该 bundle patch 条目的 hbcTransform 元数据(原始 JSON)。
40
+ // 非空时 patch 走变换域:T(origin) → hpatch → T⁻¹。元数据不可解析或
41
+ // 变换规范版本不受支持时返回错误(调用方回退整包),绝不忽略元数据
42
+ // 直接应用——那会产出损坏的 bundle。
43
+ std::string bundle_hbc_transform_meta;
39
44
  };
40
45
 
41
46
  class BundlePatcher {
@@ -27,7 +27,8 @@ Java_cn_reactnative_modules_update_DownloadTask_applyPatchFromFileSource(
27
27
  jboolean enable_merge,
28
28
  jobjectArray copy_froms,
29
29
  jobjectArray copy_tos,
30
- jobjectArray deletes) {
30
+ jobjectArray deletes,
31
+ jstring hbc_transform_meta) {
31
32
  const std::vector<std::string> from_values = JArrayToVector(env, copy_froms);
32
33
  const std::vector<std::string> to_values = JArrayToVector(env, copy_tos);
33
34
 
@@ -44,6 +45,7 @@ Java_cn_reactnative_modules_update_DownloadTask_applyPatchFromFileSource(
44
45
  options.bundle_output_path = JStringToString(env, bundle_output_path);
45
46
  options.merge_source_subdir = JStringToString(env, merge_source_subdir);
46
47
  options.enable_merge = enable_merge == JNI_TRUE;
48
+ options.bundle_hbc_transform_meta = JStringToString(env, hbc_transform_meta);
47
49
 
48
50
  for (size_t index = 0; index < from_values.size(); ++index) {
49
51
  options.manifest.copies.push_back(pushy::patch::CopyOperation{
@@ -0,0 +1 @@
1
+ {"v":1,"hbcVersion":96,"layout":{"counts":19,"sections":[[2,16,[[0,0,25],[8,0,25]]],[3,4,[]],[4,4,[]],[5,4,[[0,1,23]]],[6,8,[[0,0,32]]],[7,1,[]],[12,1,[]],[13,1,[]],[14,1,[]],[8,8,[[0,0,32]]],[9,1,[]],[10,8,[[0,0,32]]],[11,1,[]],[16,8,[]],[17,8,[]]]}}