// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include "common/alignment.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/hle/service/ldr_ro/cro_helper.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO

namespace LDR_RO {

static const ResultCode ERROR_BUFFER_TOO_SMALL = // 0xE0E12C1F
    ResultCode(static_cast<ErrorDescription>(31), ErrorModule::RO, ErrorSummary::InvalidArgument,
               ErrorLevel::Usage);

static ResultCode CROFormatError(u32 description) {
    return ResultCode(static_cast<ErrorDescription>(description), ErrorModule::RO,
                      ErrorSummary::WrongArgument, ErrorLevel::Permanent);
}

const std::array<int, 17> CROHelper::ENTRY_SIZE{{
    1, // code
    1, // data
    1, // module name
    sizeof(SegmentEntry), sizeof(ExportNamedSymbolEntry), sizeof(ExportIndexedSymbolEntry),
    1, // export strings
    sizeof(ExportTreeEntry), sizeof(ImportModuleEntry), sizeof(ExternalRelocationEntry),
    sizeof(ImportNamedSymbolEntry), sizeof(ImportIndexedSymbolEntry),
    sizeof(ImportAnonymousSymbolEntry),
    1, // import strings
    sizeof(StaticAnonymousSymbolEntry), sizeof(InternalRelocationEntry),
    sizeof(StaticRelocationEntry),
}};

const std::array<CROHelper::HeaderField, 4> CROHelper::FIX_BARRIERS{{
    Fix0Barrier, Fix1Barrier, Fix2Barrier, Fix3Barrier,
}};

VAddr CROHelper::SegmentTagToAddress(SegmentTag segment_tag) const {
    u32 segment_num = GetField(SegmentNum);

    if (segment_tag.segment_index >= segment_num)
        return 0;

    SegmentEntry entry;
    GetEntry(segment_tag.segment_index, entry);

    if (segment_tag.offset_into_segment >= entry.size)
        return 0;

    return entry.offset + segment_tag.offset_into_segment;
}

ResultCode CROHelper::ApplyRelocation(VAddr target_address, RelocationType relocation_type,
                                      u32 addend, u32 symbol_address, u32 target_future_address) {

    switch (relocation_type) {
    case RelocationType::Nothing:
        break;
    case RelocationType::AbsoluteAddress:
    case RelocationType::AbsoluteAddress2:
        Memory::Write32(target_address, symbol_address + addend);
        break;
    case RelocationType::RelativeAddress:
        Memory::Write32(target_address, symbol_address + addend - target_future_address);
        break;
    case RelocationType::ThumbBranch:
    case RelocationType::ArmBranch:
    case RelocationType::ModifyArmBranch:
    case RelocationType::AlignedRelativeAddress:
        // TODO(wwylele): implement other types
        UNIMPLEMENTED();
        break;
    default:
        return CROFormatError(0x22);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ClearRelocation(VAddr target_address, RelocationType relocation_type) {
    switch (relocation_type) {
    case RelocationType::Nothing:
        break;
    case RelocationType::AbsoluteAddress:
    case RelocationType::AbsoluteAddress2:
    case RelocationType::RelativeAddress:
        Memory::Write32(target_address, 0);
        break;
    case RelocationType::ThumbBranch:
    case RelocationType::ArmBranch:
    case RelocationType::ModifyArmBranch:
    case RelocationType::AlignedRelativeAddress:
        // TODO(wwylele): implement other types
        UNIMPLEMENTED();
        break;
    default:
        return CROFormatError(0x22);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset) {
    if (symbol_address == 0 && !reset)
        return CROFormatError(0x10);

    VAddr relocation_address = batch;
    while (true) {
        RelocationEntry relocation;
        Memory::ReadBlock(relocation_address, &relocation, sizeof(RelocationEntry));

        VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
        if (relocation_target == 0) {
            return CROFormatError(0x12);
        }

        ResultCode result = ApplyRelocation(relocation_target, relocation.type, relocation.addend,
                                            symbol_address, relocation_target);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw);
            return result;
        }

        if (relocation.is_batch_end)
            break;

        relocation_address += sizeof(RelocationEntry);
    }

    RelocationEntry relocation;
    Memory::ReadBlock(batch, &relocation, sizeof(RelocationEntry));
    relocation.is_batch_resolved = reset ? 0 : 1;
    Memory::WriteBlock(batch, &relocation, sizeof(RelocationEntry));
    return RESULT_SUCCESS;
}

VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const {
    if (!GetField(ExportTreeNum))
        return 0;

    std::size_t len = name.size();
    ExportTreeEntry entry;
    GetEntry(0, entry);
    ExportTreeEntry::Child next;
    next.raw = entry.left.raw;
    u32 found_id;

    while (true) {
        GetEntry(next.next_index, entry);

        if (next.is_end) {
            found_id = entry.export_table_index;
            break;
        }

        u16 test_byte = entry.test_bit >> 3;
        u16 test_bit_in_byte = entry.test_bit & 7;

        if (test_byte >= len) {
            next.raw = entry.left.raw;
        } else if ((name[test_byte] >> test_bit_in_byte) & 1) {
            next.raw = entry.right.raw;
        } else {
            next.raw = entry.left.raw;
        }
    }

    u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);

    if (found_id >= export_named_symbol_num)
        return 0;

    u32 export_strings_size = GetField(ExportStringsSize);
    ExportNamedSymbolEntry symbol_entry;
    GetEntry(found_id, symbol_entry);

    if (Memory::ReadCString(symbol_entry.name_offset, export_strings_size) != name)
        return 0;

    return SegmentTagToAddress(symbol_entry.symbol_position);
}

ResultCode CROHelper::RebaseHeader(u32 cro_size) {
    ResultCode error = CROFormatError(0x11);

    // verifies magic
    if (GetField(Magic) != MAGIC_CRO0)
        return error;

    // verifies not registered
    if (GetField(NextCRO) != 0 || GetField(PreviousCRO) != 0)
        return error;

    // This seems to be a hard limit set by the RO module
    if (GetField(FileSize) > 0x10000000 || GetField(BssSize) > 0x10000000)
        return error;

    // verifies not fixed
    if (GetField(FixedSize) != 0)
        return error;

    if (GetField(CodeOffset) < CRO_HEADER_SIZE)
        return error;

    // verifies that all offsets are in the correct order
    constexpr std::array<HeaderField, 18> OFFSET_ORDER = {{
        CodeOffset, ModuleNameOffset, SegmentTableOffset, ExportNamedSymbolTableOffset,
        ExportTreeTableOffset, ExportIndexedSymbolTableOffset, ExportStringsOffset,
        ImportModuleTableOffset, ExternalRelocationTableOffset, ImportNamedSymbolTableOffset,
        ImportIndexedSymbolTableOffset, ImportAnonymousSymbolTableOffset, ImportStringsOffset,
        StaticAnonymousSymbolTableOffset, InternalRelocationTableOffset,
        StaticRelocationTableOffset, DataOffset, FileSize,
    }};

    u32 prev_offset = GetField(OFFSET_ORDER[0]);
    u32 cur_offset;
    for (std::size_t i = 1; i < OFFSET_ORDER.size(); ++i) {
        cur_offset = GetField(OFFSET_ORDER[i]);
        if (cur_offset < prev_offset)
            return error;
        prev_offset = cur_offset;
    }

    // rebases offsets
    u32 offset = GetField(NameOffset);
    if (offset != 0)
        SetField(NameOffset, offset + module_address);

    for (int field = CodeOffset; field < Fix0Barrier; field += 2) {
        HeaderField header_field = static_cast<HeaderField>(field);
        offset = GetField(header_field);
        if (offset != 0)
            SetField(header_field, offset + module_address);
    }

    // verifies everything is not beyond the buffer
    u32 file_end = module_address + cro_size;
    for (int field = CodeOffset, i = 0; field < Fix0Barrier; field += 2, ++i) {
        HeaderField offset_field = static_cast<HeaderField>(field);
        HeaderField size_field = static_cast<HeaderField>(field + 1);
        if (GetField(offset_field) + GetField(size_field) * ENTRY_SIZE[i] > file_end)
            return error;
    }

    return RESULT_SUCCESS;
}

ResultVal<VAddr> CROHelper::RebaseSegmentTable(u32 cro_size, VAddr data_segment_address,
                                               u32 data_segment_size, VAddr bss_segment_address,
                                               u32 bss_segment_size) {

    u32 prev_data_segment = 0;
    u32 segment_num = GetField(SegmentNum);
    for (u32 i = 0; i < segment_num; ++i) {
        SegmentEntry segment;
        GetEntry(i, segment);
        if (segment.type == SegmentType::Data) {
            if (segment.size != 0) {
                if (segment.size > data_segment_size)
                    return ERROR_BUFFER_TOO_SMALL;
                prev_data_segment = segment.offset;
                segment.offset = data_segment_address;
            }
        } else if (segment.type == SegmentType::BSS) {
            if (segment.size != 0) {
                if (segment.size > bss_segment_size)
                    return ERROR_BUFFER_TOO_SMALL;
                segment.offset = bss_segment_address;
            }
        } else if (segment.offset != 0) {
            segment.offset += module_address;
            if (segment.offset > module_address + cro_size)
                return CROFormatError(0x19);
        }
        SetEntry(i, segment);
    }
    return MakeResult<u32>(prev_data_segment + module_address);
}

ResultCode CROHelper::RebaseExportNamedSymbolTable() {
    VAddr export_strings_offset = GetField(ExportStringsOffset);
    VAddr export_strings_end = export_strings_offset + GetField(ExportStringsSize);

    u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
    for (u32 i = 0; i < export_named_symbol_num; ++i) {
        ExportNamedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset += module_address;
            if (entry.name_offset < export_strings_offset ||
                entry.name_offset >= export_strings_end) {
                return CROFormatError(0x11);
            }
        }

        SetEntry(i, entry);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::VerifyExportTreeTable() const {
    u32 tree_num = GetField(ExportTreeNum);
    for (u32 i = 0; i < tree_num; ++i) {
        ExportTreeEntry entry;
        GetEntry(i, entry);

        if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) {
            return CROFormatError(0x11);
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::RebaseImportModuleTable() {
    VAddr import_strings_offset = GetField(ImportStringsOffset);
    VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize);
    VAddr import_indexed_symbol_table_offset = GetField(ImportIndexedSymbolTableOffset);
    VAddr index_import_table_end =
        import_indexed_symbol_table_offset +
        GetField(ImportIndexedSymbolNum) * sizeof(ImportIndexedSymbolEntry);
    VAddr import_anonymous_symbol_table_offset = GetField(ImportAnonymousSymbolTableOffset);
    VAddr offset_import_table_end =
        import_anonymous_symbol_table_offset +
        GetField(ImportAnonymousSymbolNum) * sizeof(ImportAnonymousSymbolEntry);

    u32 module_num = GetField(ImportModuleNum);
    for (u32 i = 0; i < module_num; ++i) {
        ImportModuleEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset += module_address;
            if (entry.name_offset < import_strings_offset ||
                entry.name_offset >= import_strings_end) {
                return CROFormatError(0x18);
            }
        }

        if (entry.import_indexed_symbol_table_offset != 0) {
            entry.import_indexed_symbol_table_offset += module_address;
            if (entry.import_indexed_symbol_table_offset < import_indexed_symbol_table_offset ||
                entry.import_indexed_symbol_table_offset > index_import_table_end) {
                return CROFormatError(0x18);
            }
        }

        if (entry.import_anonymous_symbol_table_offset != 0) {
            entry.import_anonymous_symbol_table_offset += module_address;
            if (entry.import_anonymous_symbol_table_offset < import_anonymous_symbol_table_offset ||
                entry.import_anonymous_symbol_table_offset > offset_import_table_end) {
                return CROFormatError(0x18);
            }
        }

        SetEntry(i, entry);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::RebaseImportNamedSymbolTable() {
    VAddr import_strings_offset = GetField(ImportStringsOffset);
    VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize);
    VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset);
    VAddr external_relocation_table_end =
        external_relocation_table_offset +
        GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry);

    u32 num = GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportNamedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset += module_address;
            if (entry.name_offset < import_strings_offset ||
                entry.name_offset >= import_strings_end) {
                return CROFormatError(0x1B);
            }
        }

        if (entry.relocation_batch_offset != 0) {
            entry.relocation_batch_offset += module_address;
            if (entry.relocation_batch_offset < external_relocation_table_offset ||
                entry.relocation_batch_offset > external_relocation_table_end) {
                return CROFormatError(0x1B);
            }
        }

        SetEntry(i, entry);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::RebaseImportIndexedSymbolTable() {
    VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset);
    VAddr external_relocation_table_end =
        external_relocation_table_offset +
        GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry);

    u32 num = GetField(ImportIndexedSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportIndexedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.relocation_batch_offset != 0) {
            entry.relocation_batch_offset += module_address;
            if (entry.relocation_batch_offset < external_relocation_table_offset ||
                entry.relocation_batch_offset > external_relocation_table_end) {
                return CROFormatError(0x14);
            }
        }

        SetEntry(i, entry);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::RebaseImportAnonymousSymbolTable() {
    VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset);
    VAddr external_relocation_table_end =
        external_relocation_table_offset +
        GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry);

    u32 num = GetField(ImportAnonymousSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportAnonymousSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.relocation_batch_offset != 0) {
            entry.relocation_batch_offset += module_address;
            if (entry.relocation_batch_offset < external_relocation_table_offset ||
                entry.relocation_batch_offset > external_relocation_table_end) {
                return CROFormatError(0x17);
            }
        }

        SetEntry(i, entry);
    }
    return RESULT_SUCCESS;
}

VAddr CROHelper::GetOnUnresolvedAddress() {
    return SegmentTagToAddress(SegmentTag(GetField(OnUnresolvedSegmentTag)));
}

ResultCode CROHelper::ResetExternalRelocations() {
    u32 unresolved_symbol = GetOnUnresolvedAddress();
    u32 external_relocation_num = GetField(ExternalRelocationNum);
    ExternalRelocationEntry relocation;

    // Verifies that the last relocation is the end of a batch
    GetEntry(external_relocation_num - 1, relocation);
    if (!relocation.is_batch_end) {
        return CROFormatError(0x12);
    }

    bool batch_begin = true;
    for (u32 i = 0; i < external_relocation_num; ++i) {
        GetEntry(i, relocation);
        VAddr relocation_target = SegmentTagToAddress(relocation.target_position);

        if (relocation_target == 0) {
            return CROFormatError(0x12);
        }

        ResultCode result = ApplyRelocation(relocation_target, relocation.type, relocation.addend,
                                            unresolved_symbol, relocation_target);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw);
            return result;
        }

        if (batch_begin) {
            // resets to unresolved state
            relocation.is_batch_resolved = 0;
            SetEntry(i, relocation);
        }

        // if current is an end, then the next is a beginning
        batch_begin = relocation.is_batch_end != 0;
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::ClearExternalRelocations() {
    u32 external_relocation_num = GetField(ExternalRelocationNum);
    ExternalRelocationEntry relocation;

    bool batch_begin = true;
    for (u32 i = 0; i < external_relocation_num; ++i) {
        GetEntry(i, relocation);
        VAddr relocation_target = SegmentTagToAddress(relocation.target_position);

        if (relocation_target == 0) {
            return CROFormatError(0x12);
        }

        ResultCode result = ClearRelocation(relocation_target, relocation.type);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error clearing relocation %08X", result.raw);
            return result;
        }

        if (batch_begin) {
            // resets to unresolved state
            relocation.is_batch_resolved = 0;
            SetEntry(i, relocation);
        }

        // if current is an end, then the next is a beginning
        batch_begin = relocation.is_batch_end != 0;
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) {
    VAddr static_relocation_table_offset = GetField(StaticRelocationTableOffset);
    VAddr static_relocation_table_end =
        static_relocation_table_offset +
        GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry);

    CROHelper crs(crs_address);
    u32 offset_export_num = GetField(StaticAnonymousSymbolNum);
    LOG_INFO(Service_LDR, "CRO \"%s\" exports %d static anonymous symbols", ModuleName().data(),
             offset_export_num);
    for (u32 i = 0; i < offset_export_num; ++i) {
        StaticAnonymousSymbolEntry entry;
        GetEntry(i, entry);
        u32 batch_address = entry.relocation_batch_offset + module_address;

        if (batch_address < static_relocation_table_offset ||
            batch_address > static_relocation_table_end) {
            return CROFormatError(0x16);
        }

        u32 symbol_address = SegmentTagToAddress(entry.symbol_position);
        LOG_TRACE(Service_LDR, "CRO \"%s\" exports 0x%08X to the static module",
                  ModuleName().data(), symbol_address);
        ResultCode result = crs.ApplyRelocationBatch(batch_address, symbol_address);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
    u32 segment_num = GetField(SegmentNum);
    u32 internal_relocation_num = GetField(InternalRelocationNum);
    for (u32 i = 0; i < internal_relocation_num; ++i) {
        InternalRelocationEntry relocation;
        GetEntry(i, relocation);
        VAddr target_addressB = SegmentTagToAddress(relocation.target_position);
        if (target_addressB == 0) {
            return CROFormatError(0x15);
        }

        VAddr target_address;
        SegmentEntry target_segment;
        GetEntry(relocation.target_position.segment_index, target_segment);

        if (target_segment.type == SegmentType::Data) {
            // If the relocation is to the .data segment, we need to relocate it in the old buffer
            target_address =
                old_data_segment_address + relocation.target_position.offset_into_segment;
        } else {
            target_address = target_addressB;
        }

        if (relocation.symbol_segment >= segment_num) {
            return CROFormatError(0x15);
        }

        SegmentEntry symbol_segment;
        GetEntry(relocation.symbol_segment, symbol_segment);
        LOG_TRACE(Service_LDR, "Internally relocates 0x%08X with 0x%08X", target_address,
                  symbol_segment.offset);
        ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend,
                                            symbol_segment.offset, target_addressB);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ClearInternalRelocations() {
    u32 internal_relocation_num = GetField(InternalRelocationNum);
    for (u32 i = 0; i < internal_relocation_num; ++i) {
        InternalRelocationEntry relocation;
        GetEntry(i, relocation);
        VAddr target_address = SegmentTagToAddress(relocation.target_position);

        if (target_address == 0) {
            return CROFormatError(0x15);
        }

        ResultCode result = ClearRelocation(target_address, relocation.type);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error clearing relocation %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

void CROHelper::UnrebaseImportAnonymousSymbolTable() {
    u32 num = GetField(ImportAnonymousSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportAnonymousSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.relocation_batch_offset != 0) {
            entry.relocation_batch_offset -= module_address;
        }

        SetEntry(i, entry);
    }
}

void CROHelper::UnrebaseImportIndexedSymbolTable() {
    u32 num = GetField(ImportIndexedSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportIndexedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.relocation_batch_offset != 0) {
            entry.relocation_batch_offset -= module_address;
        }

        SetEntry(i, entry);
    }
}

void CROHelper::UnrebaseImportNamedSymbolTable() {
    u32 num = GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < num; ++i) {
        ImportNamedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset -= module_address;
        }

        if (entry.relocation_batch_offset) {
            entry.relocation_batch_offset -= module_address;
        }

        SetEntry(i, entry);
    }
}

void CROHelper::UnrebaseImportModuleTable() {
    u32 module_num = GetField(ImportModuleNum);
    for (u32 i = 0; i < module_num; ++i) {
        ImportModuleEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset -= module_address;
        }

        if (entry.import_indexed_symbol_table_offset) {
            entry.import_indexed_symbol_table_offset -= module_address;
        }

        if (entry.import_anonymous_symbol_table_offset) {
            entry.import_anonymous_symbol_table_offset -= module_address;
        }

        SetEntry(i, entry);
    }
}

void CROHelper::UnrebaseExportNamedSymbolTable() {
    u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
    for (u32 i = 0; i < export_named_symbol_num; ++i) {
        ExportNamedSymbolEntry entry;
        GetEntry(i, entry);

        if (entry.name_offset != 0) {
            entry.name_offset -= module_address;
        }

        SetEntry(i, entry);
    }
}

void CROHelper::UnrebaseSegmentTable() {
    u32 segment_num = GetField(SegmentNum);
    for (u32 i = 0; i < segment_num; ++i) {
        SegmentEntry segment;
        GetEntry(i, segment);

        if (segment.type == SegmentType::BSS) {
            segment.offset = 0;
        } else if (segment.offset != 0) {
            segment.offset -= module_address;
        }

        SetEntry(i, segment);
    }
}

void CROHelper::UnrebaseHeader() {
    u32 offset = GetField(NameOffset);
    if (offset != 0)
        SetField(NameOffset, offset - module_address);

    for (int field = CodeOffset; field < Fix0Barrier; field += 2) {
        HeaderField header_field = static_cast<HeaderField>(field);
        offset = GetField(header_field);
        if (offset != 0)
            SetField(header_field, offset - module_address);
    }
}

ResultCode CROHelper::ApplyImportNamedSymbol(VAddr crs_address) {
    u32 import_strings_size = GetField(ImportStringsSize);
    u32 symbol_import_num = GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < symbol_import_num; ++i) {
        ImportNamedSymbolEntry entry;
        GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        if (!relocation_entry.is_batch_resolved) {
            ResultCode result =
                ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal<bool> {
                    std::string symbol_name =
                        Memory::ReadCString(entry.name_offset, import_strings_size);
                    u32 symbol_address = source.FindExportNamedSymbol(symbol_name);

                    if (symbol_address != 0) {
                        LOG_TRACE(Service_LDR, "CRO \"%s\" imports \"%s\" from \"%s\"",
                                  ModuleName().data(), symbol_name.data(),
                                  source.ModuleName().data());

                        ResultCode result = ApplyRelocationBatch(relocation_addr, symbol_address);
                        if (result.IsError()) {
                            LOG_ERROR(Service_LDR, "Error applying relocation batch %08X",
                                      result.raw);
                            return result;
                        }

                        return MakeResult<bool>(false);
                    }

                    return MakeResult<bool>(true);
                });
            if (result.IsError()) {
                return result;
            }
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ResetImportNamedSymbol() {
    u32 unresolved_symbol = GetOnUnresolvedAddress();

    u32 symbol_import_num = GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < symbol_import_num; ++i) {
        ImportNamedSymbolEntry entry;
        GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ResetImportIndexedSymbol() {
    u32 unresolved_symbol = GetOnUnresolvedAddress();

    u32 import_num = GetField(ImportIndexedSymbolNum);
    for (u32 i = 0; i < import_num; ++i) {
        ImportIndexedSymbolEntry entry;
        GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ResetImportAnonymousSymbol() {
    u32 unresolved_symbol = GetOnUnresolvedAddress();

    u32 import_num = GetField(ImportAnonymousSymbolNum);
    for (u32 i = 0; i < import_num; ++i) {
        ImportAnonymousSymbolEntry entry;
        GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw);
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) {
    u32 import_strings_size = GetField(ImportStringsSize);

    u32 import_module_num = GetField(ImportModuleNum);
    for (u32 i = 0; i < import_module_num; ++i) {
        ImportModuleEntry entry;
        GetEntry(i, entry);
        std::string want_cro_name = Memory::ReadCString(entry.name_offset, import_strings_size);

        ResultCode result =
            ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal<bool> {
                if (want_cro_name == source.ModuleName()) {
                    LOG_INFO(Service_LDR, "CRO \"%s\" imports %d indexed symbols from \"%s\"",
                             ModuleName().data(), entry.import_indexed_symbol_num,
                             source.ModuleName().data());
                    for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
                        ImportIndexedSymbolEntry im;
                        entry.GetImportIndexedSymbolEntry(j, im);
                        ExportIndexedSymbolEntry ex;
                        source.GetEntry(im.index, ex);
                        u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position);
                        LOG_TRACE(Service_LDR, "    Imports 0x%08X", symbol_address);
                        ResultCode result =
                            ApplyRelocationBatch(im.relocation_batch_offset, symbol_address);
                        if (result.IsError()) {
                            LOG_ERROR(Service_LDR, "Error applying relocation batch %08X",
                                      result.raw);
                            return result;
                        }
                    }
                    LOG_INFO(Service_LDR, "CRO \"%s\" imports %d anonymous symbols from \"%s\"",
                             ModuleName().data(), entry.import_anonymous_symbol_num,
                             source.ModuleName().data());
                    for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
                        ImportAnonymousSymbolEntry im;
                        entry.GetImportAnonymousSymbolEntry(j, im);
                        u32 symbol_address = source.SegmentTagToAddress(im.symbol_position);
                        LOG_TRACE(Service_LDR, "    Imports 0x%08X", symbol_address);
                        ResultCode result =
                            ApplyRelocationBatch(im.relocation_batch_offset, symbol_address);
                        if (result.IsError()) {
                            LOG_ERROR(Service_LDR, "Error applying relocation batch %08X",
                                      result.raw);
                            return result;
                        }
                    }
                    return MakeResult<bool>(false);
                }
                return MakeResult<bool>(true);
            });
        if (result.IsError()) {
            return result;
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyExportNamedSymbol(CROHelper target) {
    LOG_DEBUG(Service_LDR, "CRO \"%s\" exports named symbols to \"%s\"", ModuleName().data(),
              target.ModuleName().data());
    u32 target_import_strings_size = target.GetField(ImportStringsSize);
    u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < target_symbol_import_num; ++i) {
        ImportNamedSymbolEntry entry;
        target.GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        if (!relocation_entry.is_batch_resolved) {
            std::string symbol_name =
                Memory::ReadCString(entry.name_offset, target_import_strings_size);
            u32 symbol_address = FindExportNamedSymbol(symbol_name);
            if (symbol_address != 0) {
                LOG_TRACE(Service_LDR, "    exports symbol \"%s\"", symbol_name.data());
                ResultCode result = target.ApplyRelocationBatch(relocation_addr, symbol_address);
                if (result.IsError()) {
                    LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                    return result;
                }
            }
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ResetExportNamedSymbol(CROHelper target) {
    LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports named symbols to \"%s\"", ModuleName().data(),
              target.ModuleName().data());
    u32 unresolved_symbol = target.GetOnUnresolvedAddress();
    u32 target_import_strings_size = target.GetField(ImportStringsSize);
    u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < target_symbol_import_num; ++i) {
        ImportNamedSymbolEntry entry;
        target.GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        if (relocation_entry.is_batch_resolved) {
            std::string symbol_name =
                Memory::ReadCString(entry.name_offset, target_import_strings_size);
            u32 symbol_address = FindExportNamedSymbol(symbol_name);
            if (symbol_address != 0) {
                LOG_TRACE(Service_LDR, "    unexports symbol \"%s\"", symbol_name.data());
                ResultCode result =
                    target.ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
                if (result.IsError()) {
                    LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                    return result;
                }
            }
        }
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyModuleExport(CROHelper target) {
    std::string module_name = ModuleName();
    u32 target_import_string_size = target.GetField(ImportStringsSize);
    u32 target_import_module_num = target.GetField(ImportModuleNum);
    for (u32 i = 0; i < target_import_module_num; ++i) {
        ImportModuleEntry entry;
        target.GetEntry(i, entry);

        if (Memory::ReadCString(entry.name_offset, target_import_string_size) != module_name)
            continue;

        LOG_INFO(Service_LDR, "CRO \"%s\" exports %d indexed symbols to \"%s\"", module_name.data(),
                 entry.import_indexed_symbol_num, target.ModuleName().data());
        for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
            ImportIndexedSymbolEntry im;
            entry.GetImportIndexedSymbolEntry(j, im);
            ExportIndexedSymbolEntry ex;
            GetEntry(im.index, ex);
            u32 symbol_address = SegmentTagToAddress(ex.symbol_position);
            LOG_TRACE(Service_LDR, "    exports symbol 0x%08X", symbol_address);
            ResultCode result =
                target.ApplyRelocationBatch(im.relocation_batch_offset, symbol_address);
            if (result.IsError()) {
                LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                return result;
            }
        }

        LOG_INFO(Service_LDR, "CRO \"%s\" exports %d anonymous symbols to \"%s\"",
                 module_name.data(), entry.import_anonymous_symbol_num, target.ModuleName().data());
        for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
            ImportAnonymousSymbolEntry im;
            entry.GetImportAnonymousSymbolEntry(j, im);
            u32 symbol_address = SegmentTagToAddress(im.symbol_position);
            LOG_TRACE(Service_LDR, "    exports symbol 0x%08X", symbol_address);
            ResultCode result =
                target.ApplyRelocationBatch(im.relocation_batch_offset, symbol_address);
            if (result.IsError()) {
                LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                return result;
            }
        }
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::ResetModuleExport(CROHelper target) {
    u32 unresolved_symbol = target.GetOnUnresolvedAddress();

    std::string module_name = ModuleName();
    u32 target_import_string_size = target.GetField(ImportStringsSize);
    u32 target_import_module_num = target.GetField(ImportModuleNum);
    for (u32 i = 0; i < target_import_module_num; ++i) {
        ImportModuleEntry entry;
        target.GetEntry(i, entry);

        if (Memory::ReadCString(entry.name_offset, target_import_string_size) != module_name)
            continue;

        LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports indexed symbols to \"%s\"", module_name.data(),
                  target.ModuleName().data());
        for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
            ImportIndexedSymbolEntry im;
            entry.GetImportIndexedSymbolEntry(j, im);
            ResultCode result =
                target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
            if (result.IsError()) {
                LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                return result;
            }
        }

        LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports anonymous symbols to \"%s\"",
                  module_name.data(), target.ModuleName().data());
        for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
            ImportAnonymousSymbolEntry im;
            entry.GetImportAnonymousSymbolEntry(j, im);
            ResultCode result =
                target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
            if (result.IsError()) {
                LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw);
                return result;
            }
        }
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::ApplyExitRelocations(VAddr crs_address) {
    u32 import_strings_size = GetField(ImportStringsSize);
    u32 symbol_import_num = GetField(ImportNamedSymbolNum);
    for (u32 i = 0; i < symbol_import_num; ++i) {
        ImportNamedSymbolEntry entry;
        GetEntry(i, entry);
        VAddr relocation_addr = entry.relocation_batch_offset;
        ExternalRelocationEntry relocation_entry;
        Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry));

        if (Memory::ReadCString(entry.name_offset, import_strings_size) == "__aeabi_atexit") {
            ResultCode result =
                ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal<bool> {
                    u32 symbol_address = source.FindExportNamedSymbol("nnroAeabiAtexit_");

                    if (symbol_address != 0) {
                        LOG_DEBUG(Service_LDR, "CRO \"%s\" import exit function from \"%s\"",
                                  ModuleName().data(), source.ModuleName().data());

                        ResultCode result = ApplyRelocationBatch(relocation_addr, symbol_address);
                        if (result.IsError()) {
                            LOG_ERROR(Service_LDR, "Error applying relocation batch %08X",
                                      result.raw);
                            return result;
                        }

                        return MakeResult<bool>(false);
                    }

                    return MakeResult<bool>(true);
                });
            if (result.IsError()) {
                LOG_ERROR(Service_LDR, "Error applying exit relocation %08X", result.raw);
                return result;
            }
        }
    }
    return RESULT_SUCCESS;
}

/**
 * Verifies a string or a string table matching a predicted size (i.e. terminated by 0)
 * if it is not empty. There can be many other nulls in the string table because
 * they are composed by many sub strings. This function is to check whether the
 * whole string (table) is terminated properly, despite that it is not actually one string.
 * @param address the virtual address of the string (table)
 * @param size the size of the string (table), including the terminating 0
 * @returns ResultCode RESULT_SUCCESS if the size matches, otherwise error code.
 */
static ResultCode VerifyStringTableLength(VAddr address, u32 size) {
    if (size != 0) {
        if (Memory::Read8(address + size - 1) != 0)
            return CROFormatError(0x0B);
    }
    return RESULT_SUCCESS;
}

ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_addresss,
                             u32 data_segment_size, VAddr bss_segment_address, u32 bss_segment_size,
                             bool is_crs) {

    ResultCode result = RebaseHeader(cro_size);
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing header %08X", result.raw);
        return result;
    }

    result = VerifyStringTableLength(GetField(ModuleNameOffset), GetField(ModuleNameSize));
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error verifying module name %08X", result.raw);
        return result;
    }

    u32 prev_data_segment_address = 0;
    if (!is_crs) {
        auto result_val = RebaseSegmentTable(cro_size, data_segment_addresss, data_segment_size,
                                             bss_segment_address, bss_segment_size);
        if (result_val.Failed()) {
            LOG_ERROR(Service_LDR, "Error rebasing segment table %08X", result_val.Code().raw);
            return result_val.Code();
        }
        prev_data_segment_address = *result_val;
    }

    result = RebaseExportNamedSymbolTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing symbol export table %08X", result.raw);
        return result;
    }

    result = VerifyExportTreeTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error verifying export tree %08X", result.raw);
        return result;
    }

    result = VerifyStringTableLength(GetField(ExportStringsOffset), GetField(ExportStringsSize));
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error verifying export strings %08X", result.raw);
        return result;
    }

    result = RebaseImportModuleTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing object table %08X", result.raw);
        return result;
    }

    result = ResetExternalRelocations();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error resetting all external relocations %08X", result.raw);
        return result;
    }

    result = RebaseImportNamedSymbolTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing symbol import table %08X", result.raw);
        return result;
    }

    result = RebaseImportIndexedSymbolTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing index import table %08X", result.raw);
        return result;
    }

    result = RebaseImportAnonymousSymbolTable();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error rebasing offset import table %08X", result.raw);
        return result;
    }

    result = VerifyStringTableLength(GetField(ImportStringsOffset), GetField(ImportStringsSize));
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error verifying import strings %08X", result.raw);
        return result;
    }

    if (!is_crs) {
        result = ApplyStaticAnonymousSymbolToCRS(crs_address);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying offset export to CRS %08X", result.raw);
            return result;
        }
    }

    result = ApplyInternalRelocations(prev_data_segment_address);
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error applying internal relocations %08X", result.raw);
        return result;
    }

    if (!is_crs) {
        result = ApplyExitRelocations(crs_address);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying exit relocations %08X", result.raw);
            return result;
        }
    }

    return RESULT_SUCCESS;
}

void CROHelper::Unrebase(bool is_crs) {
    UnrebaseImportAnonymousSymbolTable();
    UnrebaseImportIndexedSymbolTable();
    UnrebaseImportNamedSymbolTable();
    UnrebaseImportModuleTable();
    UnrebaseExportNamedSymbolTable();

    if (!is_crs)
        UnrebaseSegmentTable();

    SetNextModule(0);
    SetPreviousModule(0);

    SetField(FixedSize, 0);

    UnrebaseHeader();
}

ResultCode CROHelper::VerifyHash(u32 cro_size, VAddr crr) const {
    // TODO(wwylele): actually verify the hash
    return RESULT_SUCCESS;
}

ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
    ResultCode result = RESULT_SUCCESS;

    {
        VAddr data_segment_address;
        if (link_on_load_bug_fix) {
            // this is a bug fix introduced by 7.2.0-17's LoadCRO_New
            // The bug itself is:
            // If a relocation target is in .data segment, it will relocate to the
            // user-specified buffer. But if this is linking during loading,
            // the .data segment hasn't been tranfer from CRO to the buffer,
            // thus the relocation will be overwritten by data transfer.
            // To fix this bug, we need temporarily restore the old .data segment
            // offset and apply imported symbols.

            // RO service seems assuming segment_index == segment_type,
            // so we do the same
            if (GetField(SegmentNum) >= 2) { // means we have .data segment
                SegmentEntry entry;
                GetEntry(2, entry);
                ASSERT(entry.type == SegmentType::Data);
                data_segment_address = entry.offset;
                entry.offset = GetField(DataOffset);
                SetEntry(2, entry);
            }
        }
        SCOPE_EXIT({
            // Restore the new .data segment address after importing
            if (link_on_load_bug_fix) {
                if (GetField(SegmentNum) >= 2) {
                    SegmentEntry entry;
                    GetEntry(2, entry);
                    entry.offset = data_segment_address;
                    SetEntry(2, entry);
                }
            }
        });

        // Imports named symbols from other modules
        result = ApplyImportNamedSymbol(crs_address);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying symbol import %08X", result.raw);
            return result;
        }

        // Imports indexed and anonymous symbols from other modules
        result = ApplyModuleImport(crs_address);
        if (result.IsError()) {
            LOG_ERROR(Service_LDR, "Error applying module import %08X", result.raw);
            return result;
        }
    }

    // Exports symbols to other modules
    result = ForEachAutoLinkCRO(crs_address, [this](CROHelper target) -> ResultVal<bool> {
        ResultCode result = ApplyExportNamedSymbol(target);
        if (result.IsError())
            return result;

        result = ApplyModuleExport(target);
        if (result.IsError())
            return result;

        return MakeResult<bool>(true);
    });
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error applying export %08X", result.raw);
        return result;
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::Unlink(VAddr crs_address) {

    // Resets all imported named symbols
    ResultCode result = ResetImportNamedSymbol();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error resetting symbol import %08X", result.raw);
        return result;
    }

    // Resets all imported indexed symbols
    result = ResetImportIndexedSymbol();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error resetting indexed import %08X", result.raw);
        return result;
    }

    // Resets all imported anonymous symbols
    result = ResetImportAnonymousSymbol();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error resetting anonymous import %08X", result.raw);
        return result;
    }

    // Resets all symbols in other modules imported from this module
    // Note: the RO service seems only searching in auto-link modules
    result = ForEachAutoLinkCRO(crs_address, [this](CROHelper target) -> ResultVal<bool> {
        ResultCode result = ResetExportNamedSymbol(target);
        if (result.IsError())
            return result;

        result = ResetModuleExport(target);
        if (result.IsError())
            return result;

        return MakeResult<bool>(true);
    });
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error resetting export %08X", result.raw);
        return result;
    }

    return RESULT_SUCCESS;
}

ResultCode CROHelper::ClearRelocations() {
    ResultCode result = ClearExternalRelocations();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error clearing external relocations %08X", result.raw);
        return result;
    }

    result = ClearInternalRelocations();
    if (result.IsError()) {
        LOG_ERROR(Service_LDR, "Error clearing internal relocations %08X", result.raw);
        return result;
    }
    return RESULT_SUCCESS;
}

void CROHelper::InitCRS() {
    SetNextModule(0);
    SetPreviousModule(0);
}

void CROHelper::Register(VAddr crs_address, bool auto_link) {
    CROHelper crs(crs_address);
    CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule());

    if (head.module_address) {
        // there are already CROs registered
        // register as the new tail
        CROHelper tail(head.PreviousModule());

        // link with the old tail
        ASSERT(tail.NextModule() == 0);
        SetPreviousModule(tail.module_address);
        tail.SetNextModule(module_address);

        // set previous of the head pointing to the new tail
        head.SetPreviousModule(module_address);
    } else {
        // register as the first CRO
        // set previous to self as tail
        SetPreviousModule(module_address);

        // set self as head
        if (auto_link)
            crs.SetNextModule(module_address);
        else
            crs.SetPreviousModule(module_address);
    }

    // the new one is the tail
    SetNextModule(0);
}

void CROHelper::Unregister(VAddr crs_address) {
    CROHelper crs(crs_address);
    CROHelper next_head(crs.NextModule()), previous_head(crs.PreviousModule());
    CROHelper next(NextModule()), previous(PreviousModule());

    if (module_address == next_head.module_address ||
        module_address == previous_head.module_address) {
        // removing head
        if (next.module_address) {
            // the next is new head
            // let its previous point to the tail
            next.SetPreviousModule(previous.module_address);
        }

        // set new head
        if (module_address == previous_head.module_address) {
            crs.SetPreviousModule(next.module_address);
        } else {
            crs.SetNextModule(next.module_address);
        }
    } else if (next.module_address) {
        // link previous and next
        previous.SetNextModule(next.module_address);
        next.SetPreviousModule(previous.module_address);
    } else {
        // removing tail
        // set previous as new tail
        previous.SetNextModule(0);

        // let head's previous point to the new tail
        if (next_head.module_address && next_head.PreviousModule() == module_address) {
            next_head.SetPreviousModule(previous.module_address);
        } else if (previous_head.module_address &&
                   previous_head.PreviousModule() == module_address) {
            previous_head.SetPreviousModule(previous.module_address);
        } else {
            UNREACHABLE();
        }
    }

    // unlink self
    SetNextModule(0);
    SetPreviousModule(0);
}

u32 CROHelper::GetFixEnd(u32 fix_level) const {
    u32 end = CRO_HEADER_SIZE;
    end = std::max<u32>(end, GetField(CodeOffset) + GetField(CodeSize));

    u32 entry_size_i = 2;
    int field = ModuleNameOffset;
    while (true) {
        end = std::max<u32>(end, GetField(static_cast<HeaderField>(field)) +
                                     GetField(static_cast<HeaderField>(field + 1)) *
                                         ENTRY_SIZE[entry_size_i]);

        ++entry_size_i;
        field += 2;

        if (field == FIX_BARRIERS[fix_level])
            return end;
    }
}

u32 CROHelper::Fix(u32 fix_level) {
    u32 fix_end = GetFixEnd(fix_level);

    if (fix_level != 0) {
        SetField(Magic, MAGIC_FIXD);

        for (int field = FIX_BARRIERS[fix_level]; field < Fix0Barrier; field += 2) {
            SetField(static_cast<HeaderField>(field), fix_end);
            SetField(static_cast<HeaderField>(field + 1), 0);
        }
    }

    fix_end = Common::AlignUp(fix_end, Memory::PAGE_SIZE);

    u32 fixed_size = fix_end - module_address;
    SetField(FixedSize, fixed_size);
    return fixed_size;
}

bool CROHelper::IsLoaded() const {
    u32 magic = GetField(Magic);
    if (magic != MAGIC_CRO0 && magic != MAGIC_FIXD)
        return false;

    // TODO(wwylele): verify memory state here after memory aliasing is implemented

    return true;
}

std::tuple<VAddr, u32> CROHelper::GetExecutablePages() const {
    u32 segment_num = GetField(SegmentNum);
    for (u32 i = 0; i < segment_num; ++i) {
        SegmentEntry entry;
        GetEntry(i, entry);
        if (entry.type == SegmentType::Code && entry.size != 0) {
            VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE);
            VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE);
            return std::make_tuple(begin, end - begin);
        }
    }
    return std::make_tuple(0, 0);
}

} // namespace
