#include "loader/elf.h"

#include "common/assert.h"
#include "common/file.h"
#include "common/logging/log.h"

#include <cstring>
#include <memory>

namespace Loader {

static std::unique_ptr<SelfHeader> LoadSelf(Common::File& f) {
	if (f.Remaining() < sizeof(SelfHeader)) {
		return nullptr;
	}

	auto self = std::make_unique<SelfHeader>();

	f.Read(self.get(), sizeof(SelfHeader));

	return self;
}

static std::unique_ptr<SelfSegment[]> LoadSelfSegments(Common::File& f, uint16_t num) {
	auto segs = std::make_unique<SelfSegment[]>(num);

	f.Read(segs.get(), sizeof(SelfSegment) * num);

	return segs;
}

static std::unique_ptr<Elf64_Ehdr> LoadEhdr64(Common::File& f) {
	if (f.Remaining() < sizeof(Elf64_Ehdr)) {
		return nullptr;
	}

	auto ehdr = std::make_unique<Elf64_Ehdr>();

	f.Read(ehdr.get(), sizeof(Elf64_Ehdr));

	return ehdr;
}

static void SaveEhdr64(Common::File& f, const Elf64_Ehdr* ehdr) {
	EXIT_IF(ehdr == nullptr);

	uint32_t bytes_written = 0;

	f.Write(ehdr, sizeof(Elf64_Ehdr), &bytes_written);

	EXIT_IF(bytes_written == 0);
}

static std::unique_ptr<Elf64_Phdr[]> LoadPhdr64(Common::File& f, uint64_t offset, Elf64_Half num) {
	auto phdr = std::make_unique<Elf64_Phdr[]>(num);

	f.Seek(offset);
	f.Read(phdr.get(), sizeof(Elf64_Phdr) * num);

	return phdr;
}

static void SavePhdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Phdr* phdr) {
	EXIT_IF(phdr == nullptr);

	uint32_t bytes_written = 0;

	f.Seek(offset);
	f.Write(phdr, sizeof(Elf64_Phdr) * num, &bytes_written);

	EXIT_IF(bytes_written == 0);
}

static std::unique_ptr<Elf64_Shdr[]> LoadShdr64(Common::File& f, uint64_t offset, Elf64_Half num) {
	if (num == 0) {
		return nullptr;
	}

	auto shdr = std::make_unique<Elf64_Shdr[]>(num);

	f.Seek(offset);
	f.Read(shdr.get(), sizeof(Elf64_Shdr) * num);

	return shdr;
}

static void SaveShdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Shdr* shdr) {
	if (num == 0) {
		return;
	}

	EXIT_IF(shdr == nullptr);

	uint32_t bytes_written = 0;

	f.Seek(offset);
	f.Write(shdr, sizeof(Elf64_Shdr) * num, &bytes_written);

	EXIT_IF(bytes_written == 0);
}

static std::unique_ptr<uint8_t[]> LoadDynamic64(Elf64* f, uint64_t offset, uint64_t size) {
	auto dynamic_data = std::make_unique<uint8_t[]>(size);

	// f.Seek(offset);
	// f.Read(dynamic_data, size);

	f->LoadSegment(reinterpret_cast<uint64_t>(dynamic_data.get()), offset, size);

	return dynamic_data;
}

static std::unique_ptr<char[]> LoadStrTable(Common::File& f, uint64_t offset, uint32_t size) {
	if (size == 0) {
		return nullptr;
	}

	auto str_table = std::make_unique<char[]>(size);
	f.Seek(offset);
	f.Read(str_table.get(), size);
	return str_table;
}

static void DbgPrintEhdr64(Elf64_Ehdr* ehdr, Common::File& f) {
	f.Printf("ehdr->e_ident = ");
	for (auto i: ehdr->e_ident) {
		f.Printf("%02x", i);
	}
	f.Printf("\n");

	f.Printf("ehdr->e_type = 0x%04" PRIx16 "\n", ehdr->e_type);
	f.Printf("ehdr->e_machine = 0x%04" PRIx16 "\n", ehdr->e_machine);
	f.Printf("ehdr->e_version = 0x%08" PRIx32 "\n", ehdr->e_version);

	f.Printf("ehdr->e_entry = 0x%016" PRIx64 "\n", ehdr->e_entry);
	f.Printf("ehdr->e_phoff = 0x%016" PRIx64 "\n", ehdr->e_phoff);
	f.Printf("ehdr->e_shoff = 0x%016" PRIx64 "\n", ehdr->e_shoff);
	f.Printf("ehdr->e_flags = 0x%08" PRIx32 "\n", ehdr->e_flags);
	f.Printf("ehdr->e_ehsize = 0x%04" PRIx16 "\n", ehdr->e_ehsize);
	f.Printf("ehdr->e_phentsize = 0x%04" PRIx16 "\n", ehdr->e_phentsize);
	f.Printf("ehdr->e_phnum = %" PRIu16 "\n", ehdr->e_phnum);
	f.Printf("ehdr->e_shentsize = 0x%04" PRIx16 "\n", ehdr->e_shentsize);
	f.Printf("ehdr->e_shnum = %" PRIu16 "\n", ehdr->e_shnum);
	f.Printf("ehdr->e_shstrndx = %" PRIu16 "\n", ehdr->e_shstrndx);
}

static void DbgPrintPhdr64(Elf64_Phdr* phdr, Common::File& f) {
	f.Printf("phdr->p_type = 0x%08" PRIx32 "\n", phdr->p_type);
	f.Printf("phdr->p_flags = 0x%08" PRIx32 "\n", phdr->p_flags);
	f.Printf("phdr->p_offset = 0x%016" PRIx64 "\n", phdr->p_offset);
	f.Printf("phdr->p_vaddr = 0x%016" PRIx64 "\n", phdr->p_vaddr);
	f.Printf("phdr->p_paddr = 0x%016" PRIx64 "\n", phdr->p_paddr);
	f.Printf("phdr->p_filesz = 0x%016" PRIx64 "\n", phdr->p_filesz);
	f.Printf("phdr->p_memsz = 0x%016" PRIx64 "\n", phdr->p_memsz);
	f.Printf("phdr->p_align = 0x%016" PRIx64 "\n", phdr->p_align);
}

static void DbgPrintShdr64(Elf64_Shdr* shdr, Common::File& f) {
	f.Printf("shdr->sh_name = %d\n", shdr->sh_name);
	f.Printf("shdr->sh_type = 0x%08" PRIx32 "\n", shdr->sh_type);
	f.Printf("shdr->sh_flags = 0x%016" PRIx64 "\n", shdr->sh_flags);
	f.Printf("shdr->sh_addr = 0x%016" PRIx64 "\n", shdr->sh_addr);
	f.Printf("shdr->sh_offset = 0x%016" PRIx64 "\n", shdr->sh_offset);
	f.Printf("shdr->sh_size = 0x%016" PRIx64 "\n", shdr->sh_size);
	f.Printf("shdr->sh_link = %" PRId32 "\n", shdr->sh_link);
	f.Printf("shdr->sh_info = 0x%08" PRIx32 "\n", shdr->sh_info);
	f.Printf("shdr->sh_addralign = 0x%016" PRIx64 "\n", shdr->sh_addralign);
	f.Printf("shdr->sh_entsize = 0x%016" PRIx64 "\n", shdr->sh_entsize);
}

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DBG_NAME(tag)                                                                              \
	case tag: name = #tag; break;

static void DbgPrintDynamic64(const Elf64_Dyn* dyn, Common::File& f) {
	const char* name = "Unknown";
	switch (dyn->d_tag) {
		DBG_NAME(DT_OS_HASH)
		DBG_NAME(DT_HASH)
		DBG_NAME(DT_OS_STRTAB)
		DBG_NAME(DT_OS_STRSZ)
		DBG_NAME(DT_STRTAB)
		DBG_NAME(DT_STRSZ)
		DBG_NAME(DT_OS_SYMTAB)
		DBG_NAME(DT_SYMTAB)
		DBG_NAME(DT_OS_HASHSZ)
		DBG_NAME(DT_OS_SYMTABSZ)
		DBG_NAME(DT_INIT)
		DBG_NAME(DT_FINI)
		DBG_NAME(DT_OS_PLTGOT)
		DBG_NAME(DT_PLTGOT)
		DBG_NAME(DT_OS_JMPREL)
		DBG_NAME(DT_JMPREL)
		DBG_NAME(DT_OS_PLTRELSZ)
		DBG_NAME(DT_PLTRELSZ)
		DBG_NAME(DT_OS_PLTREL)
		DBG_NAME(DT_PLTREL)
		DBG_NAME(DT_OS_RELA)
		DBG_NAME(DT_RELA)
		DBG_NAME(DT_OS_RELASZ)
		DBG_NAME(DT_RELASZ)
		DBG_NAME(DT_OS_RELAENT)
		DBG_NAME(DT_RELAENT)
		DBG_NAME(DT_INIT_ARRAY)
		DBG_NAME(DT_INIT_ARRAYSZ)
		DBG_NAME(DT_FINI_ARRAY)
		DBG_NAME(DT_FINI_ARRAYSZ)
		DBG_NAME(DT_PREINIT_ARRAY)
		DBG_NAME(DT_PREINIT_ARRAYSZ)
		DBG_NAME(DT_OS_SYMENT)
		DBG_NAME(DT_SYMENT)
		DBG_NAME(DT_DEBUG)
		DBG_NAME(DT_TEXTREL)
		DBG_NAME(DT_FLAGS)
		DBG_NAME(DT_NEEDED)
		DBG_NAME(DT_OS_NEEDED_MODULE)
		DBG_NAME(DT_OS_NEEDED_MODULE_1)
		DBG_NAME(DT_OS_IMPORT_LIB)
		DBG_NAME(DT_OS_IMPORT_LIB_1)
		DBG_NAME(DT_OS_IMPORT_LIB_ATTR)
		DBG_NAME(DT_OS_FINGERPRINT)
		DBG_NAME(DT_OS_ORIGINAL_FILENAME)
		DBG_NAME(DT_OS_ORIGINAL_FILENAME_1)
		DBG_NAME(DT_OS_MODULE_INFO)
		DBG_NAME(DT_OS_MODULE_INFO_1)
		DBG_NAME(DT_OS_MODULE_ATTR)
		DBG_NAME(DT_SONAME)
		DBG_NAME(DT_OS_EXPORT_LIB)
		DBG_NAME(DT_OS_EXPORT_LIB_1)
		DBG_NAME(DT_OS_EXPORT_LIB_ATTR)
		DBG_NAME(DT_RELACOUNT)
		DBG_NAME(DT_NULL)
	}
	f.Printf("d_tag = 0x%016" PRIx64 ", d_val = 0x%016" PRIx64 ", name = %s\n", dyn->d_tag,
	         dyn->d_un.d_val, name);
}

Elf64::~Elf64() {
	Clear();
}

void Elf64::LoadSegment(uint64_t vaddr, uint64_t file_offset, uint64_t size) {
	EXIT_IF(m_f == nullptr);

	if (m_self != nullptr) {
		EXIT_IF(m_self_segments == nullptr);
		EXIT_IF(m_phdr == nullptr);

		for (uint16_t i = 0; i < m_self->segments_num; i++) {
			const auto& seg = m_self_segments[i];
			if ((seg.type & 0x800u) != 0) {
				auto phdr_id = ((seg.type >> 20u) & 0xFFFu);

				const auto& phdr = m_phdr[phdr_id];

				if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
					EXIT_NOT_IMPLEMENTED(seg.decompressed_size != phdr.p_filesz);
					EXIT_NOT_IMPLEMENTED(seg.compressed_size != seg.decompressed_size);

					auto offset = file_offset - phdr.p_offset;

					EXIT_NOT_IMPLEMENTED(offset + size > seg.decompressed_size);

					m_f->Seek(offset + seg.offset);
					m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);

					return;
				}
			}
		}

		if (m_f->Size() - m_self->file_size == size) {
			m_f->Seek(m_self->file_size);
			m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);

			return;
		}

		EXIT("missing self segment\n");
	} else {
		m_f->Seek(file_offset);
		m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);
	}
}

const Elf64_Dyn* Elf64::GetDynValue(Elf64_Sxword tag) const {
	for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
		if (dyn->d_tag == tag) {
			return dyn;
		}
	}
	return nullptr;
}

std::vector<const Elf64_Dyn*> Elf64::GetDynList(Elf64_Sxword tag) const {
	std::vector<const Elf64_Dyn*> ret;
	for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
		if (dyn->d_tag == tag) {
			ret.push_back(dyn);
		}
	}
	return ret;
}

bool Elf64::IsShared() const {
	return (m_ehdr->e_type == ET_DYNAMIC);
}

bool Elf64::IsNextGen() const {
	return (m_ehdr->e_ident[EI_ABIVERSION] == 2);
}

const char* Elf64::GetSectionName(int index) const {
	if (m_ehdr == nullptr || m_shdr == nullptr || m_str_table == nullptr || index < 0 ||
	    index >= m_ehdr->e_shnum) {
		return nullptr;
	}

	const auto name_offset = m_shdr[index].sh_name;
	if (name_offset >= m_str_table_size) {
		return nullptr;
	}

	const auto remaining = m_str_table_size - name_offset;
	if (std::memchr(m_str_table.get() + name_offset, '\0', remaining) == nullptr) {
		return nullptr;
	}

	return m_str_table.get() + name_offset;
}

void Elf64::Clear() {
	if (m_f != nullptr) {
		m_f->Close();
	}

	m_f.reset();
	m_self.reset();
	m_ehdr.reset();
	m_self_segments.reset();
	m_phdr.reset();
	m_shdr.reset();
	m_str_table.reset();
	m_str_table_size = 0;
	m_dynamic.reset();
	m_dynamic_data.reset();
}

void Elf64::DbgDump(const std::string& folder) {
	auto folder_str = Common::FixDirectorySlash(folder);

	Common::File::CreateDirectories(folder_str);

	for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
		if (m_phdr[i].p_filesz == 0u) {
			continue;
		}

		char str[512];
		int  s = snprintf(str, 512, "phdr_%03d", i);
		EXIT_NOT_IMPLEMENTED(s >= 512);

		Common::File fout;
		fout.Create(folder_str + str);

		auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_phdr[i].p_filesz));

		// m_f->Seek(m_phdr[i].p_offset);
		// m_f->Read(buf, static_cast<uint32_t>(m_phdr[i].p_filesz));

		LoadSegment(reinterpret_cast<uint64_t>(buf.get()), m_phdr[i].p_offset, m_phdr[i].p_filesz);

		fout.Write(buf.get(), static_cast<uint32_t>(m_phdr[i].p_filesz));

		fout.Close();
	}

	for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
		if (m_shdr[i].sh_size == 0u) {
			continue;
		}

		char str[512];
		int  s = snprintf(str, 512, "shdr_%03d", i);
		EXIT_NOT_IMPLEMENTED(s >= 512);

		Common::File fout;
		fout.Create(folder_str + str);

		auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_shdr[i].sh_size));

		m_f->Seek(m_shdr[i].sh_offset);
		m_f->Read(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));
		fout.Write(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));

		fout.Close();
	}

	Common::File fout;

	fout.Create(folder_str + "ehdr.txt");
	DbgPrintEhdr64(m_ehdr.get(), fout);
	fout.Close();

	fout.Create(folder_str + "phdr.txt");
	for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
		fout.Printf("--- phdr [%d] ---\n", i);
		DbgPrintPhdr64(m_phdr.get() + i, fout);
	}
	fout.Close();

	fout.Create(folder_str + "shdr.txt");
	for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
		const char* section_name = GetSectionName(i);
		fout.Printf("--- shdr [%d] %s ---\n", i, section_name != nullptr ? section_name : "");
		DbgPrintShdr64(m_shdr.get() + i, fout);
	}
	fout.Close();

	fout.Create(folder_str + "dynamic.txt");
	for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
		DbgPrintDynamic64(dyn, fout);
	}
	fout.Close();
}

uint64_t Elf64::GetEntry() {
	return m_ehdr->e_entry;
}

bool Elf64::IsSelf() const {
	if (m_f == nullptr || m_f->IsInvalid()) {
		return false;
	}

	if (m_self == nullptr) {
		return false;
	}

	const bool known_magic = (m_self->ident[0] == 0x4f && m_self->ident[1] == 0x15 &&
	                          m_self->ident[2] == 0x3d && m_self->ident[3] == 0x1d) ||
	                         (m_self->ident[0] == 0x54 && m_self->ident[1] == 0x14 &&
	                          m_self->ident[2] == 0xf5 && m_self->ident[3] == 0xee);
	if (!known_magic) {
		return false;
	}

	const bool known_ident_tail =
	    (m_self->ident[4] == 0x00 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 &&
	     m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 &&
	     m_self->ident[10] == 0x00 && m_self->ident[11] == 0x00 && m_self->unknown == 0x22) ||
	    (m_self->ident[4] == 0x10 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 &&
	     m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 &&
	     m_self->ident[10] == 0x00 && m_self->ident[11] == 0x10 && m_self->unknown == 0x32);

	if (!known_ident_tail) {
		LOGF("Unknown SELF file\n");
		return false;
	}

	return true;
}

bool Elf64::IsValid() const {
	if (m_f == nullptr || m_f->IsInvalid()) {
		return false;
	}

	if (m_ehdr == nullptr) {
		return false;
	}

	if (m_ehdr->e_ident[EI_MAG0] != '\x7f' || m_ehdr->e_ident[EI_MAG1] != 'E' ||
	    m_ehdr->e_ident[EI_MAG2] != 'L' || m_ehdr->e_ident[EI_MAG3] != 'F') {
		LOGF("Not an ELF file\n");
		return false;
	}

	if (m_ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
		LOGF("ehdr->e_ident[EI_CLASS] (0x%x) != ELFCLASS64\n", m_ehdr->e_ident[EI_CLASS]);
		return false;
	}

	if (m_ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
		LOGF("ehdr->e_ident[EI_DATA] (0x%x) != ELFDATA2LSB\n", m_ehdr->e_ident[EI_DATA]);
		return false;
	}

	if (m_ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
		LOGF("ehdr->e_ident[EI_VERSION] != EV_CURRENT\n");
		return false;
	}

	if (m_ehdr->e_ident[EI_OSABI] != ELFOSABI_FREEBSD) {
		LOGF("ehdr->e_ident[EI_OSABI] (0x%x) != ELFOSABI_FREEBSD\n", m_ehdr->e_ident[EI_OSABI]);
		return false;
	}

	if (m_ehdr->e_ident[EI_ABIVERSION] != 0 && m_ehdr->e_ident[EI_ABIVERSION] != 2) {
		LOGF("ehdr->e_ident[EI_ABIVERSION] (0x%x) != (0 or 2)\n", m_ehdr->e_ident[EI_ABIVERSION]);
		return false;
	}

	if (m_ehdr->e_type != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC) {
		LOGF("ehdr->e_type (%04x) != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC\n", m_ehdr->e_type);
		return false;
	}

	if (m_ehdr->e_machine != EM_X86_64) {
		LOGF("ehdr->e_machine (%04x) != EM_X86_64\n", m_ehdr->e_machine);
		return false;
	}

	if (m_ehdr->e_version != EV_CURRENT) {
		LOGF("ehdr->e_version != EV_CURRENT\n");
		return false;
	}

	if (m_ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
		LOGF("ehdr->e_phentsize != sizeof(Elf64_Phdr)\n");
		return false;
	}

	if (m_ehdr->e_shentsize > 0 && m_ehdr->e_shentsize != sizeof(Elf64_Shdr)) {
		LOGF("ehdr->e_shentsize (%d) != sizeof(Elf64_Shdr)\n", m_ehdr->e_shentsize);
		return false;
	}

	return true;
}

void Elf64::Open(const std::filesystem::path& file_name) {
	Clear();

	m_f = std::make_unique<Common::File>();
	m_f->Open(file_name, Common::File::Mode::Read);

	if (m_f->IsInvalid()) {
		EXIT("Can't open %s\n", Common::PathToString(file_name).c_str());
	}

	m_self = LoadSelf(*m_f);

	if (!IsSelf()) {
		m_self.reset();
		m_f->Seek(0);
	} else {
		m_self_segments = LoadSelfSegments(*m_f, m_self->segments_num);
	}

	auto ehdr_pos = m_f->Tell();

	m_ehdr = LoadEhdr64(*m_f);

	if (!IsValid()) {
		m_ehdr.reset();
	}

	if (m_ehdr != nullptr /*&& m_self == nullptr*/) {
		m_phdr = LoadPhdr64(*m_f, ehdr_pos + m_ehdr->e_phoff, m_ehdr->e_phnum);
		if (m_self == nullptr) {
			m_shdr = LoadShdr64(*m_f, ehdr_pos + m_ehdr->e_shoff, m_ehdr->e_shnum);

			if (m_shdr != nullptr) {
				if (m_ehdr->e_shstrndx < m_ehdr->e_shnum) {
					m_str_table_size = static_cast<uint32_t>(m_shdr[m_ehdr->e_shstrndx].sh_size);
					m_str_table =
					    LoadStrTable(*m_f, m_shdr[m_ehdr->e_shstrndx].sh_offset, m_str_table_size);
				}
			}
		} else if (m_ehdr->e_shnum != 0) {
			LOGF("SELF: skipping ELF section table: shoff=0x%016" PRIx64 ", shnum=%" PRIu16 "\n",
			     m_ehdr->e_shoff, m_ehdr->e_shnum);
		}

		for (Elf64_Half i = 0; i < m_ehdr->e_phnum; i++) {
			switch (m_phdr[i].p_type) {
				case PT_DYNAMIC:
					m_dynamic = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz);
					break;
				case PT_OS_DYNLIBDATA:
					m_dynamic_data = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz);
					break;
				default: break;
			}
		}
	}
}

void Elf64::Save(const std::filesystem::path& file_name) {
	EXIT_IF(!IsValid());

	if (IsValid()) {
		Common::File f;
		f.Create(file_name);

		if (f.IsInvalid()) {
			EXIT("Can't create %s\n", Common::PathToString(file_name).c_str());
		}

		SaveEhdr64(f, m_ehdr.get());

		SavePhdr64(f, m_ehdr->e_phoff, m_ehdr->e_phnum, m_phdr.get());
		SaveShdr64(f, m_ehdr->e_shoff, m_ehdr->e_shnum, m_shdr.get());

		for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
			if (m_phdr[i].p_filesz == 0u) {
				continue;
			}

			auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_phdr[i].p_filesz));

			LoadSegment(reinterpret_cast<uint64_t>(buf.get()), m_phdr[i].p_offset,
			            m_phdr[i].p_filesz);

			uint32_t bytes_written = 0;

			f.Seek(m_phdr[i].p_offset);
			f.Write(buf.get(), static_cast<uint32_t>(m_phdr[i].p_filesz), &bytes_written);

			EXIT_IF(bytes_written == 0);
		}

		for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
			if (m_shdr[i].sh_size == 0u) {
				continue;
			}

			auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_shdr[i].sh_size));

			m_f->Seek(m_shdr[i].sh_offset);
			m_f->Read(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));

			uint32_t bytes_written = 0;

			f.Seek(m_shdr[i].sh_offset);
			f.Write(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size), &bytes_written);

			EXIT_IF(bytes_written == 0);
		}

		f.Close();
	}
}

} // namespace Loader
