#ifndef EFI_HPP
#define EFI_HPP

constexpr ULONG PAYLOAD_CONTROL_CODE = CTL_CODE( FILE_DEVICE_UNKNOWN, 0x282, METHOD_BUFFERED, FILE_SPECIAL_ACCESS );
constexpr auto min_user_addr = 0x0000000000010000;
constexpr auto max_user_addr = 0x00007FFFFFFEFFFF;

namespace memory
{
	__forceinline uintptr_t to_addr( const void* pointer )
	{
		return reinterpret_cast< uintptr_t >( pointer );
	}

	__forceinline void* to_ptr( uintptr_t address )
	{
		return reinterpret_cast< void* >( address );
	}

	__forceinline bool is_valid( uintptr_t address )
	{
		return ( address >= min_user_addr && address < max_user_addr );
	}

	__forceinline bool is_valid( const void* pointer )
	{
		return is_valid( to_addr( pointer ) );
	}
}

namespace efi_hook
{
	enum class setup_error_code
	{
		payload_communication_failure,
		payload_mapping_failure,
		efi_unavailable,
		invalid_image_base,
		invalid_directory_table_base,
		system_operational,
		failed_to_gain_privilege,
		pdb_failure,
		assembly_error
	};

	enum class game_error_code
	{
		empty_process_name,
		system_operational
	};

	typedef enum _requests
	{
		invoke_start,
		invoke_base,
		invoke_read,
		invoke_is_mapped,
		invoke_hide_window,
		invoke_hide_process,
		invoke_write,
		invoke_resolve_dtb,
		invoke_query_memory,
		invoke_peb,
		invoke_module_base,
		invoke_success,
		invoke_unique,
		invoke_unload,
	}requests, * prequests;

	typedef struct _read_invoke
	{
		std::uint32_t pid;
		bool rust;
		uintptr_t address;
		void* buffer;
		size_t size;
	} read_invoke, * pread_invoke;

	typedef struct _is_mapped_invoke
	{
		bool is_mapped;
	} is_mapped_invoke, * pis_mapped;

	typedef struct _write_invoke
	{
		uint32_t pid;
		uintptr_t address;
		void* buffer;
		size_t size;
	} write_invoke, * pwrite_invoke;

	typedef struct _hide_attributes_invoke
	{
		uintptr_t handle;
		bool status;
	} hide_attributes_invoke, * phide_attributes_invoke;

	typedef struct _query_memory_invoke
	{
		uint32_t pid;
		uintptr_t address;
		uintptr_t page_base;
		uint32_t page_prot;
		size_t page_size;
		bool status;
	} query_memory_invoke, * pquery_memory_invoke;

	typedef struct _hide_process_invoke
	{
		const char* name;
		bool status;
	} hide_process_invoke, * phide_process_invoke;

	typedef struct _dtb_invoke
	{
		uint32_t pid;
		uintptr_t handle;
	} dtb_invoke, * pdtb_invoke;

	typedef struct _base_invoke
	{
		uint32_t pid;
		uintptr_t handle;
	} base_invoke, * pbase_invoke;

	typedef struct _peb_invoke
	{
		uint32_t pid;
		ULONGLONG handle;
	} peb_invoke, * ppeb_invoke;

	typedef struct _invoke_data
	{
		uint32_t unique;
		requests code;
		void* data;
	}invoke_data, * pinvoke_data;

	typedef struct _request_data
	{
		uintptr_t key;
	}request_data, * prequest_data;

	class efi_t {
	public:
		std::uint64_t image_base;
		std::uint64_t ntos_base;
		//std::wstring process_name;
		std::int32_t process_id;
		HANDLE payload_handle;

		std::uint64_t mm_copy_memory;
		std::uint64_t mm_map_space;
		std::uint64_t mm_unmap_space;
		std::uint64_t ps_initial_system_process;
		std::uint64_t ps_get_process_id;
		std::uint64_t ps_get_current_process;
		std::uint64_t mm_physical_ranges;
		std::uint64_t memcpy;
		std::uint64_t ex_allocate_pool;
		std::uint64_t ex_free_pool;

		std::uint64_t process_active_process_links_offset;
		std::uint64_t process_unique_id_offset;
		std::uint64_t process_section_base_address_offset;

		std::uint64_t thread_list_entry_offset;
		std::uint64_t thread_win32startaddress_offset;
		std::uint64_t thread_startaddress_offset;

		std::uint64_t mm_free_independent_pages_offset;
		std::uint64_t mm_set_page_protection_offset;
		std::uint64_t mm_allocate_independent_pages_offset;
	private:
		std::uint64_t cr3_header;

	public:
		[[nodiscard]] bool send_payload_cmd( void* data, requests parameters );
		[[nodiscard]] bool is_dtb_invalid( std::uint64_t cr3 );
		[[nodiscard]] bool is_payload_available( );

		[[nodiscard]] setup_error_code setup( const std::wstring& process_name );
		//[nodiscard]] attach_error_code attach( );
		[[nodiscard]] game_error_code find_game( const std::wstring& process_name );

		const std::uint32_t get_process_pid( const std::wstring& proc_name );

		const std::uint64_t get_kernel_export( const std::uint64_t image_name, const std::string& module_name );
		const std::uint64_t get_kernel_image( const std::string& module_name );
		const std::uint64_t get_kernel_import( const std::string& image, const std::string& module_name );

		// magic stuff.
		// call only once.
		std::uint64_t patch_directory_table_base( );
		std::uint64_t get_peb( );

		bool hide_window_attributes( std::uint64_t window_handle );
		bool hide_process( );

		// vm

		[[nodiscard]] std::uint64_t get_module_base( const wchar_t* module_name );
		[[nodiscard]] std::uint64_t get_image_base( );

		bool read_physical( const uintptr_t address, void* buffer, const std::uint64_t size, bool large_page = false );

		template <typename t>
		bool read_large_array_ex( uint64_t address, t out[ ], size_t len )
		{
			if ( memory::is_valid( address ) )
			{
				size_t real_size = sizeof( t ) * len;
				size_t read_size = 0;
				t* temp = out;  // Temporary pointer to keep track of the current position in the out array

				while ( read_size < real_size )
				{
					BYTE* buffer = new BYTE [ 512 ];

					size_t diff = real_size - read_size;
					if ( diff > 512 )
						diff = 512;

					if ( memory::is_valid( address + read_size ) )
					{
						this->read_physical( address + read_size, buffer, diff );

						if ( memory::is_valid( ( uint64_t ) buffer ) )
						{
							std::memcpy( temp, buffer, diff );

							read_size += diff;
							temp += ( diff / sizeof( t ) );

							delete[ ] buffer;
						}
					}
				}
			}

			return true;
		}

		bool read_large_array( std::uint64_t address, void* buffer, std::size_t size );

		bool write_physical( const uintptr_t address, void* buffer, const size_t size );

		// vm templates


		template <typename t>
		auto write( const uintptr_t address, t value ) -> bool {
			return write_physical( address, &value, sizeof( t ) );
		}

		template <class t>
		auto read( const uintptr_t address ) -> t {
			t response { };

			auto result = read_physical(
				address,
				&response,
				sizeof( t )
			);

			return response;
		}

		auto get_current_executable_name( ) -> const char* {
			static char exeName[ MAX_PATH ] = { 0 };
			char fullPath[ MAX_PATH ] = { 0 };

			if ( GetModuleFileNameA( NULL, fullPath, MAX_PATH ) != 0 ) 
			{
				char* lastSlash = strrchr( fullPath, '\\' );

				if ( lastSlash ) 
				{
					strncpy_s( exeName, lastSlash + 1, MAX_PATH - 1 );
				}
				else 
				{
					strncpy_s( exeName, fullPath, MAX_PATH - 1 );
				}

				exeName[ MAX_PATH - 1 ] = '\0';
			}

			return exeName;
		}

		// payload
		[[nodiscard]] bool create_payload_handle( );
	};
} inline auto g_vm = std::make_unique<efi_hook::efi_t>( );


#endif