#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "service_guard.h"
#include <string.h>
#include "kernel/tmem.h"
#include "services/hiddbg.h"
#include "runtime/hosversion.h"

static Service g_hiddbgSrv;

static bool g_hiddbgHdlsInitialized;
static TransferMemory g_hiddbgHdlsTmem;
static void* g_hiddbgHdlsWorkmem;

static const u32 g_hiddbgDeviceTypeInternalTable[] = {
    BIT(20),    // DeviceType 0 Invalid
    BIT(0*4+2), // DeviceType 1 JoyRight
    BIT(0*4+1), // DeviceType 2 JoyLeft
    BIT(1*4+0), // DeviceType 3 FullKey
    BIT(1*4+1), // DeviceType 4 JoyLeft
    BIT(1*4+2), // DeviceType 5 JoyRight
    BIT(8),     // DeviceType 6 FullKey
    BIT(11),    // DeviceType 7 LarkLeft (HVC)
    BIT(12),    // DeviceType 8 LarkRight (HVC)
    BIT(13),    // DeviceType 9 LarkLeft (NES)
    BIT(14),    // DeviceType 10 LarkRight (NES)
    BIT(15),    // DeviceType 11 Invalid
    BIT(16),    // DeviceType 12 Palma (Invalid for DeviceTypeInternal)
    BIT(9),     // DeviceType 13 FullKey
    BIT(20),    // DeviceType 14 Invalid
    BIT(10),    // DeviceType 15 FullKey
    BIT(18),    // DeviceType 16 Invalid
    BIT(19),    // DeviceType 17 Invalid
    BIT(20),    // DeviceType 18 Invalid
    BIT(21),    // DeviceType 19 ::HidDeviceTypeBits_System with HidControllerType |= TYPE_PROCONTROLLER.
    BIT(22),    // DeviceType 20 ::HidDeviceTypeBits_System with HidControllerType |= TYPE_JOYCON_PAIR.
    BIT(23),    // DeviceType 21 ::HidDeviceType System with HidControllerType |= TYPE_JOYCON_PAIR.
};

NX_GENERATE_SERVICE_GUARD(hiddbg);

Result _hiddbgInitialize(void) {
    return smGetService(&g_hiddbgSrv, "hid:dbg");
}

void _hiddbgCleanup(void) {
    serviceClose(&g_hiddbgSrv);
}

Service* hiddbgGetServiceSession(void) {
    return &g_hiddbgSrv;
}

static Result _hiddbgCmdNoIO(u32 cmd_id) {
    return serviceDispatch(&g_hiddbgSrv, cmd_id);
}

static Result _hiddbgCmdInU8NoOut(u8 inval, u32 cmd_id) {
    return serviceDispatchIn(&g_hiddbgSrv, cmd_id, inval);
}

static Result _hiddbgCmdInBoolNoOut(bool inval, u32 cmd_id) {
    return _hiddbgCmdInU8NoOut(inval!=0, cmd_id);
}

static Result _hiddbgCmdInU64NoOut(u64 inval, u32 cmd_id) {
    return serviceDispatchIn(&g_hiddbgSrv, cmd_id, inval);
}

static Result _hiddbgCmdInHandle64NoOut(Handle handle, u64 inval, u32 cmd_id) {
    return serviceDispatchIn(&g_hiddbgSrv, cmd_id, inval,
        .in_num_handles = 1,
        .in_handles = { handle },
    );
}

static Result _hiddbgCmdInHandle64OutU64(Handle handle, u64 inval, u64 *out, u32 cmd_id) {
    return serviceDispatchInOut(&g_hiddbgSrv, cmd_id, inval, *out,
        .in_num_handles = 1,
        .in_handles = { handle },
    );
}

static Result _hiddbgCmdInTmemNoOut(TransferMemory *tmem, u32 cmd_id) {
    return _hiddbgCmdInHandle64NoOut(tmem->handle, tmem->size, cmd_id);
}

static Result _hiddbgCmdInTmemOutU64(TransferMemory *tmem, u64 *out, u32 cmd_id) {
    return _hiddbgCmdInHandle64OutU64(tmem->handle, tmem->size, out, cmd_id);
}

Result hiddbgSetDebugPadAutoPilotState(const HiddbgDebugPadAutoPilotState *state) {
    return serviceDispatchIn(&g_hiddbgSrv, 1, *state);
}

Result hiddbgUnsetDebugPadAutoPilotState(void) {
    return _hiddbgCmdNoIO(2);
}

Result hiddbgSetTouchScreenAutoPilotState(const HidTouchState *states, s32 count) {
    return serviceDispatch(&g_hiddbgSrv, 11,
        .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
        .buffers = { { states, count*sizeof(HidTouchState) } },
    );
}

Result hiddbgUnsetTouchScreenAutoPilotState(void) {
    return _hiddbgCmdNoIO(12);
}

Result hiddbgSetMouseAutoPilotState(const HiddbgMouseAutoPilotState *state) {
    return serviceDispatchIn(&g_hiddbgSrv, 21, *state);
}

Result hiddbgUnsetMouseAutoPilotState(void) {
    return _hiddbgCmdNoIO(22);
}

Result hiddbgSetKeyboardAutoPilotState(const HiddbgKeyboardAutoPilotState *state) {
    return serviceDispatchIn(&g_hiddbgSrv, 31, *state);
}

Result hiddbgUnsetKeyboardAutoPilotState(void) {
    return _hiddbgCmdNoIO(32);
}

Result hiddbgDeactivateHomeButton(void) {
    return _hiddbgCmdNoIO(110);
}

Result hiddbgSetSleepButtonAutoPilotState(const HiddbgSleepButtonAutoPilotState *state) {
    return serviceDispatchIn(&g_hiddbgSrv, 121, *state);
}

Result hiddbgUnsetSleepButtonAutoPilotState(void) {
    return _hiddbgCmdNoIO(122);
}

Result hiddbgUpdateControllerColor(u32 colorBody, u32 colorButtons, HidsysUniquePadId unique_pad_id) {
    if (hosversionBefore(3,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    const struct {
        u32 colorBody;
        u32 colorButtons;
        HidsysUniquePadId unique_pad_id;
    } in = { colorBody, colorButtons, unique_pad_id };

    return serviceDispatchIn(&g_hiddbgSrv, 221, in);
}

Result hiddbgUpdateDesignInfo(u32 colorBody, u32 colorButtons, u32 colorLeftGrip, u32 colorRightGrip, u8 inval, HidsysUniquePadId unique_pad_id) {
    if (hosversionBefore(5,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    const struct {
        u32 colorBody;
        u32 colorButtons;
        u32 colorLeftGrip;
        u32 colorRightGrip;
        u8 inval;
        u8 pad[7];
        HidsysUniquePadId unique_pad_id;
    } in = { colorBody, colorButtons, colorLeftGrip, colorRightGrip, inval, {0}, unique_pad_id };

    return serviceDispatchIn(&g_hiddbgSrv, 224, in);
}

Result hiddbgAcquireOperationEventHandle(Event* out_event, bool autoclear, HidsysUniquePadId unique_pad_id) {
    if (hosversionBefore(6,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    Handle tmp_handle = INVALID_HANDLE;
    Result rc = 0;

    rc = serviceDispatchIn(&g_hiddbgSrv, 228, unique_pad_id,
        .out_handle_attrs = { SfOutHandleAttr_HipcCopy },
        .out_handles = &tmp_handle,
    );
    if (R_SUCCEEDED(rc)) eventLoadRemote(out_event, tmp_handle, autoclear);
    return rc;
}

static Result _hiddbgReadSerialFlash(TransferMemory *tmem, u32 offset, u64 size, HidsysUniquePadId unique_pad_id) {
    const struct {
        u32 offset;
        u32 pad;
        u64 size;
        HidsysUniquePadId unique_pad_id;
    } in = { offset, 0, size, unique_pad_id };

    return serviceDispatchIn(&g_hiddbgSrv, 229, in,
        .in_num_handles = 1,
        .in_handles = { tmem->handle },
    );
}

static Result _hiddbgWriteSerialFlash(TransferMemory *tmem, u32 offset, u64 tmem_size, u64 size, HidsysUniquePadId unique_pad_id) {
    const struct {
        u32 offset;
        u32 pad;
        u64 tmem_size;
        u64 size;
        HidsysUniquePadId unique_pad_id;
    } in = { offset, 0, tmem_size, size, unique_pad_id };

    return serviceDispatchIn(&g_hiddbgSrv, 230, in,
        .in_num_handles = 1,
        .in_handles = { tmem->handle },
    );
}

// sdk-nso doesn't use hiddbgAcquireOperationEventHandle/hiddbgGetOperationResult in the *SerialFlash impl, those are used seperately by the user. [9.0.0+] sdk-nso no longer exposes *SerialFlash and related functionality.
Result hiddbgReadSerialFlash(u32 offset, void* buffer, size_t size, HidsysUniquePadId unique_pad_id) {
    Result rc=0;
    Event tmpevent={0};
    TransferMemory tmem;
    size_t sizealign = (size+0x1000) & ~0xfff;

    if (hosversionBefore(6,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    rc = tmemCreate(&tmem, sizealign, Perm_Rw);
    if (R_FAILED(rc)) return rc;

    rc = hiddbgAcquireOperationEventHandle(&tmpevent, true, unique_pad_id); // *Must* be used before _hiddbgReadSerialFlash.
    if (R_SUCCEEDED(rc)) rc = _hiddbgReadSerialFlash(&tmem, offset, size, unique_pad_id);
    if (R_SUCCEEDED(rc)) rc = eventWait(&tmpevent, UINT64_MAX);
    if (R_SUCCEEDED(rc)) rc = hiddbgGetOperationResult(unique_pad_id);
    if (R_SUCCEEDED(rc)) memcpy(buffer, tmem.src_addr, size);
    eventClose(&tmpevent);
    tmemClose(&tmem);
    return rc;
}

Result hiddbgWriteSerialFlash(u32 offset, void* buffer, size_t tmem_size, size_t size, HidsysUniquePadId unique_pad_id) {
    Result rc=0;
    Event tmpevent={0};
    TransferMemory tmem;

    if (hosversionBefore(6,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    rc = tmemCreateFromMemory(&tmem, buffer, tmem_size, Perm_R);
    if (R_FAILED(rc)) return rc;

    rc = hiddbgAcquireOperationEventHandle(&tmpevent, true, unique_pad_id); // *Must* be used before _hiddbgWriteSerialFlash.
    if (R_SUCCEEDED(rc)) rc = _hiddbgWriteSerialFlash(&tmem, offset, tmem_size, size, unique_pad_id);
    if (R_SUCCEEDED(rc)) rc = eventWait(&tmpevent, UINT64_MAX);
    if (R_SUCCEEDED(rc)) rc = hiddbgGetOperationResult(unique_pad_id);
    eventClose(&tmpevent);
    tmemClose(&tmem);
    return rc;
}

Result hiddbgGetOperationResult(HidsysUniquePadId unique_pad_id) {
    if (hosversionBefore(6,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    return _hiddbgCmdInU64NoOut(unique_pad_id.id, 231);
}

Result hiddbgGetUniquePadDeviceTypeSetInternal(HidsysUniquePadId unique_pad_id, u32 *out) {
    if (hosversionBefore(6,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    u32 tmp=0;
    Result rc = serviceDispatchInOut(&g_hiddbgSrv, 234, unique_pad_id, tmp);
    if (R_SUCCEEDED(rc) && out) { // Pre-9.0.0 output is an u32, with [9.0.0+] it's an u8.
        if (hosversionBefore(9,0,0))
            *out = tmp;
        else
            *out = tmp & 0xFF;
    }
    return rc;
}

Result hiddbgGetAbstractedPadHandles(HiddbgAbstractedPadHandle *handles, s32 count, s32 *total_out) {
    if (hosversionBefore(5,0,0) || hosversionAtLeast(9,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return serviceDispatchOut(&g_hiddbgSrv, 301, *total_out,
        .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_Out },
        .buffers = { { handles, count*sizeof(HiddbgAbstractedPadHandle) } },
    );
}

Result hiddbgGetAbstractedPadState(HiddbgAbstractedPadHandle handle, HiddbgAbstractedPadState *state) {
    if (hosversionBefore(5,0,0) || hosversionAtLeast(9,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return serviceDispatchInOut(&g_hiddbgSrv, 302, handle, *state);
}

Result hiddbgGetAbstractedPadsState(HiddbgAbstractedPadHandle *handles, HiddbgAbstractedPadState *states, s32 count, s32 *total_out) {
    if (hosversionBefore(5,0,0) || hosversionAtLeast(9,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return serviceDispatchOut(&g_hiddbgSrv, 303, *total_out,
        .buffer_attrs = {
            SfBufferAttr_HipcPointer | SfBufferAttr_Out,
            SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out,
        },
        .buffers = {
            { handles, count*sizeof(HiddbgAbstractedPadHandle) },
            { states, count*sizeof(HiddbgAbstractedPadState) },
        },
    );
}

Result hiddbgSetAutoPilotVirtualPadState(s8 AbstractedVirtualPadId, const HiddbgAbstractedPadState *state) {
    if (hosversionBefore(5,0,0) || hosversionAtLeast(9,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    const struct {
        s8 AbstractedVirtualPadId;
        u8 pad[7];
        HiddbgAbstractedPadState state;
    } in = { AbstractedVirtualPadId, {0}, *state };

    return serviceDispatchIn(&g_hiddbgSrv, 321, in);
}

Result hiddbgUnsetAutoPilotVirtualPadState(s8 AbstractedVirtualPadId) {
    if (hosversionBefore(5,0,0) || hosversionAtLeast(9,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return _hiddbgCmdInU8NoOut(AbstractedVirtualPadId, 322);
}

Result hiddbgUnsetAllAutoPilotVirtualPadState(void) {
    if (hosversionBefore(5,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return _hiddbgCmdNoIO(323);
}

static u32 _hiddbgConvertDeviceTypeToDeviceTypeInternal(u8 deviceType) {
    if (deviceType >= sizeof(g_hiddbgDeviceTypeInternalTable)/sizeof(u32)) return g_hiddbgDeviceTypeInternalTable[0];
    return g_hiddbgDeviceTypeInternalTable[deviceType];
}

static u8 _hiddbgConvertDeviceTypeInternalToDeviceType(u32 deviceType) {
    for (u32 i=0; i<sizeof(g_hiddbgDeviceTypeInternalTable)/sizeof(u32); i++) {
        if (deviceType == g_hiddbgDeviceTypeInternalTable[i]) return i;
    }
    return 0;
}

static void _hiddbgConvertHdlsDeviceInfoToV7(HiddbgHdlsDeviceInfoV7 *out, const HiddbgHdlsDeviceInfo *in) {
    memset(out, 0, sizeof(*out));

    out->deviceTypeInternal = _hiddbgConvertDeviceTypeToDeviceTypeInternal(in->deviceType);
    out->singleColorBody = in->singleColorBody;
    out->singleColorButtons = in->singleColorButtons;
    out->npadInterfaceType = in->npadInterfaceType;
}

static void _hiddbgConvertHdlsDeviceInfoFromV7(HiddbgHdlsDeviceInfo *out, const HiddbgHdlsDeviceInfoV7 *in) {
    memset(out, 0, sizeof(*out));

    out->deviceType = _hiddbgConvertDeviceTypeInternalToDeviceType(in->deviceTypeInternal);
    out->npadInterfaceType = in->npadInterfaceType;
    out->singleColorBody = in->singleColorBody;
    out->singleColorButtons = in->singleColorButtons;
    //Leave color*Grip at zero since V7 doesn't have those.
}

static void _hiddbgConvertHiddbgHdlsStateToV7(HiddbgHdlsStateV7 *out, const HiddbgHdlsState *in) {
    memset(out, 0, sizeof(*out));

    out->is_powered = (in->flags & BIT(0)) != 0;
    out->flags = (in->flags & BIT(1)) != 0;
    out->battery_level = in->battery_level;
    out->buttons = in->buttons;
    memcpy(&out->analog_stick_l, &in->analog_stick_l, sizeof(in->analog_stick_l));
    memcpy(&out->analog_stick_r, &in->analog_stick_r, sizeof(in->analog_stick_r));
    out->indicator = in->indicator;
}

static void _hiddbgConvertHiddbgHdlsStateToV9(HiddbgHdlsStateV9 *out, const HiddbgHdlsState *in) {
    memset(out, 0, sizeof(*out));

    out->battery_level = in->battery_level;
    out->flags = in->flags;
    out->buttons = in->buttons;
    memcpy(&out->analog_stick_l, &in->analog_stick_l, sizeof(in->analog_stick_l));
    memcpy(&out->analog_stick_r, &in->analog_stick_r, sizeof(in->analog_stick_r));
    out->indicator = in->indicator;
}

static void _hiddbgConvertHiddbgHdlsStateFromV7(HiddbgHdlsState *out, const HiddbgHdlsStateV7 *in) {
    memset(out, 0, sizeof(*out));

    out->battery_level = in->battery_level;
    out->flags = (in->is_powered & 1) | ((in->flags & 1)<<1);
    out->buttons = in->buttons;
    memcpy(&out->analog_stick_l, &in->analog_stick_l, sizeof(in->analog_stick_l));
    memcpy(&out->analog_stick_r, &in->analog_stick_r, sizeof(in->analog_stick_r));
    out->indicator = in->indicator;
}

static void _hiddbgConvertHiddbgHdlsStateFromV9(HiddbgHdlsState *out, const HiddbgHdlsStateV9 *in) {
    memset(out, 0, sizeof(*out));

    out->battery_level = in->battery_level;
    out->flags = in->flags;
    out->buttons = in->buttons;
    memcpy(&out->analog_stick_l, &in->analog_stick_l, sizeof(in->analog_stick_l));
    memcpy(&out->analog_stick_r, &in->analog_stick_r, sizeof(in->analog_stick_r));
    out->indicator = in->indicator;
}

static void _hiddbgConvertHdlsStateListToV7(HiddbgHdlsStateListV7 *out, const HiddbgHdlsStateList *in) {
    s32 count;
    memset(out, 0, sizeof(*out));
    out->total_entries = in->total_entries;
    count = out->total_entries > 0x10 ? 0x10 : out->total_entries;

    for (s32 i=0; i<count; i++) {
        out->entries[i].handle = in->entries[i].handle;
        _hiddbgConvertHdlsDeviceInfoToV7(&out->entries[i].device, &in->entries[i].device);
        _hiddbgConvertHiddbgHdlsStateToV7(&out->entries[i].state, &in->entries[i].state);
    }
}

static void _hiddbgConvertHdlsStateListToV9(HiddbgHdlsStateListV9 *out, const HiddbgHdlsStateList *in) {
    s32 count;
    memset(out, 0, sizeof(*out));
    out->total_entries = in->total_entries;
    count = out->total_entries > 0x10 ? 0x10 : out->total_entries;

    for (s32 i=0; i<count; i++) {
        out->entries[i].handle = in->entries[i].handle;
        memcpy(&out->entries[i].device, &in->entries[i].device, sizeof(in->entries[i].device));
        _hiddbgConvertHiddbgHdlsStateToV9(&out->entries[i].state, &in->entries[i].state);
    }
}

static void _hiddbgConvertHdlsStateListFromV7(HiddbgHdlsStateList *out, const HiddbgHdlsStateListV7 *in) {
    s32 count;
    memset(out, 0, sizeof(*out));
    out->total_entries = in->total_entries;
    count = out->total_entries > 0x10 ? 0x10 : out->total_entries;

    for (s32 i=0; i<count; i++) {
        out->entries[i].handle = in->entries[i].handle;
        _hiddbgConvertHdlsDeviceInfoFromV7(&out->entries[i].device, &in->entries[i].device);
        _hiddbgConvertHiddbgHdlsStateFromV7(&out->entries[i].state, &in->entries[i].state);
    }
}

static void _hiddbgConvertHdlsStateListFromV9(HiddbgHdlsStateList *out, const HiddbgHdlsStateListV9 *in) {
    s32 count;
    memset(out, 0, sizeof(*out));
    out->total_entries = in->total_entries;
    count = out->total_entries > 0x10 ? 0x10 : out->total_entries;

    for (s32 i=0; i<count; i++) {
        out->entries[i].handle = in->entries[i].handle;
        memcpy(&out->entries[i].device, &in->entries[i].device, sizeof(in->entries[i].device));
        _hiddbgConvertHiddbgHdlsStateFromV9(&out->entries[i].state, &in->entries[i].state);
    }
}

static Result _hiddbgAttachHdlsWorkBuffer(HiddbgHdlsSessionId *session_id, TransferMemory *tmem) {
    if (hosversionBefore(13,0,0))
        return _hiddbgCmdInTmemNoOut(tmem, 324);
    else
        return _hiddbgCmdInTmemOutU64(tmem, &session_id->id, 324);
}

Result hiddbgAttachHdlsWorkBuffer(HiddbgHdlsSessionId *session_id, void *buffer, size_t size) {
    Result rc=0;

    if (session_id) session_id->id = 0;

    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized);

    rc = tmemCreateFromMemory(&g_hiddbgHdlsTmem, buffer, size, Perm_Rw);
    if (R_FAILED(rc)) return rc;

    rc = _hiddbgAttachHdlsWorkBuffer(session_id, &g_hiddbgHdlsTmem);
    if (R_FAILED(rc)) tmemClose(&g_hiddbgHdlsTmem);
    if (R_SUCCEEDED(rc)) {
        g_hiddbgHdlsInitialized = true;
        g_hiddbgHdlsWorkmem = buffer;
    }
    return rc;
}

Result hiddbgReleaseHdlsWorkBuffer(HiddbgHdlsSessionId session_id) {
    Result rc=0;

    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    g_hiddbgHdlsInitialized = false;

    if (hosversionBefore(13,0,0))
        rc = _hiddbgCmdNoIO(325);
    else
        rc = _hiddbgCmdInU64NoOut(session_id.id, 325);

    tmemClose(&g_hiddbgHdlsTmem);
    g_hiddbgHdlsWorkmem = NULL;
    return rc;
}

Result hiddbgIsHdlsVirtualDeviceAttached(HiddbgHdlsSessionId session_id, HiddbgHdlsHandle handle, bool *out) {
    Result rc = 0;

    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (hosversionBefore(13,0,0))
        rc = _hiddbgCmdNoIO(327);
    else
        rc = _hiddbgCmdInU64NoOut(session_id.id, 327);

    if (R_FAILED(rc)) return rc;
    if (out) {
        *out = false;
        if (hosversionBefore(9,0,0)) {
            HiddbgHdlsStateListV7 *stateList = (HiddbgHdlsStateListV7*)g_hiddbgHdlsWorkmem;
            for (s32 i=0; i<stateList->total_entries; i++) {
                if (stateList->entries[i].handle.handle == handle.handle) {
                    *out = true;
                    break;
                }
            }
        }
        else if (hosversionBefore(12,0,0)) {
            HiddbgHdlsStateListV9 *stateList = (HiddbgHdlsStateListV9*)g_hiddbgHdlsWorkmem;
            for (s32 i=0; i<stateList->total_entries; i++) {
                if (stateList->entries[i].handle.handle == handle.handle) {
                    *out = true;
                    break;
                }
            }
        }
        else {
            HiddbgHdlsStateList *stateList = (HiddbgHdlsStateList*)g_hiddbgHdlsWorkmem;
            for (s32 i=0; i<stateList->total_entries; i++) {
                if (stateList->entries[i].handle.handle == handle.handle) {
                    *out = true;
                    break;
                }
            }
        }
    }
    return rc;
}

Result hiddbgDumpHdlsNpadAssignmentState(HiddbgHdlsSessionId session_id, HiddbgHdlsNpadAssignment *state) {
    Result rc=0;

    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (hosversionBefore(13,0,0))
        rc = _hiddbgCmdNoIO(326);
    else
        rc = _hiddbgCmdInU64NoOut(session_id.id, 326);

    if (R_FAILED(rc)) return rc;
    if (state) memcpy(state, g_hiddbgHdlsWorkmem, sizeof(*state));
    return rc;
}

Result hiddbgDumpHdlsStates(HiddbgHdlsSessionId session_id, HiddbgHdlsStateList *state) {
    Result rc=0;

    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (hosversionBefore(13,0,0))
        rc = _hiddbgCmdNoIO(327);
    else
        rc = _hiddbgCmdInU64NoOut(session_id.id, 327);

    if (R_FAILED(rc)) return rc;
    if (state) {
        if (hosversionBefore(9,0,0)) {
            HiddbgHdlsStateListV7 statev7;
            memcpy(&statev7, g_hiddbgHdlsWorkmem, sizeof(statev7));
            _hiddbgConvertHdlsStateListFromV7(state, &statev7);
        }
        else if (hosversionBefore(12,0,0)) {
            HiddbgHdlsStateListV9 statev9;
            memcpy(&statev9, g_hiddbgHdlsWorkmem, sizeof(statev9));
            _hiddbgConvertHdlsStateListFromV9(state, &statev9);
        }
        else
            memcpy(state, g_hiddbgHdlsWorkmem, sizeof(*state));
    }
    return rc;
}

Result hiddbgApplyHdlsNpadAssignmentState(HiddbgHdlsSessionId session_id, const HiddbgHdlsNpadAssignment *state, bool flag) {
    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (state==NULL)
        return MAKERESULT(Module_Libnx, LibnxError_BadInput);

    memcpy(g_hiddbgHdlsWorkmem, state, sizeof(*state));

    if (hosversionBefore(13,0,0))
        return _hiddbgCmdInBoolNoOut(flag, 328);
    else {
        const struct {
            u8 flag;
            u8 pad[7];
            HiddbgHdlsSessionId session_id;
        } in = { flag, {0}, session_id };

        return serviceDispatchIn(&g_hiddbgSrv, 328, in);
    }
}

Result hiddbgApplyHdlsStateList(HiddbgHdlsSessionId session_id, const HiddbgHdlsStateList *state) {
    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (state==NULL)
        return MAKERESULT(Module_Libnx, LibnxError_BadInput);

    if (hosversionBefore(9,0,0)) {
        HiddbgHdlsStateListV7 statev7;
        _hiddbgConvertHdlsStateListToV7(&statev7, state);
        memcpy(g_hiddbgHdlsWorkmem, &statev7, sizeof(statev7));
    }
    else if (hosversionBefore(12,0,0)) {
        HiddbgHdlsStateListV9 statev9;
        _hiddbgConvertHdlsStateListToV9(&statev9, state);
        memcpy(g_hiddbgHdlsWorkmem, &statev9, sizeof(statev9));
    }
    else
        memcpy(g_hiddbgHdlsWorkmem, state, sizeof(*state));

    if (hosversionBefore(13,0,0))
        return _hiddbgCmdNoIO(329);
    else
        return _hiddbgCmdInU64NoOut(session_id.id, 329);
}

static Result _hiddbgAttachHdlsVirtualDeviceV7(HiddbgHdlsHandle *handle, const HiddbgHdlsDeviceInfoV7 *info) {
    return serviceDispatchInOut(&g_hiddbgSrv, 330, *info, *handle);
}

static Result _hiddbgAttachHdlsVirtualDevice(HiddbgHdlsHandle *handle, const HiddbgHdlsDeviceInfo *info) {
    return serviceDispatchInOut(&g_hiddbgSrv, 330, *info, *handle);
}

Result hiddbgAttachHdlsVirtualDevice(HiddbgHdlsHandle *handle, const HiddbgHdlsDeviceInfo *info) {
    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (hosversionBefore(9,0,0)) {
        HiddbgHdlsDeviceInfoV7 infov7;
        _hiddbgConvertHdlsDeviceInfoToV7(&infov7, info);
        return _hiddbgAttachHdlsVirtualDeviceV7(handle, &infov7);
    }
    else
        return _hiddbgAttachHdlsVirtualDevice(handle, info);
}

Result hiddbgDetachHdlsVirtualDevice(HiddbgHdlsHandle handle) {
    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return _hiddbgCmdInU64NoOut(handle.handle, 331);
}

static Result _hiddbgSetHdlsStateV7(HiddbgHdlsHandle handle, const HiddbgHdlsState *state) {
    struct {
        HiddbgHdlsStateV7 state;
        HiddbgHdlsHandle handle;
    } in = { .handle = handle };
    _hiddbgConvertHiddbgHdlsStateToV7(&in.state, state);

    return serviceDispatchIn(&g_hiddbgSrv, 332, in);
}

static Result _hiddbgSetHdlsStateV9(HiddbgHdlsHandle handle, const HiddbgHdlsState *state) {
    struct {
        HiddbgHdlsHandle handle;
        HiddbgHdlsStateV9 state;
    } in = { .handle = handle };
    _hiddbgConvertHiddbgHdlsStateToV9(&in.state, state);

    return serviceDispatchIn(&g_hiddbgSrv, 332, in);
}

static Result _hiddbgSetHdlsState(HiddbgHdlsHandle handle, const HiddbgHdlsState *state) {
    const struct {
        HiddbgHdlsHandle handle;
        HiddbgHdlsState state;
    } in = { handle, *state };

    return serviceDispatchIn(&g_hiddbgSrv, 332, in);
}

Result hiddbgSetHdlsState(HiddbgHdlsHandle handle, const HiddbgHdlsState *state) {
    if (hosversionBefore(7,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    if (!g_hiddbgHdlsInitialized)
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    if (hosversionBefore(9,0,0))
        return _hiddbgSetHdlsStateV7(handle, state);
    else if (hosversionBefore(12,0,0))
        return _hiddbgSetHdlsStateV9(handle, state);
    else
        return _hiddbgSetHdlsState(handle, state);
}