diff options
Diffstat (limited to 'src/core')
26 files changed, 901 insertions, 122 deletions
| diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ba5b02174..51e4088d2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,8 @@ add_library(core STATIC      hle/config_mem.h      hle/ipc.h      hle/ipc_helpers.h +    hle/kernel/address_arbiter.cpp +    hle/kernel/address_arbiter.h      hle/kernel/client_port.cpp      hle/kernel/client_port.h      hle/kernel/client_session.cpp @@ -257,6 +259,8 @@ add_library(core STATIC      loader/linker.h      loader/loader.cpp      loader/loader.h +    loader/nca.cpp +    loader/nca.h      loader/nro.cpp      loader/nro.h      loader/nso.cpp diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 808254ecc..874b9e23b 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -19,13 +19,20 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz      if (file.GetSize() < sizeof(Header))          return Loader::ResultStatus::Error; +    file.Seek(offset, SEEK_SET);      // For cartridges, HFSs can get very large, so we need to calculate the size up to      // the actual content itself instead of just blindly reading in the entire file.      Header pfs_header;      if (!file.ReadBytes(&pfs_header, sizeof(Header)))          return Loader::ResultStatus::Error; -    bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); +    if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && +        pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { +        return Loader::ResultStatus::ErrorInvalidFormat; +    } + +    bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); +      size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);      size_t metadata_size =          sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; @@ -50,7 +57,12 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data,          return Loader::ResultStatus::Error;      memcpy(&pfs_header, &file_data[offset], sizeof(Header)); -    is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); +    if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && +        pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { +        return Loader::ResultStatus::ErrorInvalidFormat; +    } + +    is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');      size_t entries_offset = offset + sizeof(Header);      size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); @@ -73,21 +85,21 @@ u32 PartitionFilesystem::GetNumEntries() const {      return pfs_header.num_entries;  } -u64 PartitionFilesystem::GetEntryOffset(int index) const { +u64 PartitionFilesystem::GetEntryOffset(u32 index) const {      if (index > GetNumEntries())          return 0;      return content_offset + pfs_entries[index].fs_entry.offset;  } -u64 PartitionFilesystem::GetEntrySize(int index) const { +u64 PartitionFilesystem::GetEntrySize(u32 index) const {      if (index > GetNumEntries())          return 0;      return pfs_entries[index].fs_entry.size;  } -std::string PartitionFilesystem::GetEntryName(int index) const { +std::string PartitionFilesystem::GetEntryName(u32 index) const {      if (index > GetNumEntries())          return ""; @@ -113,7 +125,7 @@ u64 PartitionFilesystem::GetFileSize(const std::string& name) const {  }  void PartitionFilesystem::Print() const { -    NGLOG_DEBUG(Service_FS, "Magic:                  {:.4}", pfs_header.magic.data()); +    NGLOG_DEBUG(Service_FS, "Magic:                  {}", pfs_header.magic);      NGLOG_DEBUG(Service_FS, "Files:                  {}", pfs_header.num_entries);      for (u32 i = 0; i < pfs_header.num_entries; i++) {          NGLOG_DEBUG(Service_FS, " > File {}:              {} (0x{:X} bytes, at 0x{:X})", i, diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index 573c90057..9c5810cf1 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -27,9 +27,9 @@ public:      Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);      u32 GetNumEntries() const; -    u64 GetEntryOffset(int index) const; -    u64 GetEntrySize(int index) const; -    std::string GetEntryName(int index) const; +    u64 GetEntryOffset(u32 index) const; +    u64 GetEntrySize(u32 index) const; +    std::string GetEntryName(u32 index) const;      u64 GetFileOffset(const std::string& name) const;      u64 GetFileSize(const std::string& name) const; @@ -37,7 +37,7 @@ public:  private:      struct Header { -        std::array<char, 4> magic; +        u32_le magic;          u32_le num_entries;          u32_le strtab_size;          INSERT_PADDING_BYTES(0x4); diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp new file mode 100644 index 000000000..e9c8369d7 --- /dev/null +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -0,0 +1,173 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/core.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/lock.h" +#include "core/memory.h" + +namespace Kernel { +namespace AddressArbiter { + +// Performs actual address waiting logic. +static ResultCode WaitForAddress(VAddr address, s64 timeout) { +    SharedPtr<Thread> current_thread = GetCurrentThread(); +    current_thread->arb_wait_address = address; +    current_thread->status = THREADSTATUS_WAIT_ARB; +    current_thread->wakeup_callback = nullptr; + +    current_thread->WakeAfterDelay(timeout); + +    Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); +    return RESULT_TIMEOUT; +} + +// Gets the threads waiting on an address. +static void GetThreadsWaitingOnAddress(std::vector<SharedPtr<Thread>>& waiting_threads, +                                       VAddr address) { +    auto RetrieveWaitingThreads = +        [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) { +            const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); +            auto& thread_list = scheduler->GetThreadList(); + +            for (auto& thread : thread_list) { +                if (thread->arb_wait_address == arb_addr) +                    waiting_threads.push_back(thread); +            } +        }; + +    // Retrieve a list of all threads that are waiting for this address. +    RetrieveWaitingThreads(0, waiting_threads, address); +    RetrieveWaitingThreads(1, waiting_threads, address); +    RetrieveWaitingThreads(2, waiting_threads, address); +    RetrieveWaitingThreads(3, waiting_threads, address); +    // Sort them by priority, such that the highest priority ones come first. +    std::sort(waiting_threads.begin(), waiting_threads.end(), +              [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { +                  return lhs->current_priority < rhs->current_priority; +              }); +} + +// Wake up num_to_wake (or all) threads in a vector. +static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) { +    // Only process up to 'target' threads, unless 'target' is <= 0, in which case process +    // them all. +    size_t last = waiting_threads.size(); +    if (num_to_wake > 0) +        last = num_to_wake; + +    // Signal the waiting threads. +    for (size_t i = 0; i < last; i++) { +        ASSERT(waiting_threads[i]->status = THREADSTATUS_WAIT_ARB); +        waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS); +        waiting_threads[i]->arb_wait_address = 0; +        waiting_threads[i]->ResumeFromWait(); +    } +} + +// Signals an address being waited on. +ResultCode SignalToAddress(VAddr address, s32 num_to_wake) { +    // Get threads waiting on the address. +    std::vector<SharedPtr<Thread>> waiting_threads; +    GetThreadsWaitingOnAddress(waiting_threads, address); + +    WakeThreads(waiting_threads, num_to_wake); +    return RESULT_SUCCESS; +} + +// Signals an address being waited on and increments its value if equal to the value argument. +ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) { +    // Ensure that we can write to the address. +    if (!Memory::IsValidVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } + +    if (static_cast<s32>(Memory::Read32(address)) == value) { +        Memory::Write32(address, static_cast<u32>(value + 1)); +    } else { +        return ERR_INVALID_STATE; +    } + +    return SignalToAddress(address, num_to_wake); +} + +// Signals an address being waited on and modifies its value based on waiting thread count if equal +// to the value argument. +ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, +                                                         s32 num_to_wake) { +    // Ensure that we can write to the address. +    if (!Memory::IsValidVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } + +    // Get threads waiting on the address. +    std::vector<SharedPtr<Thread>> waiting_threads; +    GetThreadsWaitingOnAddress(waiting_threads, address); + +    // Determine the modified value depending on the waiting count. +    s32 updated_value; +    if (waiting_threads.size() == 0) { +        updated_value = value - 1; +    } else if (num_to_wake <= 0 || waiting_threads.size() <= num_to_wake) { +        updated_value = value + 1; +    } else { +        updated_value = value; +    } + +    if (static_cast<s32>(Memory::Read32(address)) == value) { +        Memory::Write32(address, static_cast<u32>(updated_value)); +    } else { +        return ERR_INVALID_STATE; +    } + +    WakeThreads(waiting_threads, num_to_wake); +    return RESULT_SUCCESS; +} + +// Waits on an address if the value passed is less than the argument value, optionally decrementing. +ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) { +    // Ensure that we can read the address. +    if (!Memory::IsValidVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } + +    s32 cur_value = static_cast<s32>(Memory::Read32(address)); +    if (cur_value < value) { +        Memory::Write32(address, static_cast<u32>(cur_value - 1)); +    } else { +        return ERR_INVALID_STATE; +    } +    // Short-circuit without rescheduling, if timeout is zero. +    if (timeout == 0) { +        return RESULT_TIMEOUT; +    } + +    return WaitForAddress(address, timeout); +} + +// Waits on an address if the value passed is equal to the argument value. +ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) { +    // Ensure that we can read the address. +    if (!Memory::IsValidVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } +    // Only wait for the address if equal. +    if (static_cast<s32>(Memory::Read32(address)) != value) { +        return ERR_INVALID_STATE; +    } +    // Short-circuit without rescheduling, if timeout is zero. +    if (timeout == 0) { +        return RESULT_TIMEOUT; +    } + +    return WaitForAddress(address, timeout); +} +} // namespace AddressArbiter +} // namespace Kernel diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h new file mode 100644 index 000000000..f20f3dbc0 --- /dev/null +++ b/src/core/hle/kernel/address_arbiter.h @@ -0,0 +1,32 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Kernel { + +namespace AddressArbiter { +enum class ArbitrationType { +    WaitIfLessThan = 0, +    DecrementAndWaitIfLessThan = 1, +    WaitIfEqual = 2, +}; + +enum class SignalType { +    Signal = 0, +    IncrementAndSignalIfEqual = 1, +    ModifyByWaitingCountAndSignalIfEqual = 2, +}; + +ResultCode SignalToAddress(VAddr address, s32 num_to_wake); +ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake); +ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake); + +ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement); +ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout); +} // namespace AddressArbiter + +} // namespace Kernel diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index e1b5430bf..221cb1bb5 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -20,13 +20,16 @@ enum {      MaxConnectionsReached = 52,      // Confirmed Switch OS error codes -    MisalignedAddress = 102, +    InvalidAddress = 102, +    InvalidMemoryState = 106,      InvalidProcessorId = 113,      InvalidHandle = 114,      InvalidCombination = 116,      Timeout = 117,      SynchronizationCanceled = 118,      TooLarge = 119, +    InvalidEnumValue = 120, +    InvalidState = 125,  };  } @@ -39,14 +42,15 @@ constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);  constexpr ResultCode ERR_PORT_NAME_TOO_LONG(-1);  constexpr ResultCode ERR_WRONG_PERMISSION(-1);  constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(-1); -constexpr ResultCode ERR_INVALID_ENUM_VALUE(-1); +constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);  constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);  constexpr ResultCode ERR_INVALID_COMBINATION(-1);  constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(-1);  constexpr ResultCode ERR_OUT_OF_MEMORY(-1); -constexpr ResultCode ERR_INVALID_ADDRESS(-1); -constexpr ResultCode ERR_INVALID_ADDRESS_STATE(-1); +constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress); +constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);  constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle); +constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);  constexpr ResultCode ERR_INVALID_POINTER(-1);  constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);  constexpr ResultCode ERR_NOT_AUTHORIZED(-1); diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 01904467e..b0d83f401 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -271,6 +271,11 @@ std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {  }  size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const { +    if (size == 0) { +        NGLOG_WARNING(Core, "skip empty buffer write"); +        return 0; +    } +      const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()};      const size_t buffer_size{GetWriteBufferSize(buffer_index)};      if (size > buffer_size) { diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index bc144f3de..65560226d 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -59,7 +59,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,                               Handle requesting_thread_handle) {      // The mutex address must be 4-byte aligned      if ((address % sizeof(u32)) != 0) { -        return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); +        return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);      }      SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle); @@ -97,7 +97,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,  ResultCode Mutex::Release(VAddr address) {      // The mutex address must be 4-byte aligned      if ((address % sizeof(u32)) != 0) { -        return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); +        return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);      }      auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index ec3601e8b..1a36e0d02 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -11,6 +11,7 @@  #include "common/string_util.h"  #include "core/core.h"  #include "core/core_timing.h" +#include "core/hle/kernel/address_arbiter.h"  #include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h"  #include "core/hle/kernel/event.h" @@ -316,6 +317,11 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)                        "(STUBBED) Attempted to query privileged process id bounds, returned 0");          *result = 0;          break; +    case GetInfoType::UserExceptionContextAddr: +        NGLOG_WARNING(Kernel_SVC, +                      "(STUBBED) Attempted to query user exception context address, returned 0"); +        *result = 0; +        break;      default:          UNIMPLEMENTED();      } @@ -575,7 +581,7 @@ static void SleepThread(s64 nanoseconds) {      Core::System::GetInstance().PrepareReschedule();  } -/// Signal process wide key atomic +/// Wait process wide key atomic  static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr,                                             Handle thread_handle, s64 nano_seconds) {      NGLOG_TRACE( @@ -684,6 +690,58 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target      return RESULT_SUCCESS;  } +// Wait for an address (via Address Arbiter) +static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout) { +    NGLOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", +                  address, type, value, timeout); +    // If the passed address is a kernel virtual address, return invalid memory state. +    if (Memory::IsKernelVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } +    // If the address is not properly aligned to 4 bytes, return invalid address. +    if (address % sizeof(u32) != 0) { +        return ERR_INVALID_ADDRESS; +    } + +    switch (static_cast<AddressArbiter::ArbitrationType>(type)) { +    case AddressArbiter::ArbitrationType::WaitIfLessThan: +        return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, false); +    case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan: +        return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, true); +    case AddressArbiter::ArbitrationType::WaitIfEqual: +        return AddressArbiter::WaitForAddressIfEqual(address, value, timeout); +    default: +        return ERR_INVALID_ENUM_VALUE; +    } +} + +// Signals to an address (via Address Arbiter) +static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to_wake) { +    NGLOG_WARNING(Kernel_SVC, +                  "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", address, +                  type, value, num_to_wake); +    // If the passed address is a kernel virtual address, return invalid memory state. +    if (Memory::IsKernelVirtualAddress(address)) { +        return ERR_INVALID_ADDRESS_STATE; +    } +    // If the address is not properly aligned to 4 bytes, return invalid address. +    if (address % sizeof(u32) != 0) { +        return ERR_INVALID_ADDRESS; +    } + +    switch (static_cast<AddressArbiter::SignalType>(type)) { +    case AddressArbiter::SignalType::Signal: +        return AddressArbiter::SignalToAddress(address, num_to_wake); +    case AddressArbiter::SignalType::IncrementAndSignalIfEqual: +        return AddressArbiter::IncrementAndSignalToAddressIfEqual(address, value, num_to_wake); +    case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual: +        return AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, +                                                                             num_to_wake); +    default: +        return ERR_INVALID_ENUM_VALUE; +    } +} +  /// This returns the total CPU ticks elapsed since the CPU was powered-on  static u64 GetSystemTick() {      const u64 result{CoreTiming::GetTicks()}; @@ -744,7 +802,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {          ASSERT(thread->owner_process->ideal_processor != THREADPROCESSORID_DEFAULT);          // Set the target CPU to the one specified in the process' exheader.          core = thread->owner_process->ideal_processor; -        mask = 1 << core; +        mask = 1ull << core;      }      if (mask == 0) { @@ -761,7 +819,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {      }      // Error out if the input core isn't enabled in the input mask. -    if (core < Core::NUM_CPU_CORES && (mask & (1 << core)) == 0) { +    if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) {          return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);      } @@ -856,8 +914,8 @@ static const FunctionDef SVC_Table[] = {      {0x31, nullptr, "GetResourceLimitCurrentValue"},      {0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"},      {0x33, SvcWrap<GetThreadContext>, "GetThreadContext"}, -    {0x34, nullptr, "WaitForAddress"}, -    {0x35, nullptr, "SignalToAddress"}, +    {0x34, SvcWrap<WaitForAddress>, "WaitForAddress"}, +    {0x35, SvcWrap<SignalToAddress>, "SignalToAddress"},      {0x36, nullptr, "Unknown"},      {0x37, nullptr, "Unknown"},      {0x38, nullptr, "Unknown"}, diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 40aa88cc1..79c3fe31b 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -179,6 +179,20 @@ void SvcWrap() {      FuncReturn(retval);  } +template <ResultCode func(u64, u32, s32, s64)> +void SvcWrap() { +    FuncReturn( +        func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3)) +            .raw); +} + +template <ResultCode func(u64, u32, s32, s32)> +void SvcWrap() { +    FuncReturn(func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), +                    (s32)(PARAM(3) & 0xFFFFFFFF)) +                   .raw); +} +  ////////////////////////////////////////////////////////////////////////////////////////////////////  // Function wrappers that return type u32 diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index cffa7ca83..2f333ec34 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -140,6 +140,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {          }      } +    if (thread->arb_wait_address != 0) { +        ASSERT(thread->status == THREADSTATUS_WAIT_ARB); +        thread->arb_wait_address = 0; +    } +      if (resume)          thread->ResumeFromWait();  } @@ -179,6 +184,7 @@ void Thread::ResumeFromWait() {      case THREADSTATUS_WAIT_SLEEP:      case THREADSTATUS_WAIT_IPC:      case THREADSTATUS_WAIT_MUTEX: +    case THREADSTATUS_WAIT_ARB:          break;      case THREADSTATUS_READY: diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 1d2da6d50..f1e759802 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -45,6 +45,7 @@ enum ThreadStatus {      THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false      THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true      THREADSTATUS_WAIT_MUTEX,     ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc +    THREADSTATUS_WAIT_ARB,       ///< Waiting due to a SignalToAddress/WaitForAddress svc      THREADSTATUS_DORMANT,        ///< Created but not yet made ready      THREADSTATUS_DEAD            ///< Run to completion, or forcefully terminated  }; @@ -230,6 +231,9 @@ public:      VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address      Handle wait_handle;       ///< The handle used to wait for the mutex. +    // If waiting for an AddressArbiter, this is the address being waited on. +    VAddr arb_wait_address{0}; +      std::string name;      /// Handle used by guest emulated application to access this thread diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 6e8002bc9..3dfb3fb52 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -17,7 +17,8 @@ constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 200)};  class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {  public: -    IAudioRenderer() : ServiceFramework("IAudioRenderer") { +    IAudioRenderer(AudioRendererParameter audren_params) +        : ServiceFramework("IAudioRenderer"), worker_params(audren_params) {          static const FunctionInfo functions[] = {              {0, nullptr, "GetAudioRendererSampleRate"},              {1, nullptr, "GetAudioRendererSampleCount"}, @@ -57,27 +58,37 @@ private:      }      void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { -        NGLOG_DEBUG(Service_Audio, "{}", ctx.Description()); -        AudioRendererResponseData response_data{}; - -        response_data.section_0_size = -            static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry)); -        response_data.section_1_size = static_cast<u32>(response_data.section_1.size()); -        response_data.section_2_size = static_cast<u32>(response_data.section_2.size()); -        response_data.section_3_size = static_cast<u32>(response_data.section_3.size()); -        response_data.section_4_size = static_cast<u32>(response_data.section_4.size()); -        response_data.section_5_size = static_cast<u32>(response_data.section_5.size()); -        response_data.total_size = sizeof(AudioRendererResponseData); - -        for (unsigned i = 0; i < response_data.state_entries.size(); i++) { -            // 4 = Busy and 5 = Ready? -            response_data.state_entries[i].state = 5; +        UpdateDataHeader config{}; +        auto buf = ctx.ReadBuffer(); +        std::memcpy(&config, buf.data(), sizeof(UpdateDataHeader)); +        u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4); + +        std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count); +        std::memcpy(mem_pool_info.data(), +                    buf.data() + sizeof(UpdateDataHeader) + config.behavior_size, +                    memory_pool_count * sizeof(MemoryPoolInfo)); + +        UpdateDataHeader response_data{worker_params}; + +        ASSERT(ctx.GetWriteBufferSize() == response_data.total_size); + +        std::vector<u8> output(response_data.total_size); +        std::memcpy(output.data(), &response_data, sizeof(UpdateDataHeader)); +        std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); +        for (unsigned i = 0; i < memory_pool.size(); i++) { +            if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestAttach) +                memory_pool[i].state = MemoryPoolStates::Attached; +            else if (mem_pool_info[i].pool_state == MemoryPoolStates::RequestDetach) +                memory_pool[i].state = MemoryPoolStates::Detached; +            else +                memory_pool[i].state = mem_pool_info[i].pool_state;          } +        std::memcpy(output.data() + sizeof(UpdateDataHeader), memory_pool.data(), +                    response_data.memory_pools_size); -        ctx.WriteBuffer(&response_data, response_data.total_size); +        ctx.WriteBuffer(output);          IPC::ResponseBuilder rb{ctx, 2}; -          rb.Push(RESULT_SUCCESS);          NGLOG_WARNING(Service_Audio, "(STUBBED) called"); @@ -109,48 +120,66 @@ private:          NGLOG_WARNING(Service_Audio, "(STUBBED) called");      } -    struct AudioRendererStateEntry { -        u32_le state; +    enum class MemoryPoolStates : u32 { // Should be LE +        Invalid = 0x0, +        Unknown = 0x1, +        RequestDetach = 0x2, +        Detached = 0x3, +        RequestAttach = 0x4, +        Attached = 0x5, +        Released = 0x6, +    }; + +    struct MemoryPoolEntry { +        MemoryPoolStates state;          u32_le unknown_4;          u32_le unknown_8;          u32_le unknown_c;      }; -    static_assert(sizeof(AudioRendererStateEntry) == 0x10, -                  "AudioRendererStateEntry has wrong size"); - -    struct AudioRendererResponseData { -        u32_le unknown_0; -        u32_le section_5_size; -        u32_le section_0_size; -        u32_le section_1_size; -        u32_le unknown_10; -        u32_le section_2_size; -        u32_le unknown_18; -        u32_le section_3_size; -        u32_le section_4_size; -        u32_le unknown_24; -        u32_le unknown_28; -        u32_le unknown_2c; -        u32_le unknown_30; -        u32_le unknown_34; -        u32_le unknown_38; -        u32_le total_size; +    static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); -        std::array<AudioRendererStateEntry, 0x18e> state_entries; +    struct MemoryPoolInfo { +        u64_le pool_address; +        u64_le pool_size; +        MemoryPoolStates pool_state; +        INSERT_PADDING_WORDS(3); // Unknown +    }; +    static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); + +    struct UpdateDataHeader { +        UpdateDataHeader() {} + +        UpdateDataHeader(const AudioRendererParameter& config) { +            revision = Common::MakeMagic('R', 'E', 'V', '4'); // 5.1.0 Revision +            behavior_size = 0xb0; +            memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10; +            voices_size = config.voice_count * 0x10; +            effects_size = config.effect_count * 0x10; +            sinks_size = config.sink_count * 0x20; +            performance_manager_size = 0x10; +            total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + +                         voices_size + effects_size + sinks_size + performance_manager_size; +        } -        std::array<u8, 0x600> section_1; -        std::array<u8, 0xe0> section_2; -        std::array<u8, 0x20> section_3; -        std::array<u8, 0x10> section_4; -        std::array<u8, 0xb0> section_5; +        u32_le revision; +        u32_le behavior_size; +        u32_le memory_pools_size; +        u32_le voices_size; +        u32_le voice_resource_size; +        u32_le effects_size; +        u32_le mixes_size; +        u32_le sinks_size; +        u32_le performance_manager_size; +        INSERT_PADDING_WORDS(6); +        u32_le total_size;      }; -    static_assert(sizeof(AudioRendererResponseData) == 0x20e0, -                  "AudioRendererResponseData has wrong size"); +    static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");      /// This is used to trigger the audio event callback.      CoreTiming::EventType* audio_event;      Kernel::SharedPtr<Kernel::Event> system_event; +    AudioRendererParameter worker_params;  };  class IAudioDevice final : public ServiceFramework<IAudioDevice> { @@ -248,31 +277,33 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") {  }  void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp{ctx}; +    auto params = rp.PopRaw<AudioRendererParameter>();      IPC::ResponseBuilder rb{ctx, 2, 0, 1};      rb.Push(RESULT_SUCCESS); -    rb.PushIpcInterface<Audio::IAudioRenderer>(); +    rb.PushIpcInterface<Audio::IAudioRenderer>(std::move(params));      NGLOG_DEBUG(Service_Audio, "called");  }  void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx}; -    auto params = rp.PopRaw<WorkerBufferParameters>(); +    auto params = rp.PopRaw<AudioRendererParameter>(); -    u64 buffer_sz = Common::AlignUp(4 * params.unknown8, 0x40); -    buffer_sz += params.unknownC * 1024; -    buffer_sz += 0x940 * (params.unknownC + 1); +    u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40); +    buffer_sz += params.unknown_c * 1024; +    buffer_sz += 0x940 * (params.unknown_c + 1);      buffer_sz += 0x3F0 * params.voice_count; -    buffer_sz += Common::AlignUp(8 * (params.unknownC + 1), 0x10); +    buffer_sz += Common::AlignUp(8 * (params.unknown_c + 1), 0x10);      buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);      buffer_sz += -        Common::AlignUp((0x3C0 * (params.sink_count + params.unknownC) + 4 * params.sample_count) * -                            (params.unknown8 + 6), +        Common::AlignUp((0x3C0 * (params.sink_count + params.unknown_c) + 4 * params.sample_count) * +                            (params.unknown_8 + 6),                          0x40); -    if (IsFeatureSupported(AudioFeatures::Splitter, params.magic)) { -        u32 count = params.unknownC + 1; +    if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { +        u32 count = params.unknown_c + 1;          u64 node_count = Common::AlignUp(count, 0x40);          u64 node_state_buffer_sz =              4 * (node_count * node_count) + 0xC * node_count + 2 * (node_count / 8); @@ -287,20 +318,20 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {      }      buffer_sz += 0x20 * (params.effect_count + 4 * params.voice_count) + 0x50; -    if (IsFeatureSupported(AudioFeatures::Splitter, params.magic)) { -        buffer_sz += 0xE0 * params.unknown2c; +    if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { +        buffer_sz += 0xE0 * params.unknown_2c;          buffer_sz += 0x20 * params.splitter_count; -        buffer_sz += Common::AlignUp(4 * params.unknown2c, 0x10); +        buffer_sz += Common::AlignUp(4 * params.unknown_2c, 0x10);      }      buffer_sz = Common::AlignUp(buffer_sz, 0x40) + 0x170 * params.sink_count;      u64 output_sz = buffer_sz + 0x280 * params.sink_count + 0x4B0 * params.effect_count +                      ((params.voice_count * 256) | 0x40); -    if (params.unknown1c >= 1) { +    if (params.unknown_1c >= 1) {          output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count +                                        16 * params.voice_count + 16) +                                       0x658) * -                                            (params.unknown1c + 1) + +                                            (params.unknown_1c + 1) +                                          0xc0,                                      0x40) +                      output_sz; @@ -328,7 +359,7 @@ bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {      u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap      switch (feature) {      case AudioFeatures::Splitter: -        return version_num >= 2; +        return version_num >= 2u;      default:          return false;      } diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index fe53de4ce..b9b81db4f 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -12,6 +12,24 @@ class HLERequestContext;  namespace Service::Audio { +struct AudioRendererParameter { +    u32_le sample_rate; +    u32_le sample_count; +    u32_le unknown_8; +    u32_le unknown_c; +    u32_le voice_count; +    u32_le sink_count; +    u32_le effect_count; +    u32_le unknown_1c; +    u8 unknown_20; +    INSERT_PADDING_BYTES(3); +    u32_le splitter_count; +    u32_le unknown_2c; +    INSERT_PADDING_WORDS(1); +    u32_le revision; +}; +static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); +  class AudRenU final : public ServiceFramework<AudRenU> {  public:      explicit AudRenU(); @@ -22,25 +40,6 @@ private:      void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);      void GetAudioDevice(Kernel::HLERequestContext& ctx); -    struct WorkerBufferParameters { -        u32_le sample_rate; -        u32_le sample_count; -        u32_le unknown8; -        u32_le unknownC; -        u32_le voice_count; -        u32_le sink_count; -        u32_le effect_count; -        u32_le unknown1c; -        u8 unknown20; -        u8 padding1[3]; -        u32_le splitter_count; -        u32_le unknown2c; -        u8 padding2[4]; -        u32_le magic; -    }; -    static_assert(sizeof(WorkerBufferParameters) == 52, -                  "WorkerBufferParameters is an invalid size"); -      enum class AudioFeatures : u32 {          Splitter,      }; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 00c5308ba..2696a8bf0 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -84,6 +84,10 @@ private:          for (size_t controller = 0; controller < mem.controllers.size(); controller++) {              for (int index = 0; index < HID_NUM_LAYOUTS; index++) { +                // TODO(DarkLordZach): Is this layout/controller config actually invalid? +                if (controller == Controller_Handheld && index == Layout_Single) +                    continue; +                  ControllerLayout& layout = mem.controllers[controller].layouts[index];                  layout.header.num_entries = HID_NUM_ENTRIES;                  layout.header.max_entry_index = HID_NUM_ENTRIES - 1; @@ -94,7 +98,6 @@ private:                  layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;                  ControllerInputEntry& entry = layout.entries[layout.header.latest_entry]; -                entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;                  entry.timestamp++;                  // TODO(shinyquagsire23): Is this always identical to timestamp?                  entry.timestamp_2++; @@ -103,6 +106,8 @@ private:                  if (controller != Controller_Handheld)                      continue; +                entry.connection_state = ConnectionState_Connected | ConnectionState_Wired; +                  // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?                  // For now everything is just the default handheld layout, but split Joy-Con will                  // rotate the face buttons and directions for certain layouts. diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 15eee8f01..b499308d6 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -12,7 +12,7 @@ namespace Service::HID {  // Begin enums and output structs  constexpr u32 HID_NUM_ENTRIES = 17; -constexpr u32 HID_NUM_LAYOUTS = 2; +constexpr u32 HID_NUM_LAYOUTS = 7;  constexpr s32 HID_JOYSTICK_MAX = 0x8000;  constexpr s32 HID_JOYSTICK_MIN = -0x8000; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 79aab87f9..ed7b6dc03 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -121,8 +121,9 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<  }  u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) { -    if (input.size() < sizeof(IoctlSubmitGpfifo)) +    if (input.size() < sizeof(IoctlSubmitGpfifo)) {          UNIMPLEMENTED(); +    }      IoctlSubmitGpfifo params{};      std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo));      NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index f0572bed6..baeecb0ec 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -12,9 +12,6 @@  namespace Service::Set {  void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { -    IPC::RequestParser rp{ctx}; -    u32 id = rp.Pop<u32>(); -      static constexpr std::array<LanguageCode, 17> available_language_codes = {{          LanguageCode::JA,          LanguageCode::EN_US, @@ -50,7 +47,7 @@ SET::SET() : ServiceFramework("set") {          {2, nullptr, "MakeLanguageCode"},          {3, nullptr, "GetAvailableLanguageCodeCount"},          {4, nullptr, "GetRegionCode"}, -        {5, nullptr, "GetAvailableLanguageCodes2"}, +        {5, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes2"},          {6, nullptr, "GetAvailableLanguageCodeCount2"},          {7, nullptr, "GetKeyCodeMap"},          {8, nullptr, "GetQuestFlag"}, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 6a4fd38cb..20cc0bac0 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -9,6 +9,7 @@  #include "core/hle/kernel/process.h"  #include "core/loader/deconstructed_rom_directory.h"  #include "core/loader/elf.h" +#include "core/loader/nca.h"  #include "core/loader/nro.h"  #include "core/loader/nso.h" @@ -32,6 +33,7 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {      CHECK_TYPE(ELF)      CHECK_TYPE(NSO)      CHECK_TYPE(NRO) +    CHECK_TYPE(NCA)  #undef CHECK_TYPE @@ -57,6 +59,8 @@ FileType GuessFromExtension(const std::string& extension_) {          return FileType::NRO;      else if (extension == ".nso")          return FileType::NSO; +    else if (extension == ".nca") +        return FileType::NCA;      return FileType::Unknown;  } @@ -69,6 +73,8 @@ const char* GetFileTypeString(FileType type) {          return "NRO";      case FileType::NSO:          return "NSO"; +    case FileType::NCA: +        return "NCA";      case FileType::DeconstructedRomDirectory:          return "Directory";      case FileType::Error: @@ -104,6 +110,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp      case FileType::NRO:          return std::make_unique<AppLoader_NRO>(std::move(file), filepath); +    // NX NCA file format. +    case FileType::NCA: +        return std::make_unique<AppLoader_NCA>(std::move(file), filepath); +      // NX deconstructed ROM directory.      case FileType::DeconstructedRomDirectory:          return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index b1aabb1cb..b76f7b13d 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -29,6 +29,7 @@ enum class FileType {      ELF,      NSO,      NRO, +    NCA,      DeconstructedRomDirectory,  }; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp new file mode 100644 index 000000000..067945d46 --- /dev/null +++ b/src/core/loader/nca.cpp @@ -0,0 +1,303 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> + +#include "common/common_funcs.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/program_metadata.h" +#include "core/file_sys/romfs_factory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/nca.h" +#include "core/loader/nso.h" +#include "core/memory.h" + +namespace Loader { + +// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. +constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; + +constexpr u64 SECTION_HEADER_SIZE = 0x200; +constexpr u64 SECTION_HEADER_OFFSET = 0x400; + +enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; + +enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; + +struct NcaSectionTableEntry { +    u32_le media_offset; +    u32_le media_end_offset; +    INSERT_PADDING_BYTES(0x8); +}; +static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size."); + +struct NcaHeader { +    std::array<u8, 0x100> rsa_signature_1; +    std::array<u8, 0x100> rsa_signature_2; +    u32_le magic; +    u8 is_system; +    NcaContentType content_type; +    u8 crypto_type; +    u8 key_index; +    u64_le size; +    u64_le title_id; +    INSERT_PADDING_BYTES(0x4); +    u32_le sdk_version; +    u8 crypto_type_2; +    INSERT_PADDING_BYTES(15); +    std::array<u8, 0x10> rights_id; +    std::array<NcaSectionTableEntry, 0x4> section_tables; +    std::array<std::array<u8, 0x20>, 0x4> hash_tables; +    std::array<std::array<u8, 0x10>, 0x4> key_area; +    INSERT_PADDING_BYTES(0xC0); +}; +static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size."); + +struct NcaSectionHeaderBlock { +    INSERT_PADDING_BYTES(3); +    NcaSectionFilesystemType filesystem_type; +    u8 crypto_type; +    INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size."); + +struct Pfs0Superblock { +    NcaSectionHeaderBlock header_block; +    std::array<u8, 0x20> hash; +    u32_le size; +    INSERT_PADDING_BYTES(4); +    u64_le hash_table_offset; +    u64_le hash_table_size; +    u64_le pfs0_header_offset; +    u64_le pfs0_size; +    INSERT_PADDING_BYTES(432); +}; +static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size."); + +static bool IsValidNca(const NcaHeader& header) { +    return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || +           header.magic == Common::MakeMagic('N', 'C', 'A', '3'); +} + +// TODO(DarkLordZach): Add support for encrypted. +class Nca final { +    std::vector<FileSys::PartitionFilesystem> pfs; +    std::vector<u64> pfs_offset; + +    u64 romfs_offset = 0; +    u64 romfs_size = 0; + +    boost::optional<u8> exefs_id = boost::none; + +    FileUtil::IOFile file; +    std::string path; + +    u64 GetExeFsFileOffset(const std::string& file_name) const; +    u64 GetExeFsFileSize(const std::string& file_name) const; + +public: +    ResultStatus Load(FileUtil::IOFile&& file, std::string path); + +    FileSys::PartitionFilesystem GetPfs(u8 id) const; + +    u64 GetRomFsOffset() const; +    u64 GetRomFsSize() const; + +    std::vector<u8> GetExeFsFile(const std::string& file_name); +}; + +static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) { +    // According to switchbrew, an exefs must only contain these two files: +    return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0; +} + +ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) { +    file = std::move(in_file); +    path = in_path; +    file.Seek(0, SEEK_SET); +    std::array<u8, sizeof(NcaHeader)> header_array{}; +    if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader))) +        NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + +    NcaHeader header{}; +    std::memcpy(&header, header_array.data(), sizeof(NcaHeader)); +    if (!IsValidNca(header)) +        return ResultStatus::ErrorInvalidFormat; + +    int number_sections = +        std::count_if(std::begin(header.section_tables), std::end(header.section_tables), +                      [](NcaSectionTableEntry entry) { return entry.media_offset > 0; }); + +    for (int i = 0; i < number_sections; ++i) { +        // Seek to beginning of this section. +        file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); +        std::array<u8, sizeof(NcaSectionHeaderBlock)> array{}; +        if (sizeof(NcaSectionHeaderBlock) != +            file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock))) +            NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + +        NcaSectionHeaderBlock block{}; +        std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock)); + +        if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) { +            romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; +            romfs_size = +                header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset; +        } else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) { +            Pfs0Superblock sb{}; +            // Seek back to beginning of this section. +            file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); +            if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock))) +                NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + +            u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * +                          MEDIA_OFFSET_MULTIPLIER) + +                         sb.pfs0_header_offset; +            FileSys::PartitionFilesystem npfs{}; +            ResultStatus status = npfs.Load(path, offset); + +            if (status == ResultStatus::Success) { +                pfs.emplace_back(std::move(npfs)); +                pfs_offset.emplace_back(offset); +            } +        } +    } + +    for (size_t i = 0; i < pfs.size(); ++i) { +        if (IsPfsExeFs(pfs[i])) +            exefs_id = i; +    } + +    return ResultStatus::Success; +} + +FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const { +    return pfs[id]; +} + +u64 Nca::GetExeFsFileOffset(const std::string& file_name) const { +    if (exefs_id == boost::none) +        return 0; +    return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id]; +} + +u64 Nca::GetExeFsFileSize(const std::string& file_name) const { +    if (exefs_id == boost::none) +        return 0; +    return pfs[*exefs_id].GetFileSize(file_name); +} + +u64 Nca::GetRomFsOffset() const { +    return romfs_offset; +} + +u64 Nca::GetRomFsSize() const { +    return romfs_size; +} + +std::vector<u8> Nca::GetExeFsFile(const std::string& file_name) { +    std::vector<u8> out(GetExeFsFileSize(file_name)); +    file.Seek(GetExeFsFileOffset(file_name), SEEK_SET); +    file.ReadBytes(out.data(), GetExeFsFileSize(file_name)); +    return out; +} + +AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath) +    : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + +FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) { +    file.Seek(0, SEEK_SET); +    std::array<u8, 0x400> header_enc_array{}; +    if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400)) +        return FileType::Error; + +    // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. +    NcaHeader header{}; +    std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader)); + +    if (IsValidNca(header) && header.content_type == NcaContentType::Program) +        return FileType::NCA; + +    return FileType::Error; +} + +ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { +    if (is_loaded) { +        return ResultStatus::ErrorAlreadyLoaded; +    } +    if (!file.IsOpen()) { +        return ResultStatus::Error; +    } + +    nca = std::make_unique<Nca>(); +    ResultStatus result = nca->Load(std::move(file), filepath); +    if (result != ResultStatus::Success) { +        return result; +    } + +    result = metadata.Load(nca->GetExeFsFile("main.npdm")); +    if (result != ResultStatus::Success) { +        return result; +    } +    metadata.Print(); + +    const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; +    if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit) { +        return ResultStatus::ErrorUnsupportedArch; +    } + +    VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; +    for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", +                               "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { +        const VAddr load_addr = next_load_addr; +        next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr); +        if (next_load_addr) { +            NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); +        } else { +            next_load_addr = load_addr; +        } +    } + +    process->program_id = metadata.GetTitleID(); +    process->svc_access_mask.set(); +    process->address_mappings = default_address_mappings; +    process->resource_limit = +        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); +    process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), +                 metadata.GetMainThreadStackSize()); + +    if (nca->GetRomFsSize() > 0) +        Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this), +                                                Service::FileSystem::Type::RomFS); + +    is_loaded = true; +    return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, +                                      u64& size) { +    if (nca->GetRomFsSize() == 0) { +        NGLOG_DEBUG(Loader, "No RomFS available"); +        return ResultStatus::ErrorNotUsed; +    } + +    romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); + +    offset = nca->GetRomFsOffset(); +    size = nca->GetRomFsSize(); + +    NGLOG_DEBUG(Loader, "RomFS offset:           0x{:016X}", offset); +    NGLOG_DEBUG(Loader, "RomFS size:             0x{:016X}", size); + +    return ResultStatus::Success; +} + +AppLoader_NCA::~AppLoader_NCA() = default; + +} // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h new file mode 100644 index 000000000..3b6c451d0 --- /dev/null +++ b/src/core/loader/nca.h @@ -0,0 +1,49 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/kernel.h" +#include "core/loader/loader.h" + +namespace Loader { + +class Nca; + +/// Loads an NCA file +class AppLoader_NCA final : public AppLoader { +public: +    AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath); + +    /** +     * Returns the type of the file +     * @param file FileUtil::IOFile open file +     * @param filepath Path of the file that we are opening. +     * @return FileType found, or FileType::Error if this loader doesn't know it +     */ +    static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); + +    FileType GetFileType() override { +        return IdentifyType(file, filepath); +    } + +    ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; + +    ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, +                           u64& size) override; + +    ~AppLoader_NCA(); + +private: +    std::string filepath; +    FileSys::ProgramMetadata metadata; + +    std::unique_ptr<Nca> nca; +}; + +} // namespace Loader diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 01be9e217..845ed7e90 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -66,8 +66,22 @@ FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&)      return FileType::Error;  } +static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, +                                         const NsoSegmentHeader& header) { +    std::vector<u8> uncompressed_data; +    uncompressed_data.resize(header.size); +    const int bytes_uncompressed = LZ4_decompress_safe( +        reinterpret_cast<const char*>(compressed_data.data()), +        reinterpret_cast<char*>(uncompressed_data.data()), compressed_data.size(), header.size); + +    ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), +               "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); + +    return uncompressed_data; +} +  static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeader& header, -                                   int compressed_size) { +                                   size_t compressed_size) {      std::vector<u8> compressed_data;      compressed_data.resize(compressed_size); @@ -77,22 +91,65 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade          return {};      } -    std::vector<u8> uncompressed_data; -    uncompressed_data.resize(header.size); -    const int bytes_uncompressed = LZ4_decompress_safe( -        reinterpret_cast<const char*>(compressed_data.data()), -        reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size); - -    ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), -               "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); - -    return uncompressed_data; +    return DecompressSegment(compressed_data, header);  }  static constexpr u32 PageAlignSize(u32 size) {      return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;  } +VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data, +                                VAddr load_base) { +    if (file_data.size() < sizeof(NsoHeader)) +        return {}; + +    NsoHeader nso_header; +    std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader)); + +    if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) +        return {}; + +    // Build program image +    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); +    std::vector<u8> program_image; +    for (int i = 0; i < nso_header.segments.size(); ++i) { +        std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]); +        for (int j = 0; j < nso_header.segments_compressed_size[i]; ++j) +            compressed_data[j] = file_data[nso_header.segments[i].offset + j]; +        std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]); +        program_image.resize(nso_header.segments[i].location); +        program_image.insert(program_image.end(), data.begin(), data.end()); +        codeset->segments[i].addr = nso_header.segments[i].location; +        codeset->segments[i].offset = nso_header.segments[i].location; +        codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); +    } + +    // MOD header pointer is at .text offset + 4 +    u32 module_offset; +    std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); + +    // Read MOD header +    ModHeader mod_header{}; +    // Default .bss to size in segment header if MOD0 section doesn't exist +    u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; +    std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); +    const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; +    if (has_mod_header) { +        // Resize program image to include .bss section and page align each section +        bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); +    } +    codeset->data.size += bss_size; +    const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; +    program_image.resize(image_size); + +    // Load codeset for current process +    codeset->name = name; +    codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); +    Core::CurrentProcess()->LoadModule(codeset, load_base); + +    return load_base + image_size; +} +  VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {      FileUtil::IOFile file(path, "rb");      if (!file.IsOpen()) { diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 1ae30a824..386f4d39a 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -29,6 +29,9 @@ public:          return IdentifyType(file, filepath);      } +    static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data, +                            VAddr load_base); +      static VAddr LoadModule(const std::string& path, VAddr load_base);      ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 3b81acd63..f070dee7d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -241,6 +241,10 @@ bool IsValidVirtualAddress(const VAddr vaddr) {      return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr);  } +bool IsKernelVirtualAddress(const VAddr vaddr) { +    return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END; +} +  bool IsValidPhysicalAddress(const PAddr paddr) {      return GetPhysicalPointer(paddr) != nullptr;  } diff --git a/src/core/memory.h b/src/core/memory.h index 3f56a2c6a..8d5d017a4 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -188,6 +188,11 @@ enum : VAddr {      MAP_REGION_VADDR = NEW_MAP_REGION_VADDR_END,      MAP_REGION_SIZE = 0x1000000000,      MAP_REGION_VADDR_END = MAP_REGION_VADDR + MAP_REGION_SIZE, + +    /// Kernel Virtual Address Range +    KERNEL_REGION_VADDR = 0xFFFFFF8000000000, +    KERNEL_REGION_SIZE = 0x7FFFE00000, +    KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE,  };  /// Currently active page table @@ -197,6 +202,8 @@ PageTable* GetCurrentPageTable();  /// Determines if the given VAddr is valid for the specified process.  bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr);  bool IsValidVirtualAddress(const VAddr addr); +/// Determines if the given VAddr is a kernel address +bool IsKernelVirtualAddress(const VAddr addr);  bool IsValidPhysicalAddress(const PAddr addr); | 
