#include <android/log.h>
#include <dex_helper.h>
#include <jni.h>

#define LOG_TAG "QAuxv"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#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__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...)                                                    \
  LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))

namespace {
jfieldID token_field;
jfieldID class_loader_field;
jmethodID load_class_method;
// jmethodID get_declared_method;
// jmethodID get_declared_field;
jmethodID get_name_method;
jmethodID get_declaring_class_method;
jmethodID get_parameters_method;
jmethodID get_class_name_method;
jfieldID path_list_field;
jfieldID element_field;
jfieldID dex_file_field;
jfieldID cookie_field;

jclass LoadClass(JNIEnv *env, jobject class_loader,
                 std::string_view descriptor) {
    if (descriptor.empty())
        return nullptr;
    if (descriptor[0] != 'L')
        return nullptr;
    std::string name(descriptor.substr(1, descriptor.length() - 2));
    std::replace(name.begin(), name.end(), '/', '.');
    auto java_name = env->NewStringUTF(name.data());
    auto res = (jclass)env->CallObjectMethod(class_loader, load_class_method,
                                             java_name, false);
    return res;
}

std::string GetClassDescriptor(JNIEnv *env, jclass clazz) {
    auto java_name = (jstring)env->CallObjectMethod(clazz, get_class_name_method);
    auto name = env->GetStringUTFChars(java_name, nullptr);
    std::string descriptor = name;
    std::replace(descriptor.begin(), descriptor.end(), '.', '/');
    if (descriptor[0] != '[') {
        if (descriptor == "boolean") {
            descriptor = "Z";
        } else if (descriptor == "byte") {
            descriptor = "B";
        } else if (descriptor == "short") {
            descriptor = "S";
        } else if (descriptor == "char") {
            descriptor = "C";
        } else if (descriptor == "int") {
            descriptor = "I";
        } else if (descriptor == "long") {
            descriptor = "J";
        } else if (descriptor == "float") {
            descriptor = "F";
        } else if (descriptor == "double") {
            descriptor = "D";
        }
        descriptor = "L" + std::string(name) + ";";
    }
    env->ReleaseStringUTFChars(java_name, name);
    return descriptor;
}
} // namespace

struct MyDexFile {
  const void *begin_{};
  size_t size_{};
  const uint8_t *const data_begin_{};
  const size_t data_size_{};

  virtual ~MyDexFile() = default;
};

extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findMethodUsingString(
        JNIEnv *env, jobject thiz, jstring str, jboolean match_prefix,
        jlong return_type, jshort parameter_count, jstring parameter_shorty,
        jlong declaring_class, jlongArray parameter_types,
        jlongArray contains_parameter_types, jintArray dex_priority,
        jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    if (!str)
        return nullptr;
    auto str_ = env->GetStringUTFChars(str, nullptr);
    auto parameter_shorty_ =
            parameter_shorty ? env->GetStringUTFChars(parameter_shorty, nullptr)
                             : nullptr;
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    std::vector<size_t> parameter_types_;
    jlong *parameter_types_elements = nullptr;
    if (parameter_types) {
        parameter_types_elements =
                env->GetLongArrayElements(parameter_types, nullptr);
        parameter_types_.assign(parameter_types_elements,
                                parameter_types_elements +
                                        env->GetArrayLength(parameter_types));
    }

    std::vector<size_t> contains_parameter_types_;
    jlong *contains_parameter_types_elements = nullptr;
    if (contains_parameter_types) {
        contains_parameter_types_elements =
                env->GetLongArrayElements(contains_parameter_types, nullptr);
        contains_parameter_types_.assign(
                contains_parameter_types_elements,
                contains_parameter_types_elements +
                        env->GetArrayLength(contains_parameter_types));
    }

    auto out = helper->FindMethodUsingString(
            str_, match_prefix, return_type, parameter_count, parameter_shorty_ ? parameter_shorty_ : "",
            declaring_class, contains_parameter_types_, contains_parameter_types_,
            dex_priority_, find_first);

    env->ReleaseStringUTFChars(str, str_);
    if (parameter_shorty_)
        env->ReleaseStringUTFChars(parameter_shorty, parameter_shorty_);
    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);
    if (parameter_types_elements)
        env->ReleaseLongArrayElements(parameter_types, parameter_types_elements,
                                      JNI_ABORT);
    if (contains_parameter_types_elements)
        env->ReleaseLongArrayElements(contains_parameter_types,
                                      contains_parameter_types_elements, JNI_ABORT);
    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}

extern "C" JNIEXPORT jlong JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_load(JNIEnv *env, jobject thiz,
                                              jobject class_loader) {
    if (!class_loader)
        return 0;
    auto path_list = env->GetObjectField(class_loader, path_list_field);
    if (!path_list)
        return 0;
    auto elements = (jobjectArray)env->GetObjectField(path_list, element_field);
    if (!elements)
        return 0;
    std::vector<std::tuple<const void *, size_t, const void *, size_t>> images;
    for (auto i = 0, len = env->GetArrayLength(elements); i < len; ++i) {
        auto element = env->GetObjectArrayElement(elements, i);
        if (!element) continue;
        auto java_dex_file = env->GetObjectField(element, dex_file_field);
        if (!java_dex_file) continue;
        auto cookie = (jlongArray)env->GetObjectField(java_dex_file, cookie_field);
        if (!cookie) continue;
        auto dex_file_length = env->GetArrayLength(cookie);
        const auto *dex_files = reinterpret_cast<const MyDexFile **>(
                env->GetLongArrayElements(cookie, nullptr));
        while (dex_file_length-- > 1) {
            const auto *dex_file = dex_files[dex_file_length];
            if (!dex_file) {
                LOGE("dex_file is nullptr at %d", dex_file_length);
                continue;
            }
            LOGD("Got dex file %d", dex_file_length);
            if (dex::Reader::IsCompact(dex_file->begin_)) {
                images.emplace_back(dex_file->begin_, dex_file->size_, dex_file->data_begin_, dex_file->data_size_);
            } else {
                images.emplace_back(dex_file->begin_, dex_file->size_, nullptr, 0);
            }
        }
    }
    if (images.empty()) return 0;
    auto res = reinterpret_cast<jlong>(new DexHelper(images));
    env->SetLongField(thiz, token_field, res);
    return res;
}

extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findMethodInvoking(
        JNIEnv *env, jobject thiz, jlong method_index, jlong return_type,
        jshort parameter_count, jstring parameter_shorty, jlong declaring_class,
        jlongArray parameter_types, jlongArray contains_parameter_types,
        jintArray dex_priority, jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto parameter_shorty_ =
            parameter_shorty ? env->GetStringUTFChars(parameter_shorty, nullptr)
                             : nullptr;
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    std::vector<size_t> parameter_types_;
    jlong *parameter_types_elements = nullptr;
    if (parameter_types) {
        parameter_types_elements =
                env->GetLongArrayElements(parameter_types, nullptr);
        parameter_types_.assign(parameter_types_elements,
                                parameter_types_elements +
                                        env->GetArrayLength(parameter_types));
    }

    std::vector<size_t> contains_parameter_types_;
    jlong *contains_parameter_types_elements = nullptr;
    if (contains_parameter_types) {
        contains_parameter_types_elements =
                env->GetLongArrayElements(contains_parameter_types, nullptr);
        contains_parameter_types_.assign(
                contains_parameter_types_elements,
                contains_parameter_types_elements +
                        env->GetArrayLength(contains_parameter_types));
    }

    auto out = helper->FindMethodInvoking(
            method_index, return_type, parameter_count, parameter_shorty_ ? parameter_shorty_ : "",
            declaring_class, contains_parameter_types_, contains_parameter_types_,
            dex_priority_, find_first);

    if (parameter_shorty_)
        env->ReleaseStringUTFChars(parameter_shorty, parameter_shorty_);
    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);
    if (parameter_types_elements)
        env->ReleaseLongArrayElements(parameter_types, parameter_types_elements,
                                      JNI_ABORT);
    if (contains_parameter_types_elements)
        env->ReleaseLongArrayElements(contains_parameter_types,
                                      contains_parameter_types_elements, JNI_ABORT);
    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}
extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findMethodInvoked(
        JNIEnv *env, jobject thiz, jlong method_index, jlong return_type,
        jshort parameter_count, jstring parameter_shorty, jlong declaring_class,
        jlongArray parameter_types, jlongArray contains_parameter_types,
        jintArray dex_priority, jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto parameter_shorty_ =
            parameter_shorty ? env->GetStringUTFChars(parameter_shorty, nullptr)
                             : nullptr;
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    std::vector<size_t> parameter_types_;
    jlong *parameter_types_elements = nullptr;
    if (parameter_types) {
        parameter_types_elements =
                env->GetLongArrayElements(parameter_types, nullptr);
        parameter_types_.assign(parameter_types_elements,
                                parameter_types_elements +
                                        env->GetArrayLength(parameter_types));
    }

    std::vector<size_t> contains_parameter_types_;
    jlong *contains_parameter_types_elements = nullptr;
    if (contains_parameter_types) {
        contains_parameter_types_elements =
                env->GetLongArrayElements(contains_parameter_types, nullptr);
        contains_parameter_types_.assign(
                contains_parameter_types_elements,
                contains_parameter_types_elements +
                        env->GetArrayLength(contains_parameter_types));
    }

    auto out = helper->FindMethodInvoked(
            method_index, return_type, parameter_count, parameter_shorty_ ? parameter_shorty_ : "",
            declaring_class, contains_parameter_types_, contains_parameter_types_,
            dex_priority_, find_first);

    if (parameter_shorty_)
        env->ReleaseStringUTFChars(parameter_shorty, parameter_shorty_);
    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);
    if (parameter_types_elements)
        env->ReleaseLongArrayElements(parameter_types, parameter_types_elements,
                                      JNI_ABORT);
    if (contains_parameter_types_elements)
        env->ReleaseLongArrayElements(contains_parameter_types,
                                      contains_parameter_types_elements, JNI_ABORT);
    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}
extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findMethodSettingField(
        JNIEnv *env, jobject thiz, jlong field_index, jlong return_type,
        jshort parameter_count, jstring parameter_shorty, jlong declaring_class,
        jlongArray parameter_types, jlongArray contains_parameter_types,
        jintArray dex_priority, jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto parameter_shorty_ =
            parameter_shorty ? env->GetStringUTFChars(parameter_shorty, nullptr)
                             : nullptr;
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    std::vector<size_t> parameter_types_;
    jlong *parameter_types_elements = nullptr;
    if (parameter_types) {
        parameter_types_elements =
                env->GetLongArrayElements(parameter_types, nullptr);
        parameter_types_.assign(parameter_types_elements,
                                parameter_types_elements +
                                        env->GetArrayLength(parameter_types));
    }

    std::vector<size_t> contains_parameter_types_;
    jlong *contains_parameter_types_elements = nullptr;
    if (contains_parameter_types) {
        contains_parameter_types_elements =
                env->GetLongArrayElements(contains_parameter_types, nullptr);
        contains_parameter_types_.assign(
                contains_parameter_types_elements,
                contains_parameter_types_elements +
                        env->GetArrayLength(contains_parameter_types));
    }

    auto out = helper->FindMethodSettingField(
            field_index, return_type, parameter_count, parameter_shorty_ ? parameter_shorty_ : "",
            declaring_class, contains_parameter_types_, contains_parameter_types_,
            dex_priority_, find_first);

    if (parameter_shorty_)
        env->ReleaseStringUTFChars(parameter_shorty, parameter_shorty_);
    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);
    if (parameter_types_elements)
        env->ReleaseLongArrayElements(parameter_types, parameter_types_elements,
                                      JNI_ABORT);
    if (contains_parameter_types_elements)
        env->ReleaseLongArrayElements(contains_parameter_types,
                                      contains_parameter_types_elements, JNI_ABORT);
    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}
extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findMethodGettingField(
        JNIEnv *env, jobject thiz, jlong field_index, jlong return_type,
        jshort parameter_count, jstring parameter_shorty, jlong declaring_class,
        jlongArray parameter_types, jlongArray contains_parameter_types,
        jintArray dex_priority, jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto parameter_shorty_ =
            parameter_shorty ? env->GetStringUTFChars(parameter_shorty, nullptr)
                             : nullptr;
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    std::vector<size_t> parameter_types_;
    jlong *parameter_types_elements = nullptr;
    if (parameter_types) {
        parameter_types_elements =
                env->GetLongArrayElements(parameter_types, nullptr);
        parameter_types_.assign(parameter_types_elements,
                                parameter_types_elements +
                                        env->GetArrayLength(parameter_types));
    }

    std::vector<size_t> contains_parameter_types_;
    jlong *contains_parameter_types_elements = nullptr;
    if (contains_parameter_types) {
        contains_parameter_types_elements =
                env->GetLongArrayElements(contains_parameter_types, nullptr);
        contains_parameter_types_.assign(
                contains_parameter_types_elements,
                contains_parameter_types_elements +
                        env->GetArrayLength(contains_parameter_types));
    }

    auto out = helper->FindMethodGettingField(
            field_index, return_type, parameter_count, parameter_shorty_ ? parameter_shorty_ : "",
            declaring_class, contains_parameter_types_, contains_parameter_types_,
            dex_priority_, find_first);

    if (parameter_shorty_)
        env->ReleaseStringUTFChars(parameter_shorty, parameter_shorty_);
    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);
    if (parameter_types_elements)
        env->ReleaseLongArrayElements(parameter_types, parameter_types_elements,
                                      JNI_ABORT);
    if (contains_parameter_types_elements)
        env->ReleaseLongArrayElements(contains_parameter_types,
                                      contains_parameter_types_elements, JNI_ABORT);
    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}
extern "C" JNIEXPORT jlongArray JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_findField(JNIEnv *env, jobject thiz,
                                                   jlong type,
                                                   jintArray dex_priority,
                                                   jboolean find_first) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    std::vector<size_t> dex_priority_;
    jint *dex_priority_elements = nullptr;
    if (dex_priority) {
        dex_priority_elements = env->GetIntArrayElements(dex_priority, nullptr);
        dex_priority_.assign(dex_priority_elements,
                             dex_priority_elements +
                                     env->GetArrayLength(dex_priority));
    }

    auto out = helper->FindField(type, dex_priority_, find_first);

    if (dex_priority_elements)
        env->ReleaseIntArrayElements(dex_priority, dex_priority_elements,
                                     JNI_ABORT);

    auto res = env->NewLongArray(static_cast<int>(out.size()));
    auto res_element = env->GetLongArrayElements(res, nullptr);
    for (size_t i = 0; i < out.size(); ++i) {
        res_element[i] = out[i];
    }
    env->ReleaseLongArrayElements(res, res_element, 0);
    return res;
}

extern "C" JNIEXPORT jobject JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_decodeMethodIndex(JNIEnv *env,
                                                           jobject thiz,
                                                           jlong method_index) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto out = helper->DecodeMethod(method_index);
    auto cl = env->GetObjectField(thiz, class_loader_field);
    auto clazz = LoadClass(env, cl, out.declaring_class.name);
    env->DeleteLocalRef(cl);
    std::string sig;
    sig.reserve(4096);
    sig += "(";
    for (const auto &param : out.parameters) {
        sig += param.name;
    }
    sig += ")";
    sig += out.return_type.name;
    auto *method = env->GetMethodID(clazz, out.name.data(), sig.data());
    if ((*env).ExceptionCheck()) {
        (*env).ExceptionClear();
    }
    if (!method) {
        method = env->GetStaticMethodID(clazz, out.name.data(), sig.data());
    }
    return env->ToReflectedMethod(clazz, method, false);
}

extern "C" JNIEXPORT jobject JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_decodeFieldIndex(JNIEnv *env,
                                                          jobject thiz,
                                                          jlong field_index) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto out = helper->DecodeField(field_index);
    auto cl = env->GetObjectField(thiz, class_loader_field);
    auto clazz = LoadClass(env, cl, out.declaring_class.name);
    env->DeleteLocalRef(cl);
    return env->ToReflectedField(
            clazz, env->GetFieldID(clazz, out.name.data(), out.type.name.data()),
            false);
}

extern "C" JNIEXPORT jlong JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_encodeClassIndex(JNIEnv *env,
                                                          jobject thiz,
                                                          jclass clazz) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    return helper->CreateClassIndex(GetClassDescriptor(env, clazz));
}

extern "C" JNIEXPORT jlong JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_encodeFieldIndex(JNIEnv *env,
                                                          jobject thiz,
                                                          jobject field) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return -1;
    auto java_name = (jstring)env->CallObjectMethod(field, get_name_method);
    auto clazz = (jclass)env->CallObjectMethod(field, get_declaring_class_method);
    auto name = env->GetStringUTFChars(java_name, nullptr);
    auto res = helper->CreateFieldIndex(name, GetClassDescriptor(env, clazz));
    env->ReleaseStringUTFChars(java_name, name);
    return res;
}

extern "C" JNIEXPORT jlong JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_encodeMethodIndex(JNIEnv *env,
                                                           jobject thiz,
                                                           jobject method) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return -1;
    auto java_name = (jstring)env->CallObjectMethod(method, get_name_method);
    auto clazz =
            (jclass)env->CallObjectMethod(method, get_declaring_class_method);
    auto name = env->GetStringUTFChars(java_name, nullptr);
    auto params =
            (jobjectArray)env->CallObjectMethod(method, get_parameters_method);
    auto param_len = env->GetArrayLength(params);
    std::vector<std::string> param_descriptors;
    param_descriptors.reserve(param_len);
    for (size_t i = 0; i < param_len; ++i) {
        auto param = (jclass)env->GetObjectArrayElement(params, i);
        param_descriptors.emplace_back(GetClassDescriptor(env, param));
        env->DeleteLocalRef(param);
    }
    std::vector<std::string_view> params_name;
    params_name.reserve(param_descriptors.size());
    for (const auto &descriptor : param_descriptors) {
        params_name.emplace_back(descriptor);
    }
    auto res = helper->CreateMethodIndex(name, GetClassDescriptor(env, clazz),
                                         params_name);
    env->ReleaseStringUTFChars(java_name, name);
    return res;
}

extern "C" JNIEXPORT jclass JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_decodeClassIndex(JNIEnv *env,
                                                          jobject thiz,
                                                          jlong class_index) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return nullptr;
    auto out = helper->DecodeClass(class_index);
    auto cl = env->GetObjectField(thiz, class_loader_field);
    auto res = LoadClass(env, cl, out.name);
    env->DeleteLocalRef(cl);
    return res;
}

extern "C" JNIEXPORT void JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_close(JNIEnv *env, jobject thiz) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    env->SetLongField(thiz, token_field, jlong(0));
    delete helper;
}

extern "C" JNIEXPORT jint JNICALL BILI_JNI_OnLoad(JavaVM *vm, void *) {
    JNIEnv *env = nullptr;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    jclass helper = env->FindClass("me/iacn/biliroaming/utils/DexHelper");
    token_field = env->GetFieldID(helper, "token", "J");
    class_loader_field =
            env->GetFieldID(helper, "classLoader", "Ljava/lang/ClassLoader;");
    auto class_loader = env->FindClass("java/lang/ClassLoader");
    load_class_method = env->GetMethodID(
            class_loader, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;");
    auto member = env->FindClass("java/lang/reflect/Member");
    get_declaring_class_method =
            env->GetMethodID(member, "getDeclaringClass", "()Ljava/lang/Class;");
    get_name_method = env->GetMethodID(member, "getName", "()Ljava/lang/String;");
    auto executable = env->FindClass("java/lang/reflect/Executable");
    get_parameters_method =
            env->GetMethodID(executable, "getParameterTypes", "()[Ljava/lang/Class;");
    auto clazz = env->FindClass("java/lang/Class");
    get_class_name_method =
            env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
    auto dex_class_loader = env->FindClass("dalvik/system/BaseDexClassLoader");
    path_list_field = env->GetFieldID(dex_class_loader, "pathList",
                                      "Ldalvik/system/DexPathList;");
    auto dex_path_list = env->FindClass("dalvik/system/DexPathList");
    element_field = env->GetFieldID(dex_path_list, "dexElements",
                                    "[Ldalvik/system/DexPathList$Element;");
    auto element = env->FindClass("dalvik/system/DexPathList$Element");
    dex_file_field =
            env->GetFieldID(element, "dexFile", "Ldalvik/system/DexFile;");
    auto dex_file = env->FindClass("dalvik/system/DexFile");
    cookie_field = env->GetFieldID(dex_file, "mCookie", "Ljava/lang/Object;");
    return JNI_VERSION_1_4;
}

extern "C"
JNIEXPORT void JNICALL
Java_me_iacn_biliroaming_utils_DexHelper_createFullCache(JNIEnv *env, jobject thiz) {
    auto *helper =
            reinterpret_cast<DexHelper *>(env->GetLongField(thiz, token_field));
    if (!helper)
        return;
    helper->CreateFullCache();
}
