├── .gitignore ├── IMG_20241223_122041_536.jpg ├── IMG_20241223_122046_767.jpg ├── README.md ├── app ├── build.gradle ├── build │ └── gen │ │ └── mt │ │ └── modder │ │ └── hub │ │ └── axml │ │ ├── BuildConfig.java │ │ └── R.java ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── mt │ │ └── modder │ │ └── hub │ │ └── axml │ │ └── MainActivity.java │ └── res │ ├── drawable │ ├── ic_launcher.png │ ├── ic_launcher_background.xml │ └── ic_launcher_round.png │ └── values │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── library ├── .gitignore ├── build.gradle ├── libs │ ├── arsc.jar │ └── guava.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── attrs.xml │ ├── attrs_manifest.xml │ ├── no_nameSpace_attrs.txt │ ├── permissions_info_en.txt │ ├── r_values.ini │ └── resources.arsc │ └── java │ └── mt │ └── modder │ └── hub │ ├── axml │ ├── AXMLPrinter.java │ ├── AttributesExtractor.java │ └── NamespaceChecker.java │ └── axmlTools │ ├── AXmlResourceParser.java │ ├── ChunkUtil.java │ ├── IntReader.java │ ├── StringBlock.java │ ├── XmlResourceParser.java │ ├── arsc │ └── ResourceIdExtractor.java │ ├── escaper │ ├── AggregateTranslator.java │ ├── CharSequenceTranslator.java │ ├── CodePointTranslator.java │ ├── EntityArrays.java │ ├── LookupTranslator.java │ ├── NumericEntityEscaper.java │ ├── UnicodeUnpairedSurrogateRemover.java │ └── XmlEscaper.java │ └── utils │ ├── Attribute.java │ ├── AttributeSet.java │ └── TypedValue.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /IMG_20241223_122041_536.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/IMG_20241223_122041_536.jpg -------------------------------------------------------------------------------- /IMG_20241223_122046_767.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/IMG_20241223_122046_767.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AXMLPrinter 2 | An Advanced Axml Printer available with proper xml style/format feature 3 | # What is the use of this tool 4 | A tool for printing out Android binary XML files (It can be Android Manifest or other resources XML files) into normal raw AXML text. It is inspired by Android4ME's axmlprinter library. 5 | 6 | ## What's new on it. 7 | There are several changes made to the code, and multiple bugs have been fixed. The following changes have been made in this AXMLPrinter library: 8 | - [x] Added support for translating enum or flags based attributes (Customized function) 9 | - [x] Fixed decoding some UTF-8. 10 | - [x] Extraction of attribute name from id 11 | - [x] Extraction of flag and enum values according to android resource 12 | - [x] Fixed close tag error 13 | - [x] Fixed handling unknown attributes 14 | - [x] Fixed indentation/tab error 15 | - [x] Improved speed in xml decompilation 16 | - [x] Extraction of Attribute name according to resource id 17 | - [x] Id2Name (Reading axml file according to the resource id name) 18 | - [x] Extraction of manifest permissions short description (Customized function) 19 | - [ ] Reading pseudo-encrypted xml files 20 | 21 | ## Test the library 22 | I injected my library in **MT Manager Mod** You can download the apk file from my Telegram Channel. [Download Now](https://t.me/Modder_Hub/906) 23 | #### Sample 24 | Screenshot 25 | Screenshot 26 | 27 | ## License 28 | + Apache License V2.0 29 | 30 | ## Contributing 31 | 32 | 1. Fork repository 33 | 2. Make changes 34 | 3. Ensure tests pass (or hopefully adding tests!) 35 | 4. Submit pull request/issue 36 | 37 | ## Thanks 38 | 39 | + XML Pull Parsing: 40 | + AXMLPrinter(Android4ME) 41 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | 7 | defaultConfig { 8 | applicationId "mt.modder.hub.axml" 9 | minSdkVersion 14 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation project(':library') 25 | } 26 | -------------------------------------------------------------------------------- /app/build/gen/mt/modder/hub/axml/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** Automatically generated file. DO NOT MODIFY */ 2 | package mt.modder.hub.axml; 3 | 4 | public final class BuildConfig { 5 | public final static boolean DEBUG = true; 6 | } 7 | -------------------------------------------------------------------------------- /app/build/gen/mt/modder/hub/axml/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package mt.modder.hub.axml; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class drawable { 14 | public static final int ic_launcher_background=0x7f020000; 15 | public static final int ic_launcher_foreground=0x7f020001; 16 | public static final int ic_launcher_foreground_1=0x7f020002; 17 | } 18 | public static final class layout { 19 | public static final int main=0x7f040000; 20 | } 21 | public static final class mipmap { 22 | public static final int ic_launcher=0x7f030000; 23 | public static final int ic_launcher_round=0x7f030001; 24 | } 25 | public static final class string { 26 | public static final int app_name=0x7f050000; 27 | public static final int hello_world=0x7f050001; 28 | } 29 | public static final class style { 30 | public static final int AppTheme=0x7f060000; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepattributes SourceFile, LineNumberTable 2 | -renamesourcefileattribute SourceFile 3 | -repackageclasses 4 | -ignorewarnings 5 | -dontwarn 6 | -dontnote -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/mt/modder/hub/axml/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axml; 37 | 38 | import android.app.*; 39 | import android.os.*; 40 | import java.io.FileInputStream; 41 | import java.io.IOException; 42 | 43 | import android.widget.TextView; 44 | import java.io.File; 45 | import java.io.FileWriter; 46 | import java.io.StringWriter; 47 | import java.io.PrintWriter; 48 | import android.widget.ScrollView; 49 | import android.widget.LinearLayout.LayoutParams; 50 | import android.widget.LinearLayout; 51 | import java.util.regex.*; 52 | import java.io.*; 53 | import java.nio.*; 54 | 55 | 56 | 57 | 58 | public class MainActivity extends Activity { 59 | 60 | public String Input_Path = "/storage/emulated/0/AndroidManifest.xml"; 61 | 62 | public String outPath = "sdcard/MyDecompiledAXML.xml"; 63 | 64 | @Override 65 | protected void onCreate(Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | 68 | final ScrollView scroll = new ScrollView(this); 69 | scroll.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)); 70 | scroll.setFillViewport(true); 71 | scroll.setPadding(8,8,8,8); 72 | TextView text = new TextView(this); 73 | text.setText("Processing "+ Input_Path +" ..."); 74 | try { 75 | // Read the binary XML file into a byte array 76 | FileInputStream fis = new FileInputStream(Input_Path); 77 | byte[] byteArray = new byte[fis.available()]; 78 | fis.read(byteArray); 79 | fis.close(); 80 | 81 | // initialize the axmlprinter class 82 | AXMLPrinter axmlPrinter = new AXMLPrinter(); 83 | axmlPrinter.setEnableID2Name(true); 84 | axmlPrinter.setAttrValueTranslation(true); 85 | axmlPrinter.setExtractPermissionDescription(true); 86 | 87 | // Use the XMLDecompiler to decompile to an XML string 88 | // Place your resources.arsc file in the same directory of your xml file 89 | String xmlString = axmlPrinter.readFromFile(Input_Path); 90 | 91 | // Direct process without enabling custom resource id2name 92 | // String xmlString = axmlPrinter.convertXml(byteArray); 93 | 94 | // Output the XML string 95 | saveAsFile(xmlString, outPath); 96 | text.setText("Processing complete .File saved in " + outPath); 97 | } catch (Exception e) { 98 | // Complete extraction of error 99 | StringWriter sw = new StringWriter(); 100 | PrintWriter pw = new PrintWriter(sw); 101 | e.printStackTrace(pw); 102 | String exceptionDetails = sw.toString(); 103 | text.setText(exceptionDetails); 104 | e.printStackTrace(); 105 | } 106 | 107 | 108 | scroll.addView(text); 109 | setContentView(scroll); 110 | } 111 | 112 | public void saveAsFile(String data, String path) throws IOException{ 113 | File outputFile = new File(path); 114 | FileWriter fileWriter = new FileWriter(outputFile); 115 | fileWriter.write(data.toString()); 116 | fileWriter.close(); 117 | } 118 | 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/app/src/main/res/drawable/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AXMLPrinter 5 | Hello, World! 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.5.0' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | 7 | defaultConfig { 8 | applicationId "mt.modder.hub.axml" 9 | minSdkVersion 14 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | } 25 | -------------------------------------------------------------------------------- /library/libs/arsc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/library/libs/arsc.jar -------------------------------------------------------------------------------- /library/libs/guava.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/library/libs/guava.jar -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepattributes SourceFile, LineNumberTable 2 | -renamesourcefileattribute SourceFile 3 | -repackageclasses 4 | -ignorewarnings 5 | -dontwarn 6 | -dontnote -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /library/src/main/assets/attrs_manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 568 | 569 | 570 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 774 | 775 | 776 | 777 | 778 | 784 | 785 | 786 | 787 | 788 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 804 | 805 | 806 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 934 | 935 | 936 | 937 | -------------------------------------------------------------------------------- /library/src/main/assets/no_nameSpace_attrs.txt: -------------------------------------------------------------------------------- 1 | package 2 | platformBuildVersionCode 3 | platformBuildVersionName 4 | violationExplanation 5 | violationType 6 | coreApp 7 | style 8 | -------------------------------------------------------------------------------- /library/src/main/assets/resources.arsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developer-krushna/AXMLPrinter/e89b2beccc61d5087cdaaeaa46b4dee8056e9b62/library/src/main/assets/resources.arsc -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axml/AXMLPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2025, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axml; 37 | 38 | import java.io.*; 39 | import java.util.*; 40 | import mt.modder.hub.axmlTools.*; 41 | import mt.modder.hub.axmlTools.utils.*; 42 | import org.xmlpull.v1.*; 43 | import java.util.regex.*; 44 | import mt.modder.hub.axmlTools.arsc.*; 45 | import java.nio.charset.*; 46 | 47 | /** 48 | * AXMLPrinter 49 | * A tool for decompiling Android binary XML files into human-readable XML format. 50 | * Author: @developer-krushna 51 | * Thanks to ChatGPT for adding comments. :) 52 | */ 53 | public final class AXMLPrinter { 54 | 55 | private static final String COPYRIGHT = "AXMLPrinter\nCopyright (C) developer-krushna [https://github.com/developer-krushna/](krushnachandramaharna57@gmail.com)\nThis project is distributed under the Apache License v2.0 license"; 56 | 57 | // Constants for conversion factors and unit strings 58 | private static final float MANTISSA_MULT = 59 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT); 60 | 61 | private static final float[] RADIX_MULTS = new float[]{ 62 | 1.0f * MANTISSA_MULT /* 0.00390625f */, 63 | 1.0f / (1 << 7) * MANTISSA_MULT /* 3.051758E-5f */, 64 | 1.0f / (1 << 15) * MANTISSA_MULT /* 1.192093E-7f */, 65 | 1.0f / (1 << 23) * MANTISSA_MULT /* 4.656613E-10f */ 66 | }; 67 | private static final String[] DIMENSION_UNIT_STRS = new String[]{ 68 | "px", "dp", "sp", "pt", "in", "mm" 69 | }; 70 | private static final String[] FRACTION_UNIT_STRS = new String[]{ 71 | "%", "%p" 72 | }; 73 | 74 | 75 | private boolean enableId2Name = false;// Enable ID to name translation 76 | private boolean enableAttributeConversion = false; // Enable attribute value conversion 77 | 78 | private ResourceIdExtractor systemResourceFile = new ResourceIdExtractor(); // Handles system resource IDs 79 | private ResourceIdExtractor customResourceFile = new ResourceIdExtractor();// Handles custom resource IDs 80 | public String CUSTOM_ATTRIBUTE_TAG = "_Custom"; 81 | public String SYSTEM_ATTRIBUTE_TAG = "_Systumm"; //😂 82 | public boolean isCustomResourceFileExist = false; // Indicates if a custom resource file exists 83 | public boolean isResObfuscationCheckDone = false; 84 | public boolean isResObfuscated = false; 85 | 86 | private NamespaceChecker namespaceChecker = new NamespaceChecker(); 87 | public String ANDROID_PREFIX = "android"; 88 | public String APP_PREFIX = "app"; 89 | public String AUTO_NAMESPACE = "http://schemas.android.com/apk/res-auto"; 90 | public String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; 91 | public boolean isExistAndroidNamespace = false; 92 | public boolean isAuto_NameSpaceExists = false; 93 | public String RandomResAutoPrefix_Name = null; 94 | 95 | private Map permissionInfoMap; 96 | private boolean isPermissionInfoLoaded = false; 97 | private String PERMISSION_TAG = "uses-permission"; 98 | private boolean isExtractPermissionDescription = false; 99 | 100 | 101 | 102 | /** 103 | * Enables or disables ID-to-name conversion. 104 | * This allows translating hex IDs in XML to their corresponding resource names. 105 | * 106 | * @param enable Flag to enable or disable this feature. 107 | */ 108 | public void setEnableID2Name(boolean enable) { 109 | enableId2Name = enable; 110 | if (enable) { 111 | try { 112 | // Load System resource file 113 | loadSystemResources(); 114 | } catch (Exception e) { 115 | systemResourceFile = null; 116 | System.err.println("Failed to load system resources."); 117 | } 118 | } 119 | } 120 | 121 | 122 | /** 123 | * Enables or disables attribute value translation. 124 | * This feature interprets attribute values and converts them to human-readable formats. 125 | * 126 | * @param enable Flag to enable or disable this feature. 127 | */ 128 | public void setAttrValueTranslation(boolean enable) { 129 | enableAttributeConversion = enable; 130 | } 131 | 132 | /** 133 | * Enables or disables permission description extraction from XML. 134 | * 135 | * @param enable Flag to enable or disable this feature. 136 | */ 137 | public void setExtractPermissionDescription(boolean isExtract) { 138 | isExtractPermissionDescription = isExtract; 139 | } 140 | 141 | /** 142 | * Loads system resources from the bundled resources.arsc file. 143 | * 144 | * @throws Exception if the resource file cannot be loaded. 145 | */ 146 | private void loadSystemResources() throws Exception { 147 | try (InputStream arscStream = AXMLPrinter.class.getResourceAsStream("/assets/resources.arsc")) { 148 | systemResourceFile.loadArscData(arscStream); 149 | } 150 | } 151 | 152 | /** 153 | * Reads a binary XML file and converts it into a human-readable XML string. 154 | * 155 | * @param path Path to the binary XML file. 156 | * @return The converted XML content as a string. 157 | * @throws Exception if an error occurs while reading the file. 158 | */ 159 | public String readFromFile(String path) throws Exception { 160 | FileInputStream fis = new FileInputStream(path); 161 | byte[] byteArray = new byte[fis.available()]; 162 | fis.read(byteArray); 163 | fis.close(); 164 | 165 | if (enableId2Name) { 166 | File resourceFile = new File(path).getParentFile().toPath().resolve("resources.arsc").toFile(); 167 | if (resourceFile.exists()) { 168 | try (InputStream arscStream = new FileInputStream(resourceFile)) { 169 | customResourceFile.loadArscData(arscStream); 170 | isCustomResourceFileExist = true; 171 | } 172 | } 173 | } 174 | 175 | // Convert the binary XML to readable XML 176 | return convertXml(byteArray); 177 | } 178 | 179 | // for MT Manager 180 | public void readProcessRes(String path) throws Exception { 181 | if (!path.endsWith(".xml")) { 182 | return; 183 | } 184 | File file = new File(path); 185 | String resourceFile = file.getParent() + "/resources.arsc"; 186 | System.out.println(resourceFile); 187 | if (new File(resourceFile).exists()) { 188 | try { 189 | try (InputStream arscStream = new FileInputStream(resourceFile)) { 190 | customResourceFile.loadArscData(arscStream); 191 | } 192 | isCustomResourceFileExist = true; 193 | } catch (Exception e) { 194 | System.out.println(e); 195 | isCustomResourceFileExist = false; 196 | } 197 | } 198 | } 199 | 200 | 201 | /** 202 | * Converts a binary XML byte array into a readable XML string. 203 | * 204 | * @param byteArray The binary XML byte array. 205 | * @return The converted XML content as a string. 206 | */ 207 | public String convertXml(byte[] byteArray) { 208 | System.out.println(COPYRIGHT); 209 | if (!enableId2Name) { 210 | try { 211 | loadSystemResources(); 212 | } catch (Exception e) { 213 | systemResourceFile = null; 214 | } 215 | } 216 | try { 217 | // Initialize the XML parser with the byte array input 218 | AXmlResourceParser xmlParser = new AXmlResourceParser(); 219 | xmlParser.open(new ByteArrayInputStream(byteArray)); 220 | StringBuilder indentation = new StringBuilder(); 221 | StringBuilder xmlContent = new StringBuilder(); 222 | boolean isManuallyAddedAndroidNamespace = false; 223 | while (true) { 224 | int eventType = xmlParser.next(); 225 | int attributeCount = xmlParser.getAttributeCount(); // count attributes 226 | if (eventType == XmlPullParser.END_DOCUMENT) { 227 | // End of document 228 | String result = xmlContent.toString(); 229 | xmlParser.close(); 230 | return result; 231 | } 232 | switch (eventType) { 233 | case XmlPullParser.START_DOCUMENT: 234 | // Append XML declaration at the start of the document 235 | xmlContent.append("\n"); 236 | xmlContent.append("\n"); 237 | break; 238 | 239 | case XmlPullParser.START_TAG: 240 | // Handle the start of a new XML tag 241 | if (xmlParser.getPrevious().type == XmlPullParser.START_TAG) { 242 | xmlContent.append(">\n"); 243 | } 244 | 245 | String prefix = xmlParser.getName(); 246 | 247 | //check if the user-permission prefix found (We know its available only in AndroidManifest.xml) 248 | if (isExtractPermissionDescription) { 249 | if (prefix.contains(PERMISSION_TAG)) { 250 | if (!isPermissionInfoLoaded) { 251 | //load permissionInfo one time only 252 | permissionInfoMap = loadPermissionsInfo(); 253 | isPermissionInfoLoaded = true; 254 | } 255 | //extract permission description from corresponding permissionName 256 | if (attributeCount > 0) { 257 | for (int i = 0; i < attributeCount; i++) { 258 | String permissionName = xmlParser.getAttributeValue(i); 259 | String description = permissionInfoMap.get(permissionName); 260 | if (description != null) { 261 | // Print permission description 262 | xmlContent.append(indentation).append("\n"); 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | xmlContent.append(String.format("%s<%s%s", 270 | indentation, 271 | getMainNodeNamespacePrefix(xmlParser.getPrefix()), 272 | prefix)); 273 | indentation.append(" "); 274 | 275 | // Handle namespaces 276 | int depth = xmlParser.getDepth(); 277 | int namespaceStart = xmlParser.getNamespaceCount(depth - 1); 278 | int namespaceEnd = xmlParser.getNamespaceCount(depth); 279 | 280 | //xmlContent.append(indentation).append(""); 281 | for (int i = namespaceStart; i < namespaceEnd; i++) { 282 | String namespaceFormat = (i == namespaceStart) ? "%sxmlns:%s=\"%s\"" : "\n%sxmlns:%s=\"%s\""; 283 | String nameSpacePrefix = xmlParser.getNamespacePrefix(i); 284 | String nameSpaceUri = xmlParser.getNamespaceUri(i); 285 | if(nameSpaceUri.equals(AUTO_NAMESPACE)){ 286 | isAuto_NameSpaceExists = true; 287 | } 288 | if(nameSpaceUri.equals(AUTO_NAMESPACE) && !nameSpacePrefix.equals("app")){ 289 | RandomResAutoPrefix_Name = nameSpacePrefix; 290 | nameSpacePrefix = "app"; 291 | } 292 | 293 | xmlContent.append(String.format(namespaceFormat, 294 | (i == namespaceStart) ? " " : indentation, 295 | nameSpacePrefix, 296 | nameSpaceUri)); 297 | isExistAndroidNamespace = true; // make it true as it completed the above task 298 | 299 | } 300 | 301 | // if NamespaceUri failed to extract then add this manually 302 | if (!isExistAndroidNamespace && !isManuallyAddedAndroidNamespace) { 303 | String namespaceFormat = "%sxmlns:%s=\"%s\""; 304 | // Search android namespace if exist then add 305 | if(containsSequence(byteArray, ANDROID_NAMESPACE.getBytes(StandardCharsets.UTF_8)) || prefix.equals("manifest")){ 306 | xmlContent.append(String.format(namespaceFormat, " ", ANDROID_PREFIX, ANDROID_NAMESPACE)); 307 | isManuallyAddedAndroidNamespace = true; 308 | } 309 | // Search auto namespace if exist then add 310 | if(containsSequence(byteArray, AUTO_NAMESPACE.getBytes(StandardCharsets.UTF_8))){ 311 | xmlContent.append("\n"); 312 | xmlContent.append(String.format(namespaceFormat, indentation, APP_PREFIX, AUTO_NAMESPACE)); 313 | isAuto_NameSpaceExists = true; 314 | } 315 | isExistAndroidNamespace = false; // false because we add this mannualy 316 | } 317 | 318 | // Handle attributes 319 | if (attributeCount > 0) { 320 | if (attributeCount == 1) { 321 | xmlContent.append(""); 322 | for (int i = 0; i < attributeCount; i++) { 323 | // Skip attributes with a dot (.) 324 | if (xmlParser.getAttributeName(i).contains(".")) { 325 | continue; // Skip this attribute if its name contains a dot 326 | } 327 | 328 | String attributeFormat = (i == attributeCount - 1) ? "%s%s%s=\"%s\"" : "%s%s%s=\"%s\"\n"; 329 | String attributeName = getAttributeName(xmlParser, i); 330 | String attributeValue = getAttributeValue(xmlParser, i); 331 | // Final Addition of namespace , attribute along with its corresponding value 332 | int valueSize = attributeValue.codePointCount(0, attributeValue.length()); 333 | if(valueSize <= 14 || prefix.equals(PERMISSION_TAG)){ 334 | // Indention is not needed because it has 1 attribute only its main node 335 | xmlContent.append(String.format(attributeFormat, 336 | " ", 337 | getAttrNamespacePrefix(xmlParser, i, attributeName), 338 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""), 339 | attributeValue)); 340 | } else { 341 | xmlContent.append('\n'); 342 | xmlContent.append(String.format(attributeFormat, 343 | indentation, 344 | getAttrNamespacePrefix(xmlParser, i, attributeName), 345 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""), 346 | attributeValue)); 347 | } 348 | } 349 | } else { 350 | xmlContent.append('\n'); 351 | for (int i = 0; i < attributeCount; i++) { 352 | // Skip attributes with a dot (.) 353 | if (xmlParser.getAttributeName(i).contains(".")) { 354 | continue; // Skip this attribute if its name contains a dot 355 | } 356 | 357 | String attributeFormat = (i == attributeCount - 1) ? "%s%s%s=\"%s\"" : "%s%s%s=\"%s\"\n"; 358 | String attributeName = getAttributeName(xmlParser, i); //Attribute name 359 | // Final Addition of namespace , attribute along with its corresponding value 360 | // Indention is needed because it 2 or more attributes 361 | 362 | xmlContent.append(String.format(attributeFormat, 363 | indentation, 364 | getAttrNamespacePrefix(xmlParser, i, attributeName), 365 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""), 366 | getAttributeValue(xmlParser, i))); 367 | 368 | } 369 | } 370 | } 371 | 372 | break; 373 | 374 | case XmlPullParser.END_TAG: 375 | // Handle the end of an XML tag 376 | indentation.setLength(indentation.length() - " ".length()); 377 | if (!isEndOfPrecededXmlTag(xmlParser, xmlParser.getPrevious())) { 378 | 379 | xmlContent.append(String.format("%s\n", 380 | indentation, 381 | getMainNodeNamespacePrefix(xmlParser.getPrefix()), 382 | xmlParser.getName())); 383 | } else { 384 | xmlContent.append(" />\n"); 385 | } 386 | break; 387 | 388 | case XmlPullParser.TEXT: 389 | // Handle text within an XML tag 390 | if (xmlParser.getPrevious().type == XmlPullParser.START_TAG) { 391 | xmlContent.append(">\n"); 392 | } 393 | xmlContent.append(String.format("%s%s\n", 394 | indentation, 395 | xmlParser.getText())); 396 | break; 397 | } 398 | } 399 | } catch (Exception e) { 400 | // Handle exceptions and return the stack trace 401 | StringWriter sw = new StringWriter(); 402 | PrintWriter pw = new PrintWriter(sw); 403 | e.printStackTrace(pw); 404 | String exceptionDetails = sw.toString(); 405 | return "----StackTrace----\n" + exceptionDetails; 406 | } 407 | } 408 | 409 | /** 410 | * Retrieves the value of an attribute based on its type. 411 | * This method processes attribute types like strings, references, dimensions, fractions, etc., 412 | * and returns their human-readable representation. 413 | * 414 | * @param xmlParser The XML parser instance. 415 | * @param index The index of the attribute to retrieve. 416 | * @return A formatted string representing the attribute value. 417 | */ 418 | private String getAttributeValue(AXmlResourceParser xmlParser, int index) { 419 | 420 | String attributeName = getAttributeName(xmlParser, index).replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""); 421 | // String attributeName = getAttributeName(xmlParser, index); 422 | 423 | int attributeValueType = xmlParser.getAttributeValueType(index); 424 | 425 | int attributeValueData = xmlParser.getAttributeValueData(index); 426 | 427 | switch (attributeValueType) { 428 | case TypedValue.TYPE_STRING /* 3 */: 429 | // String value 430 | 431 | String stringValue = xmlParser.getAttributeValue(index); 432 | // Preserve newlines as \n for XML 433 | return stringValue.replace("\n", "\\n"); 434 | 435 | case TypedValue.TYPE_ATTRIBUTE /* 2 */: 436 | // Resource ID 437 | if (enableId2Name) { 438 | return "?" + extractResourecID(attributeValueData); 439 | } else { 440 | return "?" + formatToHex(attributeValueData); 441 | } 442 | 443 | case TypedValue.TYPE_REFERENCE /* 1 */: 444 | // Reference 445 | if (enableId2Name) { 446 | return "@" + extractResourecID(attributeValueData); 447 | } else { 448 | return "@" + formatToHex(attributeValueData); 449 | } 450 | 451 | case TypedValue.TYPE_FLOAT /* 4 */: 452 | // Float value 453 | return Float.toString(Float.intBitsToFloat(attributeValueData)); 454 | 455 | case TypedValue.TYPE_INT_HEX /* 17 */: 456 | // Hex integer value or flag values 457 | if (enableAttributeConversion) { 458 | String decodedValue = AttributesExtractor.getInstance().decode(attributeName, attributeValueData); 459 | if (decodedValue != null && !decodedValue.isEmpty()) { 460 | return decodedValue; // Return the decoded value if found 461 | } else { 462 | return "0x" + Integer.toHexString(attributeValueData); 463 | } 464 | } else { 465 | return "0x" + Integer.toHexString(attributeValueData); 466 | } 467 | 468 | case TypedValue.TYPE_INT_BOOLEAN /* 18 */: 469 | // Boolean value 470 | return attributeValueData != 0 ? "true" : "false"; 471 | 472 | case TypedValue.TYPE_DIMENSION /* 5 */: 473 | // Dimension value 474 | return formatDimension(attributeValueData); 475 | 476 | case TypedValue.TYPE_FRACTION /* 6 */: 477 | // Fraction value 478 | return formatFraction(attributeValueData); 479 | 480 | default: 481 | // Handle enum or flag values and other cases 482 | if (enableAttributeConversion) { 483 | String decodedValue = AttributesExtractor.getInstance().decode(attributeName, attributeValueData); 484 | if (decodedValue != null) { 485 | return decodedValue; // Return the decoded value if found 486 | } 487 | } 488 | // For unhandled types or cases 489 | String result; 490 | if (attributeValueType >= TypedValue.TYPE_FIRST_COLOR_INT && attributeValueType <= TypedValue.TYPE_LAST_COLOR_INT) { 491 | // Condition 1: attributeValueType is a color type (0x1c-0x1f) 492 | result = String.format("#%08x", attributeValueData); 493 | } else if (attributeValueType >= 0x10 && attributeValueType <= TypedValue.TYPE_LAST_INT) { 494 | // Condition 2: attributeValueType is in the general integer range but not the color range(0x10-0x1f, but not 0x1c-0x1f) 495 | result = String.valueOf(attributeValueData); 496 | } else { 497 | // Condition 3: All other cases, 498 | result = String.format("<0x%X, type 0x%02X>", attributeValueData, attributeValueType); 499 | } 500 | 501 | return result; 502 | } 503 | } 504 | 505 | 506 | 507 | 508 | // Checks if the current XML tag is the end of the previous tag 509 | private boolean isEndOfPrecededXmlTag(AXmlResourceParser xmlParser, AXmlResourceParser.PrecededXmlToken precededXmlToken) { 510 | return precededXmlToken.type == XmlPullParser.START_TAG && 511 | xmlParser.getEventType() == XmlPullParser.END_TAG && 512 | xmlParser.getName().equals(precededXmlToken.name) && 513 | ((precededXmlToken.namespace == null && xmlParser.getPrefix() == null) || 514 | (precededXmlToken.namespace != null && xmlParser.getPrefix() != null && xmlParser.getPrefix().equals(precededXmlToken.namespace))); 515 | } 516 | 517 | // Retrieves the main node namespace prefix if it exists 518 | private String getMainNodeNamespacePrefix(String prefix) { 519 | return (prefix == null || prefix.length() == 0) ? "" : prefix + ":"; 520 | } 521 | 522 | // Retrieves the attribute namespace prefix if it exists 523 | private String getAttrNamespacePrefix(AXmlResourceParser xmlParser, int position, String attributeName) { 524 | String namespace = xmlParser.getAttributePrefix(position); 525 | int attributeNameResource = xmlParser.getAttributeNameResource(position); 526 | 527 | if (attributeName.contains(CUSTOM_ATTRIBUTE_TAG)) { 528 | // Check if auto namespace exists or not 529 | if(isAuto_NameSpaceExists){ 530 | return APP_PREFIX + ":"; 531 | } 532 | return ""; 533 | // check if any unknown attributes are found and it will start from "id" 534 | } else if (isUnknownAttribute(attributeName)) { 535 | return ""; 536 | } else if (attributeName.contains(SYSTEM_ATTRIBUTE_TAG)) { 537 | return ANDROID_PREFIX + ":"; 538 | } else if (namespace.isEmpty()) { 539 | if (xmlParser.isChunkResourceIDs || !isExistAndroidNamespace) { 540 | if (namespaceChecker.isAttributeExist(attributeName)) { 541 | return ""; 542 | } else { 543 | if(attributeNameResource == 0){ 544 | return ""; 545 | } 546 | if(checkIfCustomAttribute(attributeNameResource)){ 547 | return APP_PREFIX + ":"; 548 | } else { 549 | return ANDROID_PREFIX + ":"; 550 | } 551 | } 552 | } 553 | return ""; 554 | } 555 | //check if the is res-auto nameSpaceUri's prefix name is random 556 | if(RandomResAutoPrefix_Name != null && namespace.equals(RandomResAutoPrefix_Name)){ 557 | return "app" + ":"; 558 | } 559 | return namespace + ":"; 560 | } 561 | 562 | /** 563 | * Extracts the attribute name dynamically based on system or custom resources. 564 | * 565 | * @param xmlParser The XML parser instance. 566 | * @param index The index of the attribute to retrieve. 567 | * @return The attribute name as a string. 568 | */ 569 | public String getAttributeName(AXmlResourceParser xmlParser, int index) { 570 | String attributeName = xmlParser.getAttributeName(index); 571 | int attributeNameResource = xmlParser.getAttributeNameResource(index); 572 | 573 | //check if the attributes are encrypted with attribute hex id 574 | if (xmlParser.isChunkResourceIDs || isUnknownAttribute(attributeName)) { 575 | try { 576 | String extractedName = getAttributeNameFromResources(attributeName.replace("id", "")); 577 | return extractedName != null ? extractedName.replaceAll("attr/", "") : getFallbackAttributeName(attributeName); 578 | } catch (Exception e) { 579 | return getFallbackAttributeName(attributeName); 580 | } 581 | } else { 582 | // Again ceck if custom res file is exist or not so that we can even extract more precise attribute data 583 | if(isCustomResourceFileExist){ 584 | try { 585 | String extractedName = getAttributeNameFromCustomRes(Integer.toHexString(attributeNameResource), attributeName); 586 | return extractedName != null ? extractedName.replaceAll("attr/", "") : attributeName; 587 | } catch (Exception e) { 588 | return attributeName; 589 | } 590 | } 591 | return attributeName; 592 | } 593 | } 594 | 595 | 596 | private String getAttributeNameFromCustomRes(String attribute_hexId, String attributeName) throws Exception { 597 | String nameForHexId; 598 | if (this.isCustomResourceFileExist && (nameForHexId = customResourceFile.getNameForHexId(attribute_hexId)) != null) { 599 | if(!isResObfuscationCheckDone){ 600 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(attribute_hexId)); 601 | if(nextIdNameToCheckObfuscation != null){ 602 | if(nameForHexId.equals(nextIdNameToCheckObfuscation)){ 603 | isResObfuscationCheckDone = true; 604 | isResObfuscated = true; 605 | return attributeName; //normal attribute 606 | } else{ 607 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId; 608 | } 609 | } else { 610 | return attributeName; 611 | } 612 | } 613 | 614 | //finally check of res file is obfuscated or not 615 | if(!isResObfuscated){ 616 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId; 617 | } else { 618 | return attributeName; 619 | } 620 | } 621 | return attributeName; 622 | } 623 | 624 | 625 | // Get attribute name from either system resource file or custom resource file 626 | private String getAttributeNameFromResources(String attribute_hexId) throws Exception { 627 | String systemAttribute = systemResourceFile.getNameForHexId("0" + attribute_hexId); 628 | String extractedAttributeName = null; 629 | String nameForHexId; 630 | if (systemAttribute != null) { 631 | extractedAttributeName = SYSTEM_ATTRIBUTE_TAG + systemAttribute; 632 | } 633 | // Process custom resource file if exist and also check if the system resource file don't have target hex id 634 | if (this.isCustomResourceFileExist && extractedAttributeName == null && (nameForHexId = customResourceFile.getNameForHexId(attribute_hexId)) != null) { 635 | //check if res file is obfuscated or not 636 | if(!isResObfuscationCheckDone){ 637 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(attribute_hexId)); 638 | if(nextIdNameToCheckObfuscation != null){ 639 | if(nameForHexId.equals(nextIdNameToCheckObfuscation)){ 640 | isResObfuscationCheckDone = true; 641 | isResObfuscated = true; 642 | return "id" + attribute_hexId; 643 | } else{ 644 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId; 645 | } 646 | } else { 647 | return "id" + attribute_hexId; 648 | } 649 | } 650 | 651 | //finally check of res file is obfuscated or not 652 | if(!isResObfuscated){ 653 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId; 654 | } else { 655 | return "id" + attribute_hexId; 656 | } 657 | 658 | } 659 | 660 | return extractedAttributeName; 661 | } 662 | 663 | 664 | //check the without namespace based specific attributes if matched 665 | private String getFallbackAttributeName(String attributeName) { 666 | if (namespaceChecker.isAttributeExist(attributeName)) { 667 | return attributeName; 668 | } else if (attributeName != null && attributeName.startsWith("id")) { 669 | return attributeName; 670 | } else { 671 | return "id" + attributeName; 672 | } 673 | } 674 | 675 | /** 676 | * Determines if an attribute name matches an unknown or obfuscated pattern. 677 | * 678 | * @param attributeName The name of the attribute to check. 679 | * @return True if the attribute is unknown or obfuscated, false otherwise. 680 | */ 681 | public boolean isUnknownAttribute(String attributeName) { 682 | return attributeName.matches("^id\\d[a-z0-9]*$"); 683 | } 684 | 685 | /** 686 | * Extracts the resource name based on its hexadecimal ID. 687 | * If the resource name cannot be found, the hexadecimal ID is returned. 688 | * 689 | * @param resourceId The resource ID to extract. 690 | * @return The resource name or its hexadecimal representation. 691 | */ 692 | public String extractResourecID(int resourceId) { 693 | String resHexId = formatToHex(resourceId); 694 | String systemId2Name = null; 695 | String customResId2Name = null; 696 | try { 697 | if (enableId2Name) { 698 | // Load system resource file 699 | systemId2Name = systemResourceFile.getNameForHexId(resHexId); 700 | 701 | // If System don't have the id then lets move to custom resource file 702 | if (isCustomResourceFileExist && systemId2Name == null) { 703 | customResId2Name = customResourceFile.getNameForHexId(resHexId); 704 | } 705 | // If id name is extracted from system resource then add "android:" before the attribute name 706 | if (systemId2Name != null) { 707 | return ANDROID_PREFIX + ":" + systemId2Name; 708 | } 709 | // Check if the custom id2name is not null .. and return the entry name without name space 710 | if (customResId2Name != null) { 711 | //check if res file is obfuscated or not 712 | if(!isResObfuscationCheckDone){ 713 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(resHexId)); 714 | if(nextIdNameToCheckObfuscation != null){ 715 | if(customResId2Name.equals(nextIdNameToCheckObfuscation)){ 716 | isResObfuscationCheckDone = true; 717 | isResObfuscated = true; 718 | return resHexId; 719 | } else{ 720 | return customResId2Name; 721 | } 722 | } else { 723 | return resHexId; 724 | } 725 | } 726 | 727 | //finally check of res file is obfuscated or not 728 | if(!isResObfuscated){ 729 | return customResId2Name; 730 | } else { 731 | return resHexId; 732 | } 733 | } 734 | return resHexId; 735 | } else { 736 | return resHexId; 737 | } 738 | } catch (Exception e) { 739 | return resHexId; 740 | } 741 | } 742 | 743 | 744 | //Load manifest permission description 745 | private Map loadPermissionsInfo() throws Exception { 746 | Map map = new HashMap<>(); 747 | InputStream is = AXMLPrinter.class.getResourceAsStream("/assets/permissions_info_en.txt"); 748 | InputStreamReader reader = new InputStreamReader(is); 749 | BufferedReader bufferedReader = new BufferedReader(reader); 750 | String permission = null; 751 | String description = null; 752 | String line; 753 | while ((line = bufferedReader.readLine()) != null) { 754 | line = line.trim(); 755 | // If the line is empty, we can skip it 756 | if (line.isEmpty()) { 757 | continue; 758 | } 759 | // match the permission and description with regex 760 | if (line.matches("^[a-zA-Z0-9._]+$")) { 761 | // If there's an existing permission and description, store it in the map 762 | if (permission != null && description != null) { 763 | map.put(permission, description); 764 | } 765 | // Now the new permission starts 766 | permission = line; 767 | description = null; 768 | } else { 769 | // If the line is a description, append it to the current description 770 | if (description != null) { 771 | description += " " + line; 772 | } else { 773 | description = line; 774 | } 775 | } 776 | } 777 | // Add the last permission entry to the map 778 | if (permission != null && description != null) { 779 | map.put(permission, description); 780 | } 781 | 782 | return map; 783 | } 784 | 785 | /** 786 | * Converts an integer to a hexadecimal string format. 787 | * 788 | * @param value The integer value to convert. 789 | * @return The formatted hexadecimal string. 790 | */ 791 | public String formatToHex(int i) { 792 | return String.format("%08x", Integer.valueOf(i)); 793 | } 794 | 795 | /* This method helped us to search and detect if a specific text is exists or not*/ 796 | private boolean containsSequence(byte[] source, byte[] sequence) { 797 | if (sequence.length == 0 || source.length == 0 || sequence.length > source.length) { 798 | return false; 799 | } 800 | for (int i = 0; i <= source.length - sequence.length; i++) { 801 | boolean found = true; 802 | for (int j = 0; j < sequence.length; j++) { 803 | if (source[i + j] != sequence[j]) { 804 | found = false; 805 | break; 806 | } 807 | } 808 | if (found) { 809 | return true; 810 | } 811 | } 812 | return false; 813 | } 814 | 815 | /* check if any custom attributes are used for xml ui, 816 | * Generally custom attribute dec ids are 10 digits unlike system which have 8 digits 817 | * And also custom atributes dec ids are started with 2 unlike system which generally 1 818 | * This can help us wheather it should use app: namespace or not 819 | */ 820 | public boolean checkIfCustomAttribute(int number) { 821 | String numberStr = String.valueOf(number); 822 | int digitCount = numberStr.length(); 823 | char firstDigitChar = numberStr.charAt(0); 824 | int firstDigit = Character.getNumericValue(firstDigitChar); 825 | if (digitCount == 10 || firstDigit == 2) { 826 | return true; 827 | } 828 | return false; 829 | } 830 | /** 831 | * Formats a dimension value (e.g., pixels, dp, sp). 832 | * 833 | * @param attributeValueData The raw attribute value data (as an int). 834 | * @return A string representation of the dimension, including the unit. 835 | */ 836 | private String formatDimension(int attributeValueData) { 837 | // Convert the attribute data to a float value. 838 | float floatValue = TypedValue.complexToFloat(attributeValueData); 839 | 840 | // Extract the unit from the attribute value data using a bitwise AND to get the unit index (0-15) 841 | String unit = DIMENSION_UNIT_STRS[attributeValueData & 15]; 842 | 843 | // Format the float value and append the unit to create the final output string. 844 | return formatFloat(floatValue) + unit; 845 | } 846 | 847 | 848 | /** 849 | * Formats a fraction value (e.g., percentage). 850 | * 851 | * @param attributeValueData The raw attribute value data (as an int). 852 | * @return A string representation of the fraction, including the unit. 853 | */ 854 | private String formatFraction(int attributeValueData) { 855 | // Convert the attribute data to a float value, and then converts it to percentage value by multiplying with 100 856 | float floatValue = TypedValue.complexToFloat(attributeValueData) * 100.0f; 857 | 858 | // Extract the unit from the attribute value data using a bitwise AND to get the unit index (0-15). 859 | String unit = FRACTION_UNIT_STRS[attributeValueData & 15]; 860 | 861 | // Format the float value and append the unit to create the final output string 862 | return formatFloat(floatValue) + unit; 863 | } 864 | 865 | /** 866 | * Formats a float value to a string representation. 867 | * 868 | * @param floatValue The float value to format. 869 | * @return A string representation of the float, either as an integer if possible or with one decimal place. 870 | */ 871 | private String formatFloat(float floatValue) { 872 | // Check if the float value is equivalent to an integer value 873 | if (floatValue == (int) floatValue) { 874 | // If it's an integer, return its String value without decimal places 875 | return String.valueOf((int) floatValue); 876 | } else { 877 | // If it has a decimal part, format it to one decimal place 878 | return String.format(Locale.US, "%.1f", floatValue); 879 | } 880 | } 881 | 882 | /** 883 | * Extracts the text after the last slash in a given string. 884 | * 885 | * @param text The input string to process. 886 | * @return The text after the last slash, or the original text if no slash is found. 887 | */ 888 | private String textAfterSlash(String text) { 889 | // Define the regex pattern to match any characters followed by a slash, and capture the text after the last slash 890 | Pattern pattern = Pattern.compile(".*/(.*)"); 891 | // Create a Matcher object to perform the matching operation 892 | Matcher matcher = pattern.matcher(text); 893 | 894 | // Check if the regex pattern matches the given input text 895 | if (matcher.find()) { 896 | // If a match is found, extract the captured text which is the text after the last slash 897 | String textAfterSlash = matcher.group(1); 898 | return textAfterSlash; 899 | } 900 | // If no match is found return the original text 901 | return text; 902 | } 903 | /** 904 | * Generates the next sequential hexadecimal ID. 905 | * 906 | * @param inputHex The input hexadecimal string (without "0x" prefix). 907 | * @return The next sequential hexadecimal ID as a string. Returns inputHex if parsing fails. 908 | */ 909 | public String generateNextHexId(String inputHexID) { 910 | try { 911 | // Parse the input hex string as a long (base 16) 912 | long hexValue = Long.parseLong(inputHexID, 16); 913 | 914 | // Increment the long value 915 | hexValue++; 916 | 917 | // Format the incremented value back to a hex string (8 digits) 918 | return formatToHex((int)hexValue); 919 | } 920 | catch(NumberFormatException e){ 921 | // If there's a NumberFormatException (invalid input), return original value. 922 | return inputHexID; 923 | } 924 | } 925 | 926 | } 927 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axml/AttributesExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS NTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axml; 37 | 38 | import android.util.Log; 39 | import java.io.InputStream; 40 | import java.util.ArrayList; 41 | import java.util.HashMap; 42 | import java.util.LinkedHashMap; 43 | import java.util.List; 44 | import java.util.Map; 45 | 46 | import javax.xml.parsers.DocumentBuilder; 47 | import java.util.Collections; 48 | import java.util.Comparator; 49 | import javax.xml.parsers.DocumentBuilderFactory; 50 | 51 | import org.w3c.dom.Document; 52 | import org.w3c.dom.NamedNodeMap; 53 | import org.w3c.dom.Node; 54 | import org.w3c.dom.NodeList; 55 | 56 | /*Idea from Jadx(By https://github.com/skylot/jadx)*/ 57 | public class AttributesExtractor { 58 | 59 | private static final String ATTR_XML = "/assets/attrs.xml"; 60 | private static final String MANIFEST_ATTR_XML = "/assets/attrs_manifest.xml"; 61 | 62 | private enum MAttrType { 63 | ENUM, FLAG 64 | } 65 | 66 | private static class MAttr { 67 | private final MAttrType type; 68 | private final Map values = new LinkedHashMap<>(); 69 | 70 | public MAttr(MAttrType type) { 71 | this.type = type; 72 | } 73 | 74 | public MAttrType getType() { 75 | return type; 76 | } 77 | 78 | public Map getValues() { 79 | return values; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "[" + type + ", " + values + ']'; 85 | } 86 | } 87 | 88 | private final Map attrMap = new HashMap<>(); 89 | 90 | private final Map appAttrMap = new HashMap<>(); 91 | 92 | private static AttributesExtractor instance; 93 | 94 | public static AttributesExtractor getInstance() { 95 | if (instance == null) { 96 | try { 97 | instance = new AttributesExtractor(); 98 | } catch (Exception e) { 99 | 100 | } 101 | } 102 | return instance; 103 | } 104 | 105 | private AttributesExtractor() { 106 | parseAll(); 107 | } 108 | 109 | private void parseAll() { 110 | parse(loadXML(ATTR_XML)); 111 | parse(loadXML(MANIFEST_ATTR_XML)); 112 | 113 | } 114 | 115 | private Document loadXML(String xml) { 116 | Document doc; 117 | try { 118 | InputStream xmlStream = AttributesExtractor.class.getResourceAsStream(xml); 119 | if (xmlStream == null) { 120 | throw new RuntimeException(xml + " not found in classpath"); 121 | } 122 | DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 123 | doc = dBuilder.parse(xmlStream); 124 | } catch (Exception e) { 125 | throw new RuntimeException("Xml load error, file: " + xml, e); 126 | } 127 | return doc; 128 | } 129 | 130 | private void parse(Document doc) { 131 | NodeList nodeList = doc.getChildNodes(); 132 | for (int count = 0; count < nodeList.getLength(); count++) { 133 | Node node = nodeList.item(count); 134 | if (node.getNodeType() == Node.ELEMENT_NODE 135 | && node.hasChildNodes()) { 136 | parseAttrList(node.getChildNodes()); 137 | } 138 | } 139 | } 140 | 141 | private void parseAttrList(NodeList nodeList) { 142 | for (int count = 0; count < nodeList.getLength(); count++) { 143 | Node tempNode = nodeList.item(count); 144 | if (tempNode.getNodeType() == Node.ELEMENT_NODE 145 | && tempNode.hasAttributes() 146 | && tempNode.hasChildNodes()) { 147 | String name = null; 148 | NamedNodeMap nodeMap = tempNode.getAttributes(); 149 | for (int i = 0; i < nodeMap.getLength(); i++) { 150 | Node node = nodeMap.item(i); 151 | if (node.getNodeName().equals("name")) { 152 | name = node.getNodeValue(); 153 | break; 154 | } 155 | } 156 | if (name != null && tempNode.getNodeName().equals("attr")) { 157 | parseValues(name, tempNode.getChildNodes()); 158 | } else { 159 | parseAttrList(tempNode.getChildNodes()); 160 | } 161 | } 162 | } 163 | } 164 | 165 | private void parseValues(String name, NodeList nodeList) { 166 | MAttr attr = null; 167 | for (int count = 0; count < nodeList.getLength(); count++) { 168 | Node tempNode = nodeList.item(count); 169 | if (tempNode.getNodeType() == Node.ELEMENT_NODE && tempNode.hasAttributes()) { 170 | if (attr == null) { 171 | if (tempNode.getNodeName().equals("enum")) { 172 | attr = new MAttr(MAttrType.ENUM); 173 | } else if (tempNode.getNodeName().equals("flag")) { 174 | attr = new MAttr(MAttrType.FLAG); 175 | } 176 | if (attr == null) { 177 | return; 178 | } 179 | System.out.println("Parsed attribute: " + name + " with type: " + attr.getType()); 180 | attrMap.put(name, attr); 181 | } 182 | NamedNodeMap attributes = tempNode.getAttributes(); 183 | Node nameNode = attributes.getNamedItem("name"); 184 | if (nameNode != null) { 185 | Node valueNode = attributes.getNamedItem("value"); 186 | if (valueNode != null) { 187 | try { 188 | long key; 189 | String nodeValue = valueNode.getNodeValue(); 190 | if (nodeValue.startsWith("0x")) { 191 | nodeValue = nodeValue.substring(2); 192 | key = Long.parseLong(nodeValue, 16); 193 | } else { 194 | key = Long.parseLong(nodeValue); 195 | } 196 | attr.getValues().put(key, nameNode.getNodeValue()); 197 | System.out.println("Added value: " + key + " -> " + nameNode.getNodeValue()); 198 | } catch (NumberFormatException e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | public String decode(String attrName, long value) { 208 | MAttr attr = attrMap.get(attrName); 209 | if (attr == null) { 210 | attr = appAttrMap.get(attrName); 211 | if (attr == null) { 212 | return null; 213 | } 214 | } 215 | Log.d(attrName,"" + value); 216 | System.out.println(attrName+ " : " + value); 217 | if (attr.getType() == MAttrType.ENUM) { 218 | return attr.getValues().get(value); 219 | } else if (attr.getType() == MAttrType.FLAG) { 220 | List flagList = new ArrayList<>(); 221 | List attrKeys = new ArrayList<>(attr.getValues().keySet()); 222 | Collections.sort(attrKeys, new Comparator() { 223 | @Override 224 | public int compare(Long a, Long b) { 225 | return Long.compare(b, a); // for descending order 226 | } 227 | }); 228 | for (Long key : attrKeys) { 229 | String attrValue = attr.getValues().get(key); 230 | if (value == key) { 231 | flagList.add(attrValue); 232 | break; 233 | } else if ((key != 0) && ((value & key) == key)) { 234 | flagList.add(attrValue); 235 | value ^= key; 236 | } 237 | } 238 | 239 | StringBuilder sb = new StringBuilder(); 240 | for (int i = 0; i < flagList.size(); i++) { 241 | if (i > 0) { 242 | sb.append("|"); 243 | } 244 | sb.append(flagList.get(i)); 245 | } 246 | return sb.toString(); 247 | } 248 | return null; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axml/NamespaceChecker.java: -------------------------------------------------------------------------------- 1 | package mt.modder.hub.axml; 2 | 3 | import android.content.Context; 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class NamespaceChecker { 12 | 13 | private Set attributes; 14 | 15 | public NamespaceChecker() { 16 | attributes = new HashSet<>(); 17 | loadAttributesFromFile(); 18 | } 19 | 20 | private void loadAttributesFromFile() { 21 | try { 22 | // Open the file from the assets folder 23 | InputStream inputStream = NamespaceChecker.class.getResourceAsStream("/assets/no_nameSpace_attrs.txt"); 24 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 25 | String line; 26 | // Read each line and add it to the set 27 | while ((line = reader.readLine()) != null) { 28 | attributes.add(line.trim()); 29 | } 30 | reader.close(); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | public String getNamespace(String attributeName) { 37 | if (attributes.contains(attributeName)) { 38 | return ""; // Empty namespace for specified attributes 39 | } else { 40 | return "android"; // Default namespace for others 41 | } 42 | } 43 | 44 | public boolean isAttributeExist(String str){ 45 | if(attributes.contains(str)){ 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/AXmlResourceParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools; 37 | 38 | import java.io.IOException; 39 | import java.io.InputStream; 40 | import java.io.Reader; 41 | import mt.modder.hub.axmlTools.utils.TypedValue; 42 | import org.xmlpull.v1.XmlPullParser; 43 | import org.xmlpull.v1.XmlPullParserException; 44 | import mt.modder.hub.axmlTools.utils.*; 45 | import java.nio.*; 46 | 47 | public class AXmlResourceParser implements XmlResourceParser, AutoCloseable { 48 | 49 | // constant values representing different xml chunk types 50 | private static final int CHUNK_AXML_FILE = 0x80003; 51 | private static final int CHUNK_RESOURCEIDS = 0x80180; 52 | private static final int CHUNK_XML_END_NAMESPACE = 0x100101; 53 | private static final int CHUNK_XML_END_TAG = 0x100103; 54 | private static final int CHUNK_XML_START_TAG = 0x100102; 55 | private static final String E_NOT_SUPPORTED = "Method is not supported."; 56 | public static final int XML_RESOURCE_MAP = 0x0180; 57 | 58 | private static final int XML_START_TAG_CHUNK = 1048832; 59 | private static final int XML_TEXT_CHUNK = 1048836; 60 | private static final int XML_NAMESPACE_PUSH_CHUNK = 1048832; 61 | 62 | public static final String NS_ANDROID = "http://schemas.android.com/apk/res/android"; 63 | 64 | // vriables for storing attributes and state information 65 | private int[] mAttributes; 66 | private int mClassAttribute; 67 | private boolean mDecreaseDepth; 68 | private int eventType; 69 | private int mIdAttribute; 70 | private int mLineNumber; 71 | private int mName; 72 | private int mNamespaceUri; 73 | private IntReader mReader; 74 | private int[] mResourceIDs; 75 | private StringBlock stringBlock; 76 | private int mStyleAttribute; 77 | private PrecededXmlToken precededXmlToken = null; 78 | private boolean mOperational = false; 79 | 80 | private String[] resourceMap; 81 | public static boolean isChunkResourceIDs = false; 82 | 83 | 84 | private NamespaceStack mNamespaces = new NamespaceStack(); 85 | 86 | // class to handle the namespace stack 87 | public static final class NamespaceStack { 88 | private int mCount; 89 | private int[] mData = new int[32]; 90 | private int mDataLength; 91 | private int mDepth; 92 | 93 | 94 | // ensures the internal data array has enough capacity 95 | private void ensureDataCapacity(int capacity) { 96 | int available = (mData.length - mDataLength); 97 | if (available > capacity) { 98 | return; 99 | } 100 | int newLength = (mData.length + available) * 2; 101 | int[] newData = new int[newLength]; 102 | System.arraycopy(mData, 0, newData, 0, mDataLength); 103 | mData = newData; 104 | } 105 | 106 | // finds a prefix or URI in the stack 107 | private final int find(int prefixOrUri, boolean prefix) { 108 | if (mDataLength == 0) { 109 | return -1; 110 | } 111 | int offset = mDataLength - 1; 112 | for (int i = mDepth; i != 0; --i) { 113 | int count = mData[offset]; 114 | offset -= 2; 115 | for (; count != 0; --count) { 116 | if (prefix) { 117 | if (mData[offset] == prefixOrUri) { 118 | return mData[offset + 1]; 119 | } 120 | } else { 121 | if (mData[offset + 1] == prefixOrUri) { 122 | return mData[offset]; 123 | } 124 | } 125 | offset -= 2; 126 | } 127 | } 128 | return -1; 129 | } 130 | 131 | //retrive a prefix or URI at a specific index 132 | 133 | private final int get(int index, boolean prefix) { 134 | if (mDataLength == 0 || index < 0) { 135 | return -1; 136 | } 137 | int offset = 0; 138 | for (int i = mDepth; i != 0; --i) { 139 | int count = mData[offset]; 140 | if (index >= count) { 141 | index -= count; 142 | offset += (2 + count * 2); 143 | continue; 144 | } 145 | offset += (1 + index * 2); 146 | if (!prefix) { 147 | offset += 1; 148 | } 149 | return mData[offset]; 150 | } 151 | return -1; 152 | } 153 | 154 | // decreases the depth of the namespace stack 155 | public final void decreaseDepth() { 156 | if (mDataLength == 0) { 157 | return; 158 | } 159 | int offset = mDataLength - 1; 160 | int count = mData[offset]; 161 | if ((offset - 1 - count * 2) == 0) { 162 | return; 163 | } 164 | mDataLength -= 2 + count * 2; 165 | mCount -= count; 166 | mDepth -= 1; 167 | } 168 | 169 | //finds a prefix in the stack 170 | public int findPrefix(int prefix) { 171 | return find(prefix, false); 172 | } 173 | 174 | // finds a URI in a stack 175 | public int findUri(int uri) { 176 | return find(uri, true); 177 | } 178 | 179 | // gets the accumulated count of namespace at a given depth 180 | public final int getAccumulatedCount(int depth) { 181 | if (mDataLength == 0 || depth < 0) { 182 | return 0; 183 | } 184 | if (depth > mDepth) { 185 | depth = mDepth; 186 | } 187 | int accumulatedCount = 0; 188 | int offset = 0; 189 | for (; depth != 0; --depth) { 190 | int count = mData[offset]; 191 | accumulatedCount += count; 192 | offset += (2 + count * 2); 193 | } 194 | return accumulatedCount; 195 | } 196 | 197 | // gets the current count of namespaces 198 | public final int getCurrentCount() { 199 | if (mDataLength == 0) { 200 | return 0; 201 | } 202 | int offset = mDataLength - 1; 203 | return mData[offset]; 204 | } 205 | 206 | // gets the current depth of the stack 207 | public int getDepth() { 208 | return this.mDepth; 209 | } 210 | 211 | // gets the prefix at a specific index 212 | public int getPrefix(int index) { 213 | return get(index, true); 214 | } 215 | 216 | // gets the total counts of namespace 217 | public final int getTotalCount() { 218 | return this.mCount; 219 | } 220 | 221 | // gets the URI at a specfic index 222 | public int getUri(int index) { 223 | return get(index, false); 224 | } 225 | 226 | // increases the depth of the namespace stack 227 | public final void increaseDepth() { 228 | ensureDataCapacity(2); 229 | int offset = mDataLength; 230 | mData[offset] = 0; 231 | mData[offset + 1] = 0; 232 | mDataLength += 2; 233 | mDepth += 1; 234 | } 235 | 236 | // Pops the namespace entry from the stack 237 | public final boolean pop() { 238 | if (mDataLength == 0) { 239 | return false; 240 | } 241 | int offset = mDataLength - 1; 242 | int count = mData[offset]; 243 | if (count == 0) { 244 | return false; 245 | } 246 | count -= 1; 247 | offset -= 2; 248 | mData[offset] = count; 249 | offset -= (1 + count * 2); 250 | mData[offset] = count; 251 | mDataLength -= 2; 252 | mCount -= 1; 253 | return true; 254 | } 255 | 256 | // Pops the specific prefix and URI from the stack 257 | public final boolean pop(int prefix, int uri) { 258 | if (mDataLength == 0) { 259 | return false; 260 | } 261 | int offset = mDataLength - 1; 262 | int count = mData[offset]; 263 | for (int i = 0, o = offset - 2; i != count; ++i, o -= 2) { 264 | if (mData[o] != prefix || mData[o + 1] != uri) { 265 | continue; 266 | } 267 | count -= 1; 268 | if (i == 0) { 269 | mData[o] = count; 270 | o -= (1 + count * 2); 271 | mData[o] = count; 272 | } else { 273 | mData[offset] = count; 274 | offset -= (1 + 2 + count * 2); 275 | mData[offset] = count; 276 | System.arraycopy( 277 | mData, o + 2, 278 | mData, o, 279 | mDataLength - o); 280 | } 281 | mDataLength -= 2; 282 | mCount -= 1; 283 | return true; 284 | } 285 | return false; 286 | } 287 | 288 | // pushes a prefix and URI onto the stack 289 | public final void push(int prefix, int uri) { 290 | if (mDepth == 0) { 291 | increaseDepth(); 292 | } 293 | ensureDataCapacity(2); 294 | int offset = mDataLength - 1; 295 | int count = mData[offset]; 296 | mData[offset - 1 - count * 2] = count + 1; 297 | mData[offset] = prefix; 298 | mData[offset + 1] = uri; 299 | mData[offset + 2] = count + 1; 300 | mDataLength += 2; 301 | mCount += 1; 302 | } 303 | 304 | // resets the nemespace stack 305 | public final void reset() { 306 | this.mDataLength = 0; 307 | this.mCount = 0; 308 | this.mDepth = 0; 309 | } 310 | } 311 | 312 | // Class to store information about previous xml token 313 | public static final class PrecededXmlToken { 314 | public String name; 315 | public String namespace; 316 | public int type; 317 | 318 | public PrecededXmlToken(String name, String namespace, int type) { 319 | this.name = name; 320 | this.namespace = namespace; 321 | this.type = type; 322 | } 323 | } 324 | 325 | // Constructor to intialize the parser 326 | public AXmlResourceParser() { 327 | resetEventInfo(); 328 | 329 | } 330 | 331 | 332 | 333 | 334 | // reads the next xml token and updatea the parser state 335 | private void doNext() throws IOException { 336 | int readInt = 0; 337 | if (this.stringBlock == null) { 338 | ChunkUtil.readCheckType(this.mReader, CHUNK_AXML_FILE); 339 | this.mReader.skipInt(); 340 | this.stringBlock = StringBlock.read(this.mReader); 341 | this.mNamespaces.increaseDepth(); 342 | this.mOperational = true; 343 | } 344 | if (this.eventType == XmlPullParser.END_DOCUMENT) { 345 | return; 346 | } 347 | int previousEvent = this.eventType; 348 | resetEventInfo(); 349 | while (true) { 350 | if (this.mDecreaseDepth) { 351 | this.mDecreaseDepth = false; 352 | this.mNamespaces.decreaseDepth(); 353 | } 354 | if (previousEvent == XmlPullParser.END_TAG && this.mNamespaces.getDepth() == 1 && this.mNamespaces.getCurrentCount() == 0) { 355 | this.eventType = XmlPullParser.END_DOCUMENT; 356 | return; 357 | } 358 | int chunkType = previousEvent == XmlPullParser.START_DOCUMENT ? CHUNK_XML_START_TAG : this.mReader.readInt(); 359 | if (chunkType == CHUNK_RESOURCEIDS) { 360 | readInt = this.mReader.readInt(); 361 | if (readInt < 8 || readInt % 4 != 0) { 362 | break; 363 | } 364 | this.mResourceIDs = this.mReader.readIntArray((readInt / 4) - 2); 365 | resourceMap = new String[mResourceIDs.length]; 366 | } else if (chunkType < XML_START_TAG_CHUNK || chunkType > XML_TEXT_CHUNK) { 367 | break; 368 | } else if (chunkType == CHUNK_XML_START_TAG && previousEvent == -1) { 369 | this.eventType = XmlPullParser.START_DOCUMENT; 370 | return; 371 | } else { 372 | this.mReader.skipInt(); 373 | int lineNumber = this.mReader.readInt(); 374 | this.mReader.skipInt(); 375 | if (chunkType != XML_START_TAG_CHUNK && chunkType != CHUNK_XML_END_NAMESPACE) { 376 | this.mLineNumber = lineNumber; 377 | if (chunkType == CHUNK_XML_START_TAG) { 378 | this.mNamespaceUri = this.mReader.readInt(); 379 | this.mName = this.mReader.readInt(); 380 | this.mReader.skipInt(); 381 | int attributeCount = this.mReader.readInt(); 382 | this.mIdAttribute = (attributeCount >>> 16) - 1; 383 | int classAttr = this.mReader.readInt(); 384 | this.mClassAttribute = classAttr; 385 | this.mStyleAttribute = (classAttr >>> 16) - 1; 386 | this.mClassAttribute = (65535 & classAttr) - 1; 387 | this.mAttributes = this.mReader.readIntArray((attributeCount & 65535) * 5); 388 | int i = 3; 389 | while (true) { 390 | int[] attributes = this.mAttributes; 391 | if (i >= attributes.length) { 392 | this.mNamespaces.increaseDepth(); 393 | this.eventType = 2; 394 | return; 395 | } 396 | attributes[i] = attributes[i] >>> 24; 397 | i += 5; 398 | } 399 | } else if (chunkType == CHUNK_XML_END_TAG) { 400 | this.mNamespaceUri = this.mReader.readInt(); 401 | this.mName = this.mReader.readInt(); 402 | this.eventType = 3; 403 | this.mDecreaseDepth = true; 404 | return; 405 | } else if (chunkType == XML_TEXT_CHUNK) { 406 | this.mName = this.mReader.readInt(); 407 | this.mReader.skipInt(); 408 | this.mReader.skipInt(); 409 | this.eventType = 4; 410 | return; 411 | } 412 | } else if (chunkType == XML_NAMESPACE_PUSH_CHUNK) { 413 | this.mNamespaces.push(this.mReader.readInt(), this.mReader.readInt()); 414 | } else { 415 | this.mReader.skipInt(); 416 | this.mReader.skipInt(); 417 | this.mNamespaces.pop(); 418 | } 419 | } 420 | } 421 | throw new IOException("Invalid resource ids size (" + readInt + ")."); 422 | } 423 | 424 | /** 425 | * Finds the attribute index for a given namespace and attribute name. 426 | */ 427 | 428 | private final int findAttribute(String namespace, String attributeName) { 429 | if (stringBlock == null || attributeName == null) { 430 | return -1; 431 | } 432 | 433 | int attributeIndex = stringBlock.find(attributeName); 434 | if (attributeIndex == -1) { 435 | return -1; 436 | } 437 | 438 | int namespaceIndex = namespace != null ? stringBlock.find(namespace) : -1; 439 | 440 | for (int i = 0; i < mAttributes.length; i += 5) { 441 | if (attributeIndex == mAttributes[i + 1] && 442 | (namespaceIndex == -1 || namespaceIndex == mAttributes[i])) { 443 | return i / 5; 444 | } 445 | } 446 | 447 | return -1; 448 | } 449 | 450 | 451 | 452 | /** 453 | * Gets the attribute offset for a given index. 454 | */ 455 | 456 | private int getAttributeOffset(int index) { 457 | if (this.eventType == XmlPullParser.START_TAG) { 458 | int offset = index * 5; 459 | if (offset < this.mAttributes.length) { 460 | return offset; 461 | } 462 | throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ")."); 463 | } 464 | throw new IndexOutOfBoundsException("Current event is not START_TAG."); 465 | } 466 | 467 | 468 | /** 469 | * Resets the event information. 470 | */ 471 | 472 | private final void resetEventInfo() { 473 | this.eventType = -1; 474 | this.mLineNumber = -1; 475 | this.mName = -1; 476 | this.mNamespaceUri = -1; 477 | this.mAttributes = null; 478 | this.mIdAttribute = -1; 479 | this.mClassAttribute = -1; 480 | this.mStyleAttribute = -1; 481 | } 482 | 483 | @Override 484 | public void close() { 485 | if (this.mOperational) { 486 | this.mOperational = false; 487 | this.mReader.close(); 488 | this.mReader = null; 489 | this.stringBlock = null; 490 | this.mResourceIDs = null; 491 | this.mNamespaces.reset(); 492 | resetEventInfo(); 493 | } 494 | } 495 | 496 | @Override 497 | public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { 498 | throw new XmlPullParserException("Entity replacement text not supported."); 499 | } 500 | 501 | @Override 502 | public boolean getAttributeBooleanValue(int index, boolean defaultValue) { 503 | return getAttributeIntValue(index, defaultValue ? 1 : 0) != 0; 504 | } 505 | 506 | @Override 507 | public boolean getAttributeBooleanValue(String namespace, String attributeName, boolean defaultValue) { 508 | int attributeIndex = findAttribute(namespace, attributeName); 509 | return attributeIndex == -1 ? defaultValue : getAttributeBooleanValue(attributeIndex, defaultValue); 510 | } 511 | 512 | @Override 513 | public int getAttributeCount() { 514 | if (this.eventType != XmlPullParser.START_TAG) { 515 | return -1; 516 | } 517 | return this.mAttributes.length / 5; 518 | } 519 | 520 | 521 | @Override 522 | public float getAttributeFloatValue(int index, float defaultValue) { 523 | int attributeOffset = getAttributeOffset(index); 524 | int[] attributeArray = this.mAttributes; 525 | return attributeArray[attributeOffset + 3] == TypedValue.TYPE_FLOAT ? Float.intBitsToFloat(attributeArray[attributeOffset + 4]) : defaultValue; 526 | } 527 | 528 | @Override 529 | public float getAttributeFloatValue(String namespace, String attributeName, float defaultValue) { 530 | int attributeIndex = findAttribute(namespace, attributeName); 531 | return attributeIndex == -1 ? defaultValue : getAttributeFloatValue(attributeIndex, defaultValue); 532 | } 533 | 534 | @Override 535 | public int getAttributeIntValue(int index, int defaultValue) { 536 | int attributeOffset = getAttributeOffset(index); 537 | int[] attributeArray = this.mAttributes; 538 | int attributeType = attributeArray[attributeOffset + 3]; 539 | return (attributeType < TypedValue.TYPE_FIRST_INT || attributeType > TypedValue.TYPE_LAST_INT) ? defaultValue : attributeArray[attributeOffset + 4]; 540 | } 541 | 542 | @Override 543 | public int getAttributeIntValue(String namespace, String attributeName, int defaultValue) { 544 | int attributeIndex = findAttribute(namespace, attributeName); 545 | return attributeIndex == -1 ? defaultValue : getAttributeIntValue(attributeIndex, defaultValue); 546 | } 547 | 548 | @Override 549 | public int getAttributeListValue(int index, String[] options, int defaultValue) { 550 | int attributeOffset = getAttributeOffset(index); 551 | int[] attributeArray = this.mAttributes; 552 | int type = attributeArray[attributeOffset + 3]; 553 | int value = attributeArray[attributeOffset + 4]; 554 | 555 | if (type != TypedValue.TYPE_STRING) { 556 | return defaultValue; 557 | } 558 | 559 | String attributeValue = this.stringBlock.getString(value); 560 | if (options != null) { 561 | for (int i = 0; i < options.length; i++) { 562 | if (options[i].equals(attributeValue)) { 563 | return i; 564 | } 565 | } 566 | } 567 | return defaultValue; 568 | } 569 | 570 | /////////////////////////////////// 571 | 572 | @Override 573 | public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) { 574 | int attributeIndex = findAttribute(namespace, attribute); 575 | return attributeIndex == -1 ? defaultValue : getAttributeListValue(attributeIndex, options, defaultValue); 576 | } 577 | 578 | 579 | @Override 580 | public String getAttributeName(int index) { 581 | int nameIndex = this.mAttributes[getAttributeOffset(index) + 1]; 582 | String attrName = stringBlock.getString(nameIndex); 583 | if(!attrName.isEmpty()){ 584 | isChunkResourceIDs = false; 585 | return attrName; 586 | }else{ 587 | isChunkResourceIDs = true; 588 | return Integer.toHexString(mResourceIDs[nameIndex]); 589 | } 590 | 591 | } 592 | 593 | @Override 594 | public String getAttributeNamespace(int index) { 595 | int namespaceIndex = this.mAttributes[getAttributeOffset(index)]; 596 | return namespaceIndex == -1 ? "" : this.stringBlock.getString(namespaceIndex); 597 | } 598 | 599 | @Override 600 | public int getAttributeNameResource(int index) { 601 | int resourceNameIndex = this.mAttributes[getAttributeOffset(index) + 1]; 602 | int[] resourceArray = this.mResourceIDs; 603 | if (resourceArray == null || resourceNameIndex < 0 || resourceNameIndex >= resourceArray.length) { 604 | return 0; 605 | } 606 | return resourceArray[resourceNameIndex]; 607 | } 608 | 609 | 610 | 611 | @Override 612 | public String getAttributePrefix(int index) { 613 | int findPrefix = this.mNamespaces.findPrefix(this.mAttributes[getAttributeOffset(index)]); 614 | String prefix = (findPrefix == -1) ? "" : this.stringBlock.getString(findPrefix); 615 | switch (prefix) { 616 | case "axml_auto_00": 617 | return "android"; 618 | default: 619 | return prefix; 620 | } 621 | } 622 | 623 | @Override 624 | public int getAttributeResourceValue(int index, int defaultValue) { 625 | int attributeOffset = getAttributeOffset(index); 626 | int[] resourceArray = this.mAttributes; 627 | return resourceArray[attributeOffset + 3] == 1 ? resourceArray[attributeOffset + 4] : defaultValue; 628 | } 629 | 630 | @Override 631 | public int getAttributeResourceValue(String namespace, String attributeName, int defaultValue) { 632 | int findAttribute = findAttribute(namespace, attributeName); 633 | return findAttribute == -1 ? defaultValue : getAttributeResourceValue(findAttribute, defaultValue); 634 | } 635 | 636 | @Override 637 | public String getAttributeType(int index) { 638 | return "CDATA"; 639 | } 640 | 641 | @Override 642 | public int getAttributeUnsignedIntValue(int index, int defaultValue) { 643 | return getAttributeIntValue(index, defaultValue); 644 | } 645 | 646 | @Override 647 | public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { 648 | int findAttribute = findAttribute(namespace, attribute); 649 | return findAttribute == -1 ? defaultValue : getAttributeUnsignedIntValue(findAttribute, defaultValue); 650 | } 651 | 652 | @Override 653 | public String getAttributeValue(int index) { 654 | int attributeOffset = getAttributeOffset(index); 655 | int[] attributeArray = this.mAttributes; 656 | if (attributeArray[attributeOffset + 3] == 3) { 657 | return this.stringBlock.getString(attributeArray[attributeOffset + 2]); 658 | } 659 | return ""; 660 | } 661 | 662 | @Override 663 | public String getAttributeValue(String namespace, String attribute) { 664 | int findAttribute = findAttribute(namespace, attribute); 665 | if (findAttribute == -1) { 666 | return null; 667 | } 668 | return getAttributeValue(findAttribute); 669 | } 670 | 671 | @Override 672 | public int getAttributeValueData(int index) { 673 | return this.mAttributes[getAttributeOffset(index) + 4]; 674 | } 675 | 676 | @Override 677 | public int getAttributeValueType(int index) { 678 | return this.mAttributes[getAttributeOffset(index) + 3]; 679 | } 680 | 681 | @Override 682 | public String getClassAttribute() { 683 | int i = this.mClassAttribute; 684 | if (i == -1) { 685 | return null; 686 | } 687 | return this.stringBlock.getString(this.mAttributes[getAttributeOffset(i) + 2]); 688 | } 689 | 690 | @Override 691 | public int getColumnNumber() { 692 | return -1; 693 | } 694 | 695 | @Override 696 | public int getDepth() { 697 | return this.mNamespaces.getDepth() - 1; 698 | } 699 | 700 | @Override 701 | public int getEventType() { 702 | return this.eventType; 703 | } 704 | 705 | @Override 706 | public boolean getFeature(String value) { 707 | return false; 708 | } 709 | 710 | @Override 711 | public String getIdAttribute() { 712 | int i = this.mIdAttribute; 713 | if (i == -1) { 714 | return null; 715 | } 716 | return this.stringBlock.getString(this.mAttributes[getAttributeOffset(i) + 2]); 717 | } 718 | 719 | @Override 720 | public int getIdAttributeResourceValue(int index) { 721 | int resourceValueIndex = this.mIdAttribute; 722 | if (resourceValueIndex == -1) { 723 | return index; 724 | } 725 | int attributeOffset = getAttributeOffset(resourceValueIndex); 726 | int[] attributeArray = this.mAttributes; 727 | return attributeArray[attributeOffset + 3] != 1 ? index : attributeArray[attributeOffset + 4]; 728 | } 729 | 730 | @Override 731 | public String getInputEncoding() { 732 | return null; 733 | } 734 | 735 | @Override 736 | public int getLineNumber() { 737 | return this.mLineNumber; 738 | } 739 | 740 | @Override 741 | public String getName() { 742 | int nameIndex = this.mName; 743 | if (nameIndex != -1) { 744 | int mEvent = this.eventType; 745 | if (mEvent == 2 || mEvent == 3) { 746 | return this.stringBlock.getString(nameIndex); 747 | } 748 | return null; 749 | } 750 | return null; 751 | } 752 | 753 | @Override 754 | public String getNamespace() { 755 | return this.stringBlock.getString(this.mNamespaceUri); 756 | } 757 | 758 | @Override 759 | public String getNamespace(String value) { 760 | throw new RuntimeException(E_NOT_SUPPORTED); 761 | } 762 | 763 | @Override 764 | public int getNamespaceCount(int index) { 765 | return this.mNamespaces.getAccumulatedCount(index); 766 | } 767 | 768 | @Override 769 | public String getNamespacePrefix(int i) { 770 | String namespacePrefix = this.stringBlock.getString(this.mNamespaces.getPrefix(i)); 771 | switch (namespacePrefix) { 772 | case "axml_auto_00": 773 | return "android"; 774 | default: 775 | return namespacePrefix; 776 | } 777 | } 778 | 779 | @Override 780 | public String getNamespaceUri(int pos) { 781 | String naespaceUri = this.stringBlock.getString(this.mNamespaces.getUri(pos)); 782 | if(naespaceUri != null){ 783 | return naespaceUri; 784 | } else { 785 | return NS_ANDROID; 786 | } 787 | } 788 | 789 | @Override 790 | public String getPositionDescription() { 791 | return "XML line #" + getLineNumber(); 792 | } 793 | 794 | @Override 795 | public String getPrefix() { 796 | return this.stringBlock.getString(this.mNamespaces.findPrefix(this.mNamespaceUri)); 797 | } 798 | 799 | public PrecededXmlToken getPrevious() { 800 | return this.precededXmlToken; 801 | } 802 | 803 | @Override 804 | public Object getProperty(String name) { 805 | return null; 806 | } 807 | 808 | final StringBlock getStrings() { 809 | return this.stringBlock; 810 | } 811 | 812 | @Override 813 | public int getStyleAttribute() { 814 | int i = this.mStyleAttribute; 815 | if (i == -1) { 816 | return 0; 817 | } 818 | return this.mAttributes[getAttributeOffset(i) + 4]; 819 | } 820 | 821 | @Override 822 | public String getText() { 823 | if (this.eventType == XmlPullParser.TEXT) { 824 | return this.stringBlock.getString(this.mName); 825 | } 826 | return null; 827 | } 828 | 829 | @Override 830 | public char[] getTextCharacters(int[] holderForStartAndLength) { 831 | String text = getText(); 832 | if (text == null) { 833 | holderForStartAndLength[0] = -1; 834 | holderForStartAndLength[1] = -1; 835 | return null; 836 | } 837 | holderForStartAndLength[0] = 0; 838 | holderForStartAndLength[1] = text.length(); 839 | char[] characters = new char[text.length()]; 840 | text.getChars(0, text.length(), characters, 0); 841 | return characters; 842 | } 843 | 844 | @Override 845 | public boolean isAttributeDefault(int index) { 846 | // No default attributes, returning false 847 | return false; 848 | } 849 | 850 | @Override 851 | public boolean isEmptyElementTag() { 852 | return false; 853 | } 854 | 855 | @Override 856 | public boolean isWhitespace() throws XmlPullParserException { 857 | if (this.eventType != XmlPullParser.TEXT) { 858 | throw new XmlPullParserException("Current event is not TEXT"); 859 | } 860 | String text = getText(); 861 | if (text == null) { 862 | return false; 863 | } 864 | for (int i = 0; i < text.length(); i++) { 865 | if (!Character.isWhitespace(text.charAt(i))) { 866 | return false; 867 | } 868 | } 869 | return true; 870 | } 871 | 872 | @Override 873 | public int next() throws XmlPullParserException, IOException { 874 | if (this.mReader != null) { 875 | try { 876 | if (this.stringBlock != null) { 877 | this.precededXmlToken = new PrecededXmlToken(getName(), getPrefix(), getEventType()); 878 | } 879 | doNext(); 880 | return this.eventType; 881 | } catch (IOException e) { 882 | close(); 883 | throw e; 884 | } 885 | } 886 | throw new XmlPullParserException("Parser is not opened.", this, null); 887 | } 888 | 889 | @Override 890 | public int nextTag() throws XmlPullParserException, IOException { 891 | int eventType = next(); 892 | if (eventType == XmlPullParser.TEXT && isWhitespace()) { 893 | eventType = next(); 894 | } 895 | if (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_TAG) { 896 | throw new XmlPullParserException("Expected start or end tag.", this, null); 897 | } 898 | return eventType; 899 | } 900 | 901 | @Override 902 | public String nextText() throws XmlPullParserException, IOException { 903 | if (getEventType() != XmlPullParser.START_TAG) { 904 | throw new XmlPullParserException("Parser must be on START_TAG to read next text.", this, null); 905 | } 906 | int eventType = next(); 907 | if (eventType == XmlPullParser.TEXT) { 908 | String result = getText(); 909 | eventType = next(); 910 | if (eventType != XmlPullParser.END_TAG) { 911 | throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.", this, null); 912 | } 913 | return result; 914 | } else if (eventType == XmlPullParser.END_TAG) { 915 | return ""; 916 | } else { 917 | throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.", this, null); 918 | } 919 | } 920 | 921 | @Override 922 | public int nextToken() throws XmlPullParserException, IOException { 923 | return next(); 924 | } 925 | 926 | public void open(InputStream inputStream) { 927 | close(); 928 | if (inputStream != null) { 929 | this.mReader = new IntReader(inputStream, false); 930 | } 931 | } 932 | 933 | @Override 934 | public void require(int type, String namespace, String name) throws XmlPullParserException, IOException { 935 | if (type != getEventType() || ((namespace != null && !namespace.equals(getNamespace())) || (name != null && !name.equals(getName())))) { 936 | throw new XmlPullParserException(TYPES[type] + " is expected.", this, null); 937 | } 938 | } 939 | 940 | @Override 941 | public void setFeature(String name, boolean value) throws XmlPullParserException { 942 | throw new XmlPullParserException(E_NOT_SUPPORTED); 943 | } 944 | 945 | @Override 946 | public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException { 947 | throw new XmlPullParserException(E_NOT_SUPPORTED); 948 | } 949 | 950 | @Override 951 | public void setInput(Reader reader) throws XmlPullParserException { 952 | throw new XmlPullParserException(E_NOT_SUPPORTED); 953 | } 954 | 955 | @Override 956 | public void setProperty(String name, Object value) throws XmlPullParserException { 957 | throw new XmlPullParserException(E_NOT_SUPPORTED); 958 | } 959 | } 960 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/ChunkUtil.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 4 | * Copyright 2024, developer-krushna 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are 8 | * met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following disclaimer 14 | * in the documentation and/or other materials provided with the 15 | * distribution. 16 | * * Neither the name of developer-krushna nor the names of its 17 | * contributors may be used to endorse or promote products derived from 18 | * this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 33 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 34 | * additional information or have any questions 35 | */ 36 | 37 | package mt.modder.hub.axmlTools; 38 | import java.io.IOException; 39 | 40 | 41 | public class ChunkUtil { 42 | 43 | public static final void readCheckType(IntReader intReader, int expectedType) throws IOException { 44 | int readInt = intReader.readInt(); 45 | if (readInt != expectedType) { 46 | throw new IOException("Expected chunk of type 0x" + Integer.toHexString(expectedType) + ", read 0x" + Integer.toHexString(readInt) + "."); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/IntReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools; 37 | 38 | import java.io.EOFException; 39 | import java.io.IOException; 40 | import java.io.InputStream; 41 | 42 | public final class IntReader { 43 | private boolean m_bigEndian; 44 | private int m_position; 45 | private InputStream m_stream; 46 | 47 | public IntReader() { 48 | } 49 | 50 | public IntReader(InputStream inputStream, boolean bigEndian) { 51 | reset(inputStream, bigEndian); 52 | } 53 | 54 | public final int available() throws IOException { 55 | return this.m_stream.available(); 56 | } 57 | 58 | public final void close() { 59 | InputStream inputStream = this.m_stream; 60 | if (inputStream == null) { 61 | return; 62 | } 63 | try { 64 | inputStream.close(); 65 | } catch (IOException e) { 66 | } 67 | reset(null, false); 68 | } 69 | 70 | public final int getPosition() { 71 | return this.m_position; 72 | } 73 | 74 | public final InputStream getStream() { 75 | return this.m_stream; 76 | } 77 | 78 | public final boolean isBigEndian() { 79 | return this.m_bigEndian; 80 | } 81 | 82 | public final int readByte() throws IOException { 83 | return readInt(1); 84 | } 85 | 86 | public final byte[] readByteArray(int length) throws IOException { 87 | byte[] array = new byte[length]; 88 | int read = m_stream.read(array); 89 | m_position += read; 90 | if (read != length) { 91 | throw new EOFException(); 92 | } 93 | return array; 94 | } 95 | 96 | public final int readInt() throws IOException { 97 | return readInt(4); 98 | } 99 | 100 | public final int readInt(int length) throws IOException { 101 | if (length < 0 || length > 4) { 102 | throw new IllegalArgumentException(); 103 | } 104 | int result = 0; 105 | if (m_bigEndian) { 106 | for (int i = (length - 1) * 8; i >= 0; i -= 8) { 107 | int b = m_stream.read(); 108 | if (b == -1) { 109 | throw new EOFException(); 110 | } 111 | m_position += 1; 112 | result |= (b << i); 113 | } 114 | } else { 115 | length *= 8; 116 | for (int i = 0; i != length; i += 8) { 117 | int b = m_stream.read(); 118 | if (b == -1) { 119 | throw new EOFException(); 120 | } 121 | m_position += 1; 122 | result |= (b << i); 123 | } 124 | } 125 | return result; 126 | } 127 | 128 | public final void readIntArray(int[] array, int offset, int length) throws IOException { 129 | for (; length > 0; length -= 1) { 130 | array[offset++] = readInt(); 131 | } 132 | } 133 | 134 | public final int[] readIntArray(int length) throws IOException { 135 | int[] array = new int[length]; 136 | readIntArray(array, 0, length); 137 | return array; 138 | } 139 | 140 | public final int readShort() throws IOException { 141 | return readInt(2); 142 | } 143 | 144 | public final void reset(InputStream inputStream, boolean z) { 145 | this.m_stream = inputStream; 146 | this.m_bigEndian = z; 147 | this.m_position = 0; 148 | } 149 | 150 | public final void setBigEndian(boolean z) { 151 | this.m_bigEndian = z; 152 | } 153 | 154 | public final void skip(int bytes) throws IOException { 155 | if (bytes <= 0) { 156 | return; 157 | } 158 | long skip = this.m_stream.skip(bytes); 159 | this.m_position = (int) (this.m_position + skip); 160 | if (skip != bytes) { 161 | throw new EOFException(); 162 | } 163 | } 164 | 165 | public final void skipInt() throws IOException { 166 | skip(4); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/StringBlock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools; 37 | 38 | import java.io.IOException; 39 | import java.nio.charset.Charset; 40 | import java.nio.charset.StandardCharsets; 41 | import java.util.*; 42 | import java.nio.*; 43 | import mt.modder.hub.axmlTools.escaper.*; 44 | 45 | public class StringBlock { 46 | private static final int CHUNK_TYPE = 0x001C0001; // Type identifier for the chunk 47 | public static final int UTF8_FLAG = 0x00000100; // Flag for UTF-8 encoding 48 | private boolean isUTF8; // Flag to indicate if the strings are UTF-8 encoded 49 | private int[] stringOffsets; // Offsets for the start of each string 50 | private int[] strings; // Array containing the actual string data 51 | private int[] styleOffsets; // Offsets for the start of each style 52 | private int[] styles; // Array containing the style data 53 | 54 | 55 | private StringBlock() { 56 | } 57 | 58 | // Retrieves a byte from an int array at a specified index 59 | private static int getByte(int[] array, int index) { 60 | return (array[index / 4] >>> ((index % 4) * 8)) & 255; 61 | } 62 | 63 | // Converts a segment of an int array into a byte array 64 | private static byte[] getByteArray(int[] array, int offset, int length) { 65 | byte[] bytes = new byte[length]; 66 | for (int i = 0; i < length; i++) { 67 | bytes[i] = (byte) getByte(array, offset + i); 68 | } 69 | return bytes; 70 | } 71 | 72 | // Determines the size of the length field based on the encoding 73 | private int getLengthFieldSize(int[] array, int offset) { 74 | if (!this.isUTF8) { 75 | return (32768 & getShort(array, offset)) != 0 ? 4 : 2; 76 | } 77 | int size = (getByte(array, offset) & 128) != 0 ? 2 + 1 : 2; 78 | return (getByte(array, offset) & 128) != 0 ? size + 1 : size; 79 | } 80 | 81 | // Retrieves a short value from an int array at a specified offset 82 | private static final int getShort(int[] array, int offset) { 83 | int value = array[offset / 4]; 84 | if ((offset % 4) / 2 == 0) { 85 | return (value & 0xFFFF); 86 | } else { 87 | return (value >>> 16); 88 | } 89 | } 90 | 91 | // Retrieves the length of a string from the array 92 | private int getStringLength(int[] array, int offset) { 93 | if (!this.isUTF8) { 94 | int value = getShort(array, offset); 95 | if ((32768 & value) != 0) { 96 | return getShort(array, offset + 2) | ((value & 32767) << 16); 97 | } 98 | return value; 99 | } 100 | if ((getByte(array, offset) & 128) != 0) { 101 | offset++; 102 | } 103 | int nextOffset = offset + 1; 104 | int byte1 = getByte(array, nextOffset); 105 | return (byte1 & 128) != 0 ? ((byte1 & 127) << 8) | getByte(array, nextOffset + 1) : byte1; 106 | } 107 | 108 | // Retrieves the style array for a given index 109 | private int[] getStyle(int index) { 110 | if (styleOffsets == null || styles == null || index >= styleOffsets.length) { 111 | return null; 112 | } 113 | int offsetIndex = styleOffsets[index] / 4; 114 | int count = 0; 115 | int offsetIndex2 = offsetIndex; 116 | while (true) { 117 | if (offsetIndex2 >= styles.length || styles[offsetIndex2] == -1) { 118 | break; 119 | } 120 | count++; 121 | offsetIndex2++; 122 | } 123 | if (count == 0 || count % 3 != 0) { 124 | return null; 125 | } 126 | int[] style = new int[count]; 127 | int offsetIndex3 = offsetIndex; 128 | int styleIndex = 0; 129 | while (true) { 130 | if (offsetIndex3 >= styles.length || styles[offsetIndex3] == -1) { 131 | break; 132 | } 133 | style[styleIndex++] = styles[offsetIndex3++]; 134 | } 135 | return style; 136 | } 137 | 138 | // Reads a StringBlock from the given IntReader 139 | public static StringBlock read(IntReader intReader) throws IOException { 140 | ChunkUtil.readCheckType(intReader, CHUNK_TYPE); // Check the chunk type 141 | int chunkSize = intReader.readInt(); // Total size of the chunk 142 | int stringCount = intReader.readInt(); // Number of strings 143 | int styleCount = intReader.readInt(); // Number of styles 144 | int flags = intReader.readInt(); // Flags (including UTF-8 flag) 145 | int stringDataOffset = intReader.readInt(); // Offset to string data 146 | int stylesOffset = intReader.readInt(); // Offset to styles data 147 | 148 | StringBlock stringBlock = new StringBlock(); 149 | stringBlock.isUTF8 = (flags & UTF8_FLAG) != 0; // Determine if strings are UTF-8 encoded 150 | stringBlock.stringOffsets = intReader.readIntArray(stringCount); // Read string offsets 151 | if (styleCount != 0) { 152 | stringBlock.styleOffsets = intReader.readIntArray(styleCount); // Read style offsets 153 | } 154 | 155 | int stringDataSize = (stylesOffset == 0 ? chunkSize : stylesOffset) - stringDataOffset; 156 | if (stringDataSize % 4 == 0) { 157 | stringBlock.strings = intReader.readIntArray(stringDataSize / 4); // Read string data 158 | if (stylesOffset != 0) { 159 | int stylesDataSize = chunkSize - stylesOffset; 160 | if (stylesDataSize % 4 != 0) { 161 | throw new IOException("Style data size is not multiple of 4 (" + stylesDataSize + ")."); 162 | } 163 | stringBlock.styles = intReader.readIntArray(stylesDataSize / 4); // Read styles data 164 | } 165 | return stringBlock; 166 | } 167 | throw new IOException("String data size is not multiple of 4 (" + stringDataSize + ")."); 168 | } 169 | 170 | // Finds the index of a string in the string block 171 | public int find(String str) { 172 | if (str == null) { 173 | return -1; 174 | } 175 | for (int i = 0; i < stringOffsets.length; i++) { 176 | int offset = stringOffsets[i]; 177 | int length = getShort(strings, offset); 178 | if (length == str.length()) { 179 | int j = 0; 180 | while (j != length) { 181 | offset += 2; 182 | if (str.charAt(j) != getShort(strings, offset)) { 183 | break; 184 | } 185 | j++; 186 | } 187 | if (j == length) { 188 | return i; 189 | } 190 | } 191 | } 192 | return -1; 193 | } 194 | 195 | // Gets the string at the specified index 196 | public CharSequence get(int index) { 197 | return getString(index); 198 | } 199 | 200 | // Gets the count of strings in the string block 201 | public int getCount() { 202 | if (stringOffsets != null) { 203 | return stringOffsets.length; 204 | } 205 | return 0; 206 | } 207 | 208 | // Gets the HTML representation of the string at the specified index 209 | public String getHTML(int index) { 210 | String str = getString(index); 211 | if (str == null) { 212 | return null; 213 | } 214 | int[] style = getStyle(index); 215 | if (style == null) { 216 | return str; 217 | } 218 | StringBuilder htmlBuilder = new StringBuilder(str.length() + 32); 219 | int currentIndex = 0; 220 | while (true) { 221 | int nextStyleIndex = -1; 222 | for (int i = 0; i < style.length; i += 3) { 223 | if (style[i + 1] != -1 && (nextStyleIndex == -1 || style[nextStyleIndex + 1] > style[i + 1])) { 224 | nextStyleIndex = i; 225 | } 226 | } 227 | int nextStylePosition = nextStyleIndex != -1 ? style[nextStyleIndex + 1] : str.length(); 228 | for (int i = 0; i < style.length; i += 3) { 229 | int end = style[i + 2]; 230 | if (end != -1 && end < nextStylePosition) { 231 | if (currentIndex <= end) { 232 | htmlBuilder.append(str, currentIndex, end + 1); 233 | currentIndex = end + 1; 234 | } 235 | style[i + 2] = -1; 236 | htmlBuilder.append("'); 237 | } 238 | } 239 | if (currentIndex < nextStylePosition) { 240 | htmlBuilder.append(str, currentIndex, nextStylePosition); 241 | currentIndex = nextStylePosition; 242 | } 243 | if (nextStyleIndex == -1) { 244 | return htmlBuilder.toString(); 245 | } 246 | htmlBuilder.append('<').append(getString(style[nextStyleIndex])).append('>'); 247 | style[nextStyleIndex + 1] = -1; 248 | } 249 | } 250 | 251 | // Retrieves the string at the specified index 252 | public String getString(int index) { 253 | if (index < 0 || stringOffsets == null || index >= stringOffsets.length) { 254 | return null; 255 | } 256 | int offset = stringOffsets[index]; 257 | int length = getStringLength(strings, offset); 258 | int lengthFieldSize = offset + getLengthFieldSize(strings, offset); 259 | Charset charset = this.isUTF8 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16LE; 260 | if (!this.isUTF8) { 261 | length <<= 1; 262 | } 263 | String originalString = new String(getByteArray(strings, lengthFieldSize, length), 0, length, charset); 264 | return XmlEscaper.escapeXml10(originalString); 265 | } 266 | 267 | 268 | } 269 | 270 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/XmlResourceParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools; 37 | 38 | import mt.modder.hub.axmlTools.utils.AttributeSet; 39 | import org.xmlpull.v1.XmlPullParser; 40 | 41 | /** 42 | * The XML parsing interface returned for an XML resource. This is a standard 43 | * XmlPullParser interface, as well as an extended AttributeSet interface and an 44 | * additional close() method on this interface for the client to indicate when 45 | * it is done reading the resource. 46 | */ 47 | public interface XmlResourceParser extends XmlPullParser, AttributeSet { 48 | /** 49 | * Close this interface to the resource. Calls on the interface are no 50 | * longer value after this call. 51 | */ 52 | public void close(); 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/arsc/ResourceIdExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools.arsc; 37 | 38 | import mt.modder.hub.arsc.*; 39 | import java.io.*; 40 | import java.util.*; 41 | import java.util.regex.*; 42 | 43 | /* 44 | Author @developer-krushna 45 | Thanks to ChatGPT for helping me to explain if arsc file is obfuscated with random names 46 | and also helped me to add comments for better understanding 47 | */ 48 | public class ResourceIdExtractor { 49 | private Map idToNameCache = new HashMap<>(); 50 | 51 | // Method to load and parse the ARSC file 52 | public void loadArscData(InputStream arsc) throws Exception { 53 | BinaryResourceFile resourceFile = BinaryResourceFile.fromInputStream(arsc); 54 | List chunks = resourceFile.getChunks(); 55 | 56 | for (Chunk chunk : chunks) { 57 | if (chunk instanceof ResourceTableChunk) { 58 | ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk; 59 | for (PackageChunk packageChunk : resourceTableChunk.getPackages()) { 60 | StringPoolChunk keyStringPool = packageChunk.getKeyStringPool(); 61 | for (TypeChunk typeChunk : packageChunk.getTypeChunks()) { 62 | for (Map.Entry entry : typeChunk.getEntries().entrySet()) { 63 | BinaryResourceIdentifier binaryResourceIdentifier = BinaryResourceIdentifier.create(packageChunk.getId(), typeChunk.getId(), (int) entry.getKey()); 64 | 65 | String hexId = binaryResourceIdentifier.toString(); 66 | String resourceTypeName = typeChunk.getTypeName(); // Get resource type name directly 67 | 68 | // Extract data for different resource types 69 | String extractedData = extractResourceData(resourceTypeName, keyStringPool, entry, resourceTableChunk); 70 | 71 | // Cache the extracted data based on the hex ID 72 | idToNameCache.put(hexId, extractedData); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | } 80 | 81 | private String extractResourceData(String resourceTypeName, StringPoolChunk keyStringPool, Map.Entry entry, ResourceTableChunk resourceTableChunk) { 82 | String extractedData = null; 83 | 84 | if (resourceTypeName.equals("attr") || resourceTypeName.equals("style") || resourceTypeName.equals("color")) { 85 | // Handle @attr or @style 86 | String key = keyStringPool.getString(entry.getValue().keyIndex()); 87 | extractedData = resourceTypeName + "/" + key; 88 | } else { 89 | // Handle other resource types 90 | if (entry.getValue().value() == null || entry.getValue().value().data() > resourceTableChunk.getStringPool().getStringCount() || entry.getValue().value().data() < 0) { 91 | extractedData = null; // Handle invalid data 92 | } else { 93 | String key = keyStringPool.getString(entry.getValue().keyIndex()); 94 | extractedData = resourceTypeName + "/" + key; 95 | } 96 | } 97 | 98 | return extractedData; 99 | } 100 | 101 | // Method to retrieve the name for a given hex ID 102 | public String getNameForHexId(String hexId) { 103 | return idToNameCache.get("0x" + hexId); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/AggregateTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | 22 | /** 23 | * Executes a sequence of translators one after the other. Execution ends whenever 24 | * the first translator consumes codepoints from the input. 25 | * 26 | */ 27 | class AggregateTranslator extends CharSequenceTranslator { 28 | 29 | private final CharSequenceTranslator[] translators; 30 | 31 | /** 32 | * Specify the translators to be used at creation time. 33 | * 34 | * @param translators CharSequenceTranslator array to aggregate 35 | */ 36 | public AggregateTranslator(final CharSequenceTranslator... translators) { 37 | this.translators = translators; 38 | } 39 | 40 | /** 41 | * The first translator to consume codepoints from the input is the 'winner'. 42 | * Execution stops with the number of consumed codepoints being returned. 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public int translate(final CharSequence input, final int index, final Writer out) throws IOException { 47 | for (final CharSequenceTranslator translator : translators) { 48 | final int consumed = translator.translate(input, index, out); 49 | if(consumed != 0) { 50 | return consumed; 51 | } 52 | } 53 | return 0; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/CharSequenceTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.StringWriter; 21 | import java.io.Writer; 22 | import java.util.Locale; 23 | 24 | /** 25 | * An API for translating text. 26 | * Its core use is to escape and unescape text. Because escaping and unescaping 27 | * is completely contextual, the API does not present two separate signatures. 28 | * 29 | */ 30 | abstract class CharSequenceTranslator { 31 | 32 | /** 33 | * Translate a set of codepoints, represented by an int index into a CharSequence, 34 | * into another set of codepoints. The number of codepoints consumed must be returned, 35 | * and the only IOExceptions thrown must be from interacting with the Writer so that 36 | * the top level API may reliably ignore StringWriter IOExceptions. 37 | * 38 | * @param input CharSequence that is being translated 39 | * @param index int representing the current point of translation 40 | * @param out Writer to translate the text to 41 | * @return int count of codepoints consumed 42 | * @throws IOException if and only if the Writer produces an IOException 43 | */ 44 | public abstract int translate(CharSequence input, int index, Writer out) throws IOException; 45 | 46 | /** 47 | * Helper for non-Writer usage. 48 | * @param input CharSequence to be translated 49 | * @return String output of translation 50 | */ 51 | public final String translate(final CharSequence input) { 52 | if (input == null) { 53 | return null; 54 | } 55 | try { 56 | final StringWriter writer = new StringWriter(input.length() * 2); 57 | translate(input, writer); 58 | return writer.toString(); 59 | } catch (final IOException ioe) { 60 | // this should never ever happen while writing to a StringWriter 61 | throw new RuntimeException(ioe); 62 | } 63 | } 64 | 65 | /** 66 | * Translate an input onto a Writer. This is intentionally final as its algorithm is 67 | * tightly coupled with the abstract method of this class. 68 | * 69 | * @param input CharSequence that is being translated 70 | * @param out Writer to translate the text to 71 | * @throws IOException if and only if the Writer produces an IOException 72 | */ 73 | public final void translate(final CharSequence input, final Writer out) throws IOException { 74 | if (out == null) { 75 | throw new IllegalArgumentException("The Writer must not be null"); 76 | } 77 | if (input == null) { 78 | return; 79 | } 80 | int pos = 0; 81 | final int len = input.length(); 82 | while (pos < len) { 83 | final int consumed = translate(input, pos, out); 84 | if (consumed == 0) { 85 | final char[] c = Character.toChars(Character.codePointAt(input, pos)); 86 | out.write(c); 87 | pos+= c.length; 88 | continue; 89 | } 90 | // contract with translators is that they have to understand codepoints 91 | // and they just took care of a surrogate pair 92 | for (int pt = 0; pt < consumed; pt++) { 93 | pos += Character.charCount(Character.codePointAt(input, pos)); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Helper method to create a merger of this translator with another set of 100 | * translators. Useful in customizing the standard functionality. 101 | * 102 | * @param translators CharSequenceTranslator array of translators to merge with this one 103 | * @return CharSequenceTranslator merging this translator with the others 104 | */ 105 | public final CharSequenceTranslator with(final CharSequenceTranslator... translators) { 106 | final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1]; 107 | newArray[0] = this; 108 | System.arraycopy(translators, 0, newArray, 1, translators.length); 109 | return new AggregateTranslator(newArray); 110 | } 111 | 112 | /** 113 | *

Returns an upper case hexadecimal String for the given 114 | * character.

115 | * 116 | * @param codepoint The codepoint to convert. 117 | * @return An upper case hexadecimal String 118 | */ 119 | public static String hex(final int codepoint) { 120 | return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/CodePointTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | 22 | /** 23 | * Helper subclass to CharSequenceTranslator to allow for translations that 24 | * will replace up to one character at a time. 25 | */ 26 | abstract class CodePointTranslator extends CharSequenceTranslator { 27 | 28 | /** 29 | * Implementation of translate that maps onto the abstract translate(int, Writer) method. 30 | * {@inheritDoc} 31 | */ 32 | @Override 33 | public final int translate(final CharSequence input, final int index, final Writer out) throws IOException { 34 | final int codepoint = Character.codePointAt(input, index); 35 | final boolean consumed = translate(codepoint, out); 36 | return consumed ? 1 : 0; 37 | } 38 | 39 | /** 40 | * Translate the specified codepoint into another. 41 | * 42 | * @param codepoint int character input to translate 43 | * @param out Writer to optionally push the translated output to 44 | * @return boolean as to whether translation occurred or not 45 | * @throws IOException if and only if the Writer produces an IOException 46 | */ 47 | public abstract boolean translate(int codepoint, Writer out) throws IOException; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/EntityArrays.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | /** 20 | * Class holding various entity data for HTML and XML - generally for use with 21 | * the LookupTranslator. 22 | * All arrays are of length [*][2]. 23 | */ 24 | public class EntityArrays { 25 | /** 26 | * Mapping to escape the basic XML and HTML character entities. 27 | * 28 | * Namely: {@code " & < >} 29 | * @return the mapping table 30 | */ 31 | public static String[][] BASIC_ESCAPE() { return BASIC_ESCAPE.clone(); } 32 | private static final String[][] BASIC_ESCAPE = { 33 | {"\"", """}, // " - double-quote 34 | {"&", "&"}, // & - ampersand 35 | {"<", "<"}, // < - less-than 36 | {">", ">"}, // > - greater-than 37 | }; 38 | 39 | /** 40 | * Mapping to escape the apostrophe character to its XML character entity. 41 | * @return the mapping table 42 | */ 43 | public static String[][] APOS_ESCAPE() { return APOS_ESCAPE.clone(); } 44 | private static final String[][] APOS_ESCAPE = { 45 | {"'", "'"}, // XML apostrophe 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/LookupTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | import java.util.HashMap; 22 | 23 | /** 24 | * Translates a value using a lookup table. 25 | */ 26 | class LookupTranslator extends CharSequenceTranslator { 27 | 28 | private final HashMap lookupMap; 29 | private final int shortest; 30 | private final int longest; 31 | 32 | /** 33 | * Define the lookup table to be used in translation 34 | * 35 | * Note that, as of Lang 3.1, the key to the lookup table is converted to a 36 | * java.lang.String, while the value remains as a java.lang.CharSequence. 37 | * This is because we need the key to support hashCode and equals(Object), 38 | * allowing it to be the key for a HashMap. See LANG-882. 39 | * 40 | * @param lookup CharSequence[][] table of size [*][2] 41 | */ 42 | public LookupTranslator(final CharSequence[]... lookup) { 43 | lookupMap = new HashMap<>(); 44 | int _shortest = Integer.MAX_VALUE; 45 | int _longest = 0; 46 | if (lookup != null) { 47 | for (final CharSequence[] seq : lookup) { 48 | this.lookupMap.put(seq[0].toString(), seq[1]); 49 | final int sz = seq[0].length(); 50 | if (sz < _shortest) { 51 | _shortest = sz; 52 | } 53 | if (sz > _longest) { 54 | _longest = sz; 55 | } 56 | } 57 | } 58 | shortest = _shortest; 59 | longest = _longest; 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | */ 65 | @Override 66 | public int translate(final CharSequence input, final int index, final Writer out) throws IOException { 67 | int max = longest; 68 | if (index + longest > input.length()) { 69 | max = input.length() - index; 70 | } 71 | // descend so as to get a greedy algorithm 72 | for (int i = max; i >= shortest; i--) { 73 | final CharSequence subSeq = input.subSequence(index, index + i); 74 | final CharSequence result = lookupMap.get(subSeq.toString()); 75 | if (result != null) { 76 | out.write(result.toString()); 77 | return i; 78 | } 79 | } 80 | return 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/NumericEntityEscaper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | 22 | /** 23 | * Translates codepoints to their XML numeric entity escaped value. 24 | */ 25 | class NumericEntityEscaper extends CodePointTranslator { 26 | 27 | private final int below; 28 | private final int above; 29 | private final boolean between; 30 | 31 | /** 32 | *

Constructs a NumericEntityEscaper for the specified range. This is 33 | * the underlying method for the other constructors/builders. The below 34 | * and above boundaries are inclusive when between is 35 | * true and exclusive when it is false.

36 | * 37 | * @param below int value representing the lowest codepoint boundary 38 | * @param above int value representing the highest codepoint boundary 39 | * @param between whether to escape between the boundaries or outside them 40 | */ 41 | private NumericEntityEscaper(final int below, final int above, final boolean between) { 42 | this.below = below; 43 | this.above = above; 44 | this.between = between; 45 | } 46 | 47 | /** 48 | *

Constructs a NumericEntityEscaper for all characters.

49 | */ 50 | public NumericEntityEscaper() { 51 | this(0, Integer.MAX_VALUE, true); 52 | } 53 | 54 | /** 55 | *

Constructs a NumericEntityEscaper below the specified value (exclusive).

56 | * 57 | * @param codepoint below which to escape 58 | * @return the newly created {@code NumericEntityEscaper} instance 59 | */ 60 | public static NumericEntityEscaper below(final int codepoint) { 61 | return outsideOf(codepoint, Integer.MAX_VALUE); 62 | } 63 | 64 | /** 65 | *

Constructs a NumericEntityEscaper above the specified value (exclusive).

66 | * 67 | * @param codepoint above which to escape 68 | * @return the newly created {@code NumericEntityEscaper} instance 69 | */ 70 | public static NumericEntityEscaper above(final int codepoint) { 71 | return outsideOf(0, codepoint); 72 | } 73 | 74 | /** 75 | *

Constructs a NumericEntityEscaper between the specified values (inclusive).

76 | * 77 | * @param codepointLow above which to escape 78 | * @param codepointHigh below which to escape 79 | * @return the newly created {@code NumericEntityEscaper} instance 80 | */ 81 | public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) { 82 | return new NumericEntityEscaper(codepointLow, codepointHigh, true); 83 | } 84 | 85 | /** 86 | *

Constructs a NumericEntityEscaper outside of the specified values (exclusive).

87 | * 88 | * @param codepointLow below which to escape 89 | * @param codepointHigh above which to escape 90 | * @return the newly created {@code NumericEntityEscaper} instance 91 | */ 92 | public static NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) { 93 | return new NumericEntityEscaper(codepointLow, codepointHigh, false); 94 | } 95 | 96 | /** 97 | * {@inheritDoc} 98 | */ 99 | @Override 100 | public boolean translate(final int codepoint, final Writer out) throws IOException { 101 | if(between) { 102 | if (codepoint < below || codepoint > above) { 103 | return false; 104 | } 105 | } else { 106 | if (codepoint >= below && codepoint <= above) { 107 | return false; 108 | } 109 | } 110 | 111 | out.write("&#"); 112 | out.write(Integer.toString(codepoint, 10)); 113 | out.write(';'); 114 | return true; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/UnicodeUnpairedSurrogateRemover.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package mt.modder.hub.axmlTools.escaper; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | 22 | /** 23 | * Helper subclass to CharSequenceTranslator to remove unpaired surrogates. 24 | */ 25 | class UnicodeUnpairedSurrogateRemover extends CodePointTranslator { 26 | /** 27 | * Implementation of translate that throws out unpaired surrogates. 28 | * {@inheritDoc} 29 | */ 30 | @Override 31 | public boolean translate(int codepoint, Writer out) throws IOException { 32 | if (codepoint >= Character.MIN_SURROGATE && codepoint <= Character.MAX_SURROGATE) { 33 | // It's a surrogate. Write nothing and say we've translated. 34 | return true; 35 | } else { 36 | // It's not a surrogate. Don't translate it. 37 | return false; 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/escaper/XmlEscaper.java: -------------------------------------------------------------------------------- 1 | package mt.modder.hub.axmlTools.escaper; 2 | 3 | /** 4 | * Utils method to escape xml string, copied from apache commons lang3 5 | * 6 | * @author Liu Dong {@literal } 7 | */ 8 | public class XmlEscaper { 9 | 10 | /** 11 | *

Escapes the characters in a {@code String} using XML entities.

12 | */ 13 | public static String escapeXml10(final String input) { 14 | return ESCAPE_XML10.translate(input); 15 | } 16 | 17 | public static final CharSequenceTranslator ESCAPE_XML10 = 18 | new AggregateTranslator( 19 | new LookupTranslator(EntityArrays.BASIC_ESCAPE()), 20 | new LookupTranslator(EntityArrays.APOS_ESCAPE()), 21 | new LookupTranslator( 22 | new String[][]{ 23 | {"\u0000", ""}, 24 | {"\u0001", ""}, 25 | {"\u0002", ""}, 26 | {"\u0003", ""}, 27 | {"\u0004", ""}, 28 | {"\u0005", ""}, 29 | {"\u0006", ""}, 30 | {"\u0007", ""}, 31 | {"\u0008", ""}, 32 | {"\u000b", ""}, 33 | {"\u000c", ""}, 34 | {"\u000e", ""}, 35 | {"\u000f", ""}, 36 | {"\u0010", ""}, 37 | {"\u0011", ""}, 38 | {"\u0012", ""}, 39 | {"\u0013", ""}, 40 | {"\u0014", ""}, 41 | {"\u0015", ""}, 42 | {"\u0016", ""}, 43 | {"\u0017", ""}, 44 | {"\u0018", ""}, 45 | {"\u0019", ""}, 46 | {"\u001a", ""}, 47 | {"\u001b", ""}, 48 | {"\u001c", ""}, 49 | {"\u001d", ""}, 50 | {"\u001e", ""}, 51 | {"\u001f", ""}, 52 | {"\ufffe", ""}, 53 | {"\uffff", ""} 54 | }), 55 | NumericEntityEscaper.between(0x7f, 0x84), 56 | NumericEntityEscaper.between(0x86, 0x9f), 57 | new UnicodeUnpairedSurrogateRemover() 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/utils/Attribute.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 4 | * Copyright 2024, developer-krushna 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are 8 | * met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following disclaimer 14 | * in the documentation and/or other materials provided with the 15 | * distribution. 16 | * * Neither the name of developer-krushna nor the names of its 17 | * contributors may be used to endorse or promote products derived from 18 | * this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 33 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 34 | * additional information or have any questions 35 | */ 36 | 37 | 38 | // Idea copied from : https://github.com/hsiafan/apk-parser 39 | 40 | package mt.modder.hub.axmlTools.utils; 41 | 42 | import java.util.*; 43 | import java.io.*; 44 | 45 | public class Attribute { 46 | 47 | public static class AttrIds { 48 | 49 | private static final Map ids = loadSystemAttrIds(); 50 | 51 | public static String getString(long id) { 52 | String value = ids.get((int) id); 53 | if (value == null) { 54 | value = "" + id; 55 | } 56 | return value; 57 | } 58 | } 59 | 60 | public static Map loadSystemAttrIds() { 61 | try{ 62 | BufferedReader reader = toReader("/assets/r_values.ini"); 63 | Map map = new HashMap<>(); 64 | String line; 65 | while ((line = reader.readLine()) != null) { 66 | String[] items = line.trim().split("="); 67 | if (items.length != 2) { 68 | continue; 69 | } 70 | String name = items[0].trim(); 71 | Integer id = Integer.valueOf(items[1].trim()); 72 | map.put(id, name); 73 | } 74 | return map; 75 | } catch (IOException e) { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | 80 | private static BufferedReader toReader(String path) { 81 | return new BufferedReader(new InputStreamReader(Attribute.class.getResourceAsStream(path))); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/utils/AttributeSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools.utils; 37 | 38 | public interface AttributeSet { 39 | int getAttributeCount(); 40 | 41 | String getAttributeName(int index); 42 | 43 | String getAttributeValue(int index); 44 | 45 | String getPositionDescription(); 46 | 47 | int getAttributeNameResource(int index); 48 | 49 | int getAttributeListValue(int index, String options[], int defaultValue); 50 | 51 | boolean getAttributeBooleanValue(int index, boolean defaultValue); 52 | 53 | int getAttributeResourceValue(int index, int defaultValue); 54 | 55 | int getAttributeIntValue(int index, int defaultValue); 56 | 57 | int getAttributeUnsignedIntValue(int index, int defaultValue); 58 | 59 | float getAttributeFloatValue(int index, float defaultValue); 60 | 61 | String getIdAttribute(); 62 | 63 | String getClassAttribute(); 64 | 65 | int getIdAttributeResourceValue(int index); 66 | 67 | int getStyleAttribute(); 68 | 69 | String getAttributeValue(String namespace, String attribute); 70 | 71 | int getAttributeListValue(String namespace, String attribute, 72 | String options[], int defaultValue); 73 | 74 | boolean getAttributeBooleanValue(String namespace, String attribute, 75 | boolean defaultValue); 76 | 77 | int getAttributeResourceValue(String namespace, String attribute, 78 | int defaultValue); 79 | 80 | int getAttributeIntValue(String namespace, String attribute, 81 | int defaultValue); 82 | 83 | int getAttributeUnsignedIntValue(String namespace, String attribute, 84 | int defaultValue); 85 | 86 | float getAttributeFloatValue(String namespace, String attribute, 87 | float defaultValue); 88 | 89 | // TODO: remove 90 | int getAttributeValueType(int index); 91 | 92 | int getAttributeValueData(int index); 93 | } 94 | -------------------------------------------------------------------------------- /library/src/main/java/mt/modder/hub/axmlTools/utils/TypedValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature 3 | * Copyright 2024, developer-krushna 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of developer-krushna nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need 33 | * additional information or have any questions 34 | */ 35 | 36 | package mt.modder.hub.axmlTools.utils; 37 | 38 | /** 39 | * Container for a dynamically typed data value. Primarily used with 40 | * {@link android.content.res.Resources} for holding resource values. 41 | */ 42 | public class TypedValue { 43 | /** 44 | * The value contains no data. 45 | */ 46 | public static final int TYPE_NULL = 0x00; 47 | 48 | /** 49 | * The data field holds a resource identifier. 50 | */ 51 | public static final int TYPE_REFERENCE = 0x01; 52 | /** 53 | * The data field holds an attribute resource 54 | * identifier (referencing an attribute in the current theme 55 | * style, not a resource entry). 56 | */ 57 | public static final int TYPE_ATTRIBUTE = 0x02; 58 | /** 59 | * The string field holds string data. In addition, if 60 | * data is non-zero then it is the string block 61 | * index of the string and assetCookie is the set of 62 | * assets the string came from. 63 | */ 64 | public static final int TYPE_STRING = 0x03; 65 | /** 66 | * The data field holds an IEEE 754 floating point number. 67 | */ 68 | public static final int TYPE_FLOAT = 0x04; 69 | /** 70 | * The data field holds a complex number encoding a 71 | * dimension value. 72 | */ 73 | public static final int TYPE_DIMENSION = 0x05; 74 | /** 75 | * The data field holds a complex number encoding a fraction 76 | * of a container. 77 | */ 78 | public static final int TYPE_FRACTION = 0x06; 79 | 80 | /** 81 | * Identifies the start of plain integer values. Any type value 82 | * from this to {@link #TYPE_LAST_INT} means the 83 | * data field holds a generic integer value. 84 | */ 85 | public static final int TYPE_FIRST_INT = 0x10; 86 | 87 | /** 88 | * The data field holds a number that was 89 | * originally specified in decimal. 90 | */ 91 | public static final int TYPE_INT_DEC = 0x10; 92 | /** 93 | * The data field holds a number that was 94 | * originally specified in hexadecimal (0xn). 95 | */ 96 | public static final int TYPE_INT_HEX = 0x11; 97 | /** 98 | * The data field holds 0 or 1 that was originally 99 | * specified as "false" or "true". 100 | */ 101 | public static final int TYPE_INT_BOOLEAN = 0x12; 102 | 103 | /** 104 | * Identifies the start of integer values that were specified as 105 | * color constants (starting with '#'). 106 | */ 107 | public static final int TYPE_FIRST_COLOR_INT = 0x1c; 108 | 109 | /** 110 | * The data field holds a color that was originally 111 | * specified as #aarrggbb. 112 | */ 113 | public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; 114 | /** 115 | * The data field holds a color that was originally 116 | * specified as #rrggbb. 117 | */ 118 | public static final int TYPE_INT_COLOR_RGB8 = 0x1d; 119 | /** 120 | * The data field holds a color that was originally 121 | * specified as #argb. 122 | */ 123 | public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; 124 | /** 125 | * The data field holds a color that was originally 126 | * specified as #rgb. 127 | */ 128 | public static final int TYPE_INT_COLOR_RGB4 = 0x1f; 129 | 130 | /** 131 | * Identifies the end of integer values that were specified as color 132 | * constants. 133 | */ 134 | public static final int TYPE_LAST_COLOR_INT = 0x1f; 135 | 136 | /** 137 | * Identifies the end of plain integer values. 138 | */ 139 | public static final int TYPE_LAST_INT = 0x1f; 140 | 141 | /* ------------------------------------------------------------ */ 142 | 143 | /** 144 | * Complex data: bit location of unit information. 145 | */ 146 | public static final int COMPLEX_UNIT_SHIFT = 0; 147 | /** 148 | * Complex data: mask to extract unit information (after shifting by 149 | * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as 150 | * defined below. 151 | */ 152 | public static final int COMPLEX_UNIT_MASK = 0xf; 153 | 154 | /** 155 | * {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. 156 | */ 157 | public static final int COMPLEX_UNIT_PX = 0; 158 | /** 159 | * {@link #TYPE_DIMENSION} complex unit: Value is Device Independent 160 | * Pixels. 161 | */ 162 | public static final int COMPLEX_UNIT_DIP = 1; 163 | /** 164 | * {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. 165 | */ 166 | public static final int COMPLEX_UNIT_SP = 2; 167 | /** 168 | * {@link #TYPE_DIMENSION} complex unit: Value is in points. 169 | */ 170 | public static final int COMPLEX_UNIT_PT = 3; 171 | /** 172 | * {@link #TYPE_DIMENSION} complex unit: Value is in inches. 173 | */ 174 | public static final int COMPLEX_UNIT_IN = 4; 175 | /** 176 | * {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. 177 | */ 178 | public static final int COMPLEX_UNIT_MM = 5; 179 | 180 | /** 181 | * {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall 182 | * size. 183 | */ 184 | public static final int COMPLEX_UNIT_FRACTION = 0; 185 | /** 186 | * {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. 187 | */ 188 | public static final int COMPLEX_UNIT_FRACTION_PARENT = 1; 189 | 190 | /** 191 | * Complex data: where the radix information is, telling where the decimal 192 | * place appears in the mantissa. 193 | */ 194 | public static final int COMPLEX_RADIX_SHIFT = 4; 195 | /** 196 | * Complex data: mask to extract radix information (after shifting by 197 | * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point 198 | * representations as defined below. 199 | */ 200 | public static final int COMPLEX_RADIX_MASK = 0x3; 201 | 202 | /** 203 | * Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 204 | */ 205 | public static final int COMPLEX_RADIX_23p0 = 0; 206 | /** 207 | * Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn 208 | */ 209 | public static final int COMPLEX_RADIX_16p7 = 1; 210 | /** 211 | * Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn 212 | */ 213 | public static final int COMPLEX_RADIX_8p15 = 2; 214 | /** 215 | * Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn 216 | */ 217 | public static final int COMPLEX_RADIX_0p23 = 3; 218 | 219 | /** 220 | * Complex data: bit location of mantissa information. 221 | */ 222 | public static final int COMPLEX_MANTISSA_SHIFT = 8; 223 | /** 224 | * Complex data: mask to extract mantissa information (after shifting by 225 | * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision; 226 | * the top bit is the sign. 227 | */ 228 | public static final int COMPLEX_MANTISSA_MASK = 0xffffff; 229 | 230 | /* ------------------------------------------------------------ */ 231 | 232 | /** 233 | * If {@link #density} is equal to this value, then the density should be 234 | * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. 235 | */ 236 | public static final int DENSITY_DEFAULT = 0; 237 | 238 | /** 239 | * If {@link #density} is equal to this value, then there is no density 240 | * associated with the resource and it should not be scaled. 241 | */ 242 | public static final int DENSITY_NONE = 0xffff; 243 | 244 | /* ------------------------------------------------------------ */ 245 | private static final float MANTISSA_MULT = 246 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT); 247 | private static final float[] RADIX_MULTS = new float[]{ 248 | 1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT, 249 | 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT 250 | }; 251 | private static final String[] DIMENSION_UNIT_STRS = new String[]{ 252 | "px", "dip", "sp", "pt", "in", "mm" 253 | }; 254 | private static final String[] FRACTION_UNIT_STRS = new String[]{ 255 | "%", "%p" 256 | }; 257 | /** 258 | * The type held by this value, as defined by the constants here. 259 | * This tells you how to interpret the other fields in the object. 260 | */ 261 | public int type; 262 | 263 | /** 264 | * Retrieve the base value from a complex data integer. This uses the 265 | * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of 266 | * the data to compute a floating point representation of the number they 267 | * describe. The units are ignored. 268 | * 269 | * @param complex A complex data value. 270 | * @return A floating point value corresponding to the complex data. 271 | */ 272 | public static float complexToFloat(int complex) { 273 | return (complex & (TypedValue.COMPLEX_MANTISSA_MASK 274 | << TypedValue.COMPLEX_MANTISSA_SHIFT)) 275 | * RADIX_MULTS[(complex >> TypedValue.COMPLEX_RADIX_SHIFT) 276 | & TypedValue.COMPLEX_RADIX_MASK]; 277 | } 278 | 279 | /** 280 | * Perform type conversion as per {@link #coerceToString()} on an 281 | * explicitly supplied type and data. 282 | * 283 | * @param type The data type identifier. 284 | * @param data The data value. 285 | * @return String The coerced string value. If the value is 286 | * null or the type is not known, null is returned. 287 | */ 288 | public static final String coerceToString(int type, int data) { 289 | switch (type) { 290 | case TYPE_NULL: 291 | return null; 292 | case TYPE_REFERENCE: 293 | return "@" + data; 294 | case TYPE_ATTRIBUTE: 295 | return "?" + data; 296 | case TYPE_FLOAT: 297 | return Float.toString(Float.intBitsToFloat(data)); 298 | case TYPE_DIMENSION: 299 | return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[ 300 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK]; 301 | case TYPE_FRACTION: 302 | return Float.toString(complexToFloat(data) * 100) + FRACTION_UNIT_STRS[ 303 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK]; 304 | case TYPE_INT_HEX: 305 | return "0x" + Integer.toHexString(data); 306 | case TYPE_INT_BOOLEAN: 307 | return data != 0 ? "true" : "false"; 308 | } 309 | 310 | if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) { 311 | return "#" + Integer.toHexString(data); 312 | } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) { 313 | return Integer.toString(data); 314 | } 315 | 316 | return null; 317 | } 318 | 319 | }; 320 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------