YukiHookAPI Documentation

repository·master·Indexed 24 days ago

https://github.com/highcapable/yukihookapi

A Kotlin-based Hook API and Xposed Module solution that rebuilds the Xposed API with modern Kotlin features. It provides an efficient interface for Xposed development, utilizing KavaRef as its reflection driver. The library includes features such as the encase lambda for hooking logic, YukiBaseHooker for structured projects, Zygote and Resource hooking, and support for both Xposed modules and standalone Hook API integration.

Tokens
32.5K
Snippets
76
Records
155
Agent score
84%

What's inside YukiHookAPI

  1. What is Yuki Hook API?

    master
    Yuki Hook API is a Kotlin-based library designed to provide an efficient interface for the Xposed API. It extends the standard Xposed capabilities with rich function extensions, making it easier to develop Xposed Modules. It uses KavaRef as a powerful driver for its reflection API.
  2. What is YukiHookAPI and its purpose

    master

    YukiHookAPI is a Kotlin-focused API framework designed to provide syntactic sugar and complete usage encapsulation for Xposed module development.

    Historically, Xposed development relied heavily on XposedHelpers, which lacks modern Kotlin ergonomics. YukiHookAPI aims to simplify the development process by providing a more capable and easy-to-understand API. The project's goal is to adapt to various third-party Hook Frameworks (like LSPosed) while maintaining a consistent API surface, helping developers avoid the complexities of low-level Xposed development.

  3. Use the `current` method for concise reflection calls

    master

    Instead of repeatedly passing an instance to .get(instance), you can use the current extension on an instance to create a scoped call space. This allows you to perform multiple reflection operations on the same object concisely.

    • instance.current { ... }: Provides a lambda scope where you can call method { ... } or field { ... } directly.
    • instance.current(): Returns a CurrentClass object that can be used for chained calls.
    • superClass(): Within a current block, use superClass() to access members of the parent class.
    • field { ... }.current(): Allows you to jump from a field to the instance that field belongs to.

    Note: You cannot perform inline calls like instance.current().current() because current() returns the CurrentClass object itself.

    val instance = Test()
    instance.current {
        // Execute a method on the current instance
        method {
            name = "doTask"
            param(StringClass)
        }.call("task_name")
    
        // Execute a method on the parent class
        superClass().method {
            name = "doBaseTask"
            param(StringClass)
        }.call("task_name")
    
        // Get a field value
        val name = method { name = "getName" }.string()
    }
  4. Use advanced search conditions for methods

    master

    When finding methods, you can use several strategies to handle complex or ambiguous signatures:

    1. Parameter Count: If you don't know the exact types, use paramCount to match by the number of arguments.
    2. Vague Types: Use VagueType to specify a parameter type when you only know some of the types in a signature.
    3. Conditional Logic: Use param { ... } to provide a custom predicate. The lambda provides an array of Class objects representing the parameter types. The predicate must return a Boolean.

    Example of param { ... }:

    Test::class.java.method {
         name = "release"
         param { it[0] == StringClass && it[2] == BooleanType }
    }.get(instance)
  5. Create a structured Hooker using YukiBaseHooker

    master

    For large-scale projects, extend YukiBaseHooker to classify your hooking logic. You can then pass these custom hookers into encase as a variable array of YukiBaseHooker objects.

    Key Rules:

    • Use object (singleton) to create child hookers whenever possible.
    • Do not call encase again inside the onHook method of a YukiBaseHooker; instead, write your hook code directly within onHook using loadApp, loadZygote, or loadSystem.
    • You can use loadHooker(hooker) within a YukiBaseHooker to load other hookers in a layered approach.
  6. Handle YukiHookDataChannel data size limits

    master

    When using YukiHookDataChannel to send broadcast data, the system has a limit on the size of the data sent. By default, YukiHookAPI attempts to segment data for common types like List, Map, Set, and String.

    If you attempt to send a data type that is too large and does not support automatic segmentation, you will receive an error: YukiHookDataChannel cannot send this data key of "KEY" type TYPE, because it is too large....

    Recommendation: Do not bypass this limit. If you must, use allowSendTooLargeData, but be aware that this can cause the host app to crash if the system refuses the oversized broadcast.

  7. Migrate XposedHelpers reflection to YukiHookAPI or KavaRef

    master

    The reflection API in YukiHookAPI (deprecated in 1.3.0) differs from XposedHelpers. While XposedHelpers.callMethod automatically searches superclasses, YukiHookAPI's reflection requires explicit instructions to search superclasses.

    Important: For versions 1.3.0 and later, it is recommended to migrate to KavaRef.

    To search superclasses in YukiHookAPI: Use .superClass() within the method search block or call the method directly on the superclass instance.

    Example of searching superclasses:

    instance.current().method {
        name = "test"
        superClass() // Ensures search includes superclasses
    }.call("some string")
    val instance: A = ...
    instance.current().method {
        name = "test"
        // Note that you need to add this search condition to ensure it searches for methods in superclasses.
        superClass()
    }.call("some string")
    // Or directly call the superClass() method.
    instance.current().superClass()?.method {
        name = "test"
    }
    ?.call("some string")
  8. Handle Blocking Exceptions in YukiHookAPI

    master
    Certain exceptions in YukiHookAPI are 'Blocking Exceptions'. These will cause the application to stop running (Force Close), print E level logs to the console, and cause the Hook process to terminate. Common causes include incorrect initialization of encase, missing class definitions in the current ClassLoader, or attempting to access appContext in an invalid lifecycle state.
  9. Understand the YukiHookAPI structure

    master

    YukiHookAPI follows a hierarchical structure for hooking. The primary entry points are the Host Environment, which branches into YukiMemberHookCreator for class and member hooking, and YukiResourcesHookCreator for resource manipulation (e.g., replacing drawables or injecting layouts).

    In code, this is typically expressed by resolving a target class, selecting a method via a builder, and then defining before and after hooks.

  10. Handle varying class versions with RemedyPlan

    master

    When a host application has multiple versions where a method's signature (parameters) might change, use RemedyPlan. This allows you to define multiple potential method signatures and execute logic once a match is found.

    Important: When using RemedyPlan, do not use .get() to retrieve the method instance. Instead, use the .wait(instance) or .waitAll(instance) methods to execute the logic against a specific object instance.

    val instance = Test()
    
    // Use .remedys with .wait(instance) for single match logic
    Test::class.java.method {
        name = "doTask"
        emptyParam()
    }.remedys {
        method {
            name = "doTask"
            param(StringClass)
        }.onFind {
            // Found logic
        }
        method {
            name = "doTask"
            param(StringClass, IntType)
        }.onFind {
            // Found logic
        }
    }.wait(instance) { 
        // Get the result
    }
    
    // Use .waitAll(instance) when using multiple find patterns
    Test::class.java.method {
        name = "doTask"
        emptyParam()
    }.remedys {
        method {
            name = "doTask"
            paramCount(0..1)
        }.onFind {
            // Found logic
        }
        method {
            name = "doTask"
            paramCount(1..2)
        }.onFind {
            // Found logic
        }
    }.waitAll(instance) { 
        // Get the result
    }
  11. Understanding Xposed Hooking principles

    master

    Xposed operates via parasitism. An Xposed Module follows the lifecycle of a Host (App) and completes its own lifecycle within that host's environment.

    By injecting into the Host when it is running, you can use reflection to access the Host's methods, fields, and constructors. The XposedBridge provides the hook operation, allowing you to:

    • Dynamically insert code before a method is executed.
    • Dynamically insert code after a method is executed.
    • Replace the target method entirely.
    • Intercept the target method.