diff --git a/src/core/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp index 9f7b72633..483983d84 100644 --- a/src/core/hle/service/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro.cpp @@ -101,7 +101,9 @@ struct CROHeader { u32 next_cro; u32 previous_cro; u32 file_size; - INSERT_PADDING_WORDS(0x6); + u32 unk_size1; + u32 always_zero; + INSERT_PADDING_WORDS(0x4); u32 segment_offset; u32 code_offset; u32 code_size; @@ -141,11 +143,11 @@ struct CROHeader { u8 GetImportPatchesTargetSegment() { return segment_offset & 0xF; } u32 GetImportPatchesSegmentOffset() { return segment_offset >> 4; } - SegmentTableEntry* GetSegmentTableEntry(u32 index, bool relocated = false); - void RelocateSegmentsTable(u32 base, u32 data_section0, u32 data_section1, u32& prev_data_section0); + SegmentTableEntry* GetSegmentTableEntry(u32 index); + ResultCode RelocateSegmentsTable(u32 base, u32 size, u32 data_section0, u32 data_section1, u32& prev_data_section0); ExportTableEntry* GetExportTableEntry(u32 index); - void RelocateExportsTable(u32 base); + ResultCode RelocateExportsTable(u32 base); ExportTreeEntry* GetExportTreeEntry(u32 index); @@ -170,20 +172,19 @@ struct CROHeader { void RelocateUnk2Patches(u32 base); - void RelocateOffsets(u32 base); + bool VerifyAndRelocateOffsets(u32 base, u32 size); }; static std::unordered_map loaded_exports; static std::vector loaded_cros; -SegmentTableEntry* CROHeader::GetSegmentTableEntry(u32 index, bool relocated) { - if (relocated) - return reinterpret_cast(Memory::GetPointer(segment_table_offset + sizeof(SegmentTableEntry) * index)); - - return reinterpret_cast(reinterpret_cast(this) + segment_table_offset + sizeof(SegmentTableEntry) * index); +SegmentTableEntry* CROHeader::GetSegmentTableEntry(u32 index) { + return reinterpret_cast(Memory::GetPointer(segment_table_offset + sizeof(SegmentTableEntry) * index)); } -void CROHeader::RelocateSegmentsTable(u32 base, u32 data_section0, u32 data_section1, u32& prev_data_section0) { +ResultCode CROHeader::RelocateSegmentsTable(u32 base, u32 size, u32 data_section0, u32 data_section1, u32& prev_data_section0) { + u32 cro_end = base + size; + prev_data_section0 = 0; for (int i = 0; i < segment_table_num; ++i) { SegmentTableEntry* entry = GetSegmentTableEntry(i); @@ -194,44 +195,54 @@ void CROHeader::RelocateSegmentsTable(u32 base, u32 data_section0, u32 data_sect entry->segment_offset = data_section1; } else if (entry->segment_offset) { entry->segment_offset += base; + if (entry->segment_offset > cro_end) + return ResultCode(0xD9012C19); } } + + return RESULT_SUCCESS; } ExportTreeEntry* CROHeader::GetExportTreeEntry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + export_tree_offset + sizeof(ExportTreeEntry) * index); + return reinterpret_cast(Memory::GetPointer(export_tree_offset + sizeof(ExportTreeEntry) * index)); } u32 CROHeader::GetUnk1TableEntry(u32 index) { - return *reinterpret_cast(reinterpret_cast(this) + unk1_offset + sizeof(u32) * index); + return *reinterpret_cast(Memory::GetPointer(unk1_offset + sizeof(u32) * index)); } ExportTableEntry* CROHeader::GetExportTableEntry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + export_table_offset + sizeof(ExportTableEntry) * index); + return reinterpret_cast(Memory::GetPointer(export_table_offset + sizeof(ExportTableEntry) * index)); } -void CROHeader::RelocateExportsTable(u32 base) { +ResultCode CROHeader::RelocateExportsTable(u32 base) { for (int i = 0; i < export_table_num; ++i) { ExportTableEntry* entry = GetExportTableEntry(i); if (entry->name_offset) entry->name_offset += base; + + if (entry->name_offset < export_strings_offset || + entry->name_offset > export_strings_offset + export_strings_num) + return ResultCode(0xD9012C11); } + + return RESULT_SUCCESS; } Patch* CROHeader::GetImportPatch(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + import_patches_offset + sizeof(Patch) * index); + return reinterpret_cast(Memory::GetPointer(import_patches_offset + sizeof(Patch) * index)); } ImportTableEntry* CROHeader::GetImportTable1Entry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + import_table1_offset + sizeof(ImportTableEntry) * index); + return reinterpret_cast(Memory::GetPointer(import_table1_offset + sizeof(ImportTableEntry) * index)); } ImportTableEntry* CROHeader::GetImportTable2Entry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + import_table2_offset + sizeof(ImportTableEntry) * index); + return reinterpret_cast(Memory::GetPointer(import_table2_offset + sizeof(ImportTableEntry) * index)); } ImportTableEntry* CROHeader::GetImportTable3Entry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + import_table3_offset + sizeof(ImportTableEntry) * index); + return reinterpret_cast(Memory::GetPointer(import_table3_offset + sizeof(ImportTableEntry) * index)); } void CROHeader::RelocateImportTable1(u32 base) { @@ -273,25 +284,219 @@ void CROHeader::RelocateUnk2Patches(u32 base) { } Unk3Patch* CROHeader::GetUnk3PatchEntry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + unk3_offset * sizeof(Unk3Patch) * index); + return reinterpret_cast(Memory::GetPointer(unk3_offset * sizeof(Unk3Patch) * index)); } Patch* CROHeader::GetRelocationPatchEntry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + relocation_patches_offset + sizeof(Patch) * index); + return reinterpret_cast(Memory::GetPointer(relocation_patches_offset + sizeof(Patch) * index)); } Unk2Patch* CROHeader::GetUnk2PatchEntry(u32 index) { - return reinterpret_cast(reinterpret_cast(this) + unk2_offset + sizeof(Unk2Patch) * index); + return reinterpret_cast(Memory::GetPointer(unk2_offset + sizeof(Unk2Patch) * index)); } -void CROHeader::RelocateOffsets(u32 base) { - memcpy(magic, "FIXD", 4); - segment_table_offset += base; - export_strings_offset += base; - export_table_offset += base; - export_tree_offset += base; - name_offset += base; - unk_offset += base; +bool CROHeader::VerifyAndRelocateOffsets(u32 base, u32 size) { + u32 end = base + size; + + // Error if the magic is invalid + if (strcmp(magic, "CRO0") != 0) + return false; + + // If these values are set the game might be trying to load the same CRO multiple times + if (next_cro || previous_cro) + return false; + + // This seems to be a hard limit set by the RO module + if (file_size >= 0x10000000 || unk_size1 >= 0x10000000) + return false; + + if (always_zero != 0) + return false; + + // Hard limit set by the RO module, it is unknown what the value means + if (code_offset < 0x138) + return false; + + if (module_name_offset < code_offset) + return false; + + if (module_name_offset > segment_table_offset) + return false; + + if (export_table_offset < segment_table_offset) + return false; + + if (export_table_offset > export_tree_offset) + return false; + + if (unk1_offset < export_tree_offset) + return false; + + if (unk1_offset > export_strings_offset) + return false; + + if (unk2_offset < export_strings_offset) + return false; + + if (unk2_offset > import_patches_offset) + return false; + + if (import_table1_offset < import_patches_offset) + return false; + + if (import_table1_offset > import_table2_offset) + return false; + + if (import_table3_offset < import_table2_offset) + return false; + + if (import_table3_offset > import_strings_offset) + return false; + + if (unk3_offset < import_strings_offset) + return false; + + if (unk3_offset > relocation_patches_offset) + return false; + + if (unk4_offset < relocation_patches_offset) + return false; + + if (unk4_offset > unk_offset) + return false; + + if (unk_offset > file_size) + return false; + + if (name_offset) { + name_offset += base; + if (name_offset >= end) + return false; + } + + if (code_offset) { + code_offset += base; + if (code_offset >= end) + return false; + } + + if (unk_offset) { + unk_offset += base; + if (unk_offset >= end) + return false; + } + + if (module_name_offset) { + module_name_offset += base; + if (module_name_offset >= end) + return false; + } + + if (segment_table_offset) { + segment_table_offset += base; + if (segment_table_offset >= end) + return false; + } + + if (export_table_offset) { + export_table_offset += base; + if (export_table_offset >= end) + return false; + } + + if (unk1_offset) { + unk1_offset += base; + if (unk1_offset >= end) + return false; + } + + if (export_strings_offset) { + export_strings_offset += base; + if (export_strings_offset >= end) + return false; + } + + if (export_tree_offset) { + export_tree_offset += base; + if (export_tree_offset >= end) + return false; + } + + if (unk2_offset) { + unk2_offset += base; + if (unk2_offset >= end) + return false; + } + + if (import_patches_offset) { + import_patches_offset += base; + if (import_patches_offset >= end) + return false; + } + + if (import_table1_offset) { + import_table1_offset += base; + if (import_table1_offset >= end) + return false; + } + + if (import_table2_offset) { + import_table2_offset += base; + if (import_table2_offset >= end) + return false; + } + + if (import_table3_offset) { + import_table3_offset += base; + if (import_table3_offset >= end) + return false; + } + + if (import_strings_offset) { + import_strings_offset += base; + if (import_strings_offset >= end) + return false; + } + + if (unk3_offset) { + unk3_offset += base; + if (unk3_offset >= end) + return false; + } + + if (relocation_patches_offset) { + relocation_patches_offset += base; + if (relocation_patches_offset >= end) + return false; + } + + if (unk4_offset) { + unk4_offset += base; + if (unk4_offset >= end) + return false; + } + + if (code_offset + code_size > end || + unk_offset + unk_size > end || + module_name_offset + module_name_size > end || + segment_table_offset + sizeof(SegmentTableEntry) * segment_table_num > end || + export_table_offset + sizeof(ExportTableEntry) * export_table_num > end || + unk1_offset + sizeof(u32) * unk1_size > end || + export_strings_offset + export_strings_num > end || + export_tree_offset + sizeof(ExportTreeEntry) * export_tree_num > end || + unk2_offset + sizeof(Unk2Patch) * unk2_num > end || + import_patches_offset + sizeof(Patch) * import_patches_num || + import_table1_offset + sizeof(ImportTableEntry) * import_table1_num || + import_table2_offset + sizeof(ImportTableEntry) * import_table2_num || + import_table3_offset + sizeof(ImportTableEntry) * import_table3_num || + import_strings_offset + import_strings_num > end || + unk3_offset + sizeof(Unk3Patch) * unk3_num > end || + relocation_patches_offset + sizeof(Patch) * relocation_patches_num > end || + unk4_offset + 12 * unk4_num > end) { + return false; + } + + return true; } static void ApplyPatch(Patch* patch, u32 patch_base, u32 patch_address, u32* patch_address1 = nullptr) { @@ -329,11 +534,11 @@ static void ApplyImportPatches(CROHeader* header, u32 base) { } } -static void ApplyListPatches(CROHeader* header, Patch* first_patch, u32 patch_base, bool relocated = false) { +static void ApplyListPatches(CROHeader* header, Patch* first_patch, u32 patch_base) { Patch* current_patch = first_patch; while (1) { - SegmentTableEntry* target_segment = header->GetSegmentTableEntry(current_patch->GetTargetSegment(), relocated); + SegmentTableEntry* target_segment = header->GetSegmentTableEntry(current_patch->GetTargetSegment()); ApplyPatch(current_patch, patch_base, target_segment->segment_offset + current_patch->GetSegmentOffset()); if (current_patch->unk) @@ -396,7 +601,7 @@ static void ApplyExitPatches(CROHeader* header, u32 base) { LOG_ERROR(Service_LDR, "Could not find __aeabi_atexit in the CRO imports!"); } -static void ApplyImportTable1Patches(CROHeader* header, u32 base, bool relocated) { +static void ApplyImportTable1Patches(CROHeader* header, u32 base) { for (int i = 0; i < header->import_table1_num; ++i) { ImportTableEntry* entry = header->GetImportTable1Entry(i); Patch* patch = reinterpret_cast(Memory::GetPointer(entry->symbol_offset)); @@ -410,7 +615,7 @@ static void ApplyImportTable1Patches(CROHeader* header, u32 base, bool relocated u32 patch_base = export_->second.cro_offset; Patch* first_patch = reinterpret_cast(Memory::GetPointer(entry->symbol_offset)); - ApplyListPatches(header, first_patch, patch_base, relocated); + ApplyListPatches(header, first_patch, patch_base); } } } @@ -426,7 +631,7 @@ static u32 GetCROBaseByName(char* name) { return 0; } -static void ApplyUnk2Patches(CROHeader* header, u32 base, bool relocated) { +static void ApplyUnk2Patches(CROHeader* header, u32 base) { for (int i = 0; i < header->unk2_num; ++i) { Unk2Patch* entry = header->GetUnk2PatchEntry(i); u32 cro_base = GetCROBaseByName(reinterpret_cast(Memory::GetPointer(entry->string_offset))); @@ -440,10 +645,10 @@ static void ApplyUnk2Patches(CROHeader* header, u32 base, bool relocated) { u32 unk1_table_entry = patch_cro->GetUnk1TableEntry(table1_entry->offset_or_index); u32 base_segment_id = unk1_table_entry & 0xF; u32 base_segment_offset = unk1_table_entry >> 4; - SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id, true); + SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id); Patch* first_patch = reinterpret_cast(Memory::GetPointer(base + table1_entry->patches_offset)); - ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset, relocated); + ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset); } // Apply the patches from the second table @@ -451,10 +656,10 @@ static void ApplyUnk2Patches(CROHeader* header, u32 base, bool relocated) { Unk2TableEntry* table2_entry = entry->GetTable2Entry(j); u32 base_segment_id = table2_entry->offset_or_index & 0xF; u32 base_segment_offset = table2_entry->offset_or_index >> 4; - SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id, true); + SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id); Patch* first_patch = reinterpret_cast(Memory::GetPointer(table2_entry->patches_offset)); - ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset, relocated); + ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset); } } } @@ -475,10 +680,10 @@ static void BackApplyUnk2Patches(CROHeader* header, u32 base, CROHeader* new_hea u32 unk1_table_entry = patch_cro->GetUnk1TableEntry(table1_entry->offset_or_index); u32 base_segment_id = unk1_table_entry & 0xF; u32 base_segment_offset = unk1_table_entry >> 4; - SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id, false); + SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id); Patch* first_patch = reinterpret_cast(Memory::GetPointer(base + table1_entry->patches_offset)); - ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset, true); + ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset); } // Apply the patches from the second table @@ -486,10 +691,10 @@ static void BackApplyUnk2Patches(CROHeader* header, u32 base, CROHeader* new_hea Unk2TableEntry* table2_entry = entry->GetTable2Entry(j); u32 base_segment_id = table2_entry->offset_or_index & 0xF; u32 base_segment_offset = table2_entry->offset_or_index >> 4; - SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id, false); + SegmentTableEntry* base_segment = patch_cro->GetSegmentTableEntry(base_segment_id); Patch* first_patch = reinterpret_cast(Memory::GetPointer(table2_entry->patches_offset)); - ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset, true); + ApplyListPatches(header, first_patch, base_segment->segment_offset + base_segment_offset); } } } @@ -546,17 +751,38 @@ static void LinkCROs(CROHeader* new_cro, u32 base) { } } -static void LoadCRO(u32 base, u8* cro, bool relocate_segments, u32 data_section0, u32 data_section1, bool crs) { +static ResultCode LoadCRO(u32 base, u32 size, u8* cro, u32 data_section0, u32 data_section1, bool crs) { CROHeader* header = reinterpret_cast(cro); + // Relocate all offsets + if (!header->VerifyAndRelocateOffsets(base, size)) + return ResultCode(0xD9012C11); + + if (header->module_name_size && + Memory::Read8(header->module_name_offset + header->module_name_size - 1) != 0) { + // The module name must end with '\0' + return ResultCode(0xD9012C0B); + } + u32 prev_section0 = 0; - if (relocate_segments) { + ResultCode result = RESULT_SUCCESS; + + if (!crs) { // Relocate segments - header->RelocateSegmentsTable(base, data_section0, data_section1, prev_section0); + result = header->RelocateSegmentsTable(base, size, data_section0, data_section1, prev_section0); + if (result.IsError()) + return result; } // Rebase export table - header->RelocateExportsTable(base); + result = header->RelocateExportsTable(base); + + if (result.IsError()) + return result; + + if (header->export_strings_num && + Memory::Read8(header->export_strings_offset + header->export_strings_num - 1) != 0) + return ResultCode(0xD9012C0B); // Rebase unk2 header->RelocateUnk2Patches(base); @@ -581,10 +807,10 @@ static void LoadCRO(u32 base, u8* cro, bool relocate_segments, u32 data_section0 ApplyExitPatches(header, base); // Import Table 1 - ApplyImportTable1Patches(header, base, false); + ApplyImportTable1Patches(header, base); // Apply unk2 patches - ApplyUnk2Patches(header, base, false); + ApplyUnk2Patches(header, base); // Load exports LoadExportsTable(header, base); @@ -594,21 +820,24 @@ static void LoadCRO(u32 base, u8* cro, bool relocate_segments, u32 data_section0 for (auto itr = loaded_cros.rbegin(); itr != loaded_cros.rend(); ++itr) { u32 cro_base = *itr; CROHeader* cro_header = reinterpret_cast(Memory::GetPointer(cro_base)); - ApplyImportTable1Patches(cro_header, cro_base, true); + ApplyImportTable1Patches(cro_header, cro_base); BackApplyUnk2Patches(cro_header, cro_base, header, base); } - // Relocate all offsets - header->RelocateOffsets(base); - // Link the CROs LinkCROs(header, base); loaded_cros.push_back(base); + memcpy(header->magic, "FIXD", 4); + + LOG_WARNING(Service_LDR, "Loaded CRO name %s", reinterpret_cast(Memory::GetPointer(header->name_offset))); + FileUtil::IOFile file(std::to_string(base) + ".cro", "wb+"); file.WriteBytes(header, header->file_size); file.Close(); + + return RESULT_SUCCESS; } /** @@ -644,10 +873,8 @@ static void Initialize(Service::Interface* self) { // TODO(Subv): Check what the real hardware returns for MemoryState Kernel::g_current_process->vm_manager.MapMemoryBlock(address, cro, 0, crs_size, Kernel::MemoryState::Code); - LoadCRO(address, Memory::GetPointer(address), false, 0, 0, true); - cmd_buff[0] = IPC::MakeHeader(1, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = LoadCRO(address, crs_size, Memory::GetPointer(address), 0, 0, true).raw; 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); @@ -694,11 +921,10 @@ static void LoadExeCRO(Service::Interface* self) { // TODO(Subv): Check what the real hardware returns for MemoryState Kernel::g_current_process->vm_manager.MapMemoryBlock(address, cro, 0, size, Kernel::MemoryState::Code); - LoadCRO(address, Memory::GetPointer(address), true, cmd_buff[4], cmd_buff[7], false); - cmd_buff[0] = IPC::MakeHeader(4, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = LoadCRO(address, size, Memory::GetPointer(address), cmd_buff[4], cmd_buff[7], false).raw; cmd_buff[2] = 0; + LOG_WARNING(Service_LDR, "Loading CRO address=%08X", address); }