ARSCLib

repository·main·Indexed 18 days ago

https://github.com/reandroid/arsclib

A Java library for reading, writing, and modifying Android binary resources (resources.arsc and binary XML), designed as a programmatic replacement for aapt/aapt2. It supports resource table manipulation, binary XML processing for AndroidManifest.xml, JSON and XML conversion, and efficient DEX file operations including resource linking and direct code component manipulation.

Tokens
5.4K
Snippets
9
Records
17
Agent score
63%

What's inside ARSCLib

  1. Overview of ARSCLib capabilities

    main

    ARSCLib is a Java library designed to read, write, modify, and create Android binary resources. It is based on the AOSP androidfw/ResourceTypes.h structure and serves as a replacement for aapt/aapt2.

    Supported Operations:

    • Resource Tables: Read/write/modify resources.arsc.
    • Binary XML: Read/write/modify AndroidManifest.xml and resource XML files.
    • JSON Conversion: Decodes obfuscated resources to JSON and encodes/builds JSON back to binary resources.
    • XML Conversion: Decodes un-obfuscated resources to source XML and encodes/builds source XML back to binary resources.

    Important Notes:

    • Decoding resources to XML requires all source names to be un-obfuscated and valid.
    • The library does not strictly validate XML syntax during encoding/building as aapt/aapt2 does. It is the user's responsibility to ensure values (like package names) are valid for Android devices.
  2. Overview of ARSCLib dex manipulation capabilities

    main

    ARSCLib is a Java library designed for fast and efficient reading, writing, and editing of Android DEX files. Its primary objectives include:

    • Efficient DEX Operations: High-performance read/write/edit capabilities.
    • Resource Linking: Creating referenced links between resources and DEX classes (e.g., linking resource IDs and class names).
    • Direct Manipulation: Enabling users to search, replace, add, or remove DEX code components directly without the overhead of disassembling the file into smali/assembly format.
  3. Install ARSCLib via Maven or JAR

    main

    You can integrate ARSCLib into your Java project using Maven or by manually including the JAR file.

    Maven

    Add mavenCentral() to your repositories and include the dependency:

    repositories {
        mavenCentral()
    }
    dependencies {
        implementation("io.github.reandroid:ARSCLib:+")
    }

    JAR

    If you are using a local JAR file, add it via the files dependency:

    dependencies {
        implementation(files("$rootProject.projectDir/libs/ARSCLib.jar"))
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        implementation("io.github.reandroid:ARSCLib:+")
    }
  4. Build ARSCLib from source

    main

    To build the library manually from the source repository, use the Gradle wrapper:

    1. Clone the repository.
    2. Run the ./gradlew jar command.
    3. The resulting JAR will be located in ./build/libs/ARSCLib-x.x.x.jar.
    git clone https://github.com/REAndroid/ARSCLib.git
    cd ARSCLib
    ./gradlew jar
  5. Manage Android resource configuration values with ResConfigBase

    main

    ResConfigBase is a base class used to read and write Android binary resource configuration values. It provides a structured way to access specific configuration fields such as MCC, MNC, language, region, screen dimensions, and more. The class manages an underlying byte container and handles the offsets and data types (short, byte, int, etc.) required by the Android resource format.

    Key capabilities include:

    • Field Accessors: Getters and setters for standard Android configuration parameters (e.g., getMcc(), setDensity(), getSdkVersion()).
    • Size Management: Methods to adjust the configuration size using setConfigSize(int size), trimToSize(int size), or trimToMinimumSize() to ensure the binary structure remains valid.
    • Locale Handling: Specialized methods for managing locale-related data like getLanguageBytes(), getRegionBytes(), getLocaleScriptBytes(), and getLocaleVariantBytes().
    // Example of interacting with ResConfigBase fields
    ResConfigBase config = new ResConfigBase(64);
    config.setMcc(310);
    config.setMnc(260);
    config.setDensity(420);
    config.setSdkVersion(30);
    
    int mcc = config.getMcc();
    int density = config.getDensityValue();
  6. Example: Create a new APK using ARSCLib

    main

    The following example demonstrates how to use ApkModule, TableBlock, and AndroidManifestBlock to programmatically construct a new APK, including defining resources (strings, drawables), setting manifest attributes, and adding a dummy DEX file.

    import com.reandroid.apk.AndroidFrameworks;
    import com.reandroid.apk.ApkModule;
    import com.reandroid.apk.FrameworkApk;
    import com.reandroid.archive.ByteInputSource;
    import com.reandroid.arsc.chunk.PackageBlock;
    import com.reandroid.arsc.chunk.TableBlock;
    import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
    import com.reandroid.arsc.chunk.xml.ResXmlAttribute;
    import com.reandroid.arsc.chunk.xml.ResXmlElement;
    import com.reandroid.arsc.coder.EncodeResult;
    import com.reandroid.arsc.coder.ValueCoder;
    import com.reandroid.arsc.value.Entry;
    
    import java.io.File;
    import java.io.IOException;
    
    public class ARSCLibExample {
    
        public static void createNewApk() throws IOException {
    
            ApkModule apkModule = new ApkModule();
    
            TableBlock tableBlock = new TableBlock();
            AndroidManifestBlock manifest = new AndroidManifestBlock();
    
            apkModule.setTableBlock(tableBlock);
            apkModule.setManifest(manifest);
    
            FrameworkApk framework = apkModule.initializeAndroidFramework(
                    AndroidFrameworks.getLatest().getVersionCode());
    
            PackageBlock packageBlock = tableBlock.newPackage(0x7f, "com.example");
    
            Entry appIcon = packageBlock.getOrCreate("", "drawable", "ic_launcher");
    
            EncodeResult color = ValueCoder.encode("#006400");
            appIcon.setValueAsRaw(color.valueType, color.value);
    
            Entry appNameDefault = packageBlock.getOrCreate("", "string", "app_name");
            appNameDefault.setValueAsString("My Application");
    
            Entry appNameDe = packageBlock.getOrCreate("-de", "string", "app_name");
            appNameDe.setValueAsString("Meine Bewerbung");
    
            Entry appNameRu = packageBlock.getOrCreate("-ru-rRU", "string", "app_name");
            appNameRu.setValueAsString("Мое заявление");
    
            manifest.setPackageName("com.example");
            manifest.setVersionCode(100);
            manifest.setVersionName("1.0.0");
            manifest.setIconResourceId(appIcon.getResourceId());
            manifest.setCompileSdkVersion(framework.getVersionCode());
            manifest.setCompileSdkVersionCodename(framework.getVersionName());
            manifest.setPlatformBuildVersionCode(framework.getVersionCode());
            manifest.setPlatformBuildVersionName(framework.getVersionName());
    
            manifest.addUsesPermission("android.permission.INTERNET");
            manifest.addUsesPermission("android.permission.READ_EXTERNAL_STORAGE");
    
            //all appName entries created above have the same resource ids
            manifest.setApplicationLabel(appNameDefault.getResourceId());
    
            ResXmlElement mainActivity = manifest.getOrCreateMainActivity("android.app.Activity");
            ResXmlAttribute labelAttribute = mainActivity
                    .getOrCreateAndroidAttribute(AndroidManifestBlock.NAME_label, AndroidManifestBlock.ID_label);
            labelAttribute.setValueAsString("Hello World");
    
            //Android os requires at least one dex file on base apk
            ByteInputSource dummyDex = new ByteInputSource(new byte[0], "classes.dex");
            apkModule.add(dummyDex);
    
            File outFile = new File("test_out.apk");
            apkModule.writeApk(outFile);
            //Sign and install
        }
    }
  7. Manage ResConfigBase size and validity

    main

    When working with ResConfigBase, you must ensure the configuration size is valid according to Android's resource constraints.

    • setConfigSize(int size): Sets the configuration size. It automatically aligns the size to a multiple of 4. Throws IllegalArgumentException if the size is invalid.
    • trimToSize(int size): Attempts to resize the configuration to the specified size. Returns true if successful, false otherwise.
    • trimToMinimumSize(): Resizes the configuration to the smallest valid size that accommodates the current data (removing trailing zeros).
    • isValidSize(int size): A static utility to check if a size is valid. Valid sizes include specific constants (SIZE_16, SIZE_28, etc.) or any size greater than SIZE_64.
    • nearestSize(int size): A static utility that returns the next valid size for a given input.
  8. Set resource values in an Entry

    main

    You can update the data stored within an Entry using type-specific setter methods. Most of these methods internally ensure the entry is a 'scalar' type before applying the change.

    MethodParameterDescription
    setValueAsBoolean(boolean)valueSets the value as a boolean
    setValueAsColor(AndroidColor)colorSets the value as an Android color
    setValueAsFloat(float)valueSets the value as a float
    setValueAsInteger(int)valueSets the value as an integer
    setValueAsString(String)strSets the value as a string
    setValueAsString(StyleDocument)styledStringSets the value as a styled string document
    setValueAsRaw(ValueType, int)type, dataSets a raw value with a specific ValueType
    setValueAsReference(int)resourceIdSets the value as a resource ID reference
  9. Get resource identity and metadata from an Entry

    main

    Use these methods to identify the resource and its position in the Android resource table:

    • getResourceId(): Returns the full 32-bit resource ID (Package ID << 24 | Type ID << 16 | Entry ID).
    • getName(): Returns the resource name as a String.
    • getTypeName(): Returns the resource type name (e.g., "string", "drawable").
    • getTypeId(): Returns the integer ID of the resource type.
    • getTypeString(): Returns the TypeString representation of the type.
    • getId(): Returns the specific entry ID.
    • getResourceName(): Returns a ResourceName object containing the package name, type name, and resource name.
    • getResourceEntry(): Returns a ResourceEntry object for resolving the resource.
  10. Handle diagnostic messages with DiagnosticMessage

    main

    The DiagnosticMessage interface is used by the library to emit diagnostic information, warnings, and errors. Developers can interact with these messages to understand the library's internal state or troubleshoot issues during Android binary resource processing.

    To work with these messages, you can use the StringMessage implementation, which provides a structured way to represent a message with a specific severity level (Type), an optional source (Origin), and an optional tag for categorization.

    // Example of creating a diagnostic message
    DiagnosticMessage msg = new DiagnosticMessage.StringMessage(
        DiagnosticMessage.Type.ERROR, 
        null, 
        "ResourceParser", 
        "Failed to parse XML resource"
    );
    
    System.out.println(msg.toString());
    // Output format: E: ResourceParser : Failed to parse XML resource
  11. Retrieve resource values from an Entry

    main

    The Entry class provides several methods to extract the underlying resource value as a specific Java type. These methods depend on the entry's ValueType.

    MethodDescription
    getValueAsBoolean()Returns the value as a Boolean
    getValueAsColor()Returns the value as an AndroidColor
    getValueAsFloat()Returns the value as a Float
    getValueAsInteger()Returns the value as an Integer
    getValueAsString()Returns the value as a String
    getValueAsStyleDocument()Returns the value as a StyleDocument
    getValueAsReference()Returns the value as a ResourceEntry
    getResValue()Returns the raw ResValue object
    getResValueMapArray()Returns a ResValueMapArray if the entry is a map/array
  12. Use the Entry class to manage resource entries

    main

    The com.reandroid.arsc.value.Entry class represents a single resource entry or value within an Android resource table. It provides a high-level API to interact with resource metadata (like names and types), actual values (like strings, integers, or colors), and the underlying binary structure.

    Key capabilities include:

    • Accessing Metadata: Retrieve the resource ID, type name, and resource name.
    • Reading Values: Extract values as specific types (e.g., getValueAsInteger(), getValueAsString(), getValueAsColor()).
    • Modifying Values: Update resource values (e.g., setValueAsBoolean(boolean), setValueAsString(String)).
    • Renaming: Use reName(String) to rename the entry and update all related configurations in the package.
    • JSON Interop: Convert the entry to and from JSONObject via the JSONConvert interface.
    // Example: Accessing and modifying an Entry
    Entry entry = ...;
    
    // Get resource identity
    int resourceId = entry.getResourceId();
    String name = entry.getName();
    String type = entry.getTypeName();
    
    // Read a value
    if (entry.getValueType() == ValueType.STRING) {
        String val = entry.getValueAsString();
    }
    
    // Modify a value
    entry.setValueAsBoolean(true);
    
    // Rename the entry and its references
    entry.reName("new_resource_name");
    
    // JSON conversion
    JSONObject json = entry.toJson();
    entry.fromJson(json);