//
// Created by VIP on 2021/4/25.
//

#include "bypass_sig.h"

#include "../src/native_api.h"
#include "elf_util.h"
#include "logging.h"
#include "native_util.h"
#include "patch_loader.h"
#include "utils/hook_helper.hpp"
#include "utils/jni_helper.hpp"

#include <atomic>

using lsplant::operator""_sym;

namespace lspd {

std::string apkPath;
std::string redirectPath;
bool openatHooked = false;
std::atomic_uint32_t nullOpenatPathCount = 0;

inline static constexpr auto kLibCName = "libc.so";

std::unique_ptr<const SandHook::ElfImg> &GetC(bool release = false) {
    static std::unique_ptr<const SandHook::ElfImg> kImg = nullptr;
    if (release) {
        kImg.reset();
    } else if (!kImg) {
        kImg = std::make_unique<SandHook::ElfImg>(kLibCName);
    }
    return kImg;
}

inline static auto __openat_ =
    "__openat"_sym.hook->*[]<lsplant::Backup auto backup>(int fd, const char *pathname, int flag,
                                                          int mode) static -> int {
    if (pathname == nullptr) [[unlikely]] {
        auto count = nullOpenatPathCount.fetch_add(1, std::memory_order_relaxed);
        if (count < 5) {
            LOGW("openat hook received null pathname fd={} flag={} mode={} count={}", fd, flag,
                 mode, count + 1);
        }
        return backup(fd, pathname, flag, mode);
    }
    if (pathname != nullptr && apkPath == pathname) {
        LOGD("Redirect openat from {} to {} fd={} flag={} mode={}", pathname, redirectPath, fd,
             flag, mode);
        return backup(fd, redirectPath.c_str(), flag, mode);
    }
    return backup(fd, pathname, flag, mode);
};

bool HookOpenat(const lsplant::HookHandler &handler) { return handler(__openat_); }

LSP_DEF_NATIVE_METHOD(void, SigBypass, enableOpenatHook, jstring origApkPath,
                      jstring cacheApkPath) {
    if (origApkPath == nullptr || cacheApkPath == nullptr) [[unlikely]] {
        LOGE("enableOpenatHook called with null args origApkPath={} cacheApkPath={}",
             origApkPath == nullptr, cacheApkPath == nullptr);
        return;
    }
    if (openatHooked) {
        LOGW("enableOpenatHook called after hook installed; keeping existing redirect {} -> {}",
             apkPath, redirectPath);
        return;
    }
    lsplant::JUTFString str1(env, origApkPath);
    lsplant::JUTFString str2(env, cacheApkPath);
    apkPath = str1.get();
    redirectPath = str2.get();
    LOGD("apkPath {}", apkPath.c_str());
    LOGD("redirectPath {}", redirectPath.c_str());
    if (!openatHooked) {
        auto r = HookOpenat(lsplant::InitInfo{
            .inline_hooker =
                [](auto t, auto r) {
                    void *bk = nullptr;
                    return HookInline(t, r, &bk) == 0 ? bk : nullptr;
                },
            .art_symbol_resolver = [](auto symbol) { return GetC()->getSymbAddress(symbol); },
        });
        if (!r) {
            LOGE("Hook __openat fail");
            return;
        }
        openatHooked = true;
        LOGI("Hook __openat installed for redirect {} -> {}", apkPath, redirectPath);
    }
    GetC(true);
}

static JNINativeMethod gMethods[] = {
    LSP_NATIVE_METHOD(SigBypass, enableOpenatHook, "(Ljava/lang/String;Ljava/lang/String;)V")};

void RegisterBypass(JNIEnv *env) { REGISTER_LSP_NATIVE_METHODS(SigBypass); }

}  // namespace lspd
