#include <jni.h>

// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
//    static {
//       System.loadLibrary("customtoastview");
//    }
//
// Or, in MainActivity.kt:
//    companion object {
//      init {
//         System.loadLibrary("customtoastview")
//      }
//    }

#include <jni.h>
#include <string>
#include <android/log.h>
#include <link.h>
#include <unistd.h>
#include <fcntl.h>

#define LOG_TAG "IntegrityGuard"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,  LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// Helper Macro untuk pengecekan error dan pembersihan cepat
#define CHECK_EXCEPTION(env) \
    if (env->ExceptionCheck()) { \
        env->ExceptionDescribe(); \
        env->ExceptionClear(); \
        return; \
    }

// Helper Function: dpToPx (Versi Bersih)
int dpToPx(JNIEnv *env, jobject context, int dp) {
    jclass contextClass = env->GetObjectClass(context);
    jmethodID getResourcesId = env->GetMethodID(contextClass, "getResources", "()Landroid/content/res/Resources;");
    jobject resources = env->CallObjectMethod(context, getResourcesId);

    jclass resourcesClass = env->GetObjectClass(resources);
    jmethodID getDisplayMetricsId = env->GetMethodID(resourcesClass, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
    jobject displayMetrics = env->CallObjectMethod(resources, getDisplayMetricsId);

    jclass typedValueClass = env->FindClass("android/util/TypedValue");
    jmethodID applyDimensionId = env->GetStaticMethodID(typedValueClass, "applyDimension", "(IFLandroid/util/DisplayMetrics;)F");

    float pixels = env->CallStaticFloatMethod(typedValueClass, applyDimensionId, 1, (float)dp, displayMetrics);

    // --- CLEANUP ---
    env->DeleteLocalRef(contextClass);
    env->DeleteLocalRef(resources);
    env->DeleteLocalRef(resourcesClass);
    env->DeleteLocalRef(displayMetrics);
    env->DeleteLocalRef(typedValueClass);

    return (int)pixels;
}

// ============================================================================
// MARKER MACRO DEFINITIONS FOR MULTI-ARCH
// ============================================================================

#if defined(__aarch64__)
// --- ARM64 (AArch64) ---
    // Instruksi: 'b' (Branch relative)
    #define START_MARKER_ASM \
        asm volatile ( \
            "b 1f \n" \
            ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \
            "1: \n" \
            ::: "memory" \
        )

    #define END_MARKER_ASM \
        asm volatile ( \
            "b 2f \n" \
            ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \
            "2: \n" \
            ::: "memory" \
        )

#elif defined(__arm__)
// --- ARM32 (armeabi-v7a) ---
    // Instruksi: 'b' (Branch) sama, tapi layout instruksi 4-byte (ARM) atau 2-byte (Thumb)
    // Aman menggunakan 'b' karena assembler menanganinya.
    #define START_MARKER_ASM \
        asm volatile ( \
            "b 1f \n" \
            ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \
            "1: \n" \
            ::: "memory" \
        )

    #define END_MARKER_ASM \
        asm volatile ( \
            "b 2f \n" \
            ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \
            "2: \n" \
            ::: "memory" \
        )

#elif defined(__x86_64__)
// --- x86_64 (Intel 64-bit) ---
    // Instruksi: 'jmp' (Short jump 2-byte biasanya cukup untuk melompati 4 byte)
    #define START_MARKER_ASM \
        asm volatile ( \
            "jmp 1f \n" \
            ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \
            "1: \n" \
            ::: "memory" \
        )

    #define END_MARKER_ASM \
        asm volatile ( \
            "jmp 2f \n" \
            ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \
            "2: \n" \
            ::: "memory" \
        )

#elif defined(__i386__)
// --- x86 (Intel 32-bit) ---
// Instruksi: 'jmp'
#define START_MARKER_ASM \
        asm volatile ( \
            "jmp 1f \n" \
            ".byte 0xDB, 0x6A, 0x27, 0x4F \n" \
            "1: \n" \
            ::: "memory" \
        )

#define END_MARKER_ASM \
        asm volatile ( \
            "jmp 2f \n" \
            ".byte 0x4C, 0x91, 0x3B, 0xE2 \n" \
            "2: \n" \
            ::: "memory" \
        )

#else
// --- Unknown Architecture ---
    // Fallback: Jangan pakai branch, berisiko crash SIGILL.
    // Opsi aman: compile error atau empty dummy.
    // Kita pilih error agar developer sadar.
    #error "Unsupported architecture for integrity markers!"
#endif

// --- FUNGSI UTAMA ---
void showCustomToastNative_ibr_icall_split_sub_fla_bcf_(JNIEnv *env, jobject context, const char *messageStr) {
    START_MARKER_ASM;

    // Pastikan frame JNI stack baru dibuat.
    // Ini membantu membersihkan referensi lokal otomatis jika fungsi return.
    if (env->PushLocalFrame(128) < 0) {
        return; // Out of memory
    }

    // --- 0. PREPARE CLASSES & METHODS (Cache ID untuk performa, tapi di sini kita load inline) ---
    // Load semua class yang diperlukan di awal untuk memudahkan cleanup jika error
    jclass colorClass = env->FindClass("android/graphics/Color");
    jclass relativeLayoutClass = env->FindClass("android/widget/RelativeLayout");
    jclass linearLayoutClass = env->FindClass("android/widget/LinearLayout");
    jclass viewClass = env->FindClass("android/view/View");
    jclass groupLpClass = env->FindClass("android/view/ViewGroup$LayoutParams");
    jclass gradientDrawableClass = env->FindClass("android/graphics/drawable/GradientDrawable");
    jclass relativeLpClass = env->FindClass("android/widget/RelativeLayout$LayoutParams");
    jclass textViewClass = env->FindClass("android/widget/TextView");
    jclass frameLayoutClass = env->FindClass("android/widget/FrameLayout");
    jclass marginLpClass = env->FindClass("android/view/ViewGroup$MarginLayoutParams");
    jclass imageViewClass = env->FindClass("android/widget/ImageView");
    jclass frameLpClass = env->FindClass("android/widget/FrameLayout$LayoutParams");
    jclass toastClass = env->FindClass("android/widget/Toast");
    jclass viewGroupClass = env->FindClass("android/view/ViewGroup");

    // --- CONSTANTS ---
    jmethodID parseColorId = env->GetStaticMethodID(colorClass, "parseColor", "(Ljava/lang/String;)I");
    jstring colorString = env->NewStringUTF("#FF4747");
    int toastColor = env->CallStaticIntMethod(colorClass, parseColorId, colorString);
    env->DeleteLocalRef(colorString); // Hapus string segera setelah dipakai

    int gravityCenter = 17;
    int iconSize = dpToPx(env, context, 40);
    int iconPadding = dpToPx(env, context, 8);
    int gapBetweenIconAndText = dpToPx(env, context, 8);
    int iconBgSize = iconSize + (iconPadding * 2);

    // --- 1. ROOT LAYOUT ---
    jmethodID relativeLayoutCtor = env->GetMethodID(relativeLayoutClass, "<init>", "(Landroid/content/Context;)V");
    jobject rootLayout = env->NewObject(relativeLayoutClass, relativeLayoutCtor, context);

    jmethodID groupLpCtor = env->GetMethodID(groupLpClass, "<init>", "(II)V");
    jobject rootParams = env->NewObject(groupLpClass, groupLpCtor, -2, -2); // WRAP_CONTENT

    jmethodID setLayoutParamsGroup = env->GetMethodID(viewClass, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V");
    env->CallVoidMethod(rootLayout, setLayoutParamsGroup, rootParams);
    env->DeleteLocalRef(rootParams); // Hapus params segera

    jmethodID setClipChildren = env->GetMethodID(viewGroupClass, "setClipChildren", "(Z)V");
    jmethodID setClipToPadding = env->GetMethodID(viewGroupClass, "setClipToPadding", "(Z)V");
    env->CallVoidMethod(rootLayout, setClipChildren, JNI_FALSE);
    env->CallVoidMethod(rootLayout, setClipToPadding, JNI_FALSE);

    jmethodID setPaddingId = env->GetMethodID(viewClass, "setPadding", "(IIII)V");
    env->CallVoidMethod(rootLayout, setPaddingId, 0, iconBgSize / 2, 0, 0);

    // --- 2. CONTAINER TEXT ---
    jmethodID linearLayoutCtor = env->GetMethodID(linearLayoutClass, "<init>", "(Landroid/content/Context;)V");
    jobject contentContainer = env->NewObject(linearLayoutClass, linearLayoutCtor, context);

    jmethodID generateViewId = env->GetStaticMethodID(viewClass, "generateViewId", "()I");
    int containerId = env->CallStaticIntMethod(viewClass, generateViewId);
    jmethodID setId = env->GetMethodID(viewClass, "setId", "(I)V");
    env->CallVoidMethod(contentContainer, setId, containerId);

    jmethodID setOrientation = env->GetMethodID(linearLayoutClass, "setOrientation", "(I)V");
    jmethodID setGravityLinear = env->GetMethodID(linearLayoutClass, "setGravity", "(I)V");
    env->CallVoidMethod(contentContainer, setOrientation, 1);
    env->CallVoidMethod(contentContainer, setGravityLinear, gravityCenter);

    int dynamicPaddingTop = (iconBgSize / 2) + gapBetweenIconAndText;
    int paddingBottom = dpToPx(env, context, 16);
    int paddingSide = dpToPx(env, context, 32);
    env->CallVoidMethod(contentContainer, setPaddingId, paddingSide, dynamicPaddingTop, paddingSide, paddingBottom);

    // Background Body
    jmethodID gradientCtor = env->GetMethodID(gradientDrawableClass, "<init>", "()V");
    jobject bodyShape = env->NewObject(gradientDrawableClass, gradientCtor);

    jmethodID setShape = env->GetMethodID(gradientDrawableClass, "setShape", "(I)V");
    jmethodID setColorDrawable = env->GetMethodID(gradientDrawableClass, "setColor", "(I)V");
    jmethodID setCornerRadius = env->GetMethodID(gradientDrawableClass, "setCornerRadius", "(F)V");

    env->CallVoidMethod(bodyShape, setShape, 0);
    env->CallVoidMethod(bodyShape, setColorDrawable, toastColor);
    env->CallVoidMethod(bodyShape, setCornerRadius, (float)dpToPx(env, context, 24));

    jmethodID setBackground = env->GetMethodID(viewClass, "setBackground", "(Landroid/graphics/drawable/Drawable;)V");
    env->CallVoidMethod(contentContainer, setBackground, bodyShape);
    env->DeleteLocalRef(bodyShape); // Hapus shape

    // Container Params
    jmethodID relativeLpCtor = env->GetMethodID(relativeLpClass, "<init>", "(II)V");
    jobject containerParams = env->NewObject(relativeLpClass, relativeLpCtor, -2, -2);
    jmethodID addRule = env->GetMethodID(relativeLpClass, "addRule", "(I)V");
    env->CallVoidMethod(containerParams, addRule, 14);
    env->CallVoidMethod(contentContainer, setLayoutParamsGroup, containerParams);
    env->DeleteLocalRef(containerParams);

    // --- 3. TEXTVIEW ---
    jmethodID textViewCtor = env->GetMethodID(textViewClass, "<init>", "(Landroid/content/Context;)V");
    jobject textView = env->NewObject(textViewClass, textViewCtor, context);

    jmethodID setText = env->GetMethodID(textViewClass, "setText", "(Ljava/lang/CharSequence;)V");
    jmethodID setTextColor = env->GetMethodID(textViewClass, "setTextColor", "(I)V");
    jmethodID setTypeface = env->GetMethodID(textViewClass, "setTypeface", "(Landroid/graphics/Typeface;I)V");
    jmethodID setGravityText = env->GetMethodID(textViewClass, "setGravity", "(I)V");
    jmethodID setTextSize = env->GetMethodID(textViewClass, "setTextSize", "(IF)V");

    jstring msgJString = env->NewStringUTF(messageStr);
    env->CallVoidMethod(textView, setText, msgJString);
    env->DeleteLocalRef(msgJString); // Hapus string pesan segera

    env->CallVoidMethod(textView, setTextColor, -1); // 0xFFFFFFFF or use (jint)0xFFFFFFFF
    env->CallVoidMethod(textView, setTypeface, (jobject)nullptr, 1);
    env->CallVoidMethod(textView, setGravityText, 17);
    env->CallVoidMethod(textView, setTextSize, 2, 14.0f);

    jmethodID addView = env->GetMethodID(viewGroupClass, "addView", "(Landroid/view/View;)V");
    env->CallVoidMethod(contentContainer, addView, textView);
    env->DeleteLocalRef(textView); // TextView sudah dipegang container, hapus ref lokal

    // Add Container to Root
    env->CallVoidMethod(rootLayout, addView, contentContainer);
    env->DeleteLocalRef(contentContainer); // Sudah dipegang root

    // --- 4. ICON WRAPPER ---
    jmethodID frameLayoutCtor = env->GetMethodID(frameLayoutClass, "<init>", "(Landroid/content/Context;)V");
    jobject iconWrapper = env->NewObject(frameLayoutClass, frameLayoutCtor, context);

    jobject iconBgShape = env->NewObject(gradientDrawableClass, gradientCtor);
    env->CallVoidMethod(iconBgShape, setShape, 1); // OVAL
    env->CallVoidMethod(iconBgShape, setColorDrawable, toastColor);
    env->CallVoidMethod(iconWrapper, setBackground, iconBgShape);
    env->DeleteLocalRef(iconBgShape);

    jobject wrapperParams = env->NewObject(relativeLpClass, relativeLpCtor, iconBgSize, iconBgSize);
    env->CallVoidMethod(wrapperParams, addRule, 14);
    jmethodID addRuleAnchor = env->GetMethodID(relativeLpClass, "addRule", "(II)V");
    env->CallVoidMethod(wrapperParams, addRuleAnchor, 6, containerId);

    jfieldID topMarginField = env->GetFieldID(marginLpClass, "topMargin", "I");
    env->SetIntField(wrapperParams, topMarginField, -(iconBgSize / 2));

    env->CallVoidMethod(iconWrapper, setLayoutParamsGroup, wrapperParams);
    env->DeleteLocalRef(wrapperParams);

    // --- 5. IMAGEVIEW ---
    jmethodID imageViewCtor = env->GetMethodID(imageViewClass, "<init>", "(Landroid/content/Context;)V");
    jobject iconView = env->NewObject(imageViewClass, imageViewCtor, context);

    jmethodID frameLpCtor = env->GetMethodID(frameLpClass, "<init>", "(II)V");
    jobject iconParams = env->NewObject(frameLpClass, frameLpCtor, iconSize, iconSize);
    jfieldID gravityField = env->GetFieldID(frameLpClass, "gravity", "I");
    env->SetIntField(iconParams, gravityField, gravityCenter);
    env->CallVoidMethod(iconView, setLayoutParamsGroup, iconParams);
    env->DeleteLocalRef(iconParams);

    // Get App Icon
    jclass contextClass = env->GetObjectClass(context);
    jmethodID getPackageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject pm = env->CallObjectMethod(context, getPackageManager);
    jmethodID getPackageName = env->GetMethodID(contextClass, "getPackageName", "()Ljava/lang/String;");
    auto pkgName = (jstring)env->CallObjectMethod(context, getPackageName);

    jclass pmClass = env->GetObjectClass(pm);
    jmethodID getAppIcon = env->GetMethodID(pmClass, "getApplicationIcon", "(Ljava/lang/String;)Landroid/graphics/drawable/Drawable;");

    jobject appIconDrawable = nullptr;
    // Try catch block aman untuk exception
    if (!env->ExceptionCheck()) {
        appIconDrawable = env->CallObjectMethod(pm, getAppIcon, pkgName);
        if (env->ExceptionCheck()) {
            env->ExceptionClear(); // Hapus exception jika icon tidak ketemu
            appIconDrawable = nullptr;
        }
    }

    if (appIconDrawable != nullptr) {
        jmethodID setImageDrawable = env->GetMethodID(imageViewClass, "setImageDrawable", "(Landroid/graphics/drawable/Drawable;)V");
        env->CallVoidMethod(iconView, setImageDrawable, appIconDrawable);
        env->DeleteLocalRef(appIconDrawable);
    }
    // Cleanup PM objects
    env->DeleteLocalRef(contextClass);
    env->DeleteLocalRef(pm);
    env->DeleteLocalRef(pkgName);
    env->DeleteLocalRef(pmClass);

    env->CallVoidMethod(iconWrapper, addView, iconView);
    env->DeleteLocalRef(iconView);

    env->CallVoidMethod(rootLayout, addView, iconWrapper);
    env->DeleteLocalRef(iconWrapper);

    // --- 6. SHOW TOAST ---
    jmethodID makeText = env->GetStaticMethodID(toastClass, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
    jstring emptyStr = env->NewStringUTF("");
    jobject dummyToast = env->CallStaticObjectMethod(toastClass, makeText, context, emptyStr, 0);
    env->DeleteLocalRef(emptyStr);

    jmethodID getGravity = env->GetMethodID(toastClass, "getGravity", "()I");
    jmethodID getXOffset = env->GetMethodID(toastClass, "getXOffset", "()I");
    jmethodID getYOffset = env->GetMethodID(toastClass, "getYOffset", "()I");

    int dGravity = env->CallIntMethod(dummyToast, getGravity);
    int dX = env->CallIntMethod(dummyToast, getXOffset);
    int dY = env->CallIntMethod(dummyToast, getYOffset);
    env->DeleteLocalRef(dummyToast); // Hapus dummy

    jmethodID toastCtor = env->GetMethodID(toastClass, "<init>", "(Landroid/content/Context;)V");
    jobject toast = env->NewObject(toastClass, toastCtor, context);

    jmethodID setGravityToast = env->GetMethodID(toastClass, "setGravity", "(III)V");
    jmethodID setDuration = env->GetMethodID(toastClass, "setDuration", "(I)V");
    jmethodID setView = env->GetMethodID(toastClass, "setView", "(Landroid/view/View;)V");
    jmethodID show = env->GetMethodID(toastClass, "show", "()V");

    env->CallVoidMethod(toast, setGravityToast, dGravity, dX, dY);
    env->CallVoidMethod(toast, setDuration, 1);
    env->CallVoidMethod(toast, setView, rootLayout);
    env->CallVoidMethod(toast, show);

    // Root layout sekarang dipegang Toast, tapi kita masih pegang referensi lokalnya.
    // PushLocalFrame akan otomatis menghapus 'toast', 'rootLayout', dan semua 'jclass'
    // ketika kita memanggil PopLocalFrame.

    // --- CLEANUP OTOMATIS ---
    // PopLocalFrame menghapus SEMUA local reference yang dibuat SEJAK PushLocalFrame.
    // Ini adalah cara paling aman dan ringkas untuk mencegah leak.
    env->PopLocalFrame(nullptr);

    END_MARKER_ASM;
}

// Konteks untuk callback
typedef struct {
    void     *target;
    uintptr_t base;
    size_t    size;
    int       found;
} find_ctx_t;

static int phdr_cb(struct dl_phdr_info *info, size_t size, void *data) {
    (void)size;
    auto *ctx = (find_ctx_t *)data;
    auto t = (uintptr_t)ctx->target;

    for (int i = 0; i < info->dlpi_phnum; ++i) {
        const ElfW(Phdr) *ph = &info->dlpi_phdr[i];

        if (ph->p_type != PT_LOAD)
            continue;
        if ((ph->p_flags & PF_X) == 0)
            continue; // hanya segmen executable (text)[web:20][web:160][web:161]

        uintptr_t seg_start = (uintptr_t)info->dlpi_addr + ph->p_vaddr;
        uintptr_t seg_end   = seg_start + ph->p_memsz;

        if (t >= seg_start && t < seg_end) {
            ctx->base  = seg_start;
            ctx->size  = ph->p_memsz;
            ctx->found = 1;
            return 1; // stop iterasi
        }
    }
    return 0;
}

int get_code_segment_for_addr(void *func_addr, uintptr_t *out_base, size_t *out_size) {
    if (!func_addr || !out_base || !out_size)
        return -1;

    find_ctx_t ctx;
    memset(&ctx, 0, sizeof(ctx));
    ctx.target = func_addr;

    dl_iterate_phdr(phdr_cb, &ctx); // standard di glibc & Bionic untuk enumerasi objek ELF runtime.[web:5][web:134]

    if (!ctx.found)
        return -2;

    *out_base = ctx.base;
    *out_size = ctx.size;
    return 0;
}

int elf64_get_code_segment_from_file(const char *path,
                                     uint64_t *out_file_offset,
                                     uint64_t *out_file_size) {
    if (!path || !out_file_offset || !out_file_size) {
        LOGE("elf64_get_code_segment_from_file: invalid args");
        return -1;
    }

    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        LOGE("elf64_get_code_segment_from_file: open('%s') failed, errno=%d", path, errno);
        return -2;
    }

    Elf64_Ehdr eh;
    ssize_t n = read(fd, &eh, sizeof(eh));
    if (n != (ssize_t)sizeof(eh)) {
        LOGE("elf64_get_code_segment_from_file: read ehdr failed, n=%zd", n);
        close(fd);
        return -3;
    }

    // Validasi magic ELF dan kelas 64-bit
    if (memcmp(eh.e_ident, ELFMAG, SELFMAG) != 0) {
        LOGE("elf64_get_code_segment_from_file: not an ELF file");
        close(fd);
        return -4;
    }

    if (eh.e_ident[EI_CLASS] != ELFCLASS64) {
        LOGE("elf64_get_code_segment_from_file: not ELF64, EI_CLASS=%d", eh.e_ident[EI_CLASS]);
        close(fd);
        return -5;  // di sini nanti bisa kamu ganti tambah parser ELF32 kalau perlu
    }

    LOGI("ELF '%s': phoff=0x%llx, phnum=%u, phentsize=%u",
         path,
         (unsigned long long)eh.e_phoff,
         (unsigned)eh.e_phnum,
         (unsigned)eh.e_phentsize);

    // Lompat ke program header table
    if (lseek(fd, (off_t)eh.e_phoff, SEEK_SET) < 0) {
        LOGE("elf64_get_code_segment_from_file: lseek to phoff failed");
        close(fd);
        return -6;
    }

    // Validasi ukuran & jumlah program header
    if (eh.e_phnum == 0 || eh.e_phentsize != sizeof(Elf64_Phdr)) {
        LOGE("elf64_get_code_segment_from_file: invalid phnum=%u phentsize=%u",
             (unsigned)eh.e_phnum, (unsigned)eh.e_phentsize);
        close(fd);
        return -7;
    }

    Elf64_Phdr ph;
    int found = 0;

    for (uint16_t i = 0; i < eh.e_phnum; ++i) {
        n = read(fd, &ph, sizeof(ph));
        if (n != (ssize_t)sizeof(ph)) {
            LOGE("elf64_get_code_segment_from_file: read phdr[%u] failed, n=%zd", (unsigned)i, n);
            close(fd);
            return -8;
        }

        LOGI("PH[%u]: type=0x%x flags=0x%x off=0x%llx vaddr=0x%llx filesz=0x%llx memsz=0x%llx",
             (unsigned)i,
             ph.p_type,
             ph.p_flags,
             (unsigned long long)ph.p_offset,
             (unsigned long long)ph.p_vaddr,
             (unsigned long long)ph.p_filesz,
             (unsigned long long)ph.p_memsz);

        if (ph.p_type != PT_LOAD)
            continue;
        if ((ph.p_flags & PF_X) == 0)
            continue; // hanya segmen executable (kode)[web:20][web:162]

        // Ambil segmen pertama yang executable sebagai segmen kode utama
        *out_file_offset = ph.p_offset;
        *out_file_size   = ph.p_filesz;
        found = 1;
        LOGI("Code segment found: off=0x%llx size=0x%llx",
             (unsigned long long)*out_file_offset,
             (unsigned long long)*out_file_size);
        break;
    }

    close(fd);
    if (!found) {
        LOGE("elf64_get_code_segment_from_file: no PT_LOAD | PF_X segment found");
        return -9;
    }
    return 0;
}

// Hash contoh (ganti SHA-256 kalau mau lebih kuat)
static uint32_t simple_hash(const uint8_t *data, size_t len) {
    uint32_t h = 0x12345678u;
    for (size_t i = 0; i < len; ++i) {
        h ^= data[i];
        h = (h << 5) | (h >> (32 - 5));
    }
    return h;
}

static uint8_t *find_pattern(uint8_t *hay, size_t hay_len,
                             const uint8_t *needle, size_t needle_len) {
    if (!hay || !needle || needle_len == 0 || hay_len < needle_len)
        return NULL;

    uint8_t first = needle[0];
    uint8_t *p = hay;
    uint8_t *end = hay + hay_len - needle_len + 1;

    while (p < end) {
        if (*p == first) {
            if (memcmp(p, needle, needle_len) == 0)
                return p;
        }
        ++p;
    }
    return NULL;
}

#define START_MARK_LEN 4
#define END_MARK_LEN   4

// --- Helper untuk membangun marker secara runtime (Anti-Self-Detection) ---
static void get_start_marker(uint8_t *buf) {
    buf[0] = 0xDB;
    buf[1] = 0x6A;
    buf[2] = 0x27;
    buf[3] = 0x4F;
}

static void get_end_marker(uint8_t *buf) {
    buf[0] = 0x4C;
    buf[1] = 0x91;
    buf[2] = 0x3B;
    buf[3] = 0xE2;
}

/* ---------- Hash dari MEMORY (dl_iterate_phdr) ---------- */

int integrity_hash_memory(void *marker_func, uint32_t *out_hash) {
    if (!marker_func || !out_hash)
        return -1;

    uintptr_t base;
    size_t size;

    int r = get_code_segment_for_addr(marker_func, &base, &size);
    if (r != 0)
        return -2;

    auto *code = (uint8_t *)base;

    // 1. Bangun marker di stack (runtime)
    uint8_t scan_start[START_MARK_LEN];
    uint8_t scan_end[END_MARK_LEN];
    get_start_marker(scan_start);
    get_end_marker(scan_end);

    // 2. Cari START marker
    uint8_t *start_mark = find_pattern(
            code, size, scan_start, START_MARK_LEN);
    if (!start_mark)
        return -3;

    // 3. Cari END marker setelah START
    uint8_t *search_from = start_mark + START_MARK_LEN;
    size_t remain = (code + size) - search_from;

    uint8_t *end_mark = find_pattern(
            search_from, remain, scan_end, END_MARK_LEN);
    if (!end_mark)
        return -4;

    uint8_t *range_begin = start_mark + START_MARK_LEN;
    uint8_t *range_end   = end_mark;

    LOGI("MEMORY: base=%p start=%p end=%p range_len=%zu",
         (void*)base, start_mark, end_mark, (size_t)(range_end - range_begin));

    if (range_end <= range_begin)
        return -5;

    auto range_len = (size_t)(range_end - range_begin);
    *out_hash = simple_hash(range_begin, range_len);
    return 0;
}

/* ---------- Hash dari FILE ELF di DISK ---------- */

int integrity_hash_file(const char *elf_path, uint32_t *out_hash) {
    if (!elf_path || !out_hash)
        return -1;

    uint64_t off, size;
    int r = elf64_get_code_segment_from_file(elf_path, &off, &size);
    LOGI("FILE: elf64_get_code_segment_from_file('%s') = %d, off=0x%llx size=0x%llx",
         elf_path, r, (unsigned long long)off, (unsigned long long)size);
    if (r != 0)
        return -2;

    int fd = open(elf_path, O_RDONLY);
    if (fd < 0)
        return -3;

    auto *buf = (uint8_t *)malloc(size);
    if (!buf) {
        close(fd);
        return -4;
    }

    if (lseek(fd, (off_t)off, SEEK_SET) < 0) {
        free(buf);
        close(fd);
        return -5;
    }

    ssize_t n = read(fd, buf, (size_t)size);
    close(fd);
    if (n != (ssize_t)size) {
        free(buf);
        return -6;
    }

    // 1. Bangun marker di stack (runtime)
    uint8_t scan_start[START_MARK_LEN];
    uint8_t scan_end[END_MARK_LEN];
    get_start_marker(scan_start);
    get_end_marker(scan_end);

    // 2. Cari START marker
    uint8_t *start_mark = find_pattern(buf, (size_t)size,
                                       scan_start, START_MARK_LEN);
    if (!start_mark) {
        free(buf);
        return -7;
    }

    // 3. Cari END marker setelah START
    uint8_t *search_from = start_mark + START_MARK_LEN;
    size_t remain = (buf + size) - search_from;

    uint8_t *end_mark = find_pattern(search_from, remain,
                                     scan_end, END_MARK_LEN);
    if (!end_mark) {
        free(buf);
        return -8;
    }

    uint8_t *range_begin = start_mark + START_MARK_LEN;
    uint8_t *range_end   = end_mark;

    LOGI("FILE: buf_base=%p start_offset=%zu end_offset=%zu range_len=%zu",
         buf, (size_t)(start_mark - buf), (size_t)(end_mark - buf), (size_t)(range_end - range_begin));

    if (range_end <= range_begin) {
        free(buf);
        return -9;
    }

    size_t range_len = (size_t)(range_end - range_begin);
    *out_hash = simple_hash(range_begin, range_len);
    free(buf);
    return 0;
}

/* ---------- Bandingkan hash memory vs file ---------- */

int integrity_compare_self(const char *elf_path, void *marker_func) {
    uint32_t h_mem, h_file;
    int r1 = integrity_hash_memory(marker_func, &h_mem);
    LOGI("hash memory = %d, hash = %d", r1, h_mem);
    if (r1 != 0)
        return -10 + r1;



    int r2 = integrity_hash_file(elf_path, &h_file);
    LOGI("hash file = %d, hash = %d", r2, h_file);
    if (r2 != 0)
        return -20 + r2;

    return (h_mem == h_file) ? 0 : 1;
}

typedef struct {
    const char *lib_name;
    char       *out_path;
    size_t      out_size;
    int         found;
} so_find_ctx_t;

static int has_suffix(const char *str, const char *suffix) {
    size_t len_str = strlen(str);
    size_t len_suf = strlen(suffix);
    if (len_suf > len_str) return 0;
    return memcmp(str + (len_str - len_suf), suffix, len_suf) == 0;
}

static int so_iter_cb(struct dl_phdr_info *info, size_t size, void *data) {
    (void)size;
    so_find_ctx_t *ctx = (so_find_ctx_t *)data;

    const char *name = info->dlpi_name;
    if (!name || name[0] == '\0') {
        // entry pertama sering kosong untuk main exe[web:145]
        return 0;
    }

    if (has_suffix(name, ctx->lib_name)) {
        size_t len = strlen(name);
        if (len + 1 <= ctx->out_size) {
            memcpy(ctx->out_path, name, len + 1);
            ctx->found = 1;
            return 1; // stop iterasi
        }
    }
    return 0;
}

int find_so_full_path(const char *lib_name, char *out_path, size_t out_size) {
    if (!lib_name || !out_path || out_size == 0)
        return -1;

    so_find_ctx_t ctx;
    ctx.lib_name = lib_name;
    ctx.out_path = out_path;
    ctx.out_size = out_size;
    ctx.found    = 0;

    dl_iterate_phdr(so_iter_cb, &ctx); // enumerasi semua shared object yang sudah di-load.[web:5][web:168]

    return ctx.found ? 0 : -2;
}

static bool get_self_lib_path(JNIEnv *env, jobject context,
                              const char *lib_name,
                              char *out_path, size_t out_size) {
    if (!env || !context || !lib_name || !out_path || out_size == 0)
        return false;

    jclass ctxCls = env->GetObjectClass(context);
    jmethodID midGetAppInfo = env->GetMethodID(
            ctxCls, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
    jobject appInfo = env->CallObjectMethod(context, midGetAppInfo);

    jclass appInfoCls = env->GetObjectClass(appInfo);
    jfieldID fidNativeDir = env->GetFieldID(
            appInfoCls, "nativeLibraryDir", "Ljava/lang/String;");
    jstring jNativeDir = (jstring) env->GetObjectField(appInfo, fidNativeDir);

    const char *nativeDir = env->GetStringUTFChars(jNativeDir, nullptr);
    if (!nativeDir) {
        env->DeleteLocalRef(jNativeDir);
        env->DeleteLocalRef(appInfoCls);
        env->DeleteLocalRef(appInfo);
        env->DeleteLocalRef(ctxCls);
        return false;
    }

    // Build path: <nativeLibraryDir>/<lib_name>
    // mis: "/data/app/.../lib/arm64/libcustomtoastview.so"
    int written = snprintf(out_path, out_size, "%s/%s", nativeDir, lib_name);

    env->ReleaseStringUTFChars(jNativeDir, nativeDir);
    env->DeleteLocalRef(jNativeDir);
    env->DeleteLocalRef(appInfoCls);
    env->DeleteLocalRef(appInfo);
    env->DeleteLocalRef(ctxCls);

    return written > 0 && (size_t)written < out_size;
}


extern "C"
JNIEXPORT void JNICALL
Java_com_example_customtoastview_MainActivity_showToast(JNIEnv *env, jobject thiz, jstring str) {
    // 1. Konversi jstring (Java String) ke const char* (C String)
    const char *nativeString = env->GetStringUTFChars(str, nullptr);

    char path[512];

    // Misal library yang ingin dicek: "libhello.so"
    if (!get_self_lib_path(env, thiz, "libcustomtoastview.so", path, sizeof(path))) {
        LOGE("Cannot resolve libcustomtoastview.so path from nativeLibraryDir");
        return;
    }

    LOGI("Resolved libhello.so path: %s", path);

    int res = integrity_compare_self(path, (void *) showCustomToastNative_ibr_icall_split_sub_fla_bcf_);
    if (res == 0) {
        LOGI("Integrity OK for libhello.so");
    } else if (res == 1) {
        LOGE("Integrity MISMATCH for libhello.so (possible hook/patch)");
    } else {
        LOGE("Integrity check error: %d", res);
    }

    // 2. Panggil fungsi implementasi kita
    // 'thiz' di sini adalah MainActivity, yang juga merupakan Context.
    // Jadi bisa langsung dipassing sebagai argumen context.
    showCustomToastNative_ibr_icall_split_sub_fla_bcf_(env, thiz, nativeString);

    // 3. Lepaskan string C (PENTING! Jangan lupa ini atau memory leak)
    env->ReleaseStringUTFChars(str, nativeString);
}