diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index c839a414c..7d7a5dac1 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -4,7 +4,9 @@ #include #include +#include "common/literals.h" #include "common/logging/log.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" @@ -36,16 +38,21 @@ namespace Loader { * The BSS section must be cleared manually by the application. */ -enum THREEDSX_Error { ERROR_NONE = 0, ERROR_READ = 1, ERROR_FILE = 2, ERROR_ALLOC = 3 }; +enum class THREEDSX_Error : u32 { + None = 0, + Read = 1, + File = 2, + Alloc = 3, +}; -static const u32 RELOCBUFSIZE = 512; -static const unsigned int NUM_SEGMENTS = 3; +static constexpr u32 MAX_RELOCATIONS = 512; +static constexpr u32 NUM_SEGMENTS = 3; // File header -#pragma pack(1) struct THREEDSX_Header { u32 magic; - u16 header_size, reloc_hdr_size; + u16 header_size; + u16 reloc_hdr_size; u32 format_ver; u32 flags; @@ -57,6 +64,7 @@ struct THREEDSX_Header { // offset to filesystem u32 fs_offset; }; +static_assert(sizeof(THREEDSX_Header) == 44); // Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts. struct THREEDSX_RelocHdr { @@ -71,12 +79,25 @@ struct THREEDSX_RelocHdr { // - Absolute relocations // - Relative relocations }; +static_assert(sizeof(THREEDSX_RelocHdr) == 8); // Relocation entry: from the current pointer, skip X words and patch Y words struct THREEDSX_Reloc { - u16 skip, patch; + u16 skip; + u16 patch; }; -#pragma pack() +static_assert(sizeof(THREEDSX_Reloc) == 4); + +struct PrmStruct { + u32 magic; + VAddr srv_override; + u32 apt_app_id; + u32 heap_size; + u32 linear_heap_size; + VAddr arg_list; + u32 run_flags; +}; +static_assert(sizeof(PrmStruct) == 28); struct THREEloadinfo { u8* seg_ptrs[3]; // code, rodata & data @@ -84,7 +105,7 @@ struct THREEloadinfo { u32 seg_sizes[3]; }; -static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets) { +static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, std::span offsets) { if (addr < offsets[0]) return loadinfo->seg_addrs[0] + addr; if (addr < offsets[1]) @@ -93,33 +114,41 @@ static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets) } using Kernel::CodeSet; +using namespace Common::Literals; static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, u32 base_addr, std::shared_ptr* out_codeset) { - if (!file.IsOpen()) - return ERROR_FILE; + if (!file.IsOpen()) { + return THREEDSX_Error::File; + } // Reset read pointer in case this file has been read before. file.Seek(0, SEEK_SET); THREEDSX_Header hdr; - if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr)) - return ERROR_READ; + if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr)) { + return THREEDSX_Error::Read; + } + static constexpr size_t PageSize = Memory::CITRA_PAGE_SIZE; + + // Loadinfo segments must be a multiple of 0x1000 THREEloadinfo loadinfo; - // loadinfo segments must be a multiple of 0x1000 - loadinfo.seg_sizes[0] = (hdr.code_seg_size + 0xFFF) & ~0xFFF; - loadinfo.seg_sizes[1] = (hdr.rodata_seg_size + 0xFFF) & ~0xFFF; - loadinfo.seg_sizes[2] = (hdr.data_seg_size + 0xFFF) & ~0xFFF; - // prevent integer overflow leading to heap-buffer-overflow + loadinfo.seg_sizes[0] = Common::AlignUp(hdr.code_seg_size, PageSize); + loadinfo.seg_sizes[1] = Common::AlignUp(hdr.rodata_seg_size, PageSize); + loadinfo.seg_sizes[2] = Common::AlignUp(hdr.data_seg_size, PageSize); + + // Prevent integer overflow leading to heap-buffer-overflow if (loadinfo.seg_sizes[0] < hdr.code_seg_size || loadinfo.seg_sizes[1] < hdr.rodata_seg_size || loadinfo.seg_sizes[2] < hdr.data_seg_size) { - return ERROR_READ; + return THREEDSX_Error::Read; } - u32 offsets[2] = {loadinfo.seg_sizes[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1]}; - u32 n_reloc_tables = hdr.reloc_hdr_size / sizeof(u32); + + const u32 n_reloc_tables = hdr.reloc_hdr_size / sizeof(u32); + std::array offsets = {loadinfo.seg_sizes[0], + loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1]}; std::vector program_image(loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + - loadinfo.seg_sizes[2]); + loadinfo.seg_sizes[2] + PageSize); loadinfo.seg_addrs[0] = base_addr; loadinfo.seg_addrs[1] = loadinfo.seg_addrs[0] + loadinfo.seg_sizes[0]; @@ -133,27 +162,31 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, // Read the relocation headers std::vector relocs(n_reloc_tables * NUM_SEGMENTS); - for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { - std::size_t size = n_reloc_tables * sizeof(u32); - if (file.ReadBytes(&relocs[current_segment * n_reloc_tables], size) != size) - return ERROR_READ; + for (u32 current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { + const std::size_t size = n_reloc_tables * sizeof(u32); + if (file.ReadBytes(&relocs[current_segment * n_reloc_tables], size) != size) { + return THREEDSX_Error::Read; + } } // Read the segments - if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size) - return ERROR_READ; - if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size) - return ERROR_READ; + if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size) { + return THREEDSX_Error::Read; + } + if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size) { + return THREEDSX_Error::Read; + } if (file.ReadBytes(loadinfo.seg_ptrs[2], hdr.data_seg_size - hdr.bss_size) != - hdr.data_seg_size - hdr.bss_size) - return ERROR_READ; + hdr.data_seg_size - hdr.bss_size) { + return THREEDSX_Error::Read; + } // BSS clear - std::memset((char*)loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size); + std::memset(loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size); // Relocate the segments - for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { - for (unsigned current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables; + for (u32 current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { + for (u32 current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables; current_segment_reloc_table++) { u32 n_relocs = relocs[current_segment * n_reloc_tables + current_segment_reloc_table]; if (current_segment_reloc_table >= 2) { @@ -161,21 +194,22 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, file.Seek(n_relocs * sizeof(THREEDSX_Reloc), SEEK_CUR); continue; } - THREEDSX_Reloc reloc_table[RELOCBUFSIZE]; + THREEDSX_Reloc reloc_table[MAX_RELOCATIONS]; u32* pos = (u32*)loadinfo.seg_ptrs[current_segment]; const u32* end_pos = pos + (loadinfo.seg_sizes[current_segment] / 4); while (n_relocs) { - u32 remaining = std::min(RELOCBUFSIZE, n_relocs); + const u32 remaining = std::min(MAX_RELOCATIONS, n_relocs); n_relocs -= remaining; if (file.ReadBytes(reloc_table, remaining * sizeof(THREEDSX_Reloc)) != - remaining * sizeof(THREEDSX_Reloc)) - return ERROR_READ; + remaining * sizeof(THREEDSX_Reloc)) { + return THREEDSX_Error::Read; + } - for (unsigned current_inprogress = 0; - current_inprogress < remaining && pos < end_pos; current_inprogress++) { + for (u32 current_inprogress = 0; current_inprogress < remaining && pos < end_pos; + current_inprogress++) { const auto& table = reloc_table[current_inprogress]; LOG_TRACE(Loader, "(t={},skip={},patch={})", current_segment_reloc_table, static_cast(table.skip), static_cast(table.patch)); @@ -191,13 +225,14 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, addr, current_segment_reloc_table, *pos); switch (current_segment_reloc_table) { case 0: { - if (sub_type != 0) - return ERROR_READ; + if (sub_type != 0) { + return THREEDSX_Error::Read; + } *pos = addr; break; } case 1: { - u32 data = addr - in_addr; + const u32 data = addr - in_addr; switch (sub_type) { case 0: // 32-bit signed offset *pos = data; @@ -206,7 +241,7 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, *pos = data & ~(1U << 31); break; default: - return ERROR_READ; + return THREEDSX_Error::Read; } break; } @@ -221,8 +256,36 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, } } + // Detect and fill _prm structure + PrmStruct pst; + std::memcpy(&pst, program_image.data() + sizeof(u32), sizeof(PrmStruct)); + if (pst.magic == MakeMagic('_', 'p', 'r', 'm')) { + static constexpr u32 Argc = 1; + static constexpr std::string_view Argv = "Citra"; + + // Initialize the argument buffer + const size_t extra_page_offset = program_image.size() - PageSize; + const auto argv_buf = std::span{program_image}.subspan(extra_page_offset, PageSize); + std::memcpy(argv_buf.data(), &Argc, sizeof(Argc)); + std::memcpy(argv_buf.data() + sizeof(Argc), Argv.data(), Argv.size()); + + // Pass the arguments to the application. + static constexpr u32 RUNFLAG_APTREINIT = 1 << 1; + const VAddr extra_page_addr = loadinfo.seg_addrs[2] + loadinfo.seg_sizes[2]; + pst.arg_list = extra_page_addr; + pst.run_flags |= RUNFLAG_APTREINIT; + if (Settings::values.is_new_3ds) { + pst.heap_size = u32(48_MiB); + pst.linear_heap_size = u32(64_MiB); + } else { + pst.heap_size = u32(24_MiB); + pst.linear_heap_size = u32(32_MiB); + } + std::memcpy(program_image.data() + sizeof(u32), &pst, sizeof(PrmStruct)); + } + // Create the CodeSet - std::shared_ptr code_set = system.Kernel().CreateCodeSet("", 0); + const auto code_set = system.Kernel().CreateCodeSet("", 0); code_set->CodeSegment().offset = loadinfo.seg_ptrs[0] - program_image.data(); code_set->CodeSegment().addr = loadinfo.seg_addrs[0]; @@ -234,7 +297,7 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, code_set->DataSegment().offset = loadinfo.seg_ptrs[2] - program_image.data(); code_set->DataSegment().addr = loadinfo.seg_addrs[2]; - code_set->DataSegment().size = loadinfo.seg_sizes[2]; + code_set->DataSegment().size = loadinfo.seg_sizes[2] + PageSize; code_set->entrypoint = code_set->CodeSegment().addr; code_set->memory = std::move(program_image); @@ -245,31 +308,36 @@ static THREEDSX_Error Load3DSXFile(Core::System& system, FileUtil::IOFile& file, hdr.bss_size); *out_codeset = code_set; - return ERROR_NONE; + return THREEDSX_Error::None; } FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) { u32 magic; file.Seek(0, SEEK_SET); - if (1 != file.ReadArray(&magic, 1)) + if (1 != file.ReadArray(&magic, 1)) { return FileType::Error; + } - if (MakeMagic('3', 'D', 'S', 'X') == magic) + if (MakeMagic('3', 'D', 'S', 'X') == magic) { return FileType::THREEDSX; + } return FileType::Error; } ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr& process) { - if (is_loaded) + if (is_loaded) { return ResultStatus::ErrorAlreadyLoaded; + } - if (!file.IsOpen()) + if (!file.IsOpen()) { return ResultStatus::Error; + } std::shared_ptr codeset; - if (Load3DSXFile(system, file, Memory::PROCESS_IMAGE_VADDR, &codeset) != ERROR_NONE) + if (Load3DSXFile(system, file, Memory::PROCESS_IMAGE_VADDR, &codeset) != THREEDSX_Error::None) { return ResultStatus::Error; + } codeset->name = filename; process = system.Kernel().CreateProcess(std::move(codeset)); @@ -292,18 +360,21 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr& process) } ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr& romfs_file) { - if (!file.IsOpen()) + if (!file.IsOpen()) { return ResultStatus::Error; + } // Reset read pointer in case this file has been read before. file.Seek(0, SEEK_SET); THREEDSX_Header hdr; - if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) + if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) { return ResultStatus::Error; + } - if (hdr.header_size != sizeof(THREEDSX_Header)) + if (hdr.header_size != sizeof(THREEDSX_Header)) { return ResultStatus::Error; + } // Check if the 3DSX has a RomFS... if (hdr.fs_offset != 0) { @@ -323,31 +394,36 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr return ResultStatus::Success; } + LOG_DEBUG(Loader, "3DSX has no RomFS"); return ResultStatus::ErrorNotUsed; } ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector& buffer) { - if (!file.IsOpen()) + if (!file.IsOpen()) { return ResultStatus::Error; + } // Reset read pointer in case this file has been read before. file.Seek(0, SEEK_SET); THREEDSX_Header hdr; - if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) + if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) { return ResultStatus::Error; + } - if (hdr.header_size != sizeof(THREEDSX_Header)) + if (hdr.header_size != sizeof(THREEDSX_Header)) { return ResultStatus::Error; + } // Check if the 3DSX has a SMDH... if (hdr.smdh_offset != 0) { file.Seek(hdr.smdh_offset, SEEK_SET); buffer.resize(hdr.smdh_size); - if (file.ReadBytes(buffer.data(), hdr.smdh_size) != hdr.smdh_size) + if (file.ReadBytes(buffer.data(), hdr.smdh_size) != hdr.smdh_size) { return ResultStatus::Error; + } return ResultStatus::Success; }