diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 0773339a9..174e9dc79 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -95,7 +95,9 @@ set(SRCS
             hle/service/ir/ir_rst.cpp
             hle/service/ir/ir_u.cpp
             hle/service/ir/ir_user.cpp
-            hle/service/ldr_ro.cpp
+            hle/service/ldr_ro/cro_helper.cpp
+            hle/service/ldr_ro/ldr_ro.cpp
+            hle/service/ldr_ro/memory_synchronizer.cpp
             hle/service/mic_u.cpp
             hle/service/ndm/ndm.cpp
             hle/service/ndm/ndm_u.cpp
@@ -238,7 +240,9 @@ set(HEADERS
             hle/service/ir/ir_rst.h
             hle/service/ir/ir_u.h
             hle/service/ir/ir_user.h
-            hle/service/ldr_ro.h
+            hle/service/ldr_ro/cro_helper.h
+            hle/service/ldr_ro/ldr_ro.h
+            hle/service/ldr_ro/memory_synchronizer.h
             hle/service/mic_u.h
             hle/service/ndm/ndm.h
             hle/service/ndm/ndm_u.h
diff --git a/src/core/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp
deleted file mode 100644
index ecec2ce32..000000000
--- a/src/core/hle/service/ldr_ro.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/common_types.h"
-#include "common/logging/log.h"
-
-#include "core/hle/service/ldr_ro.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Namespace LDR_RO
-
-namespace LDR_RO {
-
-/**
- * LDR_RO::Initialize service function
- *  Inputs:
- *      1 : CRS buffer pointer
- *      2 : CRS Size
- *      3 : Process memory address where the CRS will be mapped
- *      4 : Value, must be zero
- *      5 : KProcess handle
- *  Outputs:
- *      0 : Return header
- *      1 : Result of function, 0 on success, otherwise error code
- */
-static void Initialize(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 crs_buffer_ptr = cmd_buff[1];
-    u32 crs_size       = cmd_buff[2];
-    u32 address        = cmd_buff[3];
-    u32 value          = cmd_buff[4];
-    u32 process        = cmd_buff[5];
-
-    if (value != 0) {
-        LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
-    }
-
-    // TODO(purpasmart96): Verify return header on HW
-
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
-    LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, address=0x%08X, value=0x%08X, process=0x%08X",
-                crs_buffer_ptr, crs_size, address, value, process);
-}
-
-/**
- * LDR_RO::LoadCRR service function
- *  Inputs:
- *      1 : CRS buffer pointer
- *      2 : CRS Size
- *      3 : Value, must be zero
- *      4 : KProcess handle
- *  Outputs:
- *      0 : Return header
- *      1 : Result of function, 0 on success, otherwise error code
- */
-static void LoadCRR(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 crs_buffer_ptr = cmd_buff[1];
-    u32 crs_size       = cmd_buff[2];
-    u32 value          = cmd_buff[3];
-    u32 process        = cmd_buff[4];
-
-    if (value != 0) {
-        LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
-    }
-
-    // TODO(purpasmart96): Verify return header on HW
-
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
-    LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, value=0x%08X, process=0x%08X",
-                crs_buffer_ptr, crs_size, value, process);
-}
-
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x000100C2, Initialize,            "Initialize"},
-    {0x00020082, LoadCRR,               "LoadCRR"},
-    {0x00030042, nullptr,               "UnloadCCR"},
-    {0x000402C2, nullptr,               "LoadExeCRO"},
-    {0x000500C2, nullptr,               "LoadCROSymbols"},
-    {0x00060042, nullptr,               "CRO_Load?"},
-    {0x00070042, nullptr,               "LoadCROSymbols"},
-    {0x00080042, nullptr,               "Shutdown"},
-    {0x000902C2, nullptr,               "LoadExeCRO_New?"},
-};
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Interface class
-
-Interface::Interface() {
-    Register(FunctionTable);
-}
-
-} // namespace
diff --git a/src/core/hle/service/ldr_ro/cro_helper.cpp b/src/core/hle/service/ldr_ro/cro_helper.cpp
new file mode 100644
index 000000000..3d2a613ee
--- /dev/null
+++ b/src/core/hle/service/ldr_ro/cro_helper.cpp
@@ -0,0 +1,1477 @@
+// 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
diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h
new file mode 100644
index 000000000..34e357afd
--- /dev/null
+++ b/src/core/hle/service/ldr_ro/cro_helper.h
@@ -0,0 +1,691 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <tuple>
+
+#include "common/common_types.h"
+#include "common/swap.h"
+
+#include "core/memory.h"
+#include "core/hle/result.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace LDR_RO
+
+namespace LDR_RO {
+
+// GCC versions < 5.0 do not implement std::is_trivially_copyable.
+// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable.
+#if (__GNUC__ >= 5) || defined(__clang__)
+    #define ASSERT_CRO_STRUCT(name, size) \
+        static_assert(std::is_standard_layout<name>::value, "CRO structure " #name " doesn't use standard layout"); \
+        static_assert(std::is_trivially_copyable<name>::value, "CRO structure " #name " isn't trivially copyable"); \
+        static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name)
+#else
+    #define ASSERT_CRO_STRUCT(name, size) \
+        static_assert(std::is_standard_layout<name>::value, "CRO structure " #name " doesn't use standard layout"); \
+        static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name)
+#endif
+
+static constexpr u32 CRO_HEADER_SIZE = 0x138;
+static constexpr u32 CRO_HASH_SIZE = 0x80;
+
+/// Represents a loaded module (CRO) with interfaces manipulating it.
+class CROHelper final {
+public:
+    explicit CROHelper(VAddr cro_address) : module_address(cro_address) {
+    }
+
+    std::string ModuleName() const {
+        return Memory::ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
+    }
+
+    u32 GetFileSize() const {
+        return GetField(FileSize);
+    }
+
+    /**
+     * Rebases the module according to its address.
+     * @param crs_address the virtual address of the static module
+     * @param cro_size the size of the CRO file
+     * @param data_segment_address buffer address for .data segment
+     * @param data_segment_size the buffer size for .data segment
+     * @param bss_segment_address the buffer address for .bss segment
+     * @param bss_segment_size the buffer size for .bss segment
+     * @param is_crs true if the module itself is the static module
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode 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);
+
+    /**
+     * Unrebases the module.
+     * @param is_crs true if the module itself is the static module
+     */
+    void Unrebase(bool is_crs);
+
+    /**
+     * Verifies module hash by CRR.
+     * @param cro_size the size of the CRO
+     * @param crr the virtual address of the CRR
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode VerifyHash(u32 cro_size, VAddr crr) const;
+
+    /**
+     * Links this module with all registered auto-link module.
+     * @param crs_address the virtual address of the static module
+     * @param link_on_load_bug_fix true if links when loading and fixes the bug
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode Link(VAddr crs_address, bool link_on_load_bug_fix);
+
+    /**
+     * Unlinks this module with other modules.
+     * @param crs_address the virtual address of the static module
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode Unlink(VAddr crs_address);
+
+    /**
+     * Clears all relocations to zero.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ClearRelocations();
+
+    /// Initialize this module as the static module (CRS)
+    void InitCRS();
+
+    /**
+     * Registers this module and adds it to the module list.
+     * @param crs_address the virtual address of the static module
+     * @auto_link whether to register as an auto link module
+     */
+    void Register(VAddr crs_address, bool auto_link);
+
+    /**
+     * Unregisters this module and removes from the module list.
+     * @param crs_address the virtual address of the static module
+     */
+    void Unregister(VAddr crs_address);
+
+    /**
+     * Gets the end of reserved data according to the fix level.
+     * @param fix_level fix level from 0 to 3
+     * @returns the end of reserved data.
+     */
+    u32 GetFixEnd(u32 fix_level) const;
+
+    /**
+     * Zeros offsets to cropped data according to the fix level and marks as fixed.
+     * @param fix_level fix level from 0 to 3
+     * @returns page-aligned size of the module after fixing.
+     */
+    u32 Fix(u32 fix_level);
+
+    bool IsFixed() const {
+        return GetField(Magic) == MAGIC_FIXD;
+    }
+
+    u32 GetFixedSize() const {
+        return GetField(FixedSize);
+    }
+
+    bool IsLoaded() const;
+
+    /**
+     * Gets the page address and size of the code segment.
+     * @returns a tuple of (address, size); (0, 0) if the code segment doesn't exist.
+     */
+    std::tuple<VAddr, u32> GetExecutablePages() const;
+
+private:
+    const VAddr module_address; ///< the virtual address of this module
+
+    /**
+     * Each item in this enum represents a u32 field in the header begin from address+0x80, successively.
+     * We don't directly use a struct here, to avoid GetPointer, reinterpret_cast, or Read/WriteBlock repeatedly.
+     */
+    enum HeaderField {
+        Magic = 0,
+        NameOffset,
+        NextCRO,
+        PreviousCRO,
+        FileSize,
+        BssSize,
+        FixedSize,
+        UnknownZero,
+        UnkSegmentTag,
+        OnLoadSegmentTag,
+        OnExitSegmentTag,
+        OnUnresolvedSegmentTag,
+
+        CodeOffset,
+        CodeSize,
+        DataOffset,
+        DataSize,
+        ModuleNameOffset,
+        ModuleNameSize,
+        SegmentTableOffset,
+        SegmentNum,
+
+        ExportNamedSymbolTableOffset,
+        ExportNamedSymbolNum,
+        ExportIndexedSymbolTableOffset,
+        ExportIndexedSymbolNum,
+        ExportStringsOffset,
+        ExportStringsSize,
+        ExportTreeTableOffset,
+        ExportTreeNum,
+
+        ImportModuleTableOffset,
+        ImportModuleNum,
+        ExternalRelocationTableOffset,
+        ExternalRelocationNum,
+        ImportNamedSymbolTableOffset,
+        ImportNamedSymbolNum,
+        ImportIndexedSymbolTableOffset,
+        ImportIndexedSymbolNum,
+        ImportAnonymousSymbolTableOffset,
+        ImportAnonymousSymbolNum,
+        ImportStringsOffset,
+        ImportStringsSize,
+
+        StaticAnonymousSymbolTableOffset,
+        StaticAnonymousSymbolNum,
+        InternalRelocationTableOffset,
+        InternalRelocationNum,
+        StaticRelocationTableOffset,
+        StaticRelocationNum,
+        Fix0Barrier,
+
+        Fix3Barrier = ExportNamedSymbolTableOffset,
+        Fix2Barrier = ImportModuleTableOffset,
+        Fix1Barrier = StaticAnonymousSymbolTableOffset,
+    };
+    static_assert(Fix0Barrier == (CRO_HEADER_SIZE - CRO_HASH_SIZE) / 4, "CRO Header fields are wrong!");
+
+    enum class SegmentType : u32 {
+        Code   = 0,
+        ROData = 1,
+        Data   = 2,
+        BSS    = 3,
+    };
+
+    /**
+     * Identifies a program location inside of a segment.
+     * Required to refer to program locations because individual segments may be relocated independently of each other.
+     */
+    union SegmentTag {
+        u32_le raw;
+        BitField<0, 4, u32_le> segment_index;
+        BitField<4, 28, u32_le> offset_into_segment;
+
+        SegmentTag() = default;
+        explicit SegmentTag(u32 raw_) : raw(raw_) {}
+    };
+
+    /// Information of a segment in this module.
+    struct SegmentEntry {
+        u32_le offset;
+        u32_le size;
+        SegmentType type;
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = SegmentTableOffset;
+    };
+    ASSERT_CRO_STRUCT(SegmentEntry, 12);
+
+    /// Identifies a named symbol exported from this module.
+    struct ExportNamedSymbolEntry {
+        u32_le name_offset;         // pointing to a substring in ExportStrings
+        SegmentTag symbol_position; // to self's segment
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ExportNamedSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ExportNamedSymbolEntry, 8);
+
+    /// Identifies an indexed symbol exported from this module.
+    struct ExportIndexedSymbolEntry {
+        SegmentTag symbol_position; // to self's segment
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ExportIndexedSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ExportIndexedSymbolEntry, 4);
+
+    /// A tree node in the symbol lookup tree.
+    struct ExportTreeEntry {
+        u16_le test_bit; // bit address into the name to test
+        union Child {
+            u16_le raw;
+            BitField<0, 15, u16_le> next_index;
+            BitField<15, 1, u16_le> is_end;
+        } left, right;
+        u16_le export_table_index; // index of an ExportNamedSymbolEntry
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ExportTreeTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ExportTreeEntry, 8);
+
+    /// Identifies a named symbol imported from another module.
+    struct ImportNamedSymbolEntry {
+        u32_le name_offset;             // pointing to a substring in ImportStrings
+        u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ImportNamedSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ImportNamedSymbolEntry, 8);
+
+    /// Identifies an indexed symbol imported from another module.
+    struct ImportIndexedSymbolEntry {
+        u32_le index;                   // index of an ExportIndexedSymbolEntry in the exporting module
+        u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ImportIndexedSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ImportIndexedSymbolEntry, 8);
+
+    /// Identifies an anonymous symbol imported from another module.
+    struct ImportAnonymousSymbolEntry {
+        SegmentTag symbol_position;      // in the exporting segment
+        u32_le relocation_batch_offset;  // pointing to a relocation batch in ExternalRelocationTable
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ImportAnonymousSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ImportAnonymousSymbolEntry, 8);
+
+    /// Information of a imported module and symbols imported from it.
+    struct ImportModuleEntry {
+        u32_le name_offset;                          // pointing to a substring in ImportStrings
+        u32_le import_indexed_symbol_table_offset;   // pointing to a subtable in ImportIndexedSymbolTable
+        u32_le import_indexed_symbol_num;
+        u32_le import_anonymous_symbol_table_offset; // pointing to a subtable in ImportAnonymousSymbolTable
+        u32_le import_anonymous_symbol_num;
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ImportModuleTableOffset;
+
+        void GetImportIndexedSymbolEntry(u32 index, ImportIndexedSymbolEntry& entry) {
+            Memory::ReadBlock(import_indexed_symbol_table_offset + index * sizeof(ImportIndexedSymbolEntry),
+                &entry, sizeof(ImportIndexedSymbolEntry));
+        }
+
+        void GetImportAnonymousSymbolEntry(u32 index, ImportAnonymousSymbolEntry& entry) {
+            Memory::ReadBlock(import_anonymous_symbol_table_offset + index * sizeof(ImportAnonymousSymbolEntry),
+                &entry, sizeof(ImportAnonymousSymbolEntry));
+        }
+    };
+    ASSERT_CRO_STRUCT(ImportModuleEntry, 20);
+
+    enum class RelocationType : u8 {
+        Nothing                = 0,
+        AbsoluteAddress        = 2,
+        RelativeAddress        = 3,
+        ThumbBranch            = 10,
+        ArmBranch              = 28,
+        ModifyArmBranch        = 29,
+        AbsoluteAddress2       = 38,
+        AlignedRelativeAddress = 42,
+    };
+
+    struct RelocationEntry {
+        SegmentTag target_position; // to self's segment as an ExternalRelocationEntry; to static module segment as a StaticRelocationEntry
+        RelocationType type;
+        u8 is_batch_end;
+        u8 is_batch_resolved;       // set at a batch beginning if the batch is resolved
+        INSERT_PADDING_BYTES(1);
+        u32_le addend;
+    };
+
+    /// Identifies a normal cross-module relocation.
+    struct ExternalRelocationEntry : RelocationEntry {
+        static constexpr HeaderField TABLE_OFFSET_FIELD = ExternalRelocationTableOffset;
+    };
+    ASSERT_CRO_STRUCT(ExternalRelocationEntry, 12);
+
+    /// Identifies a special static relocation (no game is known using this).
+    struct StaticRelocationEntry : RelocationEntry {
+        static constexpr HeaderField TABLE_OFFSET_FIELD = StaticRelocationTableOffset;
+    };
+    ASSERT_CRO_STRUCT(StaticRelocationEntry, 12);
+
+    /// Identifies a in-module relocation.
+    struct InternalRelocationEntry {
+        SegmentTag target_position; // to self's segment
+        RelocationType type;
+        u8 symbol_segment;
+        INSERT_PADDING_BYTES(2);
+        u32_le addend;
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = InternalRelocationTableOffset;
+    };
+    ASSERT_CRO_STRUCT(InternalRelocationEntry, 12);
+
+    /// Identifies a special static anonymous symbol (no game is known using this).
+    struct StaticAnonymousSymbolEntry {
+        SegmentTag symbol_position;      // to self's segment
+        u32_le relocation_batch_offset;  // pointing to a relocation batch in StaticRelocationTable
+
+        static constexpr HeaderField TABLE_OFFSET_FIELD = StaticAnonymousSymbolTableOffset;
+    };
+    ASSERT_CRO_STRUCT(StaticAnonymousSymbolEntry, 8);
+
+    /**
+     * Entry size of each table, from Code to StaticRelocationTable.
+     * Byte string contents (such as Code) are treated with entries of size 1.
+     * This is used for verifying the size of each table and calculating the fix end.
+     */
+    static const std::array<int, 17> ENTRY_SIZE;
+
+    /// The offset field of the table where to crop for each fix level
+    static const std::array<HeaderField, 4> FIX_BARRIERS;
+
+    static constexpr u32 MAGIC_CRO0 = 0x304F5243;
+    static constexpr u32 MAGIC_FIXD = 0x44584946;
+
+    VAddr Field(HeaderField field) const {
+        return module_address + CRO_HASH_SIZE + field * 4;
+    }
+
+    u32 GetField(HeaderField field) const {
+        return Memory::Read32(Field(field));
+    }
+
+    void SetField(HeaderField field, u32 value) {
+        Memory::Write32(Field(field), value);
+    }
+
+    /**
+     * Reads an entry in one of module tables.
+     * @param index index of the entry
+     * @param data where to put the read entry
+     * @note the entry type must have the static member TABLE_OFFSET_FIELD
+     *       indicating which table the entry is in.
+     */
+    template <typename T>
+    void GetEntry(std::size_t index, T& data) const {
+        Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
+    }
+
+    /**
+     * Writes an entry to one of module tables.
+     * @param index index of the entry
+     * @param data the entry data to write
+     * @note the entry type must have the static member TABLE_OFFSET_FIELD
+     *       indicating which table the entry is in.
+     */
+    template <typename T>
+    void SetEntry(std::size_t index, const T& data) {
+        Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
+    }
+
+    /**
+     * Converts a segment tag to virtual address in this module.
+     * @param segment_tag the segment tag to convert
+     * @returns VAddr the virtual address the segment tag points to; 0 if invalid.
+     */
+    VAddr SegmentTagToAddress(SegmentTag segment_tag) const;
+
+    VAddr NextModule() const {
+        return GetField(NextCRO);
+    }
+
+    VAddr PreviousModule() const {
+        return GetField(PreviousCRO);
+    }
+
+    void SetNextModule(VAddr next) {
+        SetField(NextCRO, next);
+    }
+
+    void SetPreviousModule(VAddr previous) {
+        SetField(PreviousCRO, previous);
+    }
+
+    /**
+     * A helper function iterating over all registered auto-link modules, including the static module.
+     * @param crs_address the virtual address of the static module
+     * @param func a function object to operate on a module. It accepts one parameter
+     *        CROHelper and returns ResultVal<bool>. It should return true to continue the iteration,
+     *        false to stop the iteration, or an error code (which will also stop the iteration).
+     * @returns ResultCode indicating the result of the operation, RESULT_SUCCESS if all iteration success,
+     *         otherwise error code of the last iteration.
+     */
+    template <typename FunctionObject>
+    static ResultCode ForEachAutoLinkCRO(VAddr crs_address, FunctionObject func) {
+        VAddr current = crs_address;
+        while (current != 0) {
+            CROHelper cro(current);
+            CASCADE_RESULT(bool next, func(cro));
+            if (!next)
+                break;
+            current = cro.NextModule();
+        }
+        return RESULT_SUCCESS;
+    }
+
+    /**
+     * Applies a relocation
+     * @param target_address where to apply the relocation
+     * @param relocation_type the type of the relocation
+     * @param addend address addend applied to the relocated symbol
+     * @param symbol_address the symbol address to be relocated with
+     * @param target_future_address the future address of the target.
+     *        Usually equals to target_address, but will be different for a target in .data segment
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyRelocation(VAddr target_address, RelocationType relocation_type,
+        u32 addend, u32 symbol_address, u32 target_future_address);
+
+    /**
+     * Clears a relocation to zero
+     * @param target_address where to apply the relocation
+     * @param relocation_type the type of the relocation
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ClearRelocation(VAddr target_address, RelocationType relocation_type);
+
+    /**
+     * Applies or resets a batch of relocations
+     * @param batch the virtual address of the first relocation in the batch
+     * @param symbol_address the symbol address to be relocated with
+     * @param reset false to set the batch to resolved state, true to reset the batch to unresolved state
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset = false);
+
+    /**
+     * Finds an exported named symbol in this module.
+     * @param name the name of the symbol to find
+     * @return VAddr the virtual address of the symbol; 0 if not found.
+     */
+    VAddr FindExportNamedSymbol(const std::string& name) const;
+
+    /**
+     * Rebases offsets in module header according to module address.
+     * @param cro_size the size of the CRO file
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseHeader(u32 cro_size);
+
+    /**
+     * Rebases offsets in segment table according to module address.
+     * @param cro_size the size of the CRO file
+     * @param data_segment_address the buffer address for .data segment
+     * @param data_segment_size the buffer size for .data segment
+     * @param bss_segment_address the buffer address for .bss segment
+     * @param bss_segment_size the buffer size for .bss segment
+     * @returns ResultVal<VAddr> with the virtual address of .data segment in CRO.
+     */
+    ResultVal<VAddr> RebaseSegmentTable(u32 cro_size,
+        VAddr data_segment_address, u32 data_segment_size,
+        VAddr bss_segment_address, u32 bss_segment_size);
+
+    /**
+     * Rebases offsets in exported named symbol table according to module address.
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseExportNamedSymbolTable();
+
+    /**
+     * Verifies indices in export tree table.
+     * @returns ResultCode RESULT_SUCCESS if all indices are verified as valid, otherwise error code.
+     */
+    ResultCode VerifyExportTreeTable() const;
+
+    /**
+     * Rebases offsets in exported module table according to module address.
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseImportModuleTable();
+
+    /**
+     * Rebases offsets in imported named symbol table according to module address.
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseImportNamedSymbolTable();
+
+    /**
+     * Rebases offsets in imported indexed symbol table according to module address.
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseImportIndexedSymbolTable();
+
+    /**
+     * Rebases offsets in imported anonymous symbol table according to module address.
+     * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
+     */
+    ResultCode RebaseImportAnonymousSymbolTable();
+
+    /**
+     * Gets the address of OnUnresolved function in this module.
+     * Used as the applied symbol for reset relocation.
+     * @returns the virtual address of OnUnresolved. 0 if not provided.
+     */
+    VAddr GetOnUnresolvedAddress();
+
+    /**
+     * Resets all external relocations to unresolved state.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetExternalRelocations();
+
+    /**
+     * Clears all external relocations to zero.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ClearExternalRelocations();
+
+    /**
+     * Applies all static anonymous symbol to the static module.
+     * @param crs_address the virtual address of the static module
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyStaticAnonymousSymbolToCRS(VAddr crs_address);
+
+    /**
+     * Applies all internal relocations to the module itself.
+     * @param old_data_segment_address the virtual address of data segment in CRO buffer
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyInternalRelocations(u32 old_data_segment_address);
+
+    /**
+     * Clears all internal relocations to zero.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ClearInternalRelocations();
+
+    /// Unrebases offsets in imported anonymous symbol table
+    void UnrebaseImportAnonymousSymbolTable();
+
+    /// Unrebases offsets in imported indexed symbol table
+    void UnrebaseImportIndexedSymbolTable();
+
+    /// Unrebases offsets in imported named symbol table
+    void UnrebaseImportNamedSymbolTable();
+
+    /// Unrebases offsets in imported module table
+    void UnrebaseImportModuleTable();
+
+    /// Unrebases offsets in exported named symbol table
+    void UnrebaseExportNamedSymbolTable();
+
+    /// Unrebases offsets in segment table
+    void UnrebaseSegmentTable();
+
+    /// Unrebases offsets in module header
+    void UnrebaseHeader();
+
+    /**
+     * Looks up all imported named symbols of this module in all registered auto-link modules, and resolves them if found.
+     * @param crs_address the virtual address of the static module
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyImportNamedSymbol(VAddr crs_address);
+
+    /**
+     * Resets all imported named symbols of this module to unresolved state.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetImportNamedSymbol();
+
+    /**
+     * Resets all imported indexed symbols of this module to unresolved state.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetImportIndexedSymbol();
+
+    /**
+     * Resets all imported anonymous symbols of this module to unresolved state.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetImportAnonymousSymbol();
+
+    /**
+     * Finds registered auto-link modules that this module imports, and resolves indexed and anonymous symbols exported by them.
+     * @param crs_address the virtual address of the static module
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyModuleImport(VAddr crs_address);
+
+    /**
+     * Resolves target module's imported named symbols that exported by this module.
+     * @param target the module to resolve.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyExportNamedSymbol(CROHelper target);
+
+    /**
+     * Resets target's named symbols imported from this module to unresolved state.
+     * @param target the module to reset.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetExportNamedSymbol(CROHelper target);
+
+    /**
+     * Resolves imported indexed and anonymous symbols in the target module which imports this module.
+     * @param target the module to resolve.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyModuleExport(CROHelper target);
+
+    /**
+     * Resets target's indexed and anonymous symbol imported from this module to unresolved state.
+     * @param target the module to reset.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ResetModuleExport(CROHelper target);
+
+    /**
+     * Resolves the exit function in this module
+     * @param crs_address the virtual address of the static module.
+     * @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
+     */
+    ResultCode ApplyExitRelocations(VAddr crs_address);
+};
+
+} // namespace
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp
new file mode 100644
index 000000000..8ba73ea8d
--- /dev/null
+++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp
@@ -0,0 +1,748 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+
+#include "core/arm/arm_interface.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/vm_manager.h"
+#include "core/hle/service/ldr_ro/cro_helper.h"
+#include "core/hle/service/ldr_ro/ldr_ro.h"
+#include "core/hle/service/ldr_ro/memory_synchronizer.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace LDR_RO
+
+namespace LDR_RO {
+
+static const ResultCode ERROR_ALREADY_INITIALIZED =   // 0xD9612FF9
+    ResultCode(ErrorDescription::AlreadyInitialized,         ErrorModule::RO, ErrorSummary::Internal,        ErrorLevel::Permanent);
+static const ResultCode ERROR_NOT_INITIALIZED =       // 0xD9612FF8
+    ResultCode(ErrorDescription::NotInitialized,             ErrorModule::RO, ErrorSummary::Internal,        ErrorLevel::Permanent);
+static const ResultCode ERROR_BUFFER_TOO_SMALL =      // 0xE0E12C1F
+    ResultCode(static_cast<ErrorDescription>(31),            ErrorModule::RO, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+static const ResultCode ERROR_MISALIGNED_ADDRESS =    // 0xD9012FF1
+    ResultCode(ErrorDescription::MisalignedAddress,          ErrorModule::RO, ErrorSummary::WrongArgument,   ErrorLevel::Permanent);
+static const ResultCode ERROR_MISALIGNED_SIZE =       // 0xD9012FF2
+    ResultCode(ErrorDescription::MisalignedSize,             ErrorModule::RO, ErrorSummary::WrongArgument,   ErrorLevel::Permanent);
+static const ResultCode ERROR_ILLEGAL_ADDRESS =       // 0xE1612C0F
+    ResultCode(static_cast<ErrorDescription>(15),            ErrorModule::RO, ErrorSummary::Internal,        ErrorLevel::Usage);
+static const ResultCode ERROR_INVALID_MEMORY_STATE =  // 0xD8A12C08
+    ResultCode(static_cast<ErrorDescription>(8),             ErrorModule::RO, ErrorSummary::InvalidState,    ErrorLevel::Permanent);
+static const ResultCode ERROR_NOT_LOADED =            // 0xD8A12C0D
+    ResultCode(static_cast<ErrorDescription>(13),            ErrorModule::RO, ErrorSummary::InvalidState,    ErrorLevel::Permanent);
+static const ResultCode ERROR_INVALID_DESCRIPTOR =    // 0xD9001830
+    ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, ErrorSummary::WrongArgument,   ErrorLevel::Permanent);
+
+static MemorySynchronizer memory_synchronizer;
+
+// TODO(wwylele): this should be in the per-client storage when we implement multi-process
+static VAddr loaded_crs; ///< the virtual address of the static module
+
+static bool VerifyBufferState(VAddr buffer_ptr, u32 size) {
+    auto vma = Kernel::g_current_process->vm_manager.FindVMA(buffer_ptr);
+    return vma != Kernel::g_current_process->vm_manager.vma_map.end()
+        && vma->second.base + vma->second.size >= buffer_ptr + size
+        && vma->second.permissions == Kernel::VMAPermission::ReadWrite
+        && vma->second.meminfo_state == Kernel::MemoryState::Private;
+}
+
+/**
+ * LDR_RO::Initialize service function
+ *  Inputs:
+ *      0 : 0x000100C2
+ *      1 : CRS buffer pointer
+ *      2 : CRS Size
+ *      3 : Process memory address where the CRS will be mapped
+ *      4 : handle translation descriptor (zero)
+ *      5 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void Initialize(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr crs_buffer_ptr = cmd_buff[1];
+    u32 crs_size         = cmd_buff[2];
+    VAddr crs_address    = cmd_buff[3];
+    u32 descriptor       = cmd_buff[4];
+    u32 process          = cmd_buff[5];
+
+    LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, descriptor=0x%08X, process=0x%08X",
+                crs_buffer_ptr, crs_address, crs_size, descriptor, process);
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    cmd_buff[0] = IPC::MakeHeader(1, 1, 0);
+
+    if (loaded_crs != 0) {
+        LOG_ERROR(Service_LDR, "Already initialized");
+        cmd_buff[1] = ERROR_ALREADY_INITIALIZED.raw;
+        return;
+    }
+
+    if (crs_size < CRO_HEADER_SIZE) {
+        LOG_ERROR(Service_LDR, "CRS is too small");
+        cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw;
+        return;
+    }
+
+    if (crs_buffer_ptr & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRS original address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (crs_address & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRS mapping address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (crs_size & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRS size is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw;
+        return;
+    }
+
+    if (!VerifyBufferState(crs_buffer_ptr, crs_size)) {
+        LOG_ERROR(Service_LDR, "CRS original buffer is in invalid state");
+        cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw;
+        return;
+    }
+
+    if (crs_address < Memory::PROCESS_IMAGE_VADDR || crs_address + crs_size > Memory::PROCESS_IMAGE_VADDR_END) {
+        LOG_ERROR(Service_LDR, "CRS mapping address is not in the process image region");
+        cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw;
+        return;
+    }
+
+    ResultCode result = RESULT_SUCCESS;
+
+    if (crs_buffer_ptr != crs_address) {
+        // TODO(wwylele): should be memory aliasing
+        std::shared_ptr<std::vector<u8>> crs_mem = std::make_shared<std::vector<u8>>(crs_size);
+        Memory::ReadBlock(crs_buffer_ptr, crs_mem->data(), crs_size);
+        result = Kernel::g_current_process->vm_manager.MapMemoryBlock(crs_address, crs_mem, 0, crs_size, Kernel::MemoryState::Code).Code();
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+
+        result = Kernel::g_current_process->vm_manager.ReprotectRange(crs_address, crs_size, Kernel::VMAPermission::Read);
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+
+        memory_synchronizer.AddMemoryBlock(crs_address, crs_buffer_ptr, crs_size);
+    } else {
+        // Do nothing if buffer_ptr == address
+        // TODO(wwylele): verify this behaviour. This is only seen in the web browser app,
+        //     and the actual behaviour is unclear. "Do nothing" is probably an incorrect implement.
+        //     There is also a chance that another issue causes the app passing wrong arguments.
+        LOG_WARNING(Service_LDR, "crs_buffer_ptr == crs_address (0x%08X)", crs_address);
+    }
+
+    CROHelper crs(crs_address);
+    crs.InitCRS();
+
+    result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error rebasing CRS 0x%08X", result.raw);
+        cmd_buff[1] = result.raw;
+        return;
+    }
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+
+    loaded_crs = crs_address;
+
+    cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+/**
+ * LDR_RO::LoadCRR service function
+ *  Inputs:
+ *      0 : 0x00020082
+ *      1 : CRR buffer pointer
+ *      2 : CRR Size
+ *      3 : handle translation descriptor (zero)
+ *      4 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void LoadCRR(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    u32 crr_buffer_ptr = cmd_buff[1];
+    u32 crr_size       = cmd_buff[2];
+    u32 descriptor     = cmd_buff[3];
+    u32 process        = cmd_buff[4];
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    cmd_buff[0] = IPC::MakeHeader(2, 1, 0);
+    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+    LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, descriptor=0x%08X, process=0x%08X",
+                crr_buffer_ptr, crr_size, descriptor, process);
+}
+
+/**
+ * LDR_RO::UnloadCRR service function
+ *  Inputs:
+ *      0 : 0x00030042
+ *      1 : CRR buffer pointer
+ *      2 : handle translation descriptor (zero)
+ *      3 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void UnloadCRR(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    u32 crr_buffer_ptr = cmd_buff[1];
+    u32 descriptor     = cmd_buff[2];
+    u32 process        = cmd_buff[3];
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    cmd_buff[0] = IPC::MakeHeader(3, 1, 0);
+    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+    LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
+                crr_buffer_ptr, descriptor, process);
+}
+
+/**
+ * LDR_RO::LoadCRO service function
+ *  Inputs:
+ *      0 : 0x000402C2 (old) / 0x000902C2 (new)
+ *      1 : CRO buffer pointer
+ *      2 : memory address where the CRO will be mapped
+ *      3 : CRO Size
+ *      4 : .data segment buffer pointer
+ *      5 : must be zero
+ *      6 : .data segment buffer size
+ *      7 : .bss segment buffer pointer
+ *      8 : .bss segment buffer size
+ *      9 : (bool) register CRO as auto-link module
+ *     10 : fix level
+ *     11 : CRR address (zero if use loaded CRR)
+ *     12 : handle translation descriptor (zero)
+ *     13 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ *      2 : CRO fixed size
+ *  Note:
+ *      This service function has two versions. The function defined here is a
+ *      unified one of two, with an additional parameter link_on_load_bug_fix.
+ *      There is a dispatcher template below.
+ */
+static void LoadCRO(Service::Interface* self, bool link_on_load_bug_fix) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr cro_buffer_ptr       = cmd_buff[1];
+    VAddr cro_address          = cmd_buff[2];
+    u32 cro_size               = cmd_buff[3];
+    VAddr data_segment_address = cmd_buff[4];
+    u32 zero                   = cmd_buff[5];
+    u32 data_segment_size      = cmd_buff[6];
+    u32 bss_segment_address    = cmd_buff[7];
+    u32 bss_segment_size       = cmd_buff[8];
+    bool auto_link             = (cmd_buff[9] & 0xFF) != 0;
+    u32 fix_level              = cmd_buff[10];
+    VAddr crr_address          = cmd_buff[11];
+    u32 descriptor             = cmd_buff[12];
+    u32 process                = cmd_buff[13];
+
+    LOG_DEBUG(Service_LDR, "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, "
+        "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, bss_segment_address=0x%08X, bss_segment_size=0x%X, "
+        "auto_link=%s, fix_level=%d, crr_address=0x%08X, descriptor=0x%08X, process=0x%08X",
+        link_on_load_bug_fix ? "new" : "old", cro_buffer_ptr, cro_address, cro_size,
+        data_segment_address, zero, data_segment_size, bss_segment_address, bss_segment_size,
+        auto_link ? "true" : "false", fix_level, crr_address, descriptor, process
+        );
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    cmd_buff[0] = IPC::MakeHeader(link_on_load_bug_fix ? 9 : 4, 2, 0);
+
+    if (loaded_crs == 0) {
+        LOG_ERROR(Service_LDR, "Not initialized");
+        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
+        return;
+    }
+
+    if (cro_size < CRO_HEADER_SIZE) {
+        LOG_ERROR(Service_LDR, "CRO too small");
+        cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw;
+        return;
+    }
+
+    if (cro_buffer_ptr & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO original address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (cro_address & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO mapping address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (cro_size & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO size is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw;
+        return;
+    }
+
+    if (!VerifyBufferState(cro_buffer_ptr, cro_size)) {
+        LOG_ERROR(Service_LDR, "CRO original buffer is in invalid state");
+        cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw;
+        return;
+    }
+
+    if (cro_address < Memory::PROCESS_IMAGE_VADDR
+        || cro_address + cro_size > Memory::PROCESS_IMAGE_VADDR_END) {
+        LOG_ERROR(Service_LDR, "CRO mapping address is not in the process image region");
+        cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw;
+        return;
+    }
+
+    if (zero) {
+        LOG_ERROR(Service_LDR, "Zero is not zero %d", zero);
+        cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage).raw;
+        return;
+    }
+
+    ResultCode result = RESULT_SUCCESS;
+
+    if (cro_buffer_ptr != cro_address) {
+        // TODO(wwylele): should be memory aliasing
+        std::shared_ptr<std::vector<u8>> cro_mem = std::make_shared<std::vector<u8>>(cro_size);
+        Memory::ReadBlock(cro_buffer_ptr, cro_mem->data(), cro_size);
+        result = Kernel::g_current_process->vm_manager.MapMemoryBlock(cro_address, cro_mem, 0, cro_size, Kernel::MemoryState::Code).Code();
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+
+        result = Kernel::g_current_process->vm_manager.ReprotectRange(cro_address, cro_size, Kernel::VMAPermission::Read);
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
+            Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+
+        memory_synchronizer.AddMemoryBlock(cro_address, cro_buffer_ptr, cro_size);
+    } else {
+        // Do nothing if buffer_ptr == address
+        // TODO(wwylele): verify this behaviour.
+        //     This is derived from the case of LoadCRS with buffer_ptr==address,
+        //     and is never seen in any game. "Do nothing" is probably an incorrect implement.
+        //     There is also a chance that this case is just prohibited.
+        LOG_WARNING(Service_LDR, "cro_buffer_ptr == cro_address (0x%08X)", cro_address);
+    }
+
+    CROHelper cro(cro_address);
+
+    result = cro.VerifyHash(cro_size, crr_address);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error verifying CRO in CRR %08X", result.raw);
+        Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
+        cmd_buff[1] = result.raw;
+        return;
+    }
+
+    result = cro.Rebase(loaded_crs, cro_size, data_segment_address, data_segment_size, bss_segment_address, bss_segment_size, false);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error rebasing CRO %08X", result.raw);
+        Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
+        cmd_buff[1] = result.raw;
+        return;
+    }
+
+    result = cro.Link(loaded_crs, link_on_load_bug_fix);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw);
+        Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
+        cmd_buff[1] = result.raw;
+        return;
+    }
+
+    cro.Register(loaded_crs, auto_link);
+
+    u32 fix_size = cro.Fix(fix_level);
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+
+    // TODO(wwylele): verify the behaviour when buffer_ptr == address
+    if (cro_buffer_ptr != cro_address) {
+        if (fix_size != cro_size) {
+            result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address + fix_size, cro_size - fix_size);
+            if (result.IsError()) {
+                LOG_ERROR(Service_LDR, "Error unmapping memory block %08X", result.raw);
+                Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
+                cmd_buff[1] = result.raw;
+                return;
+            }
+        }
+
+        // Changes the block size
+        memory_synchronizer.ResizeMemoryBlock(cro_address, cro_buffer_ptr, fix_size);
+    }
+
+    VAddr exe_begin;
+    u32 exe_size;
+    std::tie(exe_begin, exe_size) = cro.GetExecutablePages();
+    if (exe_begin) {
+        result = Kernel::g_current_process->vm_manager.ReprotectRange(exe_begin, exe_size, Kernel::VMAPermission::ReadExecute);
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
+            Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fix_size);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+    }
+
+    Core::g_app_core->ClearInstructionCache();
+
+    LOG_INFO(Service_LDR, "CRO \"%s\" loaded at 0x%08X, fixed_end=0x%08X",
+        cro.ModuleName().data(), cro_address, cro_address+fix_size);
+
+    cmd_buff[1] = RESULT_SUCCESS.raw;
+    cmd_buff[2] = fix_size;
+}
+
+template <bool link_on_load_bug_fix>
+static void LoadCRO(Service::Interface* self) {
+    LoadCRO(self, link_on_load_bug_fix);
+}
+
+/**
+ * LDR_RO::UnloadCRO service function
+ *  Inputs:
+ *      0 : 0x000500C2
+ *      1 : mapped CRO pointer
+ *      2 : zero? (RO service doesn't care)
+ *      3 : original CRO pointer
+ *      4 : handle translation descriptor (zero)
+ *      5 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void UnloadCRO(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr cro_address      = cmd_buff[1];
+    u32 zero               = cmd_buff[2];
+    VAddr cro_buffer_ptr   = cmd_buff[3];
+    u32 descriptor         = cmd_buff[4];
+    u32 process            = cmd_buff[5];
+
+    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
+        cro_address, zero, cro_buffer_ptr, descriptor, process);
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    CROHelper cro(cro_address);
+
+    cmd_buff[0] = IPC::MakeHeader(5, 1, 0);
+
+    if (loaded_crs == 0) {
+        LOG_ERROR(Service_LDR, "Not initialized");
+        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
+        return;
+    }
+
+    if (cro_address & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (!cro.IsLoaded()) {
+        LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
+        cmd_buff[1] = ERROR_NOT_LOADED.raw;
+        return;
+    }
+
+    LOG_INFO(Service_LDR, "Unloading CRO \"%s\"", cro.ModuleName().data());
+
+    u32 fixed_size = cro.GetFixedSize();
+
+    cro.Unregister(loaded_crs);
+
+    ResultCode result = cro.Unlink(loaded_crs);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw);
+        cmd_buff[1] = result.raw;
+        return;
+    }
+
+    // If the module is not fixed, clears all external/internal relocations
+    // to restore the state before loading, so that it can be loaded again(?)
+    if (!cro.IsFixed()) {
+        result = cro.ClearRelocations();
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error clearing relocations %08X", result.raw);
+            cmd_buff[1] = result.raw;
+            return;
+        }
+    }
+
+    cro.Unrebase(false);
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+
+    // TODO(wwylele): verify the behaviour when buffer_ptr == address
+    if (cro_address != cro_buffer_ptr) {
+        result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fixed_size);
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error unmapping CRO %08X", result.raw);
+        }
+        memory_synchronizer.RemoveMemoryBlock(cro_address, cro_buffer_ptr);
+    }
+
+    Core::g_app_core->ClearInstructionCache();
+
+    cmd_buff[1] = result.raw;
+}
+
+/**
+ * LDR_RO::LinkCRO service function
+ *  Inputs:
+ *      0 : 0x00060042
+ *      1 : mapped CRO pointer
+ *      2 : handle translation descriptor (zero)
+ *      3 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void LinkCRO(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr cro_address = cmd_buff[1];
+    u32 descriptor    = cmd_buff[2];
+    u32 process       = cmd_buff[3];
+
+    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X",
+        cro_address, descriptor, process);
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    CROHelper cro(cro_address);
+
+    cmd_buff[0] = IPC::MakeHeader(6, 1, 0);
+
+    if (loaded_crs == 0) {
+        LOG_ERROR(Service_LDR, "Not initialized");
+        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
+        return;
+    }
+
+    if (cro_address & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (!cro.IsLoaded()) {
+        LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
+        cmd_buff[1] = ERROR_NOT_LOADED.raw;
+        return;
+    }
+
+    LOG_INFO(Service_LDR, "Linking CRO \"%s\"", cro.ModuleName().data());
+
+    ResultCode result = cro.Link(loaded_crs, false);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw);
+    }
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+    Core::g_app_core->ClearInstructionCache();
+
+    cmd_buff[1] = result.raw;
+}
+
+/**
+ * LDR_RO::UnlinkCRO service function
+ *  Inputs:
+ *      0 : 0x00070042
+ *      1 : mapped CRO pointer
+ *      2 : handle translation descriptor (zero)
+ *      3 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void UnlinkCRO(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr cro_address = cmd_buff[1];
+    u32 descriptor    = cmd_buff[2];
+    u32 process       = cmd_buff[3];
+
+    LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X",
+        cro_address, descriptor, process);
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    CROHelper cro(cro_address);
+
+    cmd_buff[0] = IPC::MakeHeader(7, 1, 0);
+
+    if (loaded_crs == 0) {
+        LOG_ERROR(Service_LDR, "Not initialized");
+        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
+        return;
+    }
+
+    if (cro_address & Memory::PAGE_MASK) {
+        LOG_ERROR(Service_LDR, "CRO address is not aligned");
+        cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
+        return;
+    }
+
+    if (!cro.IsLoaded()) {
+        LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
+        cmd_buff[1] = ERROR_NOT_LOADED.raw;
+        return;
+    }
+
+    LOG_INFO(Service_LDR, "Unlinking CRO \"%s\"", cro.ModuleName().data());
+
+    ResultCode result = cro.Unlink(loaded_crs);
+    if (result.IsError()) {
+        LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw);
+    }
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+    Core::g_app_core->ClearInstructionCache();
+
+    cmd_buff[1] = result.raw;
+}
+
+/**
+ * LDR_RO::Shutdown service function
+ *  Inputs:
+ *      0 : 0x00080042
+ *      1 : original CRS buffer pointer
+ *      2 : handle translation descriptor (zero)
+ *      3 : KProcess handle
+ *  Outputs:
+ *      0 : Return header
+ *      1 : Result of function, 0 on success, otherwise error code
+ */
+static void Shutdown(Service::Interface* self) {
+    u32* cmd_buff = Kernel::GetCommandBuffer();
+    VAddr crs_buffer_ptr = cmd_buff[1];
+    u32 descriptor       = cmd_buff[2];
+    u32 process          = cmd_buff[3];
+
+    LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
+        crs_buffer_ptr, descriptor, process);
+
+    if (descriptor != 0) {
+        LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
+        cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
+        cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
+        return;
+    }
+
+    if (loaded_crs == 0) {
+        LOG_ERROR(Service_LDR, "Not initialized");
+        cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
+        return;
+    }
+
+    cmd_buff[0] = IPC::MakeHeader(8, 1, 0);
+
+    CROHelper crs(loaded_crs);
+    crs.Unrebase(true);
+
+    memory_synchronizer.SynchronizeOriginalMemory();
+
+    ResultCode result = RESULT_SUCCESS;
+
+    // TODO(wwylele): verify the behaviour when buffer_ptr == address
+    if (loaded_crs != crs_buffer_ptr) {
+        result = Kernel::g_current_process->vm_manager.UnmapRange(loaded_crs, crs.GetFileSize());
+        if (result.IsError()) {
+            LOG_ERROR(Service_LDR, "Error unmapping CRS %08X", result.raw);
+        }
+        memory_synchronizer.RemoveMemoryBlock(loaded_crs, crs_buffer_ptr);
+    }
+
+    loaded_crs = 0;
+    cmd_buff[1] = result.raw;
+}
+
+const Interface::FunctionInfo FunctionTable[] = {
+    {0x000100C2, Initialize,            "Initialize"},
+    {0x00020082, LoadCRR,               "LoadCRR"},
+    {0x00030042, UnloadCRR,             "UnloadCRR"},
+    {0x000402C2, LoadCRO<false>,        "LoadCRO"},
+    {0x000500C2, UnloadCRO,             "UnloadCRO"},
+    {0x00060042, LinkCRO,               "LinkCRO"},
+    {0x00070042, UnlinkCRO,             "UnlinkCRO"},
+    {0x00080042, Shutdown,              "Shutdown"},
+    {0x000902C2, LoadCRO<true>,         "LoadCRO_New"},
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Interface class
+
+Interface::Interface() {
+    Register(FunctionTable);
+
+    loaded_crs = 0;
+    memory_synchronizer.Clear();
+}
+
+} // namespace
diff --git a/src/core/hle/service/ldr_ro.h b/src/core/hle/service/ldr_ro/ldr_ro.h
similarity index 100%
rename from src/core/hle/service/ldr_ro.h
rename to src/core/hle/service/ldr_ro/ldr_ro.h
diff --git a/src/core/hle/service/ldr_ro/memory_synchronizer.cpp b/src/core/hle/service/ldr_ro/memory_synchronizer.cpp
new file mode 100644
index 000000000..4402876e6
--- /dev/null
+++ b/src/core/hle/service/ldr_ro/memory_synchronizer.cpp
@@ -0,0 +1,46 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/assert.h"
+
+#include "core/hle/service/ldr_ro/memory_synchronizer.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace LDR_RO
+
+namespace LDR_RO {
+
+auto MemorySynchronizer::FindMemoryBlock(VAddr mapping, VAddr original) {
+    auto block = std::find_if(memory_blocks.begin(), memory_blocks.end(), [=](MemoryBlock& b){
+        return b.original == original;
+    });
+    ASSERT(block->mapping == mapping);
+    return block;
+}
+
+void MemorySynchronizer::Clear() {
+    memory_blocks.clear();
+}
+
+void MemorySynchronizer::AddMemoryBlock(VAddr mapping, VAddr original, u32 size) {
+    memory_blocks.push_back(MemoryBlock{mapping, original, size});
+}
+
+void MemorySynchronizer::ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size) {
+    FindMemoryBlock(mapping, original)->size = size;
+}
+
+void MemorySynchronizer::RemoveMemoryBlock(VAddr mapping, VAddr original) {
+    memory_blocks.erase(FindMemoryBlock(mapping, original));
+}
+
+void MemorySynchronizer::SynchronizeOriginalMemory() {
+    for (auto& block : memory_blocks) {
+        Memory::CopyBlock(block.original, block.mapping, block.size);
+    }
+}
+
+} // namespace
diff --git a/src/core/hle/service/ldr_ro/memory_synchronizer.h b/src/core/hle/service/ldr_ro/memory_synchronizer.h
new file mode 100644
index 000000000..92f267912
--- /dev/null
+++ b/src/core/hle/service/ldr_ro/memory_synchronizer.h
@@ -0,0 +1,44 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "core/memory.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Namespace LDR_RO
+
+namespace LDR_RO {
+
+/**
+ * This is a work-around before we implement memory aliasing.
+ * CRS and CRO are mapped (aliased) to another memory when loading. Games can read
+ * from both the original buffer and the mapping memory. So we use this to synchronize
+ * all original buffers with mapping memory after modifying the content.
+ */
+class MemorySynchronizer {
+public:
+    void Clear();
+
+    void AddMemoryBlock(VAddr mapping, VAddr original, u32 size);
+    void ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size);
+    void RemoveMemoryBlock(VAddr mapping, VAddr original);
+
+    void SynchronizeOriginalMemory();
+
+private:
+    struct MemoryBlock {
+        VAddr mapping;
+        VAddr original;
+        u32 size;
+    };
+
+    std::vector<MemoryBlock> memory_blocks;
+
+    auto FindMemoryBlock(VAddr mapping, VAddr original);
+};
+
+} // namespace
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 395880843..5b8440b77 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -15,7 +15,6 @@
 #include "core/hle/service/gsp_gpu.h"
 #include "core/hle/service/gsp_lcd.h"
 #include "core/hle/service/http_c.h"
-#include "core/hle/service/ldr_ro.h"
 #include "core/hle/service/mic_u.h"
 #include "core/hle/service/ns_s.h"
 #include "core/hle/service/nwm_uds.h"
@@ -36,6 +35,7 @@
 #include "core/hle/service/cfg/cfg.h"
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/ir/ir.h"
+#include "core/hle/service/ldr_ro/ldr_ro.h"
 #include "core/hle/service/ndm/ndm.h"
 #include "core/hle/service/news/news.h"
 #include "core/hle/service/nim/nim.h"