use super::*;

pub(super) unsafe fn install_shell_state_service_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = SHELL_STATE_SERVICE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_state_service_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_STATE_SERVICE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_profile_startup_dispatch_hook() -> bool {
    const STOLEN_LEN: usize = 16;
    let target = PROFILE_STARTUP_DISPATCH_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            profile_startup_dispatch_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { PROFILE_STARTUP_DISPATCH_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_runtime_reset_hook() -> bool {
    const STOLEN_LEN: usize = 16;
    let target = RUNTIME_RESET_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            runtime_reset_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { RUNTIME_RESET_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_allocator_hook() -> bool {
    const STOLEN_LEN: usize = 5;
    let target = STARTUP_RUNTIME_ALLOC_THUNK_ADDR as *mut u8;
    let trampoline =
        unsafe { install_rel32_detour(target, STOLEN_LEN, allocator_detour as *const () as usize) };
    if trampoline == 0 {
        return false;
    }
    unsafe { ALLOCATOR_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_load_screen_scalar_hook() -> bool {
    const STOLEN_LEN: usize = 10;
    let target = LOAD_SCREEN_SET_SCALAR_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            load_screen_scalar_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { LOAD_SCREEN_SCALAR_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_load_screen_construct_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = LOAD_SCREEN_CONSTRUCT_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            load_screen_construct_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { LOAD_SCREEN_CONSTRUCT_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_load_screen_message_hook() -> bool {
    const STOLEN_LEN: usize = 25;
    let target = LOAD_SCREEN_HANDLE_MESSAGE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            load_screen_message_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { LOAD_SCREEN_MESSAGE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_runtime_prime_hook() -> bool {
    const STOLEN_LEN: usize = 12;
    let target = SHELL_RUNTIME_PRIME_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            runtime_prime_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { RUNTIME_PRIME_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_frame_cycle_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = SHELL_FRAME_CYCLE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(target, STOLEN_LEN, frame_cycle_detour as *const () as usize)
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { FRAME_CYCLE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_object_service_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = SHELL_OBJECT_SERVICE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            object_service_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { OBJECT_SERVICE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_child_service_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = SHELL_CHILD_SERVICE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            child_service_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { CHILD_SERVICE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_publish_hook() -> bool {
    const STOLEN_LEN: usize = 6;
    let target = SHELL_PUBLISH_WINDOW_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_publish_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_PUBLISH_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_unpublish_hook() -> bool {
    const STOLEN_LEN: usize = 10;
    let target = SHELL_UNPUBLISH_WINDOW_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_unpublish_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_UNPUBLISH_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_object_teardown_hook() -> bool {
    const STOLEN_LEN: usize = 7;
    let target = SHELL_OBJECT_TEARDOWN_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_object_teardown_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_OBJECT_TEARDOWN_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_object_range_remove_hook() -> bool {
    const STOLEN_LEN: usize = 12;
    let target = SHELL_OBJECT_RANGE_REMOVE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_object_range_remove_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_OBJECT_RANGE_REMOVE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_node_vcall_hook() -> bool {
    const STOLEN_LEN: usize = 21;
    let target = SHELL_NODE_VCALL_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_node_vcall_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_NODE_VCALL_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_shell_remove_node_hook() -> bool {
    const STOLEN_LEN: usize = 10;
    let target = SHELL_REMOVE_NODE_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            shell_remove_node_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { SHELL_REMOVE_NODE_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_mode2_teardown_hook() -> bool {
    const STOLEN_LEN: usize = 14;
    let target = MODE2_TEARDOWN_ADDR as *mut u8;
    let trampoline = unsafe {
        install_rel32_detour(
            target,
            STOLEN_LEN,
            mode2_teardown_detour as *const () as usize,
        )
    };
    if trampoline == 0 {
        return false;
    }
    unsafe { MODE2_TEARDOWN_TRAMPOLINE = trampoline };
    true
}

pub(super) unsafe fn install_rel32_detour(
    target: *mut u8,
    stolen_len: usize,
    detour: usize,
) -> usize {
    let trampoline_size = stolen_len + 5;
    let trampoline = unsafe {
        VirtualAlloc(
            ptr::null_mut(),
            trampoline_size,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE,
        )
    } as *mut u8;
    if trampoline.is_null() {
        return 0;
    }

    unsafe { ptr::copy_nonoverlapping(target, trampoline, stolen_len) };
    unsafe { write_rel32_jump(trampoline.add(stolen_len), target.add(stolen_len) as usize) };

    let mut old_protect = 0_u32;
    if unsafe {
        VirtualProtect(
            target.cast(),
            stolen_len,
            PAGE_EXECUTE_READWRITE,
            &mut old_protect,
        )
    } == 0
    {
        return 0;
    }

    unsafe { write_rel32_jump(target, detour) };
    for offset in 5..stolen_len {
        unsafe { ptr::write(target.add(offset), 0x90) };
    }
    let mut restore_protect = 0_u32;
    let _ = unsafe { VirtualProtect(target.cast(), stolen_len, old_protect, &mut restore_protect) };
    let _ = unsafe { FlushInstructionCache(GetCurrentProcess(), target.cast(), stolen_len) };
    trampoline as usize
}

pub(super) unsafe fn write_rel32_jump(location: *mut u8, destination: usize) {
    unsafe { ptr::write(location, 0xE9) };
    let next_ip = unsafe { location.add(5) } as usize;
    let relative = (destination as isize - next_ip as isize) as i32;
    unsafe { ptr::write_unaligned(location.add(1).cast::<i32>(), relative) };
}

pub(super) unsafe fn load_direct_input8_create() -> Option<DirectInput8CreateFn> {
    if let Some(callback) = unsafe { REAL_DINPUT8_CREATE } {
        return Some(callback);
    }

    let mut system_directory = [0_u8; 260];
    let length = unsafe {
        GetSystemDirectoryA(system_directory.as_mut_ptr(), system_directory.len() as u32)
    };
    if length == 0 || length as usize >= system_directory.len() {
        return None;
    }

    let mut dll_path = system_directory[..length as usize].to_vec();
    dll_path.extend_from_slice(br"\dinput8.dll");
    dll_path.push(0);

    let module = unsafe { LoadLibraryA(dll_path.as_ptr().cast()) };
    if module == 0 {
        return None;
    }

    let symbol = unsafe { GetProcAddress(module, DIRECT_INPUT8_CREATE_NAME.as_ptr().cast()) };
    if symbol.is_null() {
        return None;
    }

    let callback: DirectInput8CreateFn = unsafe { mem::transmute(symbol) };
    unsafe {
        REAL_DINPUT8_CREATE = Some(callback);
    }
    Some(callback)
}
