text: #pragma once
  
  namespace PE {
      class Module {
      private:
          fxLDR_DATA_TABLE_ENTRY *m_ldr_entry;
          IMAGE_DOS_HEADER     *m_dos;
          IMAGE_NT_HEADERS     *m_nt;
  
      public:
          __forceinline Module( ) :
              m_ldr_entry{ nullptr }, m_dos{ nullptr }, m_nt{ nullptr } {}
  
          __forceinline Module( fxLDR_DATA_TABLE_ENTRY *ldr_entry, IMAGE_DOS_HEADER *dos, IMAGE_NT_HEADERS *nt ) :
              m_ldr_entry{ ldr_entry }, m_dos{ dos }, m_nt{ nt } {}
  
          __forceinline Address GetBase( ) const {
              return ( m_ldr_entry ) ? Address( m_ldr_entry->DllBase ) : Address{};
          }
  
          // valid check.
          __forceinline operator bool( ) const {
              return GetBase( );
          }
  
          // valid check.
          __forceinline bool operator !( ) const {
              return !GetBase( );
          }
  
          __forceinline operator Address( ) const {
              return ( m_ldr_entry ) ? Address( m_ldr_entry->DllBase ) : Address{};
          }
  
          __forceinline IMAGE_DOS_HEADER *GetDOS( ) const {
              return ( m_dos ) ? m_dos : nullptr;
          }
  
          __forceinline IMAGE_NT_HEADERS *GetNT( ) const {
              return ( m_nt ) ? m_nt : nullptr;
          }
  
          __forceinline size_t GetCodeSize( ) const {
              return ( m_nt ) ? (size_t)m_nt->OptionalHeader.SizeOfCode : 0u;
          }
  
          __forceinline size_t GetImageSize( ) const {
              return ( m_nt ) ? (size_t)m_nt->OptionalHeader.SizeOfImage : 0u;
          }
  
          __forceinline std::string GetName( ) const {
              std::string     out;
              std::wstring    wide_name;
  
              if( !m_ldr_entry )
                  return {};
  
              // get wide name.
              wide_name = std::wstring( m_ldr_entry->BaseDllName.Buffer, m_ldr_entry->BaseDllName.Length >> 1 );
  
              // truncate from a unicode string to a multi-byte string.
              out = std::string( wide_name.begin( ), wide_name.end( ) );
  
              // convert out name to lowercase.
              std::transform( out.begin( ), out.end( ), out.begin( ), ::tolower );
  
              return out;
          }
      };
  
      // container type for all modules / export / exports.
      using modules_t = std::vector< Module >;
  	using export_t  = std::pair< hash32_t, Address >;
  	using exports_t = std::vector< export_t >;
  
      // get DOS / NT headers from file.
      __forceinline bool GetFileHeaders( Address base, PIMAGE_DOS_HEADER &out_dos, PIMAGE_NT_HEADERS &out_nt ) {
          auto dos = base.as< IMAGE_DOS_HEADER * >( );
          
          // check for invalid DOS / DOS signature.
          if( !dos || dos->e_magic != IMAGE_DOS_SIGNATURE /* 'MZ' */ )
              return false;
          
          // get NT headers.
          auto nt = (IMAGE_NT_HEADERS *)( (uintptr_t)dos + dos->e_lfanew );
          
          // check for invalid NT / NT signature.
          if( !nt || nt->Signature != IMAGE_NT_SIGNATURE /* 'PE\0\0' */ )
              return false;
          
          // set out dos and nt.
          out_dos = dos;
          out_nt  = nt;
          
          return true;
      }
  
      // get all modules from PEB ldr.
      static __declspec( noinline ) bool GetAllModules( modules_t &out ) {
          fx_PEB                 *peb;
          LIST_ENTRY           *list;
          fxLDR_DATA_TABLE_ENTRY *ldr_entry;
          IMAGE_DOS_HEADER     *dos{ nullptr };
          IMAGE_NT_HEADERS     *nt{ nullptr };
  
          // TEB->ProcessEnvironmentBlock.
          peb = (fx_PEB *)__readfsdword( 0x30 );
          if( !peb )
              return false;
  
          // get module linked list.
          list = &peb->Ldr->InMemoryOrderModuleList;
  
          // iterate linked list.
          for( auto it = list->Flink; it != list; it = it->Flink ) {
              // get current entry.
              ldr_entry = CONTAINING_RECORD( it, fxLDR_DATA_TABLE_ENTRY, InMemoryOrderLinks );
              if( !ldr_entry )
                  continue;
  
              // get file headers and ensure it's a valid PE file.
              if( !GetFileHeaders( ldr_entry->DllBase, dos, nt ) )
                  continue;
              
              // push module to out vector.
              out.push_back( { ldr_entry, dos, nt } );
          }
  
          return !out.empty( );
      }
  
      // gets a module from PEB ldr using a string.
      static __declspec( noinline ) Module GetModule( std::string name ) {
          modules_t modules;
      
          // get all modules.
          if( !GetAllModules( modules ) )
              return {};
  
          // convert name to lower-case.
          std::transform( name.begin( ), name.end( ), name.begin( ), ::tolower );
      
          // iterate all modules.
          for( const auto &m : modules ) {
              // compare name.
              if( m.GetName( ) == name )
                  return m;
          }
      
          return {};
      }
  
      // gets a module from PEB ldr using a hash.
      static __declspec( noinline ) Module GetModule( const hash32_t name ) {
          modules_t modules;
  
          // get all modules.
          if( !GetAllModules( modules ) )
              return {};
  
          // iterate all modules.
          for( const auto &m : modules ) {
              // compare name hash.
              if( FNV1a::get( m.GetName( ) ) == name )
                  return m;
          }
  
          return {};
      }
  
      // fwd declare GetExport ( by string )...
      static __declspec( noinline ) Address GetExport( const Module &module, const std::string &name );
  
      // gets the address of an exported function in a module by hash.
      static __declspec( noinline ) Address GetExport( const Module &module, const hash32_t name ) {
          Address     export_ptr, fwd_export_ptr;
          size_t      delim;
          std::string export_name, fwd_str, fwd_module, fwd_export;
  
          if( !module )
              return {};
  
          auto nt   = module.GetNT( );
          auto base = module.GetBase( );
      
          // get export data directory.
          auto data_dir = &nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
          if( !data_dir->VirtualAddress )
              return {};
  
          // get export dir.
          auto dir = Address::RVA< IMAGE_EXPORT_DIRECTORY * >( base, data_dir->VirtualAddress );
          if( !dir )
              return {};
  
          // names / funcs / ordinals ( all of these are RVAs ).
          auto names = Address::RVA< uint32_t * >( base, dir->AddressOfNames );
          auto funcs = Address::RVA< uint32_t * >( base, dir->AddressOfFunctions );
          auto ords  = Address::RVA< uint16_t * >( base, dir->AddressOfNameOrdinals );
          if( !names || !funcs || !ords )
              return {};
  
          // iterate names array.
          for( size_t i{ 0u }; i < dir->NumberOfNames; ++i ) {
              export_name = Address::RVA< const char * >( base, names[ i ] );
              if( export_name.empty( ) )
                  continue;
  
              // compare hash.
              if( FNV1a::get( export_name ) == name ) {
                  // get export ptr.
                  export_ptr = Address::RVA( base, funcs[ ords[ i ] ] );
  
                  // it's a forwarded export, we must resolve it.
                  if( export_ptr >= (uintptr_t)dir && export_ptr < ( (uintptr_t)dir + data_dir->Size ) ) {
                      // get forwarder string.
                      fwd_str = export_ptr.as< const char * >( );
  
                      // forwarders have a period as the delimiter.
                      delim = fwd_str.find_last_of( '.' ) + 1;
                      if( delim == std::string::npos )
                          return {};
  
                      // get forwarder module name.
                      fwd_module = fwd_str.substr( 0, delim );
                      
                      // append "dll" to fwd_module.
                      fwd_module += 'd';
                      fwd_module += 'l';
                      fwd_module += 'l';
  
                      // get forwarder export name.
                      fwd_export = fwd_str.substr( delim );
                      
                      // get real export ptr ( recursively ).
                      fwd_export_ptr = GetExport( GetModule( fwd_module ), fwd_export );
                      if( !fwd_export_ptr )
                          return {};
                      
                      // return ptr to forwarded export.
                      return fwd_export_ptr;
                  }
  
                  // return ptr to normal export.
                  return export_ptr;
              }
          }
  
          return {};
      }
  
  	// returns an array containing all exports in the specified module.
  	static __declspec( noinline ) exports_t GetAllExports( const Module &module ) {
  		Address     export_ptr, fwd_export_ptr;
  		size_t      delim;
  		std::string export_name, fwd_str, fwd_module, fwd_export;
  		exports_t   exports;
  
  		if( !module )
  			return{};
  
  		auto nt   = module.GetNT( );
  		auto base = module.GetBase( );
  
  		// get export data directory.
  		auto data_dir = &nt->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
  		if( !data_dir->VirtualAddress )
  			return{};
  
  		// get export dir.
  		auto dir = Address::RVA< IMAGE_EXPORT_DIRECTORY * >( base, data_dir->VirtualAddress );
  		if( !dir )
  			return{};
  
  		// names / funcs / ordinals ( all of these are RVAs ).
  		auto names = Address::RVA< uint32_t * >( base, dir->AddressOfNames );
  		auto funcs = Address::RVA< uint32_t * >( base, dir->AddressOfFunctions );
  		auto ords  = Address::RVA< uint16_t * >( base, dir->AddressOfNameOrdinals );
  		if( !names || !funcs || !ords )
  			return{};
  
  		// iterate names array.
  		for( size_t i{ 0u }; i < dir->NumberOfNames; ++i ) {
  			export_name = Address::RVA< const char * >( base, names[ i ] );
  			if( export_name.empty( ) )
  				continue;
  
  			// get export ptr.
  			export_ptr = Address::RVA( base, funcs[ ords[ i ] ] );
  
  			// it's a forwarded export, we must resolve it.
  			if( export_ptr >= ( uintptr_t )dir && export_ptr < ( ( uintptr_t )dir + data_dir->Size ) ) {
  				// get forwarder string.
  				fwd_str = export_ptr.as< const char* >( );
  
  				// forwarders have a period as the delimiter.
  				delim = fwd_str.find_last_of( '.' ) + 1;
  				if( delim == std::string::npos )
  					continue;
  
  				// get forwarder module name.
  				fwd_module = fwd_str.substr( 0, delim );
  
  				// append "dll" to fwd_module.
  				fwd_module += 'd';
  				fwd_module += 'l';
  				fwd_module += 'l';
  
  				// get forwarder export name.
  				fwd_export = fwd_str.substr( delim );
  
  				// get real export ptr ( recursively ).
  				fwd_export_ptr = GetExport( GetModule( fwd_module ), fwd_export );
  				if( !fwd_export_ptr )
  					continue;
  
  				exports.push_back( { FNV1a::get( export_name ), fwd_export_ptr } );
  			}
  
  			// return ptr to normal export.
  			else
                  exports.push_back( { FNV1a::get( export_name ), export_ptr } );
  		}
  
  		return exports;
  	}
  
      // gets the address of an exported function in a module by string.
      static __declspec( noinline ) Address GetExport( const Module &module, const std::string &name ) {
          return GetExport( module, FNV1a::get( name ) );
      }
  }