diff options
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/loader/loader.cpp | 6 | ||||
| -rw-r--r-- | src/core/loader/loader.h | 1 | ||||
| -rw-r--r-- | src/core/loader/nso.cpp | 295 | ||||
| -rw-r--r-- | src/core/loader/nso.h | 38 | 
5 files changed, 342 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 44d7f670b..f8bc1fdd0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -176,6 +176,7 @@ set(SRCS              loader/elf.cpp              loader/loader.cpp              loader/ncch.cpp +            loader/nso.cpp              loader/smdh.cpp              tracer/recorder.cpp              memory.cpp @@ -375,6 +376,7 @@ set(HEADERS              loader/elf.h              loader/loader.h              loader/ncch.h +            loader/nso.h              loader/smdh.h              tracer/recorder.h              tracer/citrace.h diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index be719d74c..d309e7c9e 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -10,6 +10,7 @@  #include "core/loader/3dsx.h"  #include "core/loader/elf.h"  #include "core/loader/ncch.h" +#include "core/loader/nso.h"  //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -32,6 +33,7 @@ FileType IdentifyFile(FileUtil::IOFile& file) {      CHECK_TYPE(THREEDSX)      CHECK_TYPE(ELF)      CHECK_TYPE(NCCH) +    CHECK_TYPE(NSO)  #undef CHECK_TYPE @@ -115,6 +117,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp      case FileType::CCI:          return std::make_unique<AppLoader_NCCH>(std::move(file), filepath); +    // NX NSO file format. +    case FileType::NSO: +        return std::make_unique<AppLoader_NSO>(std::move(file), filename, filepath); +      default:          return nullptr;      } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 48bbf687d..79a3bd498 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -32,6 +32,7 @@ enum class FileType {      CIA,      ELF,      THREEDSX, // 3DSX +    NSO,  };  /** diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp new file mode 100644 index 000000000..ca8c59cd5 --- /dev/null +++ b/src/core/loader/nso.cpp @@ -0,0 +1,295 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <map> +#include <string> +#include <vector> +#include <lz4.h> + +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/loader/nso.h" +#include "core/memory.h" + +using Kernel::CodeSet; +using Kernel::SharedPtr; + +namespace Loader { + +FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file) { +    u32 magic = 0; +    file.Seek(0, SEEK_SET); +    if (1 != file.ReadArray<u32>(&magic, 1)) +        return FileType::Error; + +    if (MakeMagic('N', 'S', 'O', '0') == magic) +        return FileType::NSO; + +    return FileType::Error; +} + +struct NsoSegmentHeader { +    u32_le offset; +    u32_le location; +    u32_le size; +    u32_le alignment; +}; +static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); + +struct NsoHeader { +    u32_le magic; +    INSERT_PADDING_BYTES(0xc); +    std::array<NsoSegmentHeader, 3> segments; // Text, Data, RoData (in that order) +    INSERT_PADDING_BYTES(0x20); +    std::array<u32_le, 3> segments_compressed_size; +}; + +static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); + +static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeader& header, +                                   int compressed_size) { +    std::vector<u8> compressed_data; +    compressed_data.resize(compressed_size); + +    file.Seek(header.offset, SEEK_SET); +    if (compressed_size != file.ReadBytes(compressed_data.data(), compressed_size)) { +        LOG_CRITICAL(Loader, "Failed to read %d NSO LZ4 compressed bytes", compressed_size); +        return {}; +    } + +    std::vector<u8> uncompressed_data; +    uncompressed_data.resize(header.size); +    const int bytes_uncompressed = +        LZ4_decompress_safe_partial(reinterpret_cast<const char*>(compressed_data.data()), +                                    reinterpret_cast<char*>(uncompressed_data.data()), +                                    compressed_size, header.size, header.size); + +    ASSERT_MSG(bytes_uncompressed == header.size, "%d != %d", bytes_uncompressed, header.size); + +    return uncompressed_data; +} + +struct Symbol { +    Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {} +    std::string name; +    u64 value; +}; + +struct Import { +    VAddr ea; +    s64 addend; +}; + +enum class RelocationType : u32 { +    ABS64 = 257, +    GLOB_DAT = 1025, +    JUMP_SLOT = 1026, +    RELATIVE = 1027 +}; + +enum DynamicType : u32 { +    DT_NULL = 0, +    DT_PLTRELSZ = 2, +    DT_STRTAB = 5, +    DT_SYMTAB = 6, +    DT_RELA = 7, +    DT_RELASZ = 8, +    DT_STRSZ = 10, +    DT_JMPREL = 23, +}; + +void WriteRelocations(const std::vector<Symbol>& symbols, VAddr loadbase, u64 roff, u64 size, +                      bool is_jump_relocation, std::map<std::string, Import>& imports, +                      std::map<std::string, VAddr>& exports) { +    for (u64 i = 0; i < size; i += 0x18) { +        VAddr addr = loadbase + roff + i; +        u64 offset = Memory::Read64(addr); +        u64 info = Memory::Read64(addr + 8); +        u64 addend_unsigned = Memory::Read64(addr + 16); +        s64 addend{}; +        std::memcpy(&addend, &addend_unsigned, sizeof(u64)); + +        RelocationType rtype = static_cast<RelocationType>(info & 0xFFFFFFFF); +        u32 rsym = static_cast<u32>(info >> 32); +        VAddr ea = loadbase + offset; + +        const Symbol& symbol = symbols[rsym]; + +        switch (rtype) { +        case RelocationType::RELATIVE: +            if (!symbol.name.empty()) { +                exports[symbol.name] = loadbase + addend; +            } +            Memory::Write64(ea, loadbase + addend); +            break; +        case RelocationType::JUMP_SLOT: +        case RelocationType::GLOB_DAT: +            if (!symbol.value) { +                imports[symbol.name] = {ea, 0}; +            } else { +                exports[symbol.name] = symbol.value; +                Memory::Write64(ea, symbol.value); +            } +            break; +        case RelocationType::ABS64: +            if (!symbol.value) { +                imports[symbol.name] = {ea, addend}; +            } else { +                exports[symbol.name] = symbol.value + addend; +                Memory::Write64(ea, symbol.value + addend); +            } +            break; +        default: +            LOG_CRITICAL(Loader, "Unknown relocation type: %d", rtype); +            break; +        } +    } +} + +void Relocate(VAddr loadbase, std::map<std::string, Import>& imports, +              std::map<std::string, VAddr>& exports) { +    u32 modoff = Memory::Read32(loadbase + 4); +    ASSERT_MSG(Memory::Read32(loadbase + modoff) == MakeMagic('M', 'O', 'D', '0'), +               "Expected MOD section"); + +    u64 dynoff = loadbase + modoff + Memory::Read32(loadbase + modoff + 4); +    std::map<u64, u64> dynamic; +    while (1) { +        u64 tag = Memory::Read64(dynoff); +        u64 value = Memory::Read64(dynoff + 8); +        dynoff += 16; + +        if (tag == DT_NULL) { +            break; +        } +        dynamic[tag] = value; +    } + +    u64 strtabsize = dynamic[DT_STRSZ]; +    std::vector<u8> strtab; +    strtab.resize(strtabsize); +    Memory::ReadBlock(loadbase + dynamic[DT_STRTAB], strtab.data(), strtabsize); + +    VAddr addr = loadbase + dynamic[DT_SYMTAB]; +    std::vector<Symbol> symbols; +    while (1) { +        const u32 stname = Memory::Read32(addr); +        const u16 stshndx = Memory::Read16(addr + 6); +        const u64 stvalue = Memory::Read64(addr + 8); +        addr += 24; + +        if (stname >= strtabsize) { +            break; +        } + +        std::string name = reinterpret_cast<char*>(&strtab[stname]); +        if (stvalue) { +            exports[name] = loadbase + stvalue; +            symbols.emplace_back(std::move(name), loadbase + stvalue); +        } else { +            symbols.emplace_back(std::move(name), 0); +        } +    } + +    if (dynamic.find(DT_RELA) != dynamic.end()) { +        WriteRelocations(symbols, loadbase, dynamic[DT_RELA], dynamic[DT_RELASZ], false, imports, +                         exports); +    } + +    if (dynamic.find(DT_JMPREL) != dynamic.end()) { +        WriteRelocations(symbols, loadbase, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ], true, imports, +                         exports); +    } +} + +static VAddr GetEntryPoint(const std::map<std::string, VAddr>& exports) { +    // Find nnMain function, set entrypoint to that address +    const auto& search = exports.find("nnMain"); +    if (search != exports.end()) { +        return search->second; +    } +    return {}; +} + +static SharedPtr<CodeSet> LoadModule(const std::string& filepath, VAddr loadbase, +                                     std::map<std::string, Import>& imports, +                                     std::map<std::string, VAddr>& exports) { +    FileUtil::IOFile file(filepath, "rb"); + +    if (!file.IsOpen()) +        return {}; + +    NsoHeader header{}; +    file.Seek(0, SEEK_SET); +    if (sizeof(NsoHeader) != file.ReadBytes(&header, sizeof(NsoHeader))) +        return {}; + +    // Build program image +    SharedPtr<CodeSet> codeset = CodeSet::Create("", 0); +    std::vector<u8> program_image; +    for (int i = 0; i < header.segments.size(); ++i) { +        std::vector<u8> data = +            ReadSegment(file, header.segments[i], header.segments_compressed_size[i]); +        program_image.resize(header.segments[i].location); +        program_image.insert(program_image.end(), data.begin(), data.end()); +        codeset->segments[i].addr = header.segments[i].location; +        codeset->segments[i].offset = header.segments[i].location; +        codeset->segments[i].size = (data.size() + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; +    } +    program_image.resize((program_image.size() + Memory::PAGE_MASK) & ~Memory::PAGE_MASK); + +    codeset->name = filepath; +    codeset->entrypoint = 0; // Set after relocation +    codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); + +    return codeset; +} + +ResultStatus AppLoader_NSO::Load() { +    if (is_loaded) +        return ResultStatus::ErrorAlreadyLoaded; + +    if (!file.IsOpen()) +        return ResultStatus::Error; + +    static constexpr VAddr loadbase = 0x7100000000; +    std::map<std::string, Import> imports; +    std::map<std::string, VAddr> exports; + +    // Load and relocate "main" NSO +    auto codeset = LoadModule(filepath, loadbase, imports, exports); +    Kernel::g_current_process = Kernel::Process::Create(codeset); +    Kernel::g_current_process->svc_access_mask.set(); +    Kernel::g_current_process->address_mappings = default_address_mappings; +    Kernel::g_current_process->resource_limit = +        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); +    Kernel::g_current_process->LoadModule(codeset, loadbase); +    Relocate(loadbase, imports, exports); +    codeset->entrypoint = GetEntryPoint(exports); +    Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); + +    // Load and relocate "sdk" NSO +    static constexpr VAddr sdkbase = 0x7200000000; +    const std::string sdkpath = filepath.substr(0, filepath.find_last_of("/\\")) + "/sdk"; +    auto sdk_codeset = LoadModule(sdkpath, sdkbase, imports, exports); +    Kernel::g_current_process->LoadModule(sdk_codeset, sdkbase); +    Relocate(sdkbase, imports, exports); + +    // Resolve imports +    for (const auto& import : imports) { +        const auto& search = exports.find(import.first); +        if (search != exports.end()) { +            Memory::Write64(import.second.ea, search->second + import.second.addend); +        } else { +            LOG_CRITICAL(Loader, "Unresolved import: %s", import.first.c_str()); +        } +    } + +    is_loaded = true; +    return ResultStatus::Success; +} + +} // namespace Loader diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h new file mode 100644 index 000000000..39a9bd3d9 --- /dev/null +++ b/src/core/loader/nso.h @@ -0,0 +1,38 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" +#include "common/file_util.h" +#include "core/loader/loader.h" + +namespace Loader { + +/// Loads an NSO file +class AppLoader_NSO final : public AppLoader { +public: +    AppLoader_NSO(FileUtil::IOFile&& file, std::string filename, std::string filepath) +        : AppLoader(std::move(file)), filename(std::move(filename)), filepath(std::move(filepath)) {} + +    /** +     * Returns the type of the file +     * @param file FileUtil::IOFile open file +     * @return FileType found, or FileType::Error if this loader doesn't know it +     */ +    static FileType IdentifyType(FileUtil::IOFile& file); + +    FileType GetFileType() override { +        return IdentifyType(file); +    } + +    ResultStatus Load() override; + +private: +    std::string filename; +    std::string filepath; +}; + +} // namespace Loader  | 
