/*
 * Copyright (c) Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#include 
#include "osdbg_thread_info.os.horizon.hpp"
#include "osdbg_thread_type.os.horizon.hpp"
#include "osdbg_thread_local_region.os.horizon.hpp"
namespace ams::osdbg::impl {
    namespace {
        Result GetThreadTypePointerFromThreadLocalRegion(uintptr_t *out, ThreadInfo *info) {
            /* Detect lp64. */
            const bool is_lp64 = IsLp64(info);
            /* Get the thread local region. */
            const auto *tlr_address = GetTargetThreadLocalRegion(info);
            /* Read the thread local region. */
            ThreadLocalRegionCommon tlr;
            R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast(std::addressof(tlr)), info->_debug_handle, reinterpret_cast(tlr_address), sizeof(tlr)));
            /* Detect libnx vs nintendo via magic number. */
            if (tlr.libnx.thread_vars.magic == LibnxThreadVars::Magic) {
                info->_thread_type_type = ThreadTypeType_Libnx;
                *out = reinterpret_cast(tlr.libnx.thread_vars.thread_ptr);
            } else {
                info->_thread_type_type = ThreadTypeType_Nintendo;
                *out = is_lp64 ? tlr.lp64.p_thread_type : tlr.ilp32.p_thread_type;
            }
            R_SUCCEED();
        }
        Result GetThreadArgumentAndStackPointer(u64 *out_arg, u64 *out_sp, ThreadInfo *info) {
            /* Read the thread context. */
            svc::ThreadContext thread_context;
            R_TRY(svc::GetDebugThreadContext(std::addressof(thread_context), info->_debug_handle, info->_debug_info_create_thread.thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control));
            /* Argument is in r0. */
            *out_arg = thread_context.r[0];
            /* Stack pointer varies by architecture. */
            if (Is64BitArch(info)) {
                *out_sp = thread_context.sp;
            } else {
                *out_sp = thread_context.r[13];
            }
            R_SUCCEED();
        }
        void DetectStratosphereThread(ThreadInfo *info) {
            /* Stratosphere threads are initially misdetected as libnx threads. */
            if (info->_thread_type_type != ThreadTypeType_Libnx || info->_thread_type == nullptr) {
                return;
            }
            /* Convert to a parent pointer. */
            os::ThreadType *stratosphere_ptr = util::GetParentPointer<&os::ThreadType::thread_impl_storage>(std::addressof(info->_thread_type->libnx));
            /* Read the magic. */
            u16 magic;
            if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast(std::addressof(magic)), info->_debug_handle, reinterpret_cast(std::addressof(stratosphere_ptr->magic)), sizeof(magic)))) {
                return;
            }
            /* Check the magic. */
            if (magic == os::ThreadType::Magic) {
                info->_thread_type_type = ThreadTypeType_Stratosphere;
                info->_thread_type      = reinterpret_cast(stratosphere_ptr);
            }
        }
    }
    void GetTargetThreadType(ThreadInfo *info) {
        /* Ensure we exit with correct state. */
        auto type_guard = SCOPE_GUARD {
            info->_thread_type      = nullptr;
            info->_thread_type_type = ThreadTypeType_Unknown;
        };
        /* Read the thread type pointer. */
        uintptr_t tlr_thread_type;
        if (R_FAILED(GetThreadTypePointerFromThreadLocalRegion(std::addressof(tlr_thread_type), info))) {
            return;
        }
        /* Handle the case where we have a thread type. */
        if (tlr_thread_type != 0) {
            info->_thread_type = reinterpret_cast(tlr_thread_type);
            DetectStratosphereThread(info);
            type_guard.Cancel();
            return;
        }
        /* Otherwise, the thread is just created, and we should read its context. */
        u64 arg, sp;
        if (R_FAILED(GetThreadArgumentAndStackPointer(std::addressof(arg), std::addressof(sp), info))) {
            return;
        }
        /* We may have been bamboozled into thinking a nintendo thread was a libnx thread, so check that. */
        /* Nintendo threads have argument=ThreadType, libnx threads have argument=ThreadEntryArgs. */
        if (info->_thread_type_type == ThreadTypeType_Nintendo && sp == arg) {
            /* It's a libnx thread, so we should parse the entry args. */
            info->_thread_type_type = ThreadTypeType_Libnx;
            /* Read the entry args. */
            LibnxThreadEntryArgs entry_args;
            if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast(std::addressof(entry_args)), info->_debug_handle, arg, sizeof(entry_args)))) {
                return;
            }
            info->_thread_type      = reinterpret_cast(entry_args.t);
        } else {
            info->_thread_type_type = ThreadTypeType_Nintendo;
            info->_thread_type      = reinterpret_cast(arg);
        }
        /* If we got the thread type, we don't need to reset our state. */
        if (info->_thread_type != nullptr) {
            type_guard.Cancel();
            DetectStratosphereThread(info);
        }
    }
}