---
name: morphe-patcher
description: Architecture, patch typology (bytecodePatch, resourcePatch, rawResourcePatch), universal patches, stringOption DSL, fingerprint resolution, compatibility contracts (Constants.kt), and diagnostic telemetry invariants.
---

<!-- Mirror: this skill also exists in kveld-extra-morphe-patches/.agents/skills (hardlinked to its .claude/skills). When editing shared core guidance, replicate the change there. -->

# Morphe Patcher Architectural Guidelines

## 1. Patch DSL & Typology

Morphe patches are declared using functional Kotlin builder DSLs provided by `app.morphe.patcher.patch`:

### A. `bytecodePatch`
Primary tool for Dalvik/Smali AST bytecode transformations via dexlib2 fingerprints and instructions.
```kotlin
val myPatch = bytecodePatch(
    name = "Unique Patch Name",
    description = "Concise technical description of the modification.",
    default = true // Whether enabled by default in Morphe Manager
) {
    compatibleWith(Constants.COMPATIBILITY_BRAVE)
    dependsOn(companionResourcePatch) // Optional dependency execution

    execute {
        // Fingerprinting and instruction insertion
    }
}
```

### B. `resourcePatch`
Used for parsing and transforming decompiled Android resource XML files prior to DEX assembly.
```kotlin
val myResourcePatch = resourcePatch(
    name = "Resource Defaults Patch",
    description = "Overwrites default XML attributes.",
    default = false
) {
    compatibleWith(Constants.COMPATIBILITY_BRAVE)

    execute {
        val targetFile = get("res/xml").listFiles()
            ?.firstOrNull { it.extension == "xml" && it.readText().contains("target_key") }
            ?: return@execute

        document(targetFile.absolutePath).use { doc ->
            val nodes = doc.getElementsByTagName("SwitchPreference")
            for (i in 0 until nodes.length) {
                val elem = nodes.item(i) as? Element ?: continue
                if (elem.getAttribute("android:key") == "target_key") {
                    elem.setAttribute("android:defaultValue", "true")
                }
            }
        }
    }
}
```

### C. `rawResourcePatch`
Direct byte-level modification of bundled binary shared libraries (`.so`) or uncompressed assets (`assets/index.android.bundle`).
```kotlin
val myNativePatch = rawResourcePatch(
    name = "Native Hardening Patch",
    description = "Direct binary patching of libchrome.so",
    default = false
) {
    compatibleWith(Constants.COMPATIBILITY_BRAVE)

    execute {
        val soFile = get("lib/arm64-v8a/libchrome.so")
        if (!soFile.exists()) return@execute
        // Validate offsets and mutate bytes via RandomAccessFile
    }
}
```

---

## 2. Compatibility Scope & Universal Patches

Compatibility is configured via `compatibleWith(...)`:

- **Single Target**:
  ```kotlin
  compatibleWith(Constants.COMPATIBILITY_BRAVE)
  ```
- **Multi-Compatibility Varargs**:
  Accepts multiple `Compatibility` contracts for cross-target or dual-package applications:
  ```kotlin
  compatibleWith(targetA, targetB)
  ```
- **Universal Patches**:
  **Omitting `compatibleWith(...)`** entirely produces a universal patch (e.g. `LocaleResourceSlimmerPatch`, `DpiResourceSlimmerPatch`). Universal patches are offered across all target applications in Morphe Manager and the CLI patcher. Universal patch sources live in `app.morphe.patches.universal`; `app.morphe.patches.shared` holds only contracts and helpers.

---

## 3. User Configurable Options (`stringOption` DSL)

Patches can expose configurable settings to users in Morphe Manager or CLI using the `stringOption` property delegate:

```kotlin
import app.morphe.patcher.patch.stringOption

val targetLocales by stringOption(
    key = "locales",
    title = "Locales to keep",
    description = "Comma-separated language codes to preserve (e.g. 'en, es, pt, fr, de'). English fallback is always retained.",
    default = "en",
    required = false,
)
```

### Metadata Synchronization Rule
When patch options, descriptions, titles, or defaults are added or modified in Kotlin code, verify catalog registration by running the patch list generator against the built `.mpp` from a temporary working directory outside the repository, and confirm the expected entries appear. Do NOT run `./gradlew generatePatchesList` in the repository checkout: it rewrites the tracked `patches-list.json`, which the release pipeline regenerates (see `AGENTS.md`, rule 11).

---

## 4. Fingerprint Resolution Strategies

Fingerprints locate target methods across obfuscated versions without hardcoding method names:

1. **String Literals**: Most resilient anchor. Locate methods referencing unique log strings or preference keys.
   ```kotlin
   Fingerprint(
       returnType = "V",
       strings = listOf("brave.origin.package_name_android", "brave.origin.product_id_android")
   )
   ```

2. **Signature & Parameter Filtering**: Match methods by strict parameter and return type signatures.
   ```kotlin
   Fingerprint(
       returnType = "Z",
       parameters = listOf("Lorg/chromium/chrome/browser/profiles/Profile;"),
       strings = listOf("getIsSubscriptionActive profile is null")
   )
   ```

3. **Instruction Filters & Register Sniffing**:
   ```kotlin
   val fp = Fingerprint(
       definingClass = "Lorg/chromium/chrome/browser/settings/BraveOriginPreferences;",
       returnType = "V",
       parameters = listOf("Ljava/lang/String;", "Landroid/os/Bundle;"),
       filters = listOf(
           methodCall(definingClass = "Lcom/target/Class;", name = "predicate", returnType = "Z")
       )
   )
   val matchIndex = fp.instructionMatches.first().index
   val targetReg = fp.method.getInstruction<OneRegisterInstruction>(matchIndex + 1).registerA
   ```

   > **Runtime safety (device-proven):** never `remove`+`replace` a lone invoke and never grow the register frame. Both cause device-only `VerifyError` boot crashes that a green `runPatchTest` does not catch. Insert-only after `move-result`, reusing existing registers. See `agy-orchestrator` `references/observations.md:40-41`.

---

## 5. Metadata Contracts (`Constants.kt`)

Every patch must reference the shared compatibility object defined centrally in `Constants.kt`. Never inline `Compatibility(...)` objects.

Active targets defined in `Constants.kt`:
1. **Brave**: `Constants.COMPATIBILITY_BRAVE` (`com.brave.browser`)
2. **Gboard Lite**: `Constants.COMPATIBILITY_GBOARD` (`com.google.android.inputmethod.latin`)
3. **Hevy**: `Constants.COMPATIBILITY_HEVY` (`com.hevy`)
4. **TikTok**: `Constants.COMPATIBILITY_TIKTOK` (`com.zhiliaoapp.musically`)
5. **NokoPrint**: `Constants.COMPATIBILITY_NOKOPRINT` (`com.nokoprint`)
6. **Xiaomi Earbuds**: `Constants.COMPATIBILITY_XIAOMI_EARBUDS` (`com.mi.earphone`)

**Single Target Version Invariant**: Every target app maintains strictly ONE active version (the latest supported release) in `targets = listOf(AppTarget(...))`. Never retain older versions or multi-version entries in `targets`.

---

## 6. Diagnostic Telemetry Invariants & Harness Compliance

Every patch execution must emit concise, high-signal diagnostic telemetry captured by Morphe Manager / CLI logs (`[WARN] [STDIO]: [...]`). Patches must satisfy the following invariants tested by RE and audit harnesses:

1. **Standardized Prefix**: Every log line must start with the bracketed patch name prefix: `println("[Patch Name] ...")`.
2. **Dynamic Mutation Counter**: Track injected modifications with a local counter:
   ```kotlin
   var patched = 0
   ```
3. **Development Triage vs Final Zero-Mismatch Gate**: Wrap risky hook/fingerprint blocks in `try/catch` during development to allow partial degradation and pinpoint shifting targets without halting the suite prematurely:
   ```kotlin
   try {
       // fingerprint resolution and hook injection
       patched++
   } catch (e: Exception) {
       println("[Patch Name] Component note: ${e.message}")
   }
   ```
   **CRITICAL GATE**: Prior to final verification, committing, or release, every single fingerprint failure (`Failed to match the fingerprint`) MUST be resolved to the new shifted target or pruned if the feature was deleted upstream. Retaining failing fingerprints in production/committed code is strictly forbidden.
4. **Consolidated Summary Log**: Emit a quantifiable summary upon completing operations:
   ```kotlin
   println("[Patch Name] Successfully applied $patched hooks across target components.")
   ```
5. **Anti-Spam & Bounded Output**: Never dump thousands of lines or unbounded file trees. Repetitive items must be summarized or bounded to short representative samples (e.g. `.take(6)`).
6. **Failure & Guard Transparency**: If an operation is skipped or safely aborted (e.g. missing targets or preconditions), log an explicit descriptive reason so issues can be immediately diagnosed from user-submitted logs.
7. **Zero Emojis Policy**: Never use emojis in telemetry logs, exceptions, or console output. All logging must use clean, standard ASCII / plain-text formatting (e.g. `[INFO]`, `[WARN]`, `[PASS]`, `[FAIL]`).

---

## 7. Mandatory Verification Gate

Static checks are never enough. Run the in-situ patching gate defined in `AGENTS.md` (Section 3, Step 4): `./gradlew runPatchTest -Papp=<targetApp>` (targets: `gboard`, `tiktok`, `brave`, `hevy`, `nokoprint`, `xiaomi_earbuds`), adding `-PallOptions=true` when the change sits behind an option.
