1 /* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos.restool; 17 18 import ohos.BundleException; 19 import ohos.Log; 20 import ohos.ResourceIndexResult; 21 22 import java.nio.ByteBuffer; 23 import java.nio.ByteOrder; 24 import java.nio.charset.StandardCharsets; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Optional; 30 31 /** 32 * Resources Parser V2. 33 * 34 * @since 2025-06-06 35 */ 36 public class ResourcesParserV2 implements ResourcesParser { 37 /** 38 * Parses resources default id. 39 */ 40 public static final int RESOURCE_DEFAULT_ID = -1; 41 42 private static final Log LOG = new Log(ResourcesParserV2.class.toString()); 43 private static final int VERSION_BYTE_LENGTH = 128; 44 private static final int TAG_BYTE_LENGTH = 4; 45 private static final int TYPE_BYTE_LENGTH = 4; 46 private static final int VALUE_BYTE_LENGTH = 4; 47 private static final int IDSS_TYPE_BYTE_LENGTH = 4; 48 private static final int DATA_OFFSET_LENGTH = 4; 49 private static final String LEFT_BRACKET = "<"; 50 private static final String RIGHT_BRACKET = ">"; 51 private static final String VERTICAL = "vertical"; 52 private static final String HORIZONTAL = "horizontal"; 53 private static final String DARK = "dark"; 54 private static final String LIGHT = "light"; 55 private static final String MCC = "mcc"; 56 private static final String MNC = "mnc"; 57 private static final String CONFIG_CONJUNCTION = "-"; 58 private static final String MCC_CONJUNCTION = "_"; 59 private static final String BASE = "base"; 60 private static final int CHAR_LENGTH = 1; 61 private static final String EMPTY_STRING = ""; 62 63 private static int idssOffset; 64 private static Map<String, Map<String, KeysItemV2>> keysMap = new HashMap<>(); 65 private static Map<Integer, Map<Integer, IdssItemV2>> idssMap = new HashMap<>(); 66 private static Map<Integer, Map<Integer, DataItemV2>> dataMap = new HashMap<>(); 67 68 /** 69 * ResourcesParserV2 70 * 71 * @param data resource index data 72 */ ResourcesParserV2(byte[] data)73 public ResourcesParserV2(byte[] data) { 74 parseZone(data); 75 } 76 77 private enum ResType { 78 VALUES(0, "Values"), 79 ANIMATOR(1, "Animator"), 80 DRAWABLE(2, "Drawable"), 81 LAYOUT(3, "Layout"), 82 MENU(4, "Menu"), 83 MIPMAP(5, "Mipmap"), 84 RAW(6, "Raw"), 85 XML(7, "Xml"), 86 INTEGER(8, "Integer"), 87 STRING(9, "String"), 88 STR_ARRAY(10, "StrArray"), 89 INT_ARRAY(11, "IntArray"), 90 BOOLEAN_TYPE(12, "Boolean"), 91 DIMEN(13, "Dimen"), 92 COLOR(14, "Color"), 93 ID(15, "Id"), 94 THEME(16, "Theme"), 95 PLURALS(17, "Plurals"), 96 FLOAT_TYPE(18, "Float"), 97 MEDIA(19, "Media"), 98 PROF(20, "Prof"), 99 SVG(21, "Svg"), 100 PATTERN(22, "Pattern"); 101 102 private final int index; 103 private final String type; 104 ResType(int index, String type)105 private ResType(int index, String type) { 106 this.index = index; 107 this.type = type; 108 } 109 getType(int index)110 public static String getType(int index) { 111 for (ResType resType : ResType.values()) { 112 if (resType.getIndex() == index) { 113 return resType.type; 114 } 115 } 116 return ""; 117 } 118 getIndex()119 public int getIndex() { 120 return index; 121 } getType()122 public String getType() { 123 return type; 124 } 125 } 126 127 private enum DeviceType { 128 PHONE(0, "phone"), 129 TABLET(1, "tablet"), 130 CAR(2, "car"), 131 PC(3, "pc"), 132 TV(4, "tv"), 133 SPEAKER(5, "speaker"), 134 WEARABLE(6, "wearable"), 135 GLASSES(7, "glasses"), 136 HEADSET(8, "headset"); 137 138 private final int index; 139 private final String type; 140 private static final Map<Integer, DeviceType> INDEX_MAP; 141 142 static { 143 INDEX_MAP = new HashMap<>(); 144 for (DeviceType deviceType : values()) { INDEX_MAP.put(deviceType.index, deviceType)145 INDEX_MAP.put(deviceType.index, deviceType); 146 } 147 } 148 DeviceType(int index, String type)149 private DeviceType(int index, String type) { 150 this.index = index; 151 this.type = type; 152 } 153 154 /** 155 * get index 156 * 157 * @return index of DeviceType 158 */ getIndex()159 public int getIndex() { 160 return index; 161 } 162 163 /** 164 * get type 165 * 166 * @return type of DeviceType 167 */ getType()168 public String getType() { 169 return type; 170 } 171 172 /** 173 * get type by index 174 * 175 * @param index index of DeviceType 176 * @return type of DeviceType 177 */ getTypeByIndex(int index)178 public static String getTypeByIndex(int index) { 179 DeviceType deviceType = INDEX_MAP.get(index); 180 return deviceType != null ? deviceType.type : ""; 181 } 182 } 183 184 private enum Resolution { 185 NODPI(-2, "nodpi"), 186 ANYDPI(-1, "anydpi"), 187 SDPI(120, "sdpi"), 188 MDPI(160, "mdpi"), 189 TVDPI(213, "tvdpi"), 190 LDPI(240, "ldpi"), 191 XLDPI(320, "xldpi"), 192 XXLDPI(480, "xxldpi"), 193 XXXLDPI(640, "xxxldpi"); 194 195 private final int index; 196 private final String type; 197 private static final Map<Integer, Resolution> INDEX_MAP; 198 199 static { 200 INDEX_MAP = new HashMap<>(); 201 for (Resolution resolution : values()) { INDEX_MAP.put(resolution.index, resolution)202 INDEX_MAP.put(resolution.index, resolution); 203 } 204 } 205 Resolution(int index, String type)206 private Resolution(int index, String type) { 207 this.index = index; 208 this.type = type; 209 } 210 211 /** 212 * get index 213 * 214 * @return index of Resolution 215 */ getIndex()216 public int getIndex() { 217 return index; 218 } 219 220 /** 221 * get type 222 * 223 * @return type of Resolution 224 */ getType()225 public String getType() { 226 return type; 227 } 228 229 /** 230 * get type by index 231 * 232 * @param index index of Resolution 233 * @return type of Resolution 234 */ getTypeByIndex(int index)235 public static String getTypeByIndex(int index) { 236 Resolution resolution = INDEX_MAP.get(index); 237 return resolution != null ? resolution.type : ""; 238 } 239 } 240 241 private enum ConfigType { 242 LANGUAGE, 243 REGION, 244 RESOLUTION, 245 DIRECTION, 246 DEVICE_TYPE, 247 SCRIPT, 248 LIGHT_MODE, 249 MCC, 250 MNC 251 } 252 253 /** 254 * Key Param. 255 */ 256 static class KeyParamV2 { 257 int keyType; 258 int value; 259 } 260 261 /** 262 * Config Index. 263 */ 264 static class ConfigIndexV2 { 265 String tag; 266 int resCfgId; 267 int keyCount; 268 KeyParamV2[] params; 269 } 270 271 /** 272 * KEYS item. 273 */ 274 static class KeysItemV2 { 275 String tag; 276 int resCfgId; 277 String type; 278 String value; 279 } 280 281 /** 282 * IDSS item. 283 */ 284 static class IdssItemV2 { 285 int type; 286 int resId; 287 int offset; 288 int nameLen; 289 String name; 290 } 291 292 /** 293 * DATA item. 294 */ 295 static class DataItemV2 { 296 int type; 297 int resId; 298 int resCfgId; 299 String name; 300 String value; 301 } 302 303 /** 304 * Get resource value by resource id. 305 * 306 * @param resourceId resource id 307 * @param data resource index data 308 * @return the resourceId value 309 * @throws BundleException IOException. 310 */ 311 @Override getResourceById(int resourceId, byte[] data)312 public String getResourceById(int resourceId, byte[] data) throws BundleException { 313 String resourceIdValue = ""; 314 if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { 315 LOG.error("ResourcesParserV2::getIconPath data byte or ResourceId is null"); 316 return resourceIdValue; 317 } 318 319 List<String> result = getResource(resourceId); 320 if (result != null && result.size() > 0 && result.get(0) != null && !EMPTY_STRING.equals(result.get(0))) { 321 resourceIdValue = result.get(0); 322 } 323 return resourceIdValue; 324 } 325 326 /** 327 * Get base resource value by resource id. 328 * 329 * @param resourceId resource id 330 * @param data resource index data 331 * @return the resource value 332 * @throws BundleException IOException. 333 */ 334 @Override getBaseResourceById(int resourceId, byte[] data)335 public String getBaseResourceById(int resourceId, byte[] data) throws BundleException { 336 String resourceIdValue = ""; 337 if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { 338 LOG.error("ResourcesParserV2::getBaseResourceById data byte or ResourceId is null"); 339 return resourceIdValue; 340 } 341 resourceIdValue = getBaseResource(resourceId, data); 342 return resourceIdValue; 343 } 344 345 /** 346 * Get base resource. 347 * 348 * @param resId resource id 349 * @param data resource index data array 350 * @return the resource value 351 * @throws BundleException IOException. 352 */ getBaseResource(int resId, byte[] data)353 static String getBaseResource(int resId, byte[] data) { 354 ByteBuffer byteBuf = ByteBuffer.wrap(data); 355 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 356 byte[] version = new byte[VERSION_BYTE_LENGTH]; 357 byteBuf.get(version); 358 byteBuf.getInt(); // length 359 int configCount = byteBuf.getInt(); 360 byteBuf.getInt(); // dataBrokeOffset 361 Optional<ConfigIndexV2> optionalConfigIndex = loadBaseConfig(byteBuf, configCount); 362 if (!optionalConfigIndex.isPresent()) { 363 LOG.error("ResourcesParserV2::getBaseResource configIndex is null"); 364 return ""; 365 } 366 return readBaseItem(resId, optionalConfigIndex.get()); 367 } 368 369 /** 370 * Load index config. 371 * 372 * @param bufBuf config byte buffer 373 * @param count config count 374 * @return the base config index 375 * @throws BundleException IOException. 376 */ loadBaseConfig(ByteBuffer bufBuf, int count)377 static Optional<ConfigIndexV2> loadBaseConfig(ByteBuffer bufBuf, int count) { 378 for (int i = 0; i < count; i++) { 379 ConfigIndexV2 cfg = new ConfigIndexV2(); 380 byte[] tag = new byte[TAG_BYTE_LENGTH]; 381 bufBuf.get(tag); 382 cfg.tag = new String(tag, StandardCharsets.UTF_8); 383 cfg.resCfgId = bufBuf.getInt(); 384 cfg.keyCount = bufBuf.getInt(); 385 cfg.params = new KeyParamV2[cfg.keyCount]; 386 for (int j = 0; j < cfg.keyCount; j++) { 387 cfg.params[j] = new KeyParamV2(); 388 cfg.params[j].keyType = bufBuf.getInt(); 389 cfg.params[j].value = bufBuf.getInt(); 390 } 391 if (cfg.keyCount == 0) { 392 return Optional.of(cfg); 393 } 394 } 395 return Optional.empty(); 396 } 397 398 /** 399 * Read base config item. 400 * 401 * @param resId resource id 402 * @param configIndex the config index 403 * @return the base item 404 * @throws BundleException IOException. 405 */ readBaseItem(int resId, ConfigIndexV2 configIndex)406 static String readBaseItem(int resId, ConfigIndexV2 configIndex) { 407 Map<Integer, DataItemV2> map = dataMap.get(resId); 408 return map.get(configIndex.resCfgId).value; 409 } 410 411 /** 412 * Get Icon resource. 413 * 414 * @param resId resource id 415 * @return the result 416 * @throws BundleException IOException. 417 */ getResource(int resId)418 static List<String> getResource(int resId) { 419 return readAllItem(resId); 420 } 421 422 /** 423 * Load index config. 424 * 425 * @param bufBuf config byte buffer 426 * @param count config count 427 * @return the config list 428 * @throws BundleException IOException. 429 */ loadConfig(ByteBuffer bufBuf, int count)430 static List<ConfigIndexV2> loadConfig(ByteBuffer bufBuf, int count) { 431 List<ConfigIndexV2> configList = new ArrayList<>(count); 432 for (int i = 0; i < count; i++) { 433 ConfigIndexV2 cfg = new ConfigIndexV2(); 434 byte[] tag = new byte[TAG_BYTE_LENGTH]; 435 bufBuf.get(tag); 436 cfg.tag = new String(tag, StandardCharsets.UTF_8); 437 cfg.resCfgId = bufBuf.getInt(); 438 cfg.keyCount = bufBuf.getInt(); 439 cfg.params = new KeyParamV2[cfg.keyCount]; 440 for (int j = 0; j < cfg.keyCount; j++) { 441 cfg.params[j] = new KeyParamV2(); 442 cfg.params[j].keyType = bufBuf.getInt(); 443 cfg.params[j].value = bufBuf.getInt(); 444 } 445 configList.add(cfg); 446 } 447 return configList; 448 } 449 450 /** 451 * Read all config item. 452 * 453 * @param resId resource id 454 * @return the item list 455 * @throws BundleException IOException. 456 */ readAllItem(int resId)457 static List<String> readAllItem(int resId) { 458 List<String> result = new ArrayList<>(); 459 Map<Integer, DataItemV2> map = dataMap.get(resId); 460 for (DataItemV2 dataItemV2 : map.values()) { 461 result.add(dataItemV2.value); 462 } 463 return result; 464 } 465 466 /** 467 * Read all config item. 468 * 469 * @param data config byte buffer 470 * @return the item info. 471 */ 472 @Override getAllDataItem(byte[] data)473 public List<ResourceIndexResult> getAllDataItem(byte[] data) { 474 ByteBuffer byteBuf = ByteBuffer.wrap(data); 475 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 476 byte[] version = new byte[VERSION_BYTE_LENGTH]; 477 byteBuf.get(version); 478 byteBuf.getInt(); 479 int keyCount = byteBuf.getInt(); 480 byteBuf.getInt(); // dataBrokeOffset 481 List<ConfigIndexV2> cfg = loadConfig(byteBuf, keyCount); 482 return readDataAllItem(cfg, byteBuf); 483 } 484 485 /** 486 * Read resource map by id. 487 * 488 * @param resId resource id 489 * @param data config byte buffer 490 * @return the resource map of id. 491 */ 492 @Override getResourceMapById(int resId, byte[] data)493 public HashMap<String, String> getResourceMapById(int resId, byte[] data) { 494 List<ResourceIndexResult> resources = getAllDataItem(data); 495 HashMap<String, String> resourceMap = new HashMap<>(); 496 for (ResourceIndexResult indexResult : resources) { 497 if (indexResult.id == resId) { 498 resourceMap.put(indexResult.configClass, indexResult.value); 499 } 500 } 501 return resourceMap; 502 } 503 504 /** 505 * Read resource by id. 506 * 507 * @param resId resource id 508 * @param data config byte buffer 509 * @return resource 510 */ 511 @Override getResourceStringById(int resId, byte[] data)512 public String getResourceStringById(int resId, byte[] data) { 513 List<ResourceIndexResult> resources = getAllDataItem(data); 514 for (ResourceIndexResult indexResult : resources) { 515 if (indexResult.id == resId) { 516 return indexResult.value; 517 } 518 } 519 return ""; 520 } 521 522 /** 523 * Read all config item. 524 * 525 * @param configs the config list 526 * @param buf config byte buffer 527 * @return the item list 528 */ readDataAllItem(List<ConfigIndexV2> configs, ByteBuffer buf)529 static List<ResourceIndexResult> readDataAllItem(List<ConfigIndexV2> configs, ByteBuffer buf) { 530 List<ResourceIndexResult> resourceIndexResults = new ArrayList<>(); 531 for (ConfigIndexV2 index : configs) { 532 String configClass = convertConfigIndexToString(index); 533 dataMap.values() 534 .forEach( 535 integerDataItemV2Map -> 536 integerDataItemV2Map 537 .values() 538 .forEach( 539 dataItemV2 -> { 540 if (dataItemV2.resCfgId == index.resCfgId) { 541 resourceIndexResults.add( 542 parseDataItems(dataItemV2, configClass)); 543 } 544 })); 545 } 546 return resourceIndexResults; 547 } 548 549 /** 550 * convert DataItems to ResourceIndexResult. 551 * 552 * @param item Indicates the DataItem. 553 * @param configClass config info 554 * @return the final ResourceIndexResult 555 */ parseDataItems(DataItemV2 item, String configClass)556 static ResourceIndexResult parseDataItems(DataItemV2 item, String configClass) { 557 ResourceIndexResult resourceIndexResult = new ResourceIndexResult(); 558 resourceIndexResult.configClass = configClass; 559 if (item != null) { 560 resourceIndexResult.type = ResType.getType(item.type); 561 resourceIndexResult.id = item.resId; 562 resourceIndexResult.name = item.name; 563 if (requireBytesConversion(item.type)) { 564 byte[] bytes = item.value.getBytes(StandardCharsets.UTF_8); 565 resourceIndexResult.value = convertBytesToString(bytes); 566 } else { 567 resourceIndexResult.value = item.value; 568 } 569 } 570 return resourceIndexResult; 571 } 572 requireBytesConversion(int resType)573 private static boolean requireBytesConversion(int resType) { 574 return resType == ResType.STR_ARRAY.getIndex() 575 || resType == ResType.INT_ARRAY.getIndex() 576 || resType == ResType.THEME.getIndex() 577 || resType == ResType.PLURALS.getIndex() 578 || resType == ResType.PATTERN.getIndex(); 579 } 580 581 /** 582 * convert bytes to string. 583 * 584 * @param data Indicates the bytes of data. 585 * @return the final string 586 */ convertBytesToString(byte[] data)587 static String convertBytesToString(byte[] data) { 588 StringBuilder result = new StringBuilder(); 589 ByteBuffer byteBuf = ByteBuffer.wrap(data); 590 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 591 while (byteBuf.hasRemaining()) { 592 result.append(LEFT_BRACKET); 593 int len = byteBuf.getShort(); 594 if (len <= 0) { 595 LOG.info("len less than 0, dismiss"); 596 result.append(RIGHT_BRACKET); 597 break; 598 } 599 byte[] value = new byte[len + CHAR_LENGTH]; 600 byteBuf.get(value); 601 String item = new String(value, StandardCharsets.UTF_8); 602 result.append(item, 0, item.length()); 603 result.append(RIGHT_BRACKET); 604 } 605 return result.toString(); 606 } 607 608 /** 609 * convert config to string. 610 * 611 * @param configIndex Indicates the configIndex. 612 * @return the final string 613 */ convertConfigIndexToString(ConfigIndexV2 configIndex)614 static String convertConfigIndexToString(ConfigIndexV2 configIndex) { 615 if (configIndex.keyCount == 0) { 616 return BASE; 617 } 618 619 StringBuilder configClass = new StringBuilder(); 620 ConfigType lastKeyType = null; 621 622 for (int i = 0; i < configIndex.keyCount; ++i) { 623 KeyParamV2 param = configIndex.params[i]; 624 ConfigType currentType = ConfigType.values()[param.keyType]; 625 Optional<String> valuePart = processParamValue(param, currentType); 626 627 if (valuePart.isPresent()) { 628 appendValue(configClass, lastKeyType, currentType, valuePart.get()); 629 lastKeyType = currentType; 630 } 631 } 632 633 return configClass.length() == 0 ? BASE : configClass.toString(); 634 } 635 processParamValue(KeyParamV2 param, ConfigType type)636 private static Optional<String> processParamValue(KeyParamV2 param, ConfigType type) { 637 switch (type) { 638 case LANGUAGE: 639 case REGION: 640 case SCRIPT: 641 return Optional.ofNullable(parseAscii(param.value)); 642 case RESOLUTION: 643 return Optional.ofNullable(Resolution.getTypeByIndex(param.value)); 644 case DIRECTION: 645 return Optional.ofNullable(param.value == 0 ? VERTICAL : HORIZONTAL); 646 case DEVICE_TYPE: 647 return Optional.ofNullable(DeviceType.getTypeByIndex(param.value)); 648 case LIGHT_MODE: 649 return Optional.ofNullable(param.value == 0 ? DARK : LIGHT); 650 case MCC: 651 return Optional.ofNullable(MCC + param.value); 652 case MNC: 653 return Optional.ofNullable(MNC + fillUpZero(String.valueOf(param.value), 3)); 654 default: 655 return Optional.empty(); 656 } 657 } 658 appendValue( StringBuilder builder, ConfigType lastKeyType, ConfigType currentKeyType, String value)659 private static void appendValue( 660 StringBuilder builder, ConfigType lastKeyType, ConfigType currentKeyType, String value) { 661 if (builder.length() == 0) { 662 builder.append(value); 663 return; 664 } 665 666 String conjunction = shouldUseMccConjunction(lastKeyType, currentKeyType) 667 ? MCC_CONJUNCTION 668 : CONFIG_CONJUNCTION; 669 builder.append(conjunction).append(value); 670 } 671 shouldUseMccConjunction(ConfigType lastKeyType, ConfigType currentKeyType)672 private static boolean shouldUseMccConjunction(ConfigType lastKeyType, ConfigType currentKeyType) { 673 boolean isCurrentMccMnc = currentKeyType == ConfigType.MCC || currentKeyType == ConfigType.MNC; 674 boolean isLastLanguageRegionScript = 675 lastKeyType != null 676 && (lastKeyType == ConfigType.LANGUAGE 677 || lastKeyType == ConfigType.REGION 678 || lastKeyType == ConfigType.SCRIPT); 679 680 return isCurrentMccMnc || isLastLanguageRegionScript; 681 } 682 683 /** 684 * convert integer to string. 685 * 686 * @param value Indicates the Integer. 687 * @return the final string 688 */ parseAscii(Integer value)689 private static String parseAscii(Integer value) { 690 if (value == null) { 691 return ""; 692 } 693 StringBuilder result = new StringBuilder(); 694 int tempValue = value; 695 while (tempValue > 0) { 696 result.insert(0, (char) (tempValue & 0xFF)); 697 tempValue = tempValue >> 8; 698 } 699 return result.toString(); 700 } 701 702 /** 703 * fillup zero to string. 704 * 705 * @param inputString Indicates the string should to be filled. 706 * @param length Indicates the final length of String. 707 * @return the final string 708 */ fillUpZero(String inputString, int length)709 private static String fillUpZero(String inputString, int length) { 710 if (inputString.length() >= length) { 711 return inputString; 712 } 713 StringBuilder result = new StringBuilder(); 714 while (result.length() < length - inputString.length()) { 715 result.append('0'); 716 } 717 result.append(inputString); 718 return result.toString(); 719 } 720 721 /** 722 * Parse KEYS,IDSS,DATA zone. 723 * 724 * @param data resource byte 725 */ parseZone(byte[] data)726 static void parseZone(byte[] data) { 727 if (!parseKEYSZone(data)) { 728 LOG.error("ResourcesParserV2 parseKEYSZone() failed"); 729 } 730 if (!parseDATAZone(data)) { 731 LOG.error("ResourcesParserV2 parseDATAZone() failed"); 732 } 733 if (!parseIDSSZone(data)) { 734 LOG.error("ResourcesParserV2 parseIDSSZone() failed"); 735 } 736 } 737 738 /** 739 * Parse KEYS zone. 740 * 741 * @param data resource byte 742 * @return parse result 743 */ parseKEYSZone(byte[] data)744 static boolean parseKEYSZone(byte[] data) { 745 if (data == null || data.length == 0) { 746 LOG.error("ResourcesParserV2::parseKEYSZone data byte is null"); 747 return false; 748 } 749 ByteBuffer buf = ByteBuffer.wrap(data); 750 buf.order(ByteOrder.LITTLE_ENDIAN); 751 byte[] version = new byte[VERSION_BYTE_LENGTH]; 752 buf.get(version); 753 buf.getInt(); // length 754 int keyCount = buf.getInt(); 755 buf.getInt(); // dataBrokeOffset 756 // KEYS zone 757 for (int i = 0; i < keyCount; i++) { 758 Map<String, KeysItemV2> map = new HashMap<>(); 759 byte[] tag = new byte[TAG_BYTE_LENGTH]; 760 buf.get(tag); 761 String tagStr = new String(tag, StandardCharsets.UTF_8); 762 int resCfgId = buf.getInt(); 763 int keyParamCount = buf.getInt(); 764 for (int j = 0; j < keyParamCount; j++) { 765 KeysItemV2 keysItemV2 = new KeysItemV2(); 766 keysItemV2.tag = tagStr; 767 keysItemV2.resCfgId = resCfgId; 768 byte[] type = new byte[TYPE_BYTE_LENGTH]; 769 buf.get(type); 770 keysItemV2.type = new String(type, StandardCharsets.UTF_8); 771 byte[] value = new byte[VALUE_BYTE_LENGTH]; 772 buf.get(value); 773 keysItemV2.value = new String(value, StandardCharsets.UTF_8); 774 map.put(keysItemV2.type, keysItemV2); 775 } 776 keysMap.put(tagStr, map); 777 } 778 idssOffset = buf.position(); 779 return true; 780 } 781 782 /** 783 * Parse IDSS zone. 784 * 785 * @param data resource byte 786 * @return parse result 787 */ parseIDSSZone(byte[] data)788 static boolean parseIDSSZone(byte[] data) { 789 if (data == null || data.length == 0) { 790 LOG.error("ResourcesParserV2::parseIDSSZone data byte is null"); 791 return false; 792 } 793 if (idssOffset == 0) { 794 LOG.error("ResourcesParserV2::parseIDSSZone idssOffset has not parse"); 795 return false; 796 } 797 ByteBuffer buf = ByteBuffer.wrap(data); 798 buf.order(ByteOrder.LITTLE_ENDIAN); 799 buf.position(idssOffset); 800 buf.getInt(); // tag 801 buf.getInt(); // len 802 int typeCount = buf.getInt(); 803 buf.getInt(); // idCount 804 // IDSS zone 805 for (int i = 0; i < typeCount; i++) { 806 Map<Integer, IdssItemV2> map = new HashMap<>(); 807 int type = buf.getInt(); 808 buf.getInt(); // length 809 int count = buf.getInt(); 810 for (int j = 0; j < count; j++) { 811 IdssItemV2 idssItemV2 = new IdssItemV2(); 812 idssItemV2.type = type; 813 idssItemV2.resId = buf.getInt(); 814 idssItemV2.offset = buf.getInt(); 815 int nameLen = buf.getInt(); 816 byte[] name = new byte[nameLen]; 817 buf.get(name); 818 idssItemV2.nameLen = nameLen; 819 idssItemV2.name = new String(name, StandardCharsets.UTF_8); 820 dataMap.get(idssItemV2.resId) 821 .values() 822 .forEach( 823 dataItemV2 -> { 824 dataItemV2.type = idssItemV2.type; 825 dataItemV2.name = idssItemV2.name; 826 }); 827 map.put(idssItemV2.resId, idssItemV2); 828 } 829 idssMap.put(type, map); 830 } 831 return true; 832 } 833 834 /** 835 * Parse DATA zone. 836 * 837 * @param data resource byte 838 * @return data zone 839 */ parseDATAZone(byte[] data)840 static boolean parseDATAZone(byte[] data) { 841 if (data == null || data.length == 0) { 842 LOG.error("ResourcesParserV2::parseDATAZone data byte is null"); 843 return false; 844 } 845 ByteBuffer buf = ByteBuffer.wrap(data); 846 buf.order(ByteOrder.LITTLE_ENDIAN); 847 byte[] version = new byte[VERSION_BYTE_LENGTH]; 848 buf.get(version); 849 buf.getInt(); // length 850 buf.getInt(); 851 int dataZoneOffset = buf.getInt(); // dataBrokeOffset 852 buf.position(dataZoneOffset); 853 buf.getInt(); // DATA tag 854 buf.getInt(); // DATA length 855 int resIdCount = buf.getInt(); 856 for (int i = 0; i < resIdCount; i++) { 857 Map<Integer, DataItemV2> resCfgIdMap = new HashMap<>(); 858 int resId = buf.getInt(); 859 buf.getInt(); // length 860 int resCfgIdCount = buf.getInt(); 861 for (int j = 0; j < resCfgIdCount; j++) { 862 DataItemV2 dataItemV2 = new DataItemV2(); 863 dataItemV2.resId = resId; 864 dataItemV2.resCfgId = buf.getInt(); 865 int dataOffset = buf.getInt(); 866 int position = buf.position(); 867 buf.position(dataOffset); 868 short dataLen = buf.getShort(); 869 byte[] tag = new byte[dataLen]; 870 buf.get(tag); 871 dataItemV2.value = new String(tag, StandardCharsets.UTF_8); 872 buf.position(position); 873 resCfgIdMap.put(dataItemV2.resCfgId, dataItemV2); 874 } 875 dataMap.put(resId, resCfgIdMap); 876 } 877 return true; 878 } 879 } 880 881