diff options
62 files changed, 1798 insertions, 435 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index ff8385e3a..59c610732 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,10 +66,12 @@ if (NOT ENABLE_GENERIC)          detect_architecture("_M_AMD64" x86_64)          detect_architecture("_M_IX86" x86)          detect_architecture("_M_ARM" ARM) +        detect_architecture("_M_ARM64" ARM64)      else()          detect_architecture("__x86_64__" x86_64)          detect_architecture("__i386__" x86)          detect_architecture("__arm__" ARM) +        detect_architecture("__aarch64__" ARM64)      endif()  endif() diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d5d4f6f82..d9424ea91 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -29,8 +29,6 @@ add_library(common STATIC      assert.h      bit_field.h      bit_set.h -    break_points.cpp -    break_points.h      cityhash.cpp      cityhash.h      color.h @@ -40,6 +38,8 @@ add_library(common STATIC      file_util.cpp      file_util.h      hash.h +    hex_util.cpp +    hex_util.h      logging/backend.cpp      logging/backend.h      logging/filter.cpp diff --git a/src/common/break_points.cpp b/src/common/break_points.cpp deleted file mode 100644 index fa367a4ca..000000000 --- a/src/common/break_points.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <sstream> -#include "common/break_points.h" - -bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const { -    auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; }; -    auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond); -    return it != m_BreakPoints.end(); -} - -bool BreakPoints::IsTempBreakPoint(u32 iAddress) const { -    auto cond = [&iAddress](const TBreakPoint& bp) { -        return bp.iAddress == iAddress && bp.bTemporary; -    }; -    auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond); -    return it != m_BreakPoints.end(); -} - -BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const { -    TBreakPointsStr bps; -    for (auto breakpoint : m_BreakPoints) { -        if (!breakpoint.bTemporary) { -            std::stringstream bp; -            bp << std::hex << breakpoint.iAddress << " " << (breakpoint.bOn ? "n" : ""); -            bps.push_back(bp.str()); -        } -    } - -    return bps; -} - -void BreakPoints::AddFromStrings(const TBreakPointsStr& bps) { -    for (auto bps_item : bps) { -        TBreakPoint bp; -        std::stringstream bpstr; -        bpstr << std::hex << bps_item; -        bpstr >> bp.iAddress; -        bp.bOn = bps_item.find("n") != bps_item.npos; -        bp.bTemporary = false; -        Add(bp); -    } -} - -void BreakPoints::Add(const TBreakPoint& bp) { -    if (!IsAddressBreakPoint(bp.iAddress)) { -        m_BreakPoints.push_back(bp); -        // if (jit) -        //    jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4); -    } -} - -void BreakPoints::Add(u32 em_address, bool temp) { -    if (!IsAddressBreakPoint(em_address)) // only add new addresses -    { -        TBreakPoint pt; // breakpoint settings -        pt.bOn = true; -        pt.bTemporary = temp; -        pt.iAddress = em_address; - -        m_BreakPoints.push_back(pt); - -        // if (jit) -        //    jit->GetBlockCache()->InvalidateICache(em_address, 4); -    } -} - -void BreakPoints::Remove(u32 em_address) { -    auto cond = [&em_address](const TBreakPoint& bp) { return bp.iAddress == em_address; }; -    auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond); -    if (it != m_BreakPoints.end()) -        m_BreakPoints.erase(it); -} - -void BreakPoints::Clear() { -    // if (jit) -    //{ -    //    std::for_each(m_BreakPoints.begin(), m_BreakPoints.end(), -    //        [](const TBreakPoint& bp) -    //        { -    //            jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4); -    //        } -    //    ); -    //} - -    m_BreakPoints.clear(); -} diff --git a/src/common/break_points.h b/src/common/break_points.h deleted file mode 100644 index e15b9f842..000000000 --- a/src/common/break_points.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <vector> -#include "common/common_types.h" - -class DebugInterface; - -struct TBreakPoint { -    u32 iAddress; -    bool bOn; -    bool bTemporary; -}; - -// Code breakpoints. -class BreakPoints { -public: -    typedef std::vector<TBreakPoint> TBreakPoints; -    typedef std::vector<std::string> TBreakPointsStr; - -    const TBreakPoints& GetBreakPoints() { -        return m_BreakPoints; -    } - -    TBreakPointsStr GetStrings() const; -    void AddFromStrings(const TBreakPointsStr& bps); - -    // is address breakpoint -    bool IsAddressBreakPoint(u32 iAddress) const; -    bool IsTempBreakPoint(u32 iAddress) const; - -    // Add BreakPoint -    void Add(u32 em_address, bool temp = false); -    void Add(const TBreakPoint& bp); - -    // Remove Breakpoint -    void Remove(u32 iAddress); -    void Clear(); - -    void DeleteByAddress(u32 Address); - -private: -    TBreakPoints m_BreakPoints; -    u32 m_iBreakOnCount; -}; diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 3ce590062..b30a67ff9 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {  #endif  } +std::string GetNANDRegistrationDir(bool system) { +    if (system) +        return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/"; +    return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/"; +} +  size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {      return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());  } diff --git a/src/common/file_util.h b/src/common/file_util.h index 2711872ae..2f13d0b6b 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");  std::string GetHactoolConfigurationPath(); +std::string GetNANDRegistrationDir(bool system = false); +  // Returns the path to where the sys file are  std::string GetSysDirectory(); diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp new file mode 100644 index 000000000..ae17c89d4 --- /dev/null +++ b/src/common/hex_util.cpp @@ -0,0 +1,27 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/hex_util.h" + +u8 ToHexNibble(char c1) { +    if (c1 >= 65 && c1 <= 70) +        return c1 - 55; +    if (c1 >= 97 && c1 <= 102) +        return c1 - 87; +    if (c1 >= 48 && c1 <= 57) +        return c1 - 48; +    throw std::logic_error("Invalid hex digit"); +} + +std::array<u8, 16> operator""_array16(const char* str, size_t len) { +    if (len != 32) +        throw std::logic_error("Not of correct size."); +    return HexStringToArray<16>(str); +} + +std::array<u8, 32> operator""_array32(const char* str, size_t len) { +    if (len != 64) +        throw std::logic_error("Not of correct size."); +    return HexStringToArray<32>(str); +} diff --git a/src/common/hex_util.h b/src/common/hex_util.h new file mode 100644 index 000000000..13d586015 --- /dev/null +++ b/src/common/hex_util.h @@ -0,0 +1,37 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <string> +#include <fmt/format.h> +#include "common/common_types.h" + +u8 ToHexNibble(char c1); + +template <size_t Size, bool le = false> +std::array<u8, Size> HexStringToArray(std::string_view str) { +    std::array<u8, Size> out{}; +    if constexpr (le) { +        for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) +            out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); +    } else { +        for (size_t i = 0; i < 2 * Size; i += 2) +            out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); +    } +    return out; +} + +template <size_t Size> +std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { +    std::string out; +    for (u8 c : array) +        out += fmt::format(upper ? "{:02X}" : "{:02x}", c); +    return out; +} + +std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); +std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 217a87098..3fa8a3bc4 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -4,7 +4,7 @@  #include <cstddef>  #ifdef _WIN32 -#include <Windows.h> +#include <windows.h>  #else  #include <cerrno>  #include <cstring> diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index fd3fbdd4b..927da9187 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -9,10 +9,9 @@  #include "common/assert.h"  #include "common/bit_set.h" -namespace Common { -namespace X64 { +namespace Common::X64 { -int RegToIndex(const Xbyak::Reg& reg) { +inline int RegToIndex(const Xbyak::Reg& reg) {      using Kind = Xbyak::Reg::Kind;      ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,                 "RegSet only support GPRs and XMM registers."); @@ -152,8 +151,8 @@ constexpr size_t ABI_SHADOW_SPACE = 0;  #endif -void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size, -                            s32* out_subtraction, s32* out_xmm_offset) { +inline void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size, +                                   s32* out_subtraction, s32* out_xmm_offset) {      int count = (regs & ABI_ALL_GPRS).Count();      rsp_alignment -= count * 8;      size_t subtraction = 0; @@ -174,8 +173,8 @@ void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_f      *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction);  } -size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, -                                       size_t rsp_alignment, size_t needed_frame_size = 0) { +inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, +                                              size_t rsp_alignment, size_t needed_frame_size = 0) {      s32 subtraction, xmm_offset;      ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); @@ -195,8 +194,8 @@ size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs      return ABI_SHADOW_SPACE;  } -void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, size_t rsp_alignment, -                                    size_t needed_frame_size = 0) { +inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, +                                           size_t rsp_alignment, size_t needed_frame_size = 0) {      s32 subtraction, xmm_offset;      ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); @@ -217,5 +216,4 @@ void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, s      }  } -} // namespace X64 -} // namespace Common +} // namespace Common::X64 diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h index ec76e0a47..02323a017 100644 --- a/src/common/x64/xbyak_util.h +++ b/src/common/x64/xbyak_util.h @@ -8,8 +8,7 @@  #include <xbyak.h>  #include "common/x64/xbyak_abi.h" -namespace Common { -namespace X64 { +namespace Common::X64 {  // Constants for use with cmpps/cmpss  enum { @@ -45,5 +44,4 @@ inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {      }  } -} // namespace X64 -} // namespace Common +} // namespace Common::X64 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0b0ae5ccc..67ad6109a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(core STATIC      crypto/key_manager.h      crypto/ctr_encryption_layer.cpp      crypto/ctr_encryption_layer.h +    file_sys/bis_factory.cpp +    file_sys/bis_factory.h      file_sys/card_image.cpp      file_sys/card_image.h      file_sys/content_archive.cpp @@ -29,10 +31,14 @@ add_library(core STATIC      file_sys/directory.h      file_sys/errors.h      file_sys/mode.h +    file_sys/nca_metadata.cpp +    file_sys/nca_metadata.h      file_sys/partition_filesystem.cpp      file_sys/partition_filesystem.h      file_sys/program_metadata.cpp      file_sys/program_metadata.h +    file_sys/registered_cache.cpp +    file_sys/registered_cache.h      file_sys/romfs.cpp      file_sys/romfs.h      file_sys/romfs_factory.cpp @@ -43,6 +49,8 @@ add_library(core STATIC      file_sys/sdmc_factory.h      file_sys/vfs.cpp      file_sys/vfs.h +    file_sys/vfs_concat.cpp +    file_sys/vfs_concat.h      file_sys/vfs_offset.cpp      file_sys/vfs_offset.h      file_sys/vfs_real.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 83d4d742b..28038ff6f 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -5,6 +5,7 @@  #include <memory>  #include <utility>  #include "common/logging/log.h" +#include "common/string_util.h"  #include "core/core.h"  #include "core/core_timing.h"  #include "core/gdbstub/gdbstub.h" @@ -17,6 +18,7 @@  #include "core/hle/service/sm/sm.h"  #include "core/loader/loader.h"  #include "core/settings.h" +#include "file_sys/vfs_concat.h"  #include "file_sys/vfs_real.h"  #include "video_core/renderer_base.h"  #include "video_core/video_core.h" @@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {      return RunLoop(false);  } +static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, +                                                const std::string& path) { +    // To account for split 00+01+etc files. +    std::string dir_name; +    std::string filename; +    Common::SplitPath(path, &dir_name, &filename, nullptr); +    if (filename == "00") { +        const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read); +        std::vector<FileSys::VirtualFile> concat; +        for (u8 i = 0; i < 0x10; ++i) { +            auto next = dir->GetFile(fmt::format("{:02X}", i)); +            if (next != nullptr) +                concat.push_back(std::move(next)); +            else { +                next = dir->GetFile(fmt::format("{:02x}", i)); +                if (next != nullptr) +                    concat.push_back(std::move(next)); +                else +                    break; +            } +        } + +        if (concat.empty()) +            return nullptr; + +        return FileSys::ConcatenateFiles(concat, dir->GetName()); +    } + +    return vfs->OpenFile(path, FileSys::Mode::Read); +} +  System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { -    app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read)); +    app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));      if (!app_loader) {          LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index f977d1b32..7953c8720 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -56,6 +56,9 @@ static u64 event_fifo_id;  // to the event_queue by the emu thread  static Common::MPSCQueue<Event, false> ts_queue; +// the queue for unscheduling the events from other threads threadsafe +static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue; +  constexpr int MAX_SLICE_LENGTH = 20000;  static s64 idled_cycles; @@ -158,6 +161,10 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {      }  } +void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) { +    unschedule_queue.Push(std::make_pair(event_type, userdata)); +} +  void RemoveEvent(const EventType* event_type) {      auto itr = std::remove_if(event_queue.begin(), event_queue.end(),                                [&](const Event& e) { return e.type == event_type; }); @@ -194,6 +201,9 @@ void MoveEvents() {  void Advance() {      MoveEvents(); +    for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) { +        UnscheduleEvent(ev.first, ev.second); +    }      int cycles_executed = slice_length - downcount;      global_timer += cycles_executed; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index dfa161c0d..9ed757bd7 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -65,6 +65,7 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user  void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata);  void UnscheduleEvent(const EventType* event_type, u64 userdata); +void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);  /// We only permit one event of each type in the queue at a time.  void RemoveEvent(const EventType* event_type); diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index fc45e7ab5..94d92579f 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -10,44 +10,13 @@  #include <string_view>  #include "common/common_paths.h"  #include "common/file_util.h" +#include "common/hex_util.h" +#include "common/logging/log.h"  #include "core/crypto/key_manager.h"  #include "core/settings.h"  namespace Core::Crypto { -static u8 ToHexNibble(char c1) { -    if (c1 >= 65 && c1 <= 70) -        return c1 - 55; -    if (c1 >= 97 && c1 <= 102) -        return c1 - 87; -    if (c1 >= 48 && c1 <= 57) -        return c1 - 48; -    throw std::logic_error("Invalid hex digit"); -} - -template <size_t Size> -static std::array<u8, Size> HexStringToArray(std::string_view str) { -    std::array<u8, Size> out{}; -    for (size_t i = 0; i < 2 * Size; i += 2) { -        auto d1 = str[i]; -        auto d2 = str[i + 1]; -        out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); -    } -    return out; -} - -std::array<u8, 16> operator""_array16(const char* str, size_t len) { -    if (len != 32) -        throw std::logic_error("Not of correct size."); -    return HexStringToArray<16>(str); -} - -std::array<u8, 32> operator""_array32(const char* str, size_t len) { -    if (len != 64) -        throw std::logic_error("Not of correct size."); -    return HexStringToArray<32>(str); -} -  KeyManager::KeyManager() {      // Initialize keys      const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index c4c53cefc..0c62d4421 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {  namespace Core::Crypto { -std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); -std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); -  class KeyManager {  public:      KeyManager(); diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp new file mode 100644 index 000000000..ae4e33800 --- /dev/null +++ b/src/core/file_sys/bis_factory.cpp @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/bis_factory.h" + +namespace FileSys { + +static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) { +    const auto res = dir->GetDirectoryRelative(path); +    if (res == nullptr) +        return dir->CreateDirectoryRelative(path); +    return res; +} + +BISFactory::BISFactory(VirtualDir nand_root_) +    : nand_root(std::move(nand_root_)), +      sysnand_cache(std::make_shared<RegisteredCache>( +          GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), +      usrnand_cache(std::make_shared<RegisteredCache>( +          GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {} + +std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { +    return sysnand_cache; +} + +std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const { +    return usrnand_cache; +} + +} // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h new file mode 100644 index 000000000..a970a5e2e --- /dev/null +++ b/src/core/file_sys/bis_factory.h @@ -0,0 +1,30 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/loader/loader.h" +#include "registered_cache.h" + +namespace FileSys { + +/// File system interface to the Built-In Storage +/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND +/// registered caches. +class BISFactory { +public: +    explicit BISFactory(VirtualDir nand_root); + +    std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; +    std::shared_ptr<RegisteredCache> GetUserNANDContents() const; + +private: +    VirtualDir nand_root; + +    std::shared_ptr<RegisteredCache> sysnand_cache; +    std::shared_ptr<RegisteredCache> usrnand_cache; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 8e05b9d0e..1d7c7fb10 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -4,11 +4,14 @@  #include <array>  #include <string> -#include <core/loader/loader.h> + +#include <fmt/ostream.h> +  #include "common/logging/log.h"  #include "core/file_sys/card_image.h"  #include "core/file_sys/partition_filesystem.h"  #include "core/file_sys/vfs_offset.h" +#include "core/loader/loader.h"  namespace FileSys { @@ -93,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {      return GetPartition(XCIPartition::Logo);  } +const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { +    return ncas; +} +  std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {      const auto iter =          std::find_if(ncas.begin(), ncas.end(), @@ -142,7 +149,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {              const u16 error_id = static_cast<u16>(nca->GetStatus());              LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",                           partition_names[static_cast<size_t>(part)], nca->GetName(), error_id, -                         Loader::GetMessageForResultStatus(nca->GetStatus())); +                         nca->GetStatus());          }      } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 4618d9c00..a03d5264e 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -68,6 +68,7 @@ public:      VirtualDir GetUpdatePartition() const;      VirtualDir GetLogoPartition() const; +    const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;      std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;      VirtualFile GetNCAFileByType(NCAContentType type) const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 3ddc9f162..ae21ad5b9 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {      return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);  } -NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) { +NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {      file->ReadObject(raw.get());  } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 6582cc240..8c2cc1a2a 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -81,7 +81,6 @@ public:      std::string GetVersionString() const;  private: -    VirtualFile file;      std::unique_ptr<RawNACP> raw;  }; diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp new file mode 100644 index 000000000..449244444 --- /dev/null +++ b/src/core/file_sys/nca_metadata.cpp @@ -0,0 +1,131 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_funcs.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "content_archive.h" +#include "core/file_sys/nca_metadata.h" + +namespace FileSys { + +bool operator>=(TitleType lhs, TitleType rhs) { +    return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs); +} + +bool operator<=(TitleType lhs, TitleType rhs) { +    return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs); +} + +CNMT::CNMT(VirtualFile file) { +    if (file->ReadObject(&header) != sizeof(CNMTHeader)) +        return; + +    // If type is {Application, Update, AOC} has opt-header. +    if (header.type >= TitleType::Application && header.type <= TitleType::AOC) { +        if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) { +            LOG_WARNING(Loader, "Failed to read optional header."); +        } +    } + +    for (u16 i = 0; i < header.number_content_entries; ++i) { +        auto& next = content_records.emplace_back(ContentRecord{}); +        if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) + +                                        header.table_offset) != sizeof(ContentRecord)) { +            content_records.erase(content_records.end() - 1); +        } +    } + +    for (u16 i = 0; i < header.number_meta_entries; ++i) { +        auto& next = meta_records.emplace_back(MetaRecord{}); +        if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) + +                                        header.table_offset) != sizeof(MetaRecord)) { +            meta_records.erase(meta_records.end() - 1); +        } +    } +} + +CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, +           std::vector<MetaRecord> meta_records) +    : header(std::move(header)), opt_header(std::move(opt_header)), +      content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} + +u64 CNMT::GetTitleID() const { +    return header.title_id; +} + +u32 CNMT::GetTitleVersion() const { +    return header.title_version; +} + +TitleType CNMT::GetType() const { +    return header.type; +} + +const std::vector<ContentRecord>& CNMT::GetContentRecords() const { +    return content_records; +} + +const std::vector<MetaRecord>& CNMT::GetMetaRecords() const { +    return meta_records; +} + +bool CNMT::UnionRecords(const CNMT& other) { +    bool change = false; +    for (const auto& rec : other.content_records) { +        const auto iter = std::find_if(content_records.begin(), content_records.end(), +                                       [&rec](const ContentRecord& r) { +                                           return r.nca_id == rec.nca_id && r.type == rec.type; +                                       }); +        if (iter == content_records.end()) { +            content_records.emplace_back(rec); +            ++header.number_content_entries; +            change = true; +        } +    } +    for (const auto& rec : other.meta_records) { +        const auto iter = +            std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) { +                return r.title_id == rec.title_id && r.title_version == rec.title_version && +                       r.type == rec.type; +            }); +        if (iter == meta_records.end()) { +            meta_records.emplace_back(rec); +            ++header.number_meta_entries; +            change = true; +        } +    } +    return change; +} + +std::vector<u8> CNMT::Serialize() const { +    const bool has_opt_header = +        header.type >= TitleType::Application && header.type <= TitleType::AOC; +    const auto dead_zone = header.table_offset + sizeof(CNMTHeader); +    std::vector<u8> out( +        std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) + +        content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord)); +    memcpy(out.data(), &header, sizeof(CNMTHeader)); + +    // Optional Header +    if (has_opt_header) { +        memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader)); +    } + +    auto offset = header.table_offset; + +    for (const auto& rec : content_records) { +        memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); +        offset += sizeof(ContentRecord); +    } + +    for (const auto& rec : meta_records) { +        memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord)); +        offset += sizeof(MetaRecord); +    } + +    return out; +} +} // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h new file mode 100644 index 000000000..88e66d4da --- /dev/null +++ b/src/core/file_sys/nca_metadata.h @@ -0,0 +1,111 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstring> +#include <memory> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { +class CNMT; + +struct CNMTHeader; +struct OptionalHeader; + +enum class TitleType : u8 { +    SystemProgram = 0x01, +    SystemDataArchive = 0x02, +    SystemUpdate = 0x03, +    FirmwarePackageA = 0x04, +    FirmwarePackageB = 0x05, +    Application = 0x80, +    Update = 0x81, +    AOC = 0x82, +    DeltaTitle = 0x83, +}; + +bool operator>=(TitleType lhs, TitleType rhs); +bool operator<=(TitleType lhs, TitleType rhs); + +enum class ContentRecordType : u8 { +    Meta = 0, +    Program = 1, +    Data = 2, +    Control = 3, +    Manual = 4, +    Legal = 5, +    Patch = 6, +}; + +struct ContentRecord { +    std::array<u8, 0x20> hash; +    std::array<u8, 0x10> nca_id; +    std::array<u8, 0x6> size; +    ContentRecordType type; +    INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); + +constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; + +struct MetaRecord { +    u64_le title_id; +    u32_le title_version; +    TitleType type; +    u8 install_byte; +    INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size."); + +struct OptionalHeader { +    u64_le title_id; +    u64_le minimum_version; +}; +static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size."); + +struct CNMTHeader { +    u64_le title_id; +    u32_le title_version; +    TitleType type; +    INSERT_PADDING_BYTES(1); +    u16_le table_offset; +    u16_le number_content_entries; +    u16_le number_meta_entries; +    INSERT_PADDING_BYTES(12); +}; +static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); + +// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or +// meta0.ncd. These describe which NCA's belong with which titles in the registered cache. +class CNMT { +public: +    explicit CNMT(VirtualFile file); +    CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, +         std::vector<MetaRecord> meta_records); + +    u64 GetTitleID() const; +    u32 GetTitleVersion() const; +    TitleType GetType() const; + +    const std::vector<ContentRecord>& GetContentRecords() const; +    const std::vector<MetaRecord>& GetMetaRecords() const; + +    bool UnionRecords(const CNMT& other); +    std::vector<u8> Serialize() const; + +private: +    CNMTHeader header; +    OptionalHeader opt_header; +    std::vector<ContentRecord> content_records; +    std::vector<MetaRecord> meta_records; + +    // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data +    // after the table. This is not documented, unfortunately. +}; + +} // namespace FileSys diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp new file mode 100644 index 000000000..a5e935f64 --- /dev/null +++ b/src/core/file_sys/registered_cache.cpp @@ -0,0 +1,476 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <regex> +#include <mbedtls/sha256.h> +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/crypto/encryption_layer.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs_concat.h" + +namespace FileSys { +std::string RegisteredCacheEntry::DebugInfo() const { +    return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); +} + +bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { +    return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); +} + +static bool FollowsTwoDigitDirFormat(std::string_view name) { +    static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | +                                                                     std::regex_constants::icase); +    return std::regex_match(name.begin(), name.end(), two_digit_regex); +} + +static bool FollowsNcaIdFormat(std::string_view name) { +    static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript | +                                                                   std::regex_constants::icase); +    return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); +} + +static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, +                                            bool within_two_digit) { +    if (!within_two_digit) +        return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper)); + +    Core::Crypto::SHA256Hash hash{}; +    mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); +    return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper)); +} + +static std::string GetCNMTName(TitleType type, u64 title_id) { +    constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{ +        "SystemProgram", +        "SystemData", +        "SystemUpdate", +        "BootImagePackage", +        "BootImagePackageSafe", +        "Application", +        "Patch", +        "AddOnContent", +        "" ///< Currently unknown 'DeltaTitle' +    }; + +    auto index = static_cast<size_t>(type); +    // If the index is after the jump in TitleType, subtract it out. +    if (index >= static_cast<size_t>(TitleType::Application)) { +        index -= static_cast<size_t>(TitleType::Application) - +                 static_cast<size_t>(TitleType::FirmwarePackageB); +    } +    return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); +} + +static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { +    switch (type) { +    case NCAContentType::Program: +        // TODO(DarkLordZach): Differentiate between Program and Patch +        return ContentRecordType::Program; +    case NCAContentType::Meta: +        return ContentRecordType::Meta; +    case NCAContentType::Control: +        return ContentRecordType::Control; +    case NCAContentType::Data: +        return ContentRecordType::Data; +    case NCAContentType::Manual: +        // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. +        return ContentRecordType::Manual; +    default: +        UNREACHABLE(); +    } +} + +VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, +                                                       std::string_view path) const { +    if (dir->GetFileRelative(path) != nullptr) +        return dir->GetFileRelative(path); +    if (dir->GetDirectoryRelative(path) != nullptr) { +        const auto nca_dir = dir->GetDirectoryRelative(path); +        VirtualFile file = nullptr; + +        const auto files = nca_dir->GetFiles(); +        if (files.size() == 1 && files[0]->GetName() == "00") { +            file = files[0]; +        } else { +            std::vector<VirtualFile> concat; +            // Since the files are a two-digit hex number, max is FF. +            for (size_t i = 0; i < 0x100; ++i) { +                auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); +                if (next != nullptr) { +                    concat.push_back(std::move(next)); +                } else { +                    next = nca_dir->GetFile(fmt::format("{:02x}", i)); +                    if (next != nullptr) +                        concat.push_back(std::move(next)); +                    else +                        break; +                } +            } + +            if (concat.empty()) +                return nullptr; + +            file = FileSys::ConcatenateFiles(concat); +        } + +        return file; +    } +    return nullptr; +} + +VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { +    VirtualFile file; +    // Try all four modes of file storage: +    // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir) +    // 00: /000000**/{:032X}.nca +    // 01: /{:032X}.nca +    // 10: /000000**/{:032x}.nca +    // 11: /{:032x}.nca +    for (u8 i = 0; i < 4; ++i) { +        const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0); +        file = OpenFileOrDirectoryConcat(dir, path); +        if (file != nullptr) +            return file; +    } +    return file; +} + +static boost::optional<NcaID> CheckMapForContentRecord( +    const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { +    if (map.find(title_id) == map.end()) +        return boost::none; + +    const auto& cnmt = map.at(title_id); + +    const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(), +                                   [type](const ContentRecord& rec) { return rec.type == type; }); +    if (iter == cnmt.GetContentRecords().end()) +        return boost::none; + +    return boost::make_optional(iter->nca_id); +} + +boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id, +                                                             ContentRecordType type) const { +    if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end()) +        return meta_id.at(title_id); + +    const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type); +    if (res1 != boost::none) +        return res1; +    return CheckMapForContentRecord(meta, title_id, type); +} + +std::vector<NcaID> RegisteredCache::AccumulateFiles() const { +    std::vector<NcaID> ids; +    for (const auto& d2_dir : dir->GetSubdirectories()) { +        if (FollowsNcaIdFormat(d2_dir->GetName())) { +            ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20))); +            continue; +        } + +        if (!FollowsTwoDigitDirFormat(d2_dir->GetName())) +            continue; + +        for (const auto& nca_dir : d2_dir->GetSubdirectories()) { +            if (!FollowsNcaIdFormat(nca_dir->GetName())) +                continue; + +            ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20))); +        } + +        for (const auto& nca_file : d2_dir->GetFiles()) { +            if (!FollowsNcaIdFormat(nca_file->GetName())) +                continue; + +            ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20))); +        } +    } + +    for (const auto& d2_file : dir->GetFiles()) { +        if (FollowsNcaIdFormat(d2_file->GetName())) +            ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20))); +    } +    return ids; +} + +void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { +    for (const auto& id : ids) { +        const auto file = GetFileAtID(id); + +        if (file == nullptr) +            continue; +        const auto nca = std::make_shared<NCA>(parser(file, id)); +        if (nca->GetStatus() != Loader::ResultStatus::Success || +            nca->GetType() != NCAContentType::Meta) { +            continue; +        } + +        const auto section0 = nca->GetSubdirectories()[0]; + +        for (const auto& file : section0->GetFiles()) { +            if (file->GetExtension() != "cnmt") +                continue; + +            meta.insert_or_assign(nca->GetTitleId(), CNMT(file)); +            meta_id.insert_or_assign(nca->GetTitleId(), id); +            break; +        } +    } +} + +void RegisteredCache::AccumulateYuzuMeta() { +    const auto dir = this->dir->GetSubdirectory("yuzu_meta"); +    if (dir == nullptr) +        return; + +    for (const auto& file : dir->GetFiles()) { +        if (file->GetExtension() != "cnmt") +            continue; + +        CNMT cnmt(file); +        yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt)); +    } +} + +void RegisteredCache::Refresh() { +    if (dir == nullptr) +        return; +    const auto ids = AccumulateFiles(); +    ProcessFiles(ids); +    AccumulateYuzuMeta(); +} + +RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function) +    : dir(std::move(dir_)), parser(std::move(parsing_function)) { +    Refresh(); +} + +bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { +    return GetEntryRaw(title_id, type) != nullptr; +} + +bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const { +    return GetEntryRaw(entry) != nullptr; +} + +VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { +    const auto id = GetNcaIDFromMetadata(title_id, type); +    if (id == boost::none) +        return nullptr; + +    return parser(GetFileAtID(id.get()), id.get()); +} + +VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const { +    return GetEntryRaw(entry.title_id, entry.type); +} + +std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { +    const auto raw = GetEntryRaw(title_id, type); +    if (raw == nullptr) +        return nullptr; +    return std::make_shared<NCA>(raw); +} + +std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const { +    return GetEntry(entry.title_id, entry.type); +} + +template <typename T> +void RegisteredCache::IterateAllMetadata( +    std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc, +    std::function<bool(const CNMT&, const ContentRecord&)> filter) const { +    for (const auto& kv : meta) { +        const auto& cnmt = kv.second; +        if (filter(cnmt, EMPTY_META_CONTENT_RECORD)) +            out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD)); +        for (const auto& rec : cnmt.GetContentRecords()) { +            if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { +                out.push_back(proc(cnmt, rec)); +            } +        } +    } +    for (const auto& kv : yuzu_meta) { +        const auto& cnmt = kv.second; +        for (const auto& rec : cnmt.GetContentRecords()) { +            if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { +                out.push_back(proc(cnmt, rec)); +            } +        } +    } +} + +std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const { +    std::vector<RegisteredCacheEntry> out; +    IterateAllMetadata<RegisteredCacheEntry>( +        out, +        [](const CNMT& c, const ContentRecord& r) { +            return RegisteredCacheEntry{c.GetTitleID(), r.type}; +        }, +        [](const CNMT& c, const ContentRecord& r) { return true; }); +    return out; +} + +std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter( +    boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type, +    boost::optional<u64> title_id) const { +    std::vector<RegisteredCacheEntry> out; +    IterateAllMetadata<RegisteredCacheEntry>( +        out, +        [](const CNMT& c, const ContentRecord& r) { +            return RegisteredCacheEntry{c.GetTitleID(), r.type}; +        }, +        [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) { +            if (title_type != boost::none && title_type.get() != c.GetType()) +                return false; +            if (record_type != boost::none && record_type.get() != r.type) +                return false; +            if (title_id != boost::none && title_id.get() != c.GetTitleID()) +                return false; +            return true; +        }); +    return out; +} + +static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { +    const auto filename = fmt::format("{}.nca", HexArrayToString(id, false)); +    const auto iter = +        std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), +                     [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); +    return iter == xci->GetNCAs().end() ? nullptr : *iter; +} + +InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, +                                            const VfsCopyFunction& copy) { +    const auto& ncas = xci->GetNCAs(); +    const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { +        return nca->GetType() == NCAContentType::Meta; +    }); + +    if (meta_iter == ncas.end()) { +        LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " +                          "is therefore malformed. Double check your encryption keys."); +        return InstallResult::ErrorMetaFailed; +    } + +    // Install Metadata File +    const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); +    const auto meta_id = HexStringToArray<16>(meta_id_raw); + +    const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id); +    if (res != InstallResult::Success) +        return res; + +    // Install all the other NCAs +    const auto section0 = (*meta_iter)->GetSubdirectories()[0]; +    const auto cnmt_file = section0->GetFiles()[0]; +    const CNMT cnmt(cnmt_file); +    for (const auto& record : cnmt.GetContentRecords()) { +        const auto nca = GetNCAFromXCIForID(xci, record.nca_id); +        if (nca == nullptr) +            return InstallResult::ErrorCopyFailed; +        const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); +        if (res2 != InstallResult::Success) +            return res2; +    } + +    Refresh(); +    return InstallResult::Success; +} + +InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, +                                            bool overwrite_if_exists, const VfsCopyFunction& copy) { +    CNMTHeader header{ +        nca->GetTitleId(), ///< Title ID +        0,                 ///< Ignore/Default title version +        type,              ///< Type +        {},                ///< Padding +        0x10,              ///< Default table offset +        1,                 ///< 1 Content Entry +        0,                 ///< No Meta Entries +        {},                ///< Padding +    }; +    OptionalHeader opt_header{0, 0}; +    ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}}; +    const auto& data = nca->GetBaseFile()->ReadBytes(0x100000); +    mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); +    memcpy(&c_rec.nca_id, &c_rec.hash, 16); +    const CNMT new_cnmt(header, opt_header, {c_rec}, {}); +    if (!RawInstallYuzuMeta(new_cnmt)) +        return InstallResult::ErrorMetaFailed; +    return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); +} + +InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, +                                             bool overwrite_if_exists, +                                             boost::optional<NcaID> override_id) { +    const auto in = nca->GetBaseFile(); +    Core::Crypto::SHA256Hash hash{}; + +    // Calculate NcaID +    // NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the +    // game is massive), we're going to cheat and only hash the first MB of the NCA. +    // Also, for XCIs the NcaID matters, so if the override id isn't none, use that. +    NcaID id{}; +    if (override_id == boost::none) { +        const auto& data = in->ReadBytes(0x100000); +        mbedtls_sha256(data.data(), data.size(), hash.data(), 0); +        memcpy(id.data(), hash.data(), 16); +    } else { +        id = override_id.get(); +    } + +    std::string path = GetRelativePathFromNcaID(id, false, true); + +    if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { +        LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); +        return InstallResult::ErrorAlreadyExists; +    } + +    if (GetFileAtID(id) != nullptr) { +        LOG_WARNING(Loader, "Overwriting existing NCA..."); +        VirtualDir c_dir; +        { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } +        c_dir->DeleteFile(FileUtil::GetFilename(path)); +    } + +    auto out = dir->CreateFileRelative(path); +    if (out == nullptr) +        return InstallResult::ErrorCopyFailed; +    return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; +} + +bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { +    // Reasoning behind this method can be found in the comment for InstallEntry, NCA overload. +    const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta"); +    const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID()); +    if (dir->GetFile(filename) == nullptr) { +        auto out = dir->CreateFile(filename); +        const auto buffer = cnmt.Serialize(); +        out->Resize(buffer.size()); +        out->WriteBytes(buffer); +    } else { +        auto out = dir->GetFile(filename); +        CNMT old_cnmt(out); +        // Returns true on change +        if (old_cnmt.UnionRecords(cnmt)) { +            out->Resize(0); +            const auto buffer = old_cnmt.Serialize(); +            out->Resize(buffer.size()); +            out->WriteBytes(buffer); +        } +    } +    Refresh(); +    return std::find_if(yuzu_meta.begin(), yuzu_meta.end(), +                        [&cnmt](const std::pair<u64, CNMT>& kv) { +                            return kv.second.GetType() == cnmt.GetType() && +                                   kv.second.GetTitleID() == cnmt.GetTitleID(); +                        }) != yuzu_meta.end(); +} +} // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h new file mode 100644 index 000000000..a7c51a59c --- /dev/null +++ b/src/core/file_sys/registered_cache.h @@ -0,0 +1,124 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <map> +#include <memory> +#include <string> +#include <vector> +#include <boost/container/flat_map.hpp> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { +class XCI; +class CNMT; + +using NcaID = std::array<u8, 0x10>; +using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; +using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; + +enum class InstallResult { +    Success, +    ErrorAlreadyExists, +    ErrorCopyFailed, +    ErrorMetaFailed, +}; + +struct RegisteredCacheEntry { +    u64 title_id; +    ContentRecordType type; + +    std::string DebugInfo() const; +}; + +// boost flat_map requires operator< for O(log(n)) lookups. +bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); + +/* + * A class that catalogues NCAs in the registered directory structure. + * Nintendo's registered format follows this structure: + * + * Root + *   | 000000XX <- XX is the ____ two digits of the NcaID + *       | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder) + *         | 00 + *         | 01 <- Actual content split along 4GB boundaries. (optional) + * + * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when + * 4GB splitting can be ignored.) + */ +class RegisteredCache { +public: +    // Parsing function defines the conversion from raw file to NCA. If there are other steps +    // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom +    // parsing function. +    explicit RegisteredCache(VirtualDir dir, +                             RegisteredCacheParsingFunction parsing_function = +                                 [](const VirtualFile& file, const NcaID& id) { return file; }); + +    void Refresh(); + +    bool HasEntry(u64 title_id, ContentRecordType type) const; +    bool HasEntry(RegisteredCacheEntry entry) const; + +    VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; +    VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; + +    std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; +    std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; + +    std::vector<RegisteredCacheEntry> ListEntries() const; +    // If a parameter is not boost::none, it will be filtered for from all entries. +    std::vector<RegisteredCacheEntry> ListEntriesFilter( +        boost::optional<TitleType> title_type = boost::none, +        boost::optional<ContentRecordType> record_type = boost::none, +        boost::optional<u64> title_id = boost::none) const; + +    // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there +    // is a meta NCA and all of them are accessible. +    InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, +                               const VfsCopyFunction& copy = &VfsRawCopy); + +    // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this +    // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a +    // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there. +    // TODO(DarkLordZach): Author real meta-type NCAs and install those. +    InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type, +                               bool overwrite_if_exists = false, +                               const VfsCopyFunction& copy = &VfsRawCopy); + +private: +    template <typename T> +    void IterateAllMetadata(std::vector<T>& out, +                            std::function<T(const CNMT&, const ContentRecord&)> proc, +                            std::function<bool(const CNMT&, const ContentRecord&)> filter) const; +    std::vector<NcaID> AccumulateFiles() const; +    void ProcessFiles(const std::vector<NcaID>& ids); +    void AccumulateYuzuMeta(); +    boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; +    VirtualFile GetFileAtID(NcaID id) const; +    VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const; +    InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, +                                bool overwrite_if_exists, +                                boost::optional<NcaID> override_id = boost::none); +    bool RawInstallYuzuMeta(const CNMT& cnmt); + +    VirtualDir dir; +    RegisteredCacheParsingFunction parser; +    // maps tid -> NcaID of meta +    boost::container::flat_map<u64, NcaID> meta_id; +    // maps tid -> meta +    boost::container::flat_map<u64, CNMT> meta; +    // maps tid -> meta for CNMT in yuzu_meta +    boost::container::flat_map<u64, CNMT> yuzu_meta; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index ff3ddb29c..e490c8ace 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t          auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);          parent->AddFile(std::make_shared<OffsetVfsFile>( -            file, entry.first.size, entry.first.offset + data_offset, entry.second, parent)); +            file, entry.first.size, entry.first.offset + data_offset, entry.second));          if (entry.first.sibling == ROMFS_ENTRY_EMPTY)              break; @@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s      while (true) {          auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);          auto current = std::make_shared<VectorVfsDirectory>( -            std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second); +            std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);          if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {              ProcessFile(file, file_offset, data_offset, entry.first.child_file, current); @@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {      const u64 file_offset = header.file_meta.offset;      const u64 dir_offset = header.directory_meta.offset + 4; -    const auto root = +    auto root =          std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, -                                             file->GetContainingDirectory(), file->GetName()); +                                             file->GetName(), file->GetContainingDirectory());      ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root); diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp new file mode 100644 index 000000000..e6bf586a3 --- /dev/null +++ b/src/core/file_sys/vfs_concat.cpp @@ -0,0 +1,94 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <utility> + +#include "core/file_sys/vfs_concat.h" + +namespace FileSys { + +VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { +    if (files.empty()) +        return nullptr; +    if (files.size() == 1) +        return files[0]; + +    return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); +} + +ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) +    : name(std::move(name)) { +    size_t next_offset = 0; +    for (const auto& file : files_) { +        files[next_offset] = file; +        next_offset += file->GetSize(); +    } +} + +std::string ConcatenatedVfsFile::GetName() const { +    if (files.empty()) +        return ""; +    if (!name.empty()) +        return name; +    return files.begin()->second->GetName(); +} + +size_t ConcatenatedVfsFile::GetSize() const { +    if (files.empty()) +        return 0; +    return files.rbegin()->first + files.rbegin()->second->GetSize(); +} + +bool ConcatenatedVfsFile::Resize(size_t new_size) { +    return false; +} + +std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const { +    if (files.empty()) +        return nullptr; +    return files.begin()->second->GetContainingDirectory(); +} + +bool ConcatenatedVfsFile::IsWritable() const { +    return false; +} + +bool ConcatenatedVfsFile::IsReadable() const { +    return true; +} + +size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { +    auto entry = files.end(); +    for (auto iter = files.begin(); iter != files.end(); ++iter) { +        if (iter->first > offset) { +            entry = --iter; +            break; +        } +    } + +    // Check if the entry should be the last one. The loop above will make it end(). +    if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize()) +        --entry; + +    if (entry == files.end()) +        return 0; + +    const auto remaining = entry->second->GetSize() + offset - entry->first; +    if (length > remaining) { +        return entry->second->Read(data, remaining, offset - entry->first) + +               Read(data + remaining, length - remaining, offset + remaining); +    } + +    return entry->second->Read(data, length, offset - entry->first); +} + +size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) { +    return 0; +} + +bool ConcatenatedVfsFile::Rename(std::string_view name) { +    return false; +} +} // namespace FileSys diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h new file mode 100644 index 000000000..686d32515 --- /dev/null +++ b/src/core/file_sys/vfs_concat.h @@ -0,0 +1,41 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string_view> +#include <boost/container/flat_map.hpp> +#include "core/file_sys/vfs.h" + +namespace FileSys { + +// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. +VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = ""); + +// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently +// read-only. +class ConcatenatedVfsFile : public VfsFile { +    friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name); + +    ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); + +public: +    std::string GetName() const override; +    size_t GetSize() const override; +    bool Resize(size_t new_size) override; +    std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; +    bool IsWritable() const override; +    bool IsReadable() const override; +    size_t Read(u8* data, size_t length, size_t offset) const override; +    size_t Write(const u8* data, size_t length, size_t offset) override; +    bool Rename(std::string_view name) override; + +private: +    // Maps starting offset to file -- more efficient. +    boost::container::flat_map<u64, VirtualFile> files; +    std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 1b5919737..0afe515f0 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {  VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {      const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); -    if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path)) -        return nullptr; +    const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); +    if (!FileUtil::Exists(path)) { +        FileUtil::CreateFullPath(path_fwd); +        if (!FileUtil::CreateEmptyFile(path)) +            return nullptr; +    }      return OpenFile(path, perms);  } @@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)  VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {      const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); -    if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path)) -        return nullptr; +    const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); +    if (!FileUtil::Exists(path)) { +        FileUtil::CreateFullPath(path_fwd); +        if (!FileUtil::CreateDir(path)) +            return nullptr; +    }      // Cannot use make_shared as RealVfsDirectory constructor is private      return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));  } @@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&  std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {      const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); -    if (!FileUtil::Exists(full_path)) +    if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))          return nullptr;      return base.OpenFile(full_path, perms);  }  std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {      const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); -    if (!FileUtil::Exists(full_path)) +    if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))          return nullptr;      return base.OpenDirectory(full_path, perms);  } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 8a1e79ef6..989803d43 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -5,7 +5,6 @@  #pragma once  #include <string_view> -  #include <boost/container/flat_map.hpp>  #include "common/file_util.h"  #include "core/file_sys/mode.h" diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index fda603960..98e7c4598 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -8,8 +8,8 @@  namespace FileSys {  VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, -                                       std::vector<VirtualDir> dirs_, VirtualDir parent_, -                                       std::string name_) +                                       std::vector<VirtualDir> dirs_, std::string name_, +                                       VirtualDir parent_)      : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),        name(std::move(name_)) {} diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index b3b468233..179f62e4b 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -13,8 +13,8 @@ namespace FileSys {  class VectorVfsDirectory : public VfsDirectory {  public:      explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, -                                std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr, -                                std::string name = ""); +                                std::vector<VirtualDir> dirs = {}, std::string name = "", +                                VirtualDir parent = nullptr);      std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;      std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 384dc7822..7006a37b3 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -34,9 +34,9 @@ class EmuWindow {  public:      /// Data structure to store emuwindow configuration      struct WindowConfig { -        bool fullscreen; -        int res_width; -        int res_height; +        bool fullscreen = false; +        int res_width = 0; +        int res_height = 0;          std::pair<unsigned, unsigned> min_client_area_size;      }; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index d09ca5992..51a1ec160 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -152,7 +152,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {      // Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the      // end of the command such that only commands following this one are handled as domains      if (convert_to_domain) { -        ASSERT_MSG(domain_request_handlers.empty(), "already a domain"); +        ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");          domain_request_handlers = {hle_handler};          convert_to_domain = false;      } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 2bce54fee..1a88e66b9 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -97,7 +97,12 @@ public:      /// Returns true if the session has been converted to a domain, otherwise False      bool IsDomain() const { -        return !domain_request_handlers.empty(); +        return !IsSession(); +    } + +    /// Returns true if this session has not been converted to a domain, otherwise false. +    bool IsSession() const { +        return domain_request_handlers.empty();      }      /// Converts the session to a domain at the end of the current command diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index b24f409b3..6be5c474e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -250,8 +250,11 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {  }  /// Break program execution -static void Break(u64 unk_0, u64 unk_1, u64 unk_2) { -    LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!"); +static void Break(u64 reason, u64 info1, u64 info2) { +    LOG_CRITICAL( +        Debug_Emulated, +        "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", +        reason, info1, info2);      ASSERT(false);  } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index a1a7867ce..cf4f94822 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -167,7 +167,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {  }  void Thread::CancelWakeupTimer() { -    CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); +    CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle);  }  static boost::optional<s32> GetNextProcessorId(u64 mask) { diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 5e416cde2..da658cbe6 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(  static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;  static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;  static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory; +static std::unique_ptr<FileSys::BISFactory> bis_factory;  ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {      ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS"); @@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {      return RESULT_SUCCESS;  } +ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { +    ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS"); +    bis_factory = std::move(factory); +    LOG_DEBUG(Service_FS, "Registred BIS"); +    return RESULT_SUCCESS; +} +  ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {      LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); @@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {      return sdmc_factory->Open();  } +std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { +    return bis_factory->GetSystemNANDContents(); +} + +std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { +    return bis_factory->GetUserNANDContents(); +} +  void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {      romfs_factory = nullptr;      save_data_factory = nullptr; @@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {      auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),                                             FileSys::Mode::ReadWrite); +    if (bis_factory == nullptr) +        bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); +      auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));      save_data_factory = std::move(savedata); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 462c13f20..1d6f922dd 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -6,6 +6,7 @@  #include <memory>  #include "common/common_types.h" +#include "core/file_sys/bis_factory.h"  #include "core/file_sys/directory.h"  #include "core/file_sys/mode.h"  #include "core/file_sys/romfs_factory.h" @@ -24,16 +25,15 @@ namespace FileSystem {  ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);  ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);  ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); +ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); -// TODO(DarkLordZach): BIS Filesystem -// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);  ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);  ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,                                              FileSys::SaveDataDescriptor save_struct);  ResultVal<FileSys::VirtualDir> OpenSDMC(); -// TODO(DarkLordZach): BIS Filesystem -// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS(); +std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); +std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();  /// Registers all Filesystem services with the specified service manager.  void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 2e99ddf51..098da2a41 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -92,7 +92,11 @@ private:          // Parse out log metadata          u32 line{}; -        std::string message, filename, function; +        std::string module; +        std::string message; +        std::string filename; +        std::string function; +        std::string thread;          while (addr < end_addr) {              const Field field{static_cast<Field>(Memory::Read8(addr++))};              const size_t length{Memory::Read8(addr++)}; @@ -102,6 +106,8 @@ private:              }              switch (field) { +            case Field::Skip: +                break;              case Field::Message:                  message = Memory::ReadCString(addr, length);                  break; @@ -114,6 +120,12 @@ private:              case Field::Function:                  function = Memory::ReadCString(addr, length);                  break; +            case Field::Module: +                module = Memory::ReadCString(addr, length); +                break; +            case Field::Thread: +                thread = Memory::ReadCString(addr, length); +                break;              }              addr += length; @@ -128,12 +140,18 @@ private:          if (!filename.empty()) {              log_stream << filename << ':';          } +        if (!module.empty()) { +            log_stream << module << ':'; +        }          if (!function.empty()) {              log_stream << function << ':';          }          if (line) {              log_stream << std::to_string(line) << ':';          } +        if (!thread.empty()) { +            log_stream << thread << ':'; +        }          if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {              log_stream << ' ';          } @@ -142,7 +160,7 @@ private:          if (header.IsTailLog()) {              switch (header.severity) {              case MessageHeader::Severity::Trace: -                LOG_TRACE(Debug_Emulated, "{}", log_stream.str()); +                LOG_DEBUG(Debug_Emulated, "{}", log_stream.str());                  break;              case MessageHeader::Severity::Info:                  LOG_INFO(Debug_Emulated, "{}", log_stream.str()); diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 08f45b78a..7b91bb258 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -9,42 +9,63 @@  namespace Service::MM { -void InstallInterfaces(SM::ServiceManager& service_manager) { -    std::make_shared<MM_U>()->InstallAsService(service_manager); -} +class MM_U final : public ServiceFramework<MM_U> { +public: +    explicit MM_U() : ServiceFramework{"mm:u"} { +        // clang-format off +        static const FunctionInfo functions[] = { +            {0, &MM_U::Initialize, "InitializeOld"}, +            {1, &MM_U::Finalize, "FinalizeOld"}, +            {2, &MM_U::SetAndWait, "SetAndWaitOld"}, +            {3, &MM_U::Get, "GetOld"}, +            {4, &MM_U::Initialize, "Initialize"}, +            {5, &MM_U::Finalize, "Finalize"}, +            {6, &MM_U::SetAndWait, "SetAndWait"}, +            {7, &MM_U::Get, "Get"}, +        }; +        // clang-format on -void MM_U::Initialize(Kernel::HLERequestContext& ctx) { -    LOG_WARNING(Service_MM, "(STUBBED) called"); -    IPC::ResponseBuilder rb{ctx, 2}; -    rb.Push(RESULT_SUCCESS); -} +        RegisterHandlers(functions); +    } -void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) { -    IPC::RequestParser rp{ctx}; -    min = rp.Pop<u32>(); -    max = rp.Pop<u32>(); -    current = min; +private: +    void Initialize(Kernel::HLERequestContext& ctx) { +        LOG_WARNING(Service_MM, "(STUBBED) called"); +        IPC::ResponseBuilder rb{ctx, 2}; +        rb.Push(RESULT_SUCCESS); +    } -    LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); -    IPC::ResponseBuilder rb{ctx, 2}; -    rb.Push(RESULT_SUCCESS); -} +    void Finalize(Kernel::HLERequestContext& ctx) { +        LOG_WARNING(Service_MM, "(STUBBED) called"); +        IPC::ResponseBuilder rb{ctx, 2}; +        rb.Push(RESULT_SUCCESS); +    } -void MM_U::Get(Kernel::HLERequestContext& ctx) { -    LOG_WARNING(Service_MM, "(STUBBED) called"); -    IPC::ResponseBuilder rb{ctx, 3}; -    rb.Push(RESULT_SUCCESS); -    rb.Push(current); -} +    void SetAndWait(Kernel::HLERequestContext& ctx) { +        IPC::RequestParser rp{ctx}; +        min = rp.Pop<u32>(); +        max = rp.Pop<u32>(); +        current = min; -MM_U::MM_U() : ServiceFramework("mm:u") { -    static const FunctionInfo functions[] = { -        {0, nullptr, "InitializeOld"},        {1, nullptr, "FinalizeOld"}, -        {2, nullptr, "SetAndWaitOld"},        {3, nullptr, "GetOld"}, -        {4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"}, -        {6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"}, -    }; -    RegisterHandlers(functions); +        LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); +        IPC::ResponseBuilder rb{ctx, 2}; +        rb.Push(RESULT_SUCCESS); +    } + +    void Get(Kernel::HLERequestContext& ctx) { +        LOG_WARNING(Service_MM, "(STUBBED) called"); +        IPC::ResponseBuilder rb{ctx, 3}; +        rb.Push(RESULT_SUCCESS); +        rb.Push(current); +    } + +    u32 min{0}; +    u32 max{0}; +    u32 current{0}; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager) { +    std::make_shared<MM_U>()->InstallAsService(service_manager);  }  } // namespace Service::MM diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h index 79eeedf9c..5439fa653 100644 --- a/src/core/hle/service/mm/mm_u.h +++ b/src/core/hle/service/mm/mm_u.h @@ -8,21 +8,6 @@  namespace Service::MM { -class MM_U final : public ServiceFramework<MM_U> { -public: -    MM_U(); -    ~MM_U() = default; - -private: -    void Initialize(Kernel::HLERequestContext& ctx); -    void SetAndWait(Kernel::HLERequestContext& ctx); -    void Get(Kernel::HLERequestContext& ctx); - -    u32 min{0}; -    u32 max{0}; -    u32 current{0}; -}; -  /// Registers all MM services with the specified service manager.  void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 518a0cc46..1cef73216 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -10,7 +10,7 @@  namespace Service::SM {  void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { -    ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain"); +    ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain");      ctx.Session()->ConvertToDomain();      IPC::ResponseBuilder rb{ctx, 3}; @@ -41,7 +41,7 @@ void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {  void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {      IPC::ResponseBuilder rb{ctx, 3};      rb.Push(RESULT_SUCCESS); -    rb.Push<u32>(0x500); +    rb.Push<u16>(0x500);      LOG_WARNING(Service, "(STUBBED) called");  } diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index de05f21d8..d575a9bea 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -118,7 +118,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(      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(), diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 401cad3ab..6420a7f11 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -398,7 +398,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {      process->LoadModule(codeset, codeset->entrypoint);      process->svc_access_mask.set(); -    process->address_mappings = default_address_mappings;      // Attach the default resource limit (APPLICATION) to the process      process->resource_limit = diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 1f2f31535..70ef5d240 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <memory> +#include <ostream>  #include <string>  #include "common/logging/log.h"  #include "common/string_util.h" @@ -17,12 +18,6 @@  namespace Loader { -const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { -    {0x1FF50000, 0x8000, true},    // part of DSP RAM -    {0x1FF70000, 0x8000, true},    // part of DSP RAM -    {0x1F000000, 0x600000, false}, // entire VRAM -}; -  FileType IdentifyFile(FileSys::VirtualFile file) {      FileType type; @@ -46,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {  FileType GuessFromFilename(const std::string& name) {      if (name == "main")          return FileType::DeconstructedRomDirectory; +    if (name == "00") +        return FileType::NCA;      const std::string extension =          Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name))); @@ -125,14 +122,9 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{      "There is no control data available.",  }; -std::string GetMessageForResultStatus(ResultStatus status) { -    return GetMessageForResultStatus(static_cast<u16>(status)); -} - -std::string GetMessageForResultStatus(u16 status) { -    if (status >= 36) -        return ""; -    return RESULT_MESSAGES[status]; +std::ostream& operator<<(std::ostream& os, ResultStatus status) { +    os << RESULT_MESSAGES.at(static_cast<size_t>(status)); +    return os;  }  /** diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 285363549..b74cfbf8a 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -5,7 +5,7 @@  #pragma once  #include <algorithm> -#include <initializer_list> +#include <iosfwd>  #include <memory>  #include <string>  #include <utility> @@ -95,8 +95,7 @@ enum class ResultStatus : u16 {      ErrorNoControl,  }; -std::string GetMessageForResultStatus(ResultStatus status); -std::string GetMessageForResultStatus(u16 status); +std::ostream& operator<<(std::ostream& os, ResultStatus status);  /// Interface for loading an application  class AppLoader : NonCopyable { @@ -208,12 +207,6 @@ protected:  };  /** - * Common address mappings found in most games, used for binary formats that don't have this - * information. - */ -extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; - -/**   * Identifies a bootable file and return a suitable loader   * @param file The bootable file   * @return the best loader for this file diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 908d91eab..2179cf2ea 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -186,7 +186,6 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {      }      process->svc_access_mask.set(); -    process->address_mappings = default_address_mappings;      process->resource_limit =          Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);      process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index fee7d58c6..a94558ac5 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -152,7 +152,6 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {      LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR);      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, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 52a649e2f..9d1549fe9 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -648,11 +648,11 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(          if (used_buffer.IsIndirect()) {              // Buffer is accessed indirectly, so upload the entire thing -            size = buffer.size * sizeof(float); +            size = buffer.size;              if (size > MaxConstbufferSize) { -                LOG_ERROR(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size, -                          MaxConstbufferSize); +                LOG_CRITICAL(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size, +                             MaxConstbufferSize);                  size = MaxConstbufferSize;              }          } else { diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 5d58ebd4f..b6947b97b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -94,11 +94,11 @@ struct FormatTuple {  static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{      {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U      {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false},                     // ABGR8S -    {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false},    // B5G6R5 +    {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false},    // B5G6R5U      {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, -     false}, // A2B10G10R10 -    {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5 -    {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},                    // R8 +     false}, // A2B10G10R10U +    {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U +    {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},                    // R8U      {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false},           // R8UI      {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false},                 // RGBA16F      {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false},              // RGBA16U @@ -119,13 +119,14 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form      {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,       true},                                                                    // BC7U      {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},        // ASTC_2D_4X4 -    {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},            // G8R8 +    {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},            // G8R8U +    {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false},                     // G8R8S      {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false},        // BGRA8      {GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false},              // RGBA32F      {GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false},                  // RG32F      {GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false},                  // R32F      {GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false},             // R16F -    {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false},          // R16UNORM +    {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false},          // R16U      {GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false},             // R16S      {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI      {GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false},           // R16I @@ -242,10 +243,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU          // clang-format off          MortonCopy<true, PixelFormat::ABGR8U>,          MortonCopy<true, PixelFormat::ABGR8S>, -        MortonCopy<true, PixelFormat::B5G6R5>, -        MortonCopy<true, PixelFormat::A2B10G10R10>, -        MortonCopy<true, PixelFormat::A1B5G5R5>, -        MortonCopy<true, PixelFormat::R8>, +        MortonCopy<true, PixelFormat::B5G6R5U>, +        MortonCopy<true, PixelFormat::A2B10G10R10U>, +        MortonCopy<true, PixelFormat::A1B5G5R5U>, +        MortonCopy<true, PixelFormat::R8U>,          MortonCopy<true, PixelFormat::R8UI>,          MortonCopy<true, PixelFormat::RGBA16F>,          MortonCopy<true, PixelFormat::RGBA16U>, @@ -260,13 +261,14 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU          MortonCopy<true, PixelFormat::DXN2SNORM>,          MortonCopy<true, PixelFormat::BC7U>,          MortonCopy<true, PixelFormat::ASTC_2D_4X4>, -        MortonCopy<true, PixelFormat::G8R8>, +        MortonCopy<true, PixelFormat::G8R8U>, +        MortonCopy<true, PixelFormat::G8R8S>,          MortonCopy<true, PixelFormat::BGRA8>,          MortonCopy<true, PixelFormat::RGBA32F>,          MortonCopy<true, PixelFormat::RG32F>,          MortonCopy<true, PixelFormat::R32F>,          MortonCopy<true, PixelFormat::R16F>, -        MortonCopy<true, PixelFormat::R16UNORM>, +        MortonCopy<true, PixelFormat::R16U>,          MortonCopy<true, PixelFormat::R16S>,          MortonCopy<true, PixelFormat::R16UI>,          MortonCopy<true, PixelFormat::R16I>, @@ -295,10 +297,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU          // clang-format off          MortonCopy<false, PixelFormat::ABGR8U>,          MortonCopy<false, PixelFormat::ABGR8S>, -        MortonCopy<false, PixelFormat::B5G6R5>, -        MortonCopy<false, PixelFormat::A2B10G10R10>, -        MortonCopy<false, PixelFormat::A1B5G5R5>, -        MortonCopy<false, PixelFormat::R8>, +        MortonCopy<false, PixelFormat::B5G6R5U>, +        MortonCopy<false, PixelFormat::A2B10G10R10U>, +        MortonCopy<false, PixelFormat::A1B5G5R5U>, +        MortonCopy<false, PixelFormat::R8U>,          MortonCopy<false, PixelFormat::R8UI>,          MortonCopy<false, PixelFormat::RGBA16F>,          MortonCopy<false, PixelFormat::RGBA16U>, @@ -315,13 +317,14 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU          nullptr,          nullptr,          nullptr, -        MortonCopy<false, PixelFormat::G8R8>, +        MortonCopy<false, PixelFormat::G8R8U>, +        MortonCopy<false, PixelFormat::G8R8S>,          MortonCopy<false, PixelFormat::BGRA8>,          MortonCopy<false, PixelFormat::RGBA32F>,          MortonCopy<false, PixelFormat::RG32F>,          MortonCopy<false, PixelFormat::R32F>,          MortonCopy<false, PixelFormat::R16F>, -        MortonCopy<false, PixelFormat::R16UNORM>, +        MortonCopy<false, PixelFormat::R16U>,          MortonCopy<false, PixelFormat::R16S>,          MortonCopy<false, PixelFormat::R16UI>,          MortonCopy<false, PixelFormat::R16I>, @@ -461,7 +464,7 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {  }  static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) { -    const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8)}; +    const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};      for (size_t y = 0; y < height; ++y) {          for (size_t x = 0; x < width; ++x) {              const size_t offset{bpp * (y * width + x)}; @@ -493,7 +496,8 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma          ConvertS8Z24ToZ24S8(data, width, height);          break; -    case PixelFormat::G8R8: +    case PixelFormat::G8R8U: +    case PixelFormat::G8R8S:          // Convert the G8R8 color format to R8G8, as OpenGL does not support G8R8.          ConvertG8R8ToR8G8(data, width, height);          break; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 36a41522b..55cf3782c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -25,10 +25,10 @@ struct SurfaceParams {      enum class PixelFormat {          ABGR8U = 0,          ABGR8S = 1, -        B5G6R5 = 2, -        A2B10G10R10 = 3, -        A1B5G5R5 = 4, -        R8 = 5, +        B5G6R5U = 2, +        A2B10G10R10U = 3, +        A1B5G5R5U = 4, +        R8U = 5,          R8UI = 6,          RGBA16F = 7,          RGBA16U = 8, @@ -43,36 +43,37 @@ struct SurfaceParams {          DXN2SNORM = 17,          BC7U = 18,          ASTC_2D_4X4 = 19, -        G8R8 = 20, -        BGRA8 = 21, -        RGBA32F = 22, -        RG32F = 23, -        R32F = 24, -        R16F = 25, -        R16UNORM = 26, -        R16S = 27, -        R16UI = 28, -        R16I = 29, -        RG16 = 30, -        RG16F = 31, -        RG16UI = 32, -        RG16I = 33, -        RG16S = 34, -        RGB32F = 35, -        SRGBA8 = 36, -        RG8U = 37, -        RG8S = 38, -        RG32UI = 39, -        R32UI = 40, +        G8R8U = 20, +        G8R8S = 21, +        BGRA8 = 22, +        RGBA32F = 23, +        RG32F = 24, +        R32F = 25, +        R16F = 26, +        R16U = 27, +        R16S = 28, +        R16UI = 29, +        R16I = 30, +        RG16 = 31, +        RG16F = 32, +        RG16UI = 33, +        RG16I = 34, +        RG16S = 35, +        RGB32F = 36, +        SRGBA8 = 37, +        RG8U = 38, +        RG8S = 39, +        RG32UI = 40, +        R32UI = 41,          MaxColorFormat,          // DepthStencil formats -        Z24S8 = 41, -        S8Z24 = 42, -        Z32F = 43, -        Z16 = 44, -        Z32FS8 = 45, +        Z24S8 = 42, +        S8Z24 = 43, +        Z32F = 44, +        Z16 = 45, +        Z32FS8 = 46,          MaxDepthStencilFormat, @@ -112,10 +113,10 @@ struct SurfaceParams {          constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{              1, // ABGR8U              1, // ABGR8S -            1, // B5G6R5 -            1, // A2B10G10R10 -            1, // A1B5G5R5 -            1, // R8 +            1, // B5G6R5U +            1, // A2B10G10R10U +            1, // A1B5G5R5U +            1, // R8U              1, // R8UI              1, // RGBA16F              1, // RGBA16U @@ -130,13 +131,14 @@ struct SurfaceParams {              4, // DXN2SNORM              4, // BC7U              4, // ASTC_2D_4X4 -            1, // G8R8 +            1, // G8R8U +            1, // G8R8S              1, // BGRA8              1, // RGBA32F              1, // RG32F              1, // R32F              1, // R16F -            1, // R16UNORM +            1, // R16U              1, // R16S              1, // R16UI              1, // R16I @@ -169,10 +171,10 @@ struct SurfaceParams {          constexpr std::array<u32, MaxPixelFormat> bpp_table = {{              32,  // ABGR8U              32,  // ABGR8S -            16,  // B5G6R5 -            32,  // A2B10G10R10 -            16,  // A1B5G5R5 -            8,   // R8 +            16,  // B5G6R5U +            32,  // A2B10G10R10U +            16,  // A1B5G5R5U +            8,   // R8U              8,   // R8UI              64,  // RGBA16F              64,  // RGBA16U @@ -187,13 +189,14 @@ struct SurfaceParams {              128, // DXN2SNORM              128, // BC7U              32,  // ASTC_2D_4X4 -            16,  // G8R8 +            16,  // G8R8U +            16,  // G8R8S              32,  // BGRA8              128, // RGBA32F              64,  // RG32F              32,  // R32F              16,  // R16F -            16,  // R16UNORM +            16,  // R16U              16,  // R16S              16,  // R16UI              16,  // R16I @@ -253,7 +256,7 @@ struct SurfaceParams {          case Tegra::RenderTargetFormat::BGRA8_UNORM:              return PixelFormat::BGRA8;          case Tegra::RenderTargetFormat::RGB10_A2_UNORM: -            return PixelFormat::A2B10G10R10; +            return PixelFormat::A2B10G10R10U;          case Tegra::RenderTargetFormat::RGBA16_FLOAT:              return PixelFormat::RGBA16F;          case Tegra::RenderTargetFormat::RGBA16_UNORM: @@ -267,11 +270,11 @@ struct SurfaceParams {          case Tegra::RenderTargetFormat::R11G11B10_FLOAT:              return PixelFormat::R11FG11FB10F;          case Tegra::RenderTargetFormat::B5G6R5_UNORM: -            return PixelFormat::B5G6R5; +            return PixelFormat::B5G6R5U;          case Tegra::RenderTargetFormat::RGBA32_UINT:              return PixelFormat::RGBA32UI;          case Tegra::RenderTargetFormat::R8_UNORM: -            return PixelFormat::R8; +            return PixelFormat::R8U;          case Tegra::RenderTargetFormat::R8_UINT:              return PixelFormat::R8UI;          case Tegra::RenderTargetFormat::RG16_FLOAT: @@ -291,7 +294,7 @@ struct SurfaceParams {          case Tegra::RenderTargetFormat::R16_FLOAT:              return PixelFormat::R16F;          case Tegra::RenderTargetFormat::R16_UNORM: -            return PixelFormat::R16UNORM; +            return PixelFormat::R16U;          case Tegra::RenderTargetFormat::R16_SNORM:              return PixelFormat::R16S;          case Tegra::RenderTargetFormat::R16_UINT: @@ -325,15 +328,33 @@ struct SurfaceParams {                           static_cast<u32>(component_type));              UNREACHABLE();          case Tegra::Texture::TextureFormat::B5G6R5: -            return PixelFormat::B5G6R5; +            switch (component_type) { +            case Tegra::Texture::ComponentType::UNORM: +                return PixelFormat::B5G6R5U; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::A2B10G10R10: -            return PixelFormat::A2B10G10R10; +            switch (component_type) { +            case Tegra::Texture::ComponentType::UNORM: +                return PixelFormat::A2B10G10R10U; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::A1B5G5R5: -            return PixelFormat::A1B5G5R5; +            switch (component_type) { +            case Tegra::Texture::ComponentType::UNORM: +                return PixelFormat::A1B5G5R5U; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::R8:              switch (component_type) {              case Tegra::Texture::ComponentType::UNORM: -                return PixelFormat::R8; +                return PixelFormat::R8U;              case Tegra::Texture::ComponentType::UINT:                  return PixelFormat::R8UI;              } @@ -341,11 +362,33 @@ struct SurfaceParams {                           static_cast<u32>(component_type));              UNREACHABLE();          case Tegra::Texture::TextureFormat::G8R8: -            return PixelFormat::G8R8; +            switch (component_type) { +            case Tegra::Texture::ComponentType::UNORM: +                return PixelFormat::G8R8U; +            case Tegra::Texture::ComponentType::SNORM: +                return PixelFormat::G8R8S; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::R16_G16_B16_A16: -            return PixelFormat::RGBA16F; +            switch (component_type) { +            case Tegra::Texture::ComponentType::UNORM: +                return PixelFormat::RGBA16U; +            case Tegra::Texture::ComponentType::FLOAT: +                return PixelFormat::RGBA16F; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::BF10GF11RF11: -            return PixelFormat::R11FG11FB10F; +            switch (component_type) { +            case Tegra::Texture::ComponentType::FLOAT: +                return PixelFormat::R11FG11FB10F; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::R32_G32_B32_A32:              switch (component_type) {              case Tegra::Texture::ComponentType::FLOAT: @@ -367,13 +410,19 @@ struct SurfaceParams {                           static_cast<u32>(component_type));              UNREACHABLE();          case Tegra::Texture::TextureFormat::R32_G32_B32: -            return PixelFormat::RGB32F; +            switch (component_type) { +            case Tegra::Texture::ComponentType::FLOAT: +                return PixelFormat::RGB32F; +            } +            LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", +                         static_cast<u32>(component_type)); +            UNREACHABLE();          case Tegra::Texture::TextureFormat::R16:              switch (component_type) {              case Tegra::Texture::ComponentType::FLOAT:                  return PixelFormat::R16F;              case Tegra::Texture::ComponentType::UNORM: -                return PixelFormat::R16UNORM; +                return PixelFormat::R16U;              case Tegra::Texture::ComponentType::SNORM:                  return PixelFormat::R16S;              case Tegra::Texture::ComponentType::UINT: @@ -396,6 +445,8 @@ struct SurfaceParams {              UNREACHABLE();          case Tegra::Texture::TextureFormat::ZF32:              return PixelFormat::Z32F; +        case Tegra::Texture::TextureFormat::Z16: +            return PixelFormat::Z16;          case Tegra::Texture::TextureFormat::Z24S8:              return PixelFormat::Z24S8;          case Tegra::Texture::TextureFormat::DXT1: diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 6834d7085..e0dfdbb9f 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -367,31 +367,32 @@ public:      }      /// Generates code representing a uniform (C buffer) register, interpreted as the input type. -    std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) { +    std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type, +                           Register::Size size = Register::Size::Word) {          declr_const_buffers[index].MarkAsUsed(index, offset, stage);          std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset / 4) + "][" +                              std::to_string(offset % 4) + ']';          if (type == GLSLRegister::Type::Float) { -            return value; +            // Do nothing, default          } else if (type == GLSLRegister::Type::Integer) { -            return "floatBitsToInt(" + value + ')'; +            value = "floatBitsToInt(" + value + ')';          } else if (type == GLSLRegister::Type::UnsignedInteger) { -            return "floatBitsToUint(" + value + ')'; +            value = "floatBitsToUint(" + value + ')';          } else {              UNREACHABLE();          } + +        return ConvertIntegerSize(value, size);      } -    std::string GetUniformIndirect(u64 index, s64 offset, const Register& index_reg, +    std::string GetUniformIndirect(u64 cbuf_index, s64 offset, const std::string& index_str,                                     GLSLRegister::Type type) { -        declr_const_buffers[index].MarkAsUsedIndirect(index, stage); - -        std::string final_offset = "((floatBitsToInt(" + GetRegister(index_reg, 0) + ") + " + -                                   std::to_string(offset) + ") / 4)"; +        declr_const_buffers[cbuf_index].MarkAsUsedIndirect(cbuf_index, stage); -        std::string value = -            'c' + std::to_string(index) + '[' + final_offset + " / 4][" + final_offset + " % 4]"; +        std::string final_offset = fmt::format("({} + {})", index_str, offset / 4); +        std::string value = 'c' + std::to_string(cbuf_index) + '[' + final_offset + " / 4][" + +                            final_offset + " % 4]";          if (type == GLSLRegister::Type::Float) {              return value; @@ -1249,20 +1250,41 @@ private:                      op_a = "abs(" + op_a + ')';                  } +                if (instr.conversion.negate_a) { +                    op_a = "-(" + op_a + ')'; +                } +                  regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,                                            1, instr.alu.saturate_d, 0, instr.conversion.dest_size);                  break;              } -            case OpCode::Id::I2F_R: { +            case OpCode::Id::I2F_R: +            case OpCode::Id::I2F_C: {                  ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented");                  ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); -                std::string op_a = regs.GetRegisterAsInteger( -                    instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); + +                std::string op_a{}; + +                if (instr.is_b_gpr) { +                    op_a = +                        regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_input_signed, +                                                  instr.conversion.src_size); +                } else { +                    op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, +                                           instr.conversion.is_input_signed +                                               ? GLSLRegister::Type::Integer +                                               : GLSLRegister::Type::UnsignedInteger, +                                           instr.conversion.src_size); +                }                  if (instr.conversion.abs_a) {                      op_a = "abs(" + op_a + ')';                  } +                if (instr.conversion.negate_a) { +                    op_a = "-(" + op_a + ')'; +                } +                  regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);                  break;              } @@ -1271,6 +1293,14 @@ private:                  ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");                  std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); +                if (instr.conversion.abs_a) { +                    op_a = "abs(" + op_a + ')'; +                } + +                if (instr.conversion.negate_a) { +                    op_a = "-(" + op_a + ')'; +                } +                  switch (instr.conversion.f2f.rounding) {                  case Tegra::Shader::F2fRoundingOp::None:                      break; @@ -1293,21 +1323,29 @@ private:                      break;                  } -                if (instr.conversion.abs_a) { -                    op_a = "abs(" + op_a + ')'; -                } -                  regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);                  break;              } -            case OpCode::Id::F2I_R: { +            case OpCode::Id::F2I_R: +            case OpCode::Id::F2I_C: {                  ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); -                std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); +                std::string op_a{}; + +                if (instr.is_b_gpr) { +                    op_a = regs.GetRegisterAsFloat(instr.gpr20); +                } else { +                    op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, +                                           GLSLRegister::Type::Float); +                }                  if (instr.conversion.abs_a) {                      op_a = "abs(" + op_a + ')';                  } +                if (instr.conversion.negate_a) { +                    op_a = "-(" + op_a + ')'; +                } +                  switch (instr.conversion.f2i.rounding) {                  case Tegra::Shader::F2iRoundingOp::None:                      break; @@ -1355,11 +1393,16 @@ private:              case OpCode::Id::LD_C: {                  ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented"); +                // Add an extra scope and declare the index register inside to prevent +                // overwriting it in case it is used as an output of the LD instruction. +                shader.AddLine("{"); +                ++shader.scope; + +                shader.AddLine("uint index = (" + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + +                               " / 4) & (MAX_CONSTBUFFER_ELEMENTS - 1);"); +                  std::string op_a = -                    regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, instr.gpr8, -                                            GLSLRegister::Type::Float); -                std::string op_b = -                    regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, instr.gpr8, +                    regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, "index",                                              GLSLRegister::Type::Float);                  switch (instr.ld_c.type.Value()) { @@ -1367,16 +1410,22 @@ private:                      regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);                      break; -                case Tegra::Shader::UniformType::Double: +                case Tegra::Shader::UniformType::Double: { +                    std::string op_b = +                        regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, +                                                "index", GLSLRegister::Type::Float);                      regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);                      regs.SetRegisterToFloat(instr.gpr0.Value() + 1, 0, op_b, 1, 1);                      break; - +                }                  default:                      LOG_CRITICAL(HW_GPU, "Unhandled type: {}",                                   static_cast<unsigned>(instr.ld_c.type.Value()));                      UNREACHABLE();                  } + +                --shader.scope; +                shader.AddLine("}");                  break;              }              case OpCode::Id::ST_A: { diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 83ea0cfc0..8f719fdd8 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -24,16 +24,25 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;  inline GLenum VertexType(Maxwell::VertexAttribute attrib) {      switch (attrib.type) { +    case Maxwell::VertexAttribute::Type::UnsignedInt:      case Maxwell::VertexAttribute::Type::UnsignedNorm: {          switch (attrib.size) {          case Maxwell::VertexAttribute::Size::Size_8:          case Maxwell::VertexAttribute::Size::Size_8_8: +        case Maxwell::VertexAttribute::Size::Size_8_8_8:          case Maxwell::VertexAttribute::Size::Size_8_8_8_8:              return GL_UNSIGNED_BYTE; +        case Maxwell::VertexAttribute::Size::Size_16:          case Maxwell::VertexAttribute::Size::Size_16_16: +        case Maxwell::VertexAttribute::Size::Size_16_16_16:          case Maxwell::VertexAttribute::Size::Size_16_16_16_16:              return GL_UNSIGNED_SHORT; +        case Maxwell::VertexAttribute::Size::Size_32: +        case Maxwell::VertexAttribute::Size::Size_32_32: +        case Maxwell::VertexAttribute::Size::Size_32_32_32: +        case Maxwell::VertexAttribute::Size::Size_32_32_32_32: +            return GL_UNSIGNED_INT;          case Maxwell::VertexAttribute::Size::Size_10_10_10_2:              return GL_UNSIGNED_INT_2_10_10_10_REV;          } @@ -43,16 +52,25 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {          return {};      } +    case Maxwell::VertexAttribute::Type::SignedInt:      case Maxwell::VertexAttribute::Type::SignedNorm: {          switch (attrib.size) { -        case Maxwell::VertexAttribute::Size::Size_32_32_32: -            return GL_INT; +        case Maxwell::VertexAttribute::Size::Size_8:          case Maxwell::VertexAttribute::Size::Size_8_8: +        case Maxwell::VertexAttribute::Size::Size_8_8_8:          case Maxwell::VertexAttribute::Size::Size_8_8_8_8:              return GL_BYTE; +        case Maxwell::VertexAttribute::Size::Size_16:          case Maxwell::VertexAttribute::Size::Size_16_16: +        case Maxwell::VertexAttribute::Size::Size_16_16_16: +        case Maxwell::VertexAttribute::Size::Size_16_16_16_16:              return GL_SHORT; +        case Maxwell::VertexAttribute::Size::Size_32: +        case Maxwell::VertexAttribute::Size::Size_32_32: +        case Maxwell::VertexAttribute::Size::Size_32_32_32: +        case Maxwell::VertexAttribute::Size::Size_32_32_32_32: +            return GL_INT;          case Maxwell::VertexAttribute::Size::Size_10_10_10_2:              return GL_INT_2_10_10_10_REV;          } @@ -62,9 +80,6 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {          return {};      } -    case Maxwell::VertexAttribute::Type::UnsignedInt: -        return GL_UNSIGNED_INT; -      case Maxwell::VertexAttribute::Type::Float:          return GL_FLOAT;      } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 85cb12594..f867118d9 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <regex>  #include <QApplication>  #include <QDir>  #include <QFileInfo> @@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {      }  } -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { -    boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; +static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, +                                      std::vector<u8>& icon, std::string& name) { +    const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); +    if (control_dir == nullptr) +        return; + +    const auto nacp_file = control_dir->GetFile("control.nacp"); +    if (nacp_file == nullptr) +        return; +    FileSys::NACP nacp(nacp_file); +    name = nacp.GetApplicationName(); + +    FileSys::VirtualFile icon_file = nullptr; +    for (const auto& language : FileSys::LANGUAGE_NAMES) { +        icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat"); +        if (icon_file != nullptr) { +            icon = icon_file->ReadAllBytes(); +            break; +        } +    } +} + +void GameListWorker::AddInstalledTitlesToGameList() { +    const auto usernand = Service::FileSystem::GetUserNANDContents(); +    const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application, +                                                             FileSys::ContentRecordType::Program); + +    for (const auto& game : installed_games) { +        const auto& file = usernand->GetEntryRaw(game); +        std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); +        if (!loader) +            continue; + +        std::vector<u8> icon; +        std::string name; +        u64 program_id; +        loader->ReadProgramId(program_id); + +        const auto& control = +            usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control); +        if (control != nullptr) +            GetMetadataFromControlNCA(control, icon, name); +        emit EntryReady({ +            new GameListItemPath( +                FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), +                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), +                program_id), +            new GameListItem( +                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), +            new GameListItemSize(file->GetSize()), +        }); +    } + +    const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application, +                                                          FileSys::ContentRecordType::Control); + +    for (const auto& entry : control_data) { +        const auto nca = usernand->GetEntry(entry); +        if (nca != nullptr) +            nca_control_map.insert_or_assign(entry.title_id, nca); +    } +} -    const auto nca_control_callback = -        [this, &nca_control_map](u64* num_entries_out, const std::string& directory, -                                 const std::string& virtual_name) -> bool { +void GameListWorker::FillControlMap(const std::string& dir_path) { +    const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, +                                             const std::string& virtual_name) -> bool {          std::string physical_name = directory + DIR_SEP + virtual_name;          if (stop_processing) @@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign      };      FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); +} -    const auto callback = [this, recursion, -                           &nca_control_map](u64* num_entries_out, const std::string& directory, -                                             const std::string& virtual_name) -> bool { +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { +    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, +                                            const std::string& virtual_name) -> bool {          std::string physical_name = directory + DIR_SEP + virtual_name;          if (stop_processing) @@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign                  // Use from metadata pool.                  if (nca_control_map.find(program_id) != nca_control_map.end()) {                      const auto nca = nca_control_map[program_id]; -                    const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); - -                    const auto nacp_file = control_dir->GetFile("control.nacp"); -                    FileSys::NACP nacp(nacp_file); -                    name = nacp.GetApplicationName(); - -                    FileSys::VirtualFile icon_file = nullptr; -                    for (const auto& language : FileSys::LANGUAGE_NAMES) { -                        icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat"); -                        if (icon_file != nullptr) { -                            icon = icon_file->ReadAllBytes(); -                            break; -                        } -                    } +                    GetMetadataFromControlNCA(nca, icon, name);                  }              } @@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign  void GameListWorker::run() {      stop_processing = false;      watch_list.append(dir_path); +    FillControlMap(dir_path.toStdString()); +    AddInstalledTitlesToGameList();      AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); +    nca_control_map.clear();      emit Finished(watch_list);  } diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 8fe5e8b80..10c2ef075 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -163,10 +163,13 @@ signals:  private:      FileSys::VirtualFilesystem vfs; +    std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;      QStringList watch_list;      QString dir_path;      bool deep_scan;      std::atomic_bool stop_processing; +    void AddInstalledTitlesToGameList(); +    void FillControlMap(const std::string& dir_path);      void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);  }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 94fb8ae6a..f7eee7769 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -6,7 +6,10 @@  #include <clocale>  #include <memory>  #include <thread> + +#include <fmt/ostream.h>  #include <glad/glad.h> +  #define QT_NO_OPENGL  #include <QDesktopWidget>  #include <QFileDialog> @@ -24,6 +27,7 @@  #include "common/string_util.h"  #include "core/core.h"  #include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h"  #include "core/file_sys/vfs_real.h"  #include "core/gdbstub/gdbstub.h"  #include "core/loader/loader.h" @@ -114,6 +118,9 @@ GMainWindow::GMainWindow()                         .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));      show(); +    // Necessary to load titles from nand in gamelist. +    Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory( +        FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));      game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);      // Show one-time "callout" messages to the user @@ -309,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() {      // File      connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);      connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); +    connect(ui.action_Install_File_NAND, &QAction::triggered, this, +            &GMainWindow::OnMenuInstallToNAND);      connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,              &GMainWindow::OnMenuSelectGameListRoot);      connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); @@ -454,7 +463,7 @@ bool GMainWindow::LoadROM(const QString& filename) {                          "While attempting to load the ROM requested, an error occured. Please "                          "refer to the yuzu wiki for more information or the yuzu discord for "                          "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", -                        loader_id, error_id, Loader::GetMessageForResultStatus(error_id)))); +                        loader_id, error_id, static_cast<Loader::ResultStatus>(error_id))));              } else {                  QMessageBox::critical(                      this, tr("Error while loading ROM!"), @@ -612,6 +621,143 @@ void GMainWindow::OnMenuLoadFolder() {      }  } +void GMainWindow::OnMenuInstallToNAND() { +    const QString file_filter = +        tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge " +           "Image (*.xci)"); +    QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), +                                                    UISettings::values.roms_path, file_filter); + +    const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { +        if (src == nullptr || dest == nullptr) +            return false; +        if (!dest->Resize(src->GetSize())) +            return false; + +        QProgressDialog progress(fmt::format("Installing file \"{}\"...", src->GetName()).c_str(), +                                 "Cancel", 0, src->GetSize() / 0x1000, this); +        progress.setWindowModality(Qt::WindowModal); + +        std::array<u8, 0x1000> buffer{}; +        for (size_t i = 0; i < src->GetSize(); i += 0x1000) { +            if (progress.wasCanceled()) { +                dest->Resize(0); +                return false; +            } + +            progress.setValue(i / 0x1000); +            const auto read = src->Read(buffer.data(), buffer.size(), i); +            dest->Write(buffer.data(), read, i); +        } + +        return true; +    }; + +    const auto success = [this]() { +        QMessageBox::information(this, tr("Successfully Installed"), +                                 tr("The file was successfully installed.")); +        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +    }; + +    const auto failed = [this]() { +        QMessageBox::warning( +            this, tr("Failed to Install"), +            tr("There was an error while attempting to install the provided file. It " +               "could have an incorrect format or be missing metadata. Please " +               "double-check your file and try again.")); +    }; + +    const auto overwrite = [this]() { +        return QMessageBox::question(this, "Failed to Install", +                                     "The file you are attempting to install already exists " +                                     "in the cache. Would you like to overwrite it?") == +               QMessageBox::Yes; +    }; + +    if (!filename.isEmpty()) { +        if (filename.endsWith("xci", Qt::CaseInsensitive)) { +            const auto xci = std::make_shared<FileSys::XCI>( +                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); +            if (xci->GetStatus() != Loader::ResultStatus::Success) { +                failed(); +                return; +            } +            const auto res = +                Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); +            if (res == FileSys::InstallResult::Success) { +                success(); +            } else { +                if (res == FileSys::InstallResult::ErrorAlreadyExists) { +                    if (overwrite()) { +                        const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( +                            xci, true, qt_raw_copy); +                        if (res2 == FileSys::InstallResult::Success) { +                            success(); +                        } else { +                            failed(); +                        } +                    } +                } else { +                    failed(); +                } +            } +        } else { +            const auto nca = std::make_shared<FileSys::NCA>( +                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); +            if (nca->GetStatus() != Loader::ResultStatus::Success) { +                failed(); +                return; +            } + +            static const QStringList tt_options{"System Application", +                                                "System Archive", +                                                "System Application Update", +                                                "Firmware Package (Type A)", +                                                "Firmware Package (Type B)", +                                                "Game", +                                                "Game Update", +                                                "Game DLC", +                                                "Delta Title"}; +            bool ok; +            const auto item = QInputDialog::getItem( +                this, tr("Select NCA Install Type..."), +                tr("Please select the type of title you would like to install this NCA as:\n(In " +                   "most instances, the default 'Game' is fine.)"), +                tt_options, 5, false, &ok); + +            auto index = tt_options.indexOf(item); +            if (!ok || index == -1) { +                QMessageBox::warning(this, tr("Failed to Install"), +                                     tr("The title type you selected for the NCA is invalid.")); +                return; +            } + +            if (index >= 5) +                index += 0x7B; + +            const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry( +                nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); +            if (res == FileSys::InstallResult::Success) { +                success(); +            } else { +                if (res == FileSys::InstallResult::ErrorAlreadyExists) { +                    if (overwrite()) { +                        const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( +                            nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); +                        if (res2 == FileSys::InstallResult::Success) { +                            success(); +                        } else { +                            failed(); +                        } +                    } +                } else { +                    failed(); +                } +            } +        } +    } +} +  void GMainWindow::OnMenuSelectGameListRoot() {      QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));      if (!dir_path.isEmpty()) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 74487c58c..5f4d2ab9a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -125,6 +125,7 @@ private slots:      void OnGameListOpenSaveFolder(u64 program_id);      void OnMenuLoadFile();      void OnMenuLoadFolder(); +    void OnMenuInstallToNAND();      /// Called whenever a user selects the "File->Select Game List Root" menu item      void OnMenuSelectGameListRoot();      void OnMenuRecentFile(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 22c4cad08..a3bfb2af3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -57,6 +57,8 @@        <string>Recent Files</string>       </property>      </widget> +     <addaction name="action_Install_File_NAND" /> +     <addaction name="separator"/>      <addaction name="action_Load_File"/>      <addaction name="action_Load_Folder"/>      <addaction name="separator"/> @@ -102,6 +104,11 @@     <addaction name="menu_View"/>     <addaction name="menu_Help"/>    </widget> +   <action name="action_Install_File_NAND"> +     <property name="text"> +       <string>Install File to NAND...</string> +     </property> +   </action>    <action name="action_Load_File">     <property name="text">      <string>Load File...</string> diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index e44a98311..9095cf27d 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -7,6 +7,8 @@  #include <string>  #include <thread> +#include <fmt/ostream.h> +  #include "common/common_paths.h"  #include "common/logging/backend.h"  #include "common/logging/filter.h" @@ -194,7 +196,7 @@ int main(int argc, char** argv) {                           "While attempting to load the ROM requested, an error occured. Please "                           "refer to the yuzu wiki for more information or the yuzu discord for "                           "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", -                         loader_id, error_id, Loader::GetMessageForResultStatus(error_id)); +                         loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));          }      } | 
