diff options
Diffstat (limited to 'src')
93 files changed, 2853 insertions, 683 deletions
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..609144def --- /dev/null +++ b/src/common/hex_util.cpp @@ -0,0 +1,31 @@ +// 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" + +namespace Common { + +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); +} + +} // namespace Common diff --git a/src/common/hex_util.h b/src/common/hex_util.h new file mode 100644 index 000000000..5fb79bb72 --- /dev/null +++ b/src/common/hex_util.h @@ -0,0 +1,41 @@ +// 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" + +namespace Common { + +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); + +} // namespace Common 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..31a7bf6fd 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 @@ -114,6 +122,8 @@ add_library(core STATIC hle/service/acc/acc_u0.h hle/service/acc/acc_u1.cpp hle/service/acc/acc_u1.h + hle/service/acc/profile_manager.cpp + hle/service/acc/profile_manager.h hle/service/am/am.cpp hle/service/am/am.h hle/service/am/applet_ae.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 20e5200a8..2c817d7d1 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -134,6 +134,9 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; + // Unpredictable instructions + config.define_unpredictable_behaviour = true; + return std::make_unique<Dynarmic::A64::Jit>(config); } 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.h b/src/core/core.h index d98b15a71..790e23cae 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -40,6 +40,12 @@ namespace Core { class System { public: + System(const System&) = delete; + System& operator=(const System&) = delete; + + System(System&&) = delete; + System& operator=(System&&) = delete; + ~System(); /** 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..db8b22c85 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(); @@ -83,20 +52,20 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); if (is_title_keys) { - auto rights_id_raw = HexStringToArray<16>(out[0]); + auto rights_id_raw = Common::HexStringToArray<16>(out[0]); u128 rights_id{}; std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); - Key128 key = HexStringToArray<16>(out[1]); + Key128 key = Common::HexStringToArray<16>(out[1]); SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } else { std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); if (s128_file_id.find(out[0]) != s128_file_id.end()) { const auto index = s128_file_id.at(out[0]); - Key128 key = HexStringToArray<16>(out[1]); + Key128 key = Common::HexStringToArray<16>(out[1]); SetKey(index.type, key, index.field1, index.field2); } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { const auto index = s256_file_id.at(out[0]); - Key256 key = HexStringToArray<32>(out[1]); + Key256 key = Common::HexStringToArray<32>(out[1]); SetKey(index.type, key, index.field1, index.field2); } } 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..d25eeee34 --- /dev/null +++ b/src/core/file_sys/registered_cache.cpp @@ -0,0 +1,478 @@ +// 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", Common::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], + Common::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(Common::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(Common::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( + Common::HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20))); + } + } + + for (const auto& d2_file : dir->GetFiles()) { + if (FollowsNcaIdFormat(d2_file->GetName())) + ids.push_back(Common::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", Common::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 = Common::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/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index f3c5b1b9c..979f2f892 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -3,7 +3,10 @@ // Refer to the license.txt file included. #include <array> +#include "common/common_types.h" #include "common/logging/log.h" +#include "common/swap.h" +#include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/acc/acc.h" #include "core/hle/service/acc/acc_aa.h" @@ -13,7 +16,6 @@ #include "core/settings.h" namespace Service::Account { - // TODO: RE this structure struct UserData { INSERT_PADDING_WORDS(1); @@ -25,19 +27,13 @@ struct UserData { }; static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); -struct ProfileBase { - u128 user_id; - u64 timestamp; - std::array<u8, 0x20> username; -}; -static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); - // TODO(ogniK): Generate a real user id based on username, md5(username) maybe? -static constexpr u128 DEFAULT_USER_ID{1ull, 0ull}; +static UUID DEFAULT_USER_ID{1ull, 0ull}; class IProfile final : public ServiceFramework<IProfile> { public: - explicit IProfile(u128 user_id) : ServiceFramework("IProfile"), user_id(user_id) { + explicit IProfile(UUID user_id, ProfileManager& profile_manager) + : ServiceFramework("IProfile"), user_id(user_id), profile_manager(profile_manager) { static const FunctionInfo functions[] = { {0, &IProfile::Get, "Get"}, {1, &IProfile::GetBase, "GetBase"}, @@ -49,38 +45,34 @@ public: private: void Get(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + std::array<u8, MAX_DATA> data{}; + if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}", + user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - - // TODO(Subv): Retrieve this information from somewhere. + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + if (profile_manager.GetProfileBase(user_id, profile_base)) { + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void LoadImage(Kernel::HLERequestContext& ctx) { @@ -104,7 +96,8 @@ private: rb.Push<u32>(jpeg_size); } - u128 user_id; ///< The user id this profile refers to. + const ProfileManager& profile_manager; + UUID user_id; ///< The user id this profile refers to. }; class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { @@ -141,44 +134,57 @@ private: }; void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); + rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); } void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + UUID user_id = rp.PopRaw<UUID>(); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(true); // TODO: Check when this is supposed to return true and when not + rb.Push(profile_manager->UserExists(user_id)); } void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetAllUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetOpenUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } +void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_ACC, "called"); + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser()); +} + void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u128 user_id = rp.PopRaw<u128>(); + UUID user_id = rp.PopRaw<UUID>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfile>(user_id); - LOG_DEBUG(Service_ACC, "called user_id=0x{:016X}{:016X}", user_id[1], user_id[0]); + rb.PushIpcInterface<IProfile>(user_id, *profile_manager); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); +} + +void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(profile_manager->CanSystemRegisterUser()); } void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { @@ -194,22 +200,18 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo LOG_DEBUG(Service_ACC, "called"); } -void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(DEFAULT_USER_ID); -} - -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name) + : ServiceFramework(name), module(std::move(module)), + profile_manager(std::move(profile_manager)) {} void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); - std::make_shared<ACC_AA>(module)->InstallAsService(service_manager); - std::make_shared<ACC_SU>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U0>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U1>(module)->InstallAsService(service_manager); + auto profile_manager = std::make_shared<ProfileManager>(); + std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 88cabaa01..d7c6d2415 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -4,6 +4,7 @@ #pragma once +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/service.h" namespace Service::Account { @@ -12,7 +13,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name); void GetUserCount(Kernel::HLERequestContext& ctx); void GetUserExistence(Kernel::HLERequestContext& ctx); @@ -22,9 +24,11 @@ public: void GetProfile(Kernel::HLERequestContext& ctx); void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); + void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; + std::shared_ptr<ProfileManager> profile_manager; }; }; diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index 280b3e464..9bd595a37 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") { +ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") { static const FunctionInfo functions[] = { {0, nullptr, "EnsureCacheAsync"}, {1, nullptr, "LoadCache"}, diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 796f7ef85..2e08c781a 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_AA final : public Module::Interface { public: - explicit ACC_AA(std::shared_ptr<Module> module); + explicit ACC_AA(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 8b2a71f37..0218ee859 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") { +ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") { static const FunctionInfo functions[] = { {0, &ACC_SU::GetUserCount, "GetUserCount"}, {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_SU::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index 3894a6991..79a47d88d 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -11,7 +11,8 @@ namespace Account { class ACC_SU final : public Module::Interface { public: - explicit ACC_SU(std::shared_ptr<Module> module); + explicit ACC_SU(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Account diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index d84c8b2e1..84a4d05b8 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") { static const FunctionInfo functions[] = { {0, &ACC_U0::GetUserCount, "GetUserCount"}, {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U0::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index 6ded596b3..e8a114f99 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U0 final : public Module::Interface { public: - explicit ACC_U0(std::shared_ptr<Module> module); + explicit ACC_U0(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 0ceaf06b5..495693949 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") { +ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") { static const FunctionInfo functions[] = { {0, &ACC_U1::GetUserCount, "GetUserCount"}, {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U1::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index 5e3e7659b..a77520e6f 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U1 final : public Module::Interface { public: - explicit ACC_U1(std::shared_ptr<Module> module); + explicit ACC_U1(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp new file mode 100644 index 000000000..62c2121fa --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -0,0 +1,226 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <boost/optional.hpp> +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" + +namespace Service::Account { +// TODO(ogniK): Get actual error codes +constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1); +constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2); +constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); + +ProfileManager::ProfileManager() { + // TODO(ogniK): Create the default user we have for now until loading/saving users is added + auto user_uuid = UUID{1, 0}; + CreateNewUser(user_uuid, Settings::values.username); + OpenUser(user_uuid); +} + +/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the +/// internal management of the users profiles +boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) { + if (user_count >= MAX_USERS) { + return boost::none; + } + profiles[user_count] = std::move(user); + return user_count++; +} + +/// Deletes a specific profile based on it's profile index +bool ProfileManager::RemoveProfileAtIndex(size_t index) { + if (index >= MAX_USERS || index >= user_count) { + return false; + } + if (index < user_count - 1) { + std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end()); + } + profiles.back() = {}; + user_count--; + return true; +} + +/// Helper function to register a user to the system +ResultCode ProfileManager::AddUser(ProfileInfo user) { + if (AddToProfiles(user) == boost::none) { + return ERROR_TOO_MANY_USERS; + } + return RESULT_SUCCESS; +} + +/// Create a new user on the system. If the uuid of the user already exists, the user is not +/// created. +ResultCode ProfileManager::CreateNewUser(UUID uuid, std::array<u8, 0x20>& username) { + if (user_count == MAX_USERS) { + return ERROR_TOO_MANY_USERS; + } + if (!uuid) { + return ERROR_ARGUMENT_IS_NULL; + } + if (username[0] == 0x0) { + return ERROR_ARGUMENT_IS_NULL; + } + if (std::any_of(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) { + return ERROR_USER_ALREADY_EXISTS; + } + ProfileInfo profile; + profile.user_uuid = std::move(uuid); + profile.username = username; + profile.data = {}; + profile.creation_time = 0x0; + profile.is_open = false; + return AddUser(profile); +} + +/// Creates a new user on the system. This function allows a much simpler method of registration +/// specifically by allowing an std::string for the username. This is required specifically since +/// we're loading a string straight from the config +ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) { + std::array<u8, 0x20> username_output; + if (username.size() > username_output.size()) { + std::copy_n(username.begin(), username_output.size(), username_output.begin()); + } else { + std::copy(username.begin(), username.end(), username_output.begin()); + } + return CreateNewUser(uuid, username_output); +} + +/// Returns a users profile index based on their user id. +boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { + if (!uuid) { + return boost::none; + } + auto iter = std::find_if(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; }); + if (iter == profiles.end()) { + return boost::none; + } + return static_cast<size_t>(std::distance(profiles.begin(), iter)); +} + +/// Returns a users profile index based on their profile +boost::optional<size_t> ProfileManager::GetUserIndex(ProfileInfo user) const { + return GetUserIndex(user.user_uuid); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const { + if (index == boost::none || index >= MAX_USERS) { + return false; + } + const auto& prof_info = profiles[index.get()]; + profile.user_uuid = prof_info.user_uuid; + profile.username = prof_info.username; + profile.timestamp = prof_info.creation_time; + return true; +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const { + auto idx = GetUserIndex(uuid); + return GetProfileBase(idx, profile); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(ProfileInfo user, ProfileBase& profile) const { + return GetProfileBase(user.user_uuid, profile); +} + +/// Returns the current user count on the system. We keep a variable which tracks the count so we +/// don't have to loop the internal profile array every call. +size_t ProfileManager::GetUserCount() const { + return user_count; +} + +/// Lists the current "opened" users on the system. Users are typically not open until they sign +/// into something or pick a profile. As of right now users should all be open until qlaunch is +/// booting +size_t ProfileManager::GetOpenUserCount() const { + return std::count_if(profiles.begin(), profiles.end(), + [](const ProfileInfo& p) { return p.is_open; }); +} + +/// Checks if a user id exists in our profile manager +bool ProfileManager::UserExists(UUID uuid) const { + return (GetUserIndex(uuid) != boost::none); +} + +/// Opens a specific user +void ProfileManager::OpenUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = true; + last_opened_user = uuid; +} + +/// Closes a specific user +void ProfileManager::CloseUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = false; +} + +/// Gets all valid user ids on the system +std::array<UUID, MAX_USERS> ProfileManager::GetAllUsers() const { + std::array<UUID, MAX_USERS> output; + std::transform(profiles.begin(), profiles.end(), output.begin(), + [](const ProfileInfo& p) { return p.user_uuid; }); + return output; +} + +/// Get all the open users on the system and zero out the rest of the data. This is specifically +/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out +std::array<UUID, MAX_USERS> ProfileManager::GetOpenUsers() const { + std::array<UUID, MAX_USERS> output; + std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) { + if (p.is_open) + return p.user_uuid; + return UUID{}; + }); + std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; }); + return output; +} + +/// Returns the last user which was opened +UUID ProfileManager::GetLastOpenedUser() const { + return last_opened_user; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + if (GetProfileBase(index, profile)) { + std::memcpy(data.data(), profiles[index.get()].data.data(), MAX_DATA); + return true; + } + return false; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + auto idx = GetUserIndex(uuid); + return GetProfileBaseAndData(idx, profile, data); +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(ProfileInfo user, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + return GetProfileBaseAndData(user.user_uuid, profile, data); +} + +/// Returns if the system is allowing user registrations or not +bool ProfileManager::CanSystemRegisterUser() const { + return false; // TODO(ogniK): Games shouldn't have + // access to user registration, when we + // emulate qlaunch. Update this to dynamically change. +} + +}; // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h new file mode 100644 index 000000000..314bccbf9 --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.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 <random> +#include "boost/optional.hpp" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/result.h" + +namespace Service::Account { +constexpr size_t MAX_USERS = 8; +constexpr size_t MAX_DATA = 128; +static const u128 INVALID_UUID = {0, 0}; + +struct UUID { + // UUIDs which are 0 are considered invalid! + u128 uuid = INVALID_UUID; + UUID() = default; + explicit UUID(const u128& id) : uuid{id} {} + explicit UUID(const u64 lo, const u64 hi) { + uuid[0] = lo; + uuid[1] = hi; + }; + explicit operator bool() const { + return uuid[0] != INVALID_UUID[0] || uuid[1] != INVALID_UUID[1]; + } + + bool operator==(const UUID& rhs) const { + return std::tie(uuid[0], uuid[1]) == std::tie(rhs.uuid[0], rhs.uuid[1]); + } + + bool operator!=(const UUID& rhs) const { + return !operator==(rhs); + } + + // TODO(ogniK): Properly generate uuids based on RFC-4122 + const UUID& Generate() { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<uint64_t> distribution(1, + std::numeric_limits<uint64_t>::max()); + uuid[0] = distribution(gen); + uuid[1] = distribution(gen); + return *this; + } + + // Set the UUID to {0,0} to be considered an invalid user + void Invalidate() { + uuid = INVALID_UUID; + } + std::string Format() const { + return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); + } +}; +static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); + +/// This holds general information about a users profile. This is where we store all the information +/// based on a specific user +struct ProfileInfo { + UUID user_uuid; + std::array<u8, 0x20> username; + u64 creation_time; + std::array<u8, MAX_DATA> data; // TODO(ognik): Work out what this is + bool is_open; +}; + +struct ProfileBase { + UUID user_uuid; + u64_le timestamp; + std::array<u8, 0x20> username; + + // Zero out all the fields to make the profile slot considered "Empty" + void Invalidate() { + user_uuid.Invalidate(); + timestamp = 0; + username.fill(0); + } +}; +static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size"); + +/// The profile manager is used for handling multiple user profiles at once. It keeps track of open +/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo" +/// objects +class ProfileManager { +public: + ProfileManager(); // TODO(ogniK): Load from system save + ResultCode AddUser(ProfileInfo user); + ResultCode CreateNewUser(UUID uuid, std::array<u8, 0x20>& username); + ResultCode CreateNewUser(UUID uuid, const std::string& username); + boost::optional<size_t> GetUserIndex(const UUID& uuid) const; + boost::optional<size_t> GetUserIndex(ProfileInfo user) const; + bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const; + bool GetProfileBase(UUID uuid, ProfileBase& profile) const; + bool GetProfileBase(ProfileInfo user, ProfileBase& profile) const; + bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + bool GetProfileBaseAndData(ProfileInfo user, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + size_t GetUserCount() const; + size_t GetOpenUserCount() const; + bool UserExists(UUID uuid) const; + void OpenUser(UUID uuid); + void CloseUser(UUID uuid); + std::array<UUID, MAX_USERS> GetOpenUsers() const; + std::array<UUID, MAX_USERS> GetAllUsers() const; + UUID GetLastOpenedUser() const; + + bool CanSystemRegisterUser() const; + +private: + std::array<ProfileInfo, MAX_USERS> profiles{}; + size_t user_count = 0; + boost::optional<size_t> AddToProfiles(const ProfileInfo& profile); + bool RemoveProfileAtIndex(size_t index); + UUID last_opened_user{0, 0}; +}; + +}; // namespace Service::Account diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 762763463..c524e7a48 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -145,8 +145,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {51, nullptr, "ApproveToDisplay"}, {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, {61, nullptr, "SetMediaPlaybackState"}, - {62, nullptr, "SetIdleTimeDetectionExtension"}, - {63, nullptr, "GetIdleTimeDetectionExtension"}, + {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, + {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, {64, nullptr, "SetInputDetectionSourceSet"}, {65, nullptr, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, @@ -281,6 +281,23 @@ void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ISelfController::SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + idle_time_detection_extension = rp.Pop<u32>(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(idle_time_detection_extension); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -306,7 +323,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" {52, nullptr, "SwitchLcdBacklight"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, {60, nullptr, "GetDefaultDisplayResolution"}, - {61, nullptr, "GetDefaultDisplayResolutionChangeEvent"}, + {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, + "GetDefaultDisplayResolutionChangeEvent"}, {62, nullptr, "GetHdcpAuthenticationState"}, {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"}, }; @@ -341,6 +359,16 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) { + event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 862f338ac..b763aff6f 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -87,9 +87,12 @@ private: void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); void SetScreenShotPermission(Kernel::HLERequestContext& ctx); void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); + void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::SharedPtr<Kernel::Event> launchable_event; + u32 idle_time_detection_extension = 0; }; class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { @@ -110,6 +113,7 @@ private: void GetEventHandle(Kernel::HLERequestContext& ctx); void ReceiveMessage(Kernel::HLERequestContext& ctx); void GetCurrentFocusState(Kernel::HLERequestContext& ctx); + void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx); void GetOperationMode(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); 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/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index fcf1f3da3..6cc3b1992 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -14,7 +14,8 @@ public: IParentalControlService() : ServiceFramework("IParentalControlService") { static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, - {1001, nullptr, "CheckFreeCommunicationPermission"}, + {1001, &IParentalControlService::CheckFreeCommunicationPermission, + "CheckFreeCommunicationPermission"}, {1002, nullptr, "ConfirmLaunchApplicationPermission"}, {1003, nullptr, "ConfirmResumeApplicationPermission"}, {1004, nullptr, "ConfirmSnsPostPermission"}, @@ -116,6 +117,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_SUCCESS); } + + void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { 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/nca.cpp b/src/core/loader/nca.cpp index 8498cc94b..9d50c7d42 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -3,28 +3,22 @@ // Refer to the license.txt file included. #include <utility> -#include <vector> #include "common/file_util.h" #include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/core.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/program_metadata.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nca.h" -#include "core/loader/nso.h" -#include "core/memory.h" namespace Loader { AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_) : AppLoader(std::move(file_)), nca(std::make_unique<FileSys::NCA>(file)) {} +AppLoader_NCA::~AppLoader_NCA() = default; + FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { FileSys::NCA nca(file); @@ -83,6 +77,4 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { return ResultStatus::Success; } -AppLoader_NCA::~AppLoader_NCA() = default; - } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 7f7d8ea0b..326f84857 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -4,20 +4,24 @@ #pragma once -#include <string> #include "common/common_types.h" -#include "core/file_sys/content_archive.h" -#include "core/file_sys/program_metadata.h" +#include "core/file_sys/vfs.h" #include "core/hle/kernel/object.h" #include "core/loader/loader.h" -#include "deconstructed_rom_directory.h" + +namespace FileSys { +class NCA; +} namespace Loader { +class AppLoader_DeconstructedRomDirectory; + /// Loads an NCA file class AppLoader_NCA final : public AppLoader { public: explicit AppLoader_NCA(FileSys::VirtualFile file); + ~AppLoader_NCA() override; /** * Returns the type of the file @@ -35,12 +39,7 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; - ~AppLoader_NCA(); - private: - FileSys::ProgramMetadata metadata; - - FileSys::NCAHeader header; std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; }; 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/core/loader/xci.cpp b/src/core/loader/xci.cpp index 5d67fb186..4c4979545 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -4,22 +4,14 @@ #include <vector> -#include "common/file_util.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/core.h" +#include "common/common_types.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" -#include "core/file_sys/program_metadata.h" #include "core/file_sys/romfs.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/nso.h" +#include "core/loader/nca.h" #include "core/loader/xci.h" -#include "core/memory.h" namespace Loader { diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 973833050..cc4287e17 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -6,12 +6,18 @@ #include <memory> #include "common/common_types.h" -#include "core/file_sys/card_image.h" +#include "core/file_sys/vfs.h" #include "core/loader/loader.h" -#include "core/loader/nca.h" + +namespace FileSys { +class NACP; +class XCI; +} // namespace FileSys namespace Loader { +class AppLoader_NCA; + /// Loads an XCI file class AppLoader_XCI final : public AppLoader { public: @@ -37,8 +43,6 @@ public: ResultStatus ReadTitle(std::string& title) override; private: - FileSys::ProgramMetadata metadata; - std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index a46ed4bd7..68f91cc75 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -222,6 +222,18 @@ void Maxwell3D::DrawArrays() { debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); } + // Both instance configuration registers can not be set at the same time. + ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont, + "Illegal combination of instancing parameters"); + + if (regs.draw.instance_next) { + // Increment the current instance *before* drawing. + state.current_instance += 1; + } else if (!regs.draw.instance_cont) { + // Reset the current instance to 0. + state.current_instance = 0; + } + const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; rasterizer.AccelerateDrawBatch(is_indexed); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 1b30ce018..3c869d3a1 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -638,6 +638,8 @@ public: union { u32 vertex_begin_gl; BitField<0, 16, PrimitiveTopology> topology; + BitField<26, 1, u32> instance_next; + BitField<27, 1, u32> instance_cont; }; } draw; @@ -677,7 +679,19 @@ public: INSERT_PADDING_WORDS(0x7); - INSERT_PADDING_WORDS(0x46); + INSERT_PADDING_WORDS(0x20); + + struct { + u32 is_instanced[NumVertexArrays]; + + /// Returns whether the vertex array specified by index is supposed to be + /// accessed per instance or not. + bool IsInstancingEnabled(u32 index) const { + return is_instanced[index]; + } + } instanced_arrays; + + INSERT_PADDING_WORDS(0x6); Cull cull; @@ -830,6 +844,7 @@ public: }; std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages; + u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering. }; State state{}; @@ -925,6 +940,7 @@ ASSERT_REG_POSITION(point_coord_replace, 0x581); ASSERT_REG_POSITION(code_address, 0x582); ASSERT_REG_POSITION(draw, 0x585); ASSERT_REG_POSITION(index_array, 0x5F2); +ASSERT_REG_POSITION(instanced_arrays, 0x620); ASSERT_REG_POSITION(cull, 0x646); ASSERT_REG_POSITION(clear_buffers, 0x674); ASSERT_REG_POSITION(query, 0x6C0); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 2526ebf28..3ba6fe614 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -12,6 +12,7 @@ #include <boost/optional.hpp> +#include "common/assert.h" #include "common/bit_field.h" #include "common/common_types.h" @@ -79,6 +80,9 @@ union Attribute { // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval // shader. TessCoordInstanceIDVertexID = 47, + // This attribute contains a tuple of (Unk, Unk, Unk, gl_FrontFacing) when inside a fragment + // shader. It is unknown what the other values contain. + FrontFacing = 63, }; union { @@ -141,6 +145,7 @@ enum class PredCondition : u64 { NotEqual = 5, GreaterEqual = 6, LessThanWithNan = 9, + GreaterThanWithNan = 12, NotEqualWithNan = 13, // TODO(Subv): Other condition types }; @@ -213,6 +218,18 @@ enum class FlowCondition : u64 { Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for? }; +enum class PredicateResultMode : u64 { + None = 0x0, + NotZero = 0x3, +}; + +enum class TextureType : u64 { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCube = 3, +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -253,7 +270,7 @@ union Instruction { BitField<39, 1, u64> invert_a; BitField<40, 1, u64> invert_b; BitField<41, 2, LogicOperation> operation; - BitField<44, 2, u64> unk44; + BitField<44, 2, PredicateResultMode> pred_result_mode; BitField<48, 3, Pred> pred48; } lop; @@ -283,6 +300,10 @@ union Instruction { } alu; union { + BitField<48, 1, u64> negate_b; + } fmul; + + union { BitField<48, 1, u64> is_signed; } shift; @@ -420,6 +441,8 @@ union Instruction { } conversion; union { + BitField<28, 1, u64> array; + BitField<29, 2, TextureType> texture_type; BitField<31, 4, u64> component_mask; bool IsComponentEnabled(size_t component) const { @@ -428,29 +451,88 @@ union Instruction { } tex; union { - BitField<50, 3, u64> component_mask_selector; + BitField<28, 1, u64> array; + BitField<29, 2, TextureType> texture_type; + BitField<56, 2, u64> component; + } tld4; + + union { + BitField<52, 2, u64> component; + } tld4s; + + union { BitField<0, 8, Register> gpr0; BitField<28, 8, Register> gpr28; + BitField<50, 3, u64> component_mask_selector; + BitField<53, 4, u64> texture_info; + + TextureType GetTextureType() const { + // The TEXS instruction has a weird encoding for the texture type. + if (texture_info == 0) + return TextureType::Texture1D; + if (texture_info >= 1 && texture_info <= 9) + return TextureType::Texture2D; + if (texture_info >= 10 && texture_info <= 11) + return TextureType::Texture3D; + if (texture_info >= 12 && texture_info <= 13) + return TextureType::TextureCube; + + UNIMPLEMENTED(); + } + + bool IsArrayTexture() const { + // TEXS only supports Texture2D arrays. + return texture_info >= 7 && texture_info <= 9; + } bool HasTwoDestinations() const { return gpr28.Value() != Register::ZeroIndex; } bool IsComponentEnabled(size_t component) const { - static constexpr std::array<std::array<u32, 8>, 4> mask_lut{ - {{}, - {0x1, 0x2, 0x4, 0x8, 0x3}, - {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, - {0x7, 0xb, 0xd, 0xe, 0xf}}}; + static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{ + {}, + {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, + {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, + {0x7, 0xb, 0xd, 0xe, 0xf}, + }}; size_t index{gpr0.Value() != Register::ZeroIndex ? 1U : 0U}; index |= gpr28.Value() != Register::ZeroIndex ? 2 : 0; - return ((1ull << component) & mask_lut[index][component_mask_selector]) != 0; + u32 mask = mask_lut[index][component_mask_selector]; + // A mask of 0 means this instruction uses an unimplemented mask. + ASSERT(mask != 0); + return ((1ull << component) & mask) != 0; } } texs; union { + BitField<53, 4, u64> texture_info; + + TextureType GetTextureType() const { + // The TLDS instruction has a weird encoding for the texture type. + if (texture_info >= 0 && texture_info <= 1) { + return TextureType::Texture1D; + } + if (texture_info == 2 || texture_info == 8 || texture_info == 12 || + texture_info >= 4 && texture_info <= 6) { + return TextureType::Texture2D; + } + if (texture_info == 7) { + return TextureType::Texture3D; + } + + UNIMPLEMENTED(); + } + + bool IsArrayTexture() const { + // TEXS only supports Texture2D arrays. + return texture_info == 8; + } + } tlds; + + union { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; @@ -512,10 +594,14 @@ public: LD_A, LD_C, ST_A, + LDG, // Load from global memory + STG, // Store in global memory TEX, - TEXQ, // Texture Query - TEXS, // Texture Fetch with scalar/non-vec4 source/destinations - TLDS, // Texture Load with scalar/non-vec4 source/destinations + TEXQ, // Texture Query + TEXS, // Texture Fetch with scalar/non-vec4 source/destinations + TLDS, // Texture Load with scalar/non-vec4 source/destinations + TLD4, // Texture Load 4 + TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations EXIT, IPA, FFMA_IMM, // Fused Multiply and Add @@ -723,10 +809,14 @@ private: INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"), INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"), + INST("1110111011010---", Id::LDG, Type::Memory, "LDG"), + INST("1110111011011---", Id::STG, Type::Memory, "STG"), INST("110000----111---", Id::TEX, Type::Memory, "TEX"), INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"), INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"), INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"), + INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"), + INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 5a593c1f7..9758adcfd 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -55,6 +55,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { case RenderTargetFormat::RGBA8_UNORM: case RenderTargetFormat::RGBA8_SNORM: case RenderTargetFormat::RGBA8_SRGB: + case RenderTargetFormat::RGBA8_UINT: case RenderTargetFormat::RGB10_A2_UNORM: case RenderTargetFormat::BGRA8_UNORM: case RenderTargetFormat::RG16_UNORM: diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 97dcccb92..2697e1c27 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -30,6 +30,7 @@ enum class RenderTargetFormat : u32 { RGBA8_UNORM = 0xD5, RGBA8_SRGB = 0xD6, RGBA8_SNORM = 0xD7, + RGBA8_UINT = 0xD9, RG16_UNORM = 0xDA, RG16_SNORM = 0xDB, RG16_SINT = 0xDC, diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 52a649e2f..fe1f55e85 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -98,7 +98,8 @@ RasterizerOpenGL::~RasterizerOpenGL() {} std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset) { MICROPROFILE_SCOPE(OpenGL_VAO); - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& regs = gpu.regs; state.draw.vertex_array = hw_vao.handle; state.draw.vertex_buffer = stream_buffer.GetHandle(); @@ -110,9 +111,13 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, if (!vertex_array.IsEnabled()) continue; - const Tegra::GPUVAddr start = vertex_array.StartAddress(); + Tegra::GPUVAddr start = vertex_array.StartAddress(); const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { + start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor); + } + ASSERT(end > start); u64 size = end - start + 1; @@ -124,7 +129,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset, vertex_array.stride); - ASSERT_MSG(vertex_array.divisor == 0, "Vertex buffer divisor unimplemented"); + if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { + // Tell OpenGL that this is an instanced vertex buffer to prevent accessing different + // indexes on each vertex. We do the instance indexing manually by incrementing the + // start address of the vertex buffer. + glVertexBindingDivisor(index, 1); + } else { + // Disable the vertex buffer instancing. + glVertexBindingDivisor(index, 0); + } } // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. @@ -648,11 +661,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..fb7476fb8 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -94,11 +94,12 @@ 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_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI + {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 +120,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 @@ -141,14 +143,16 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI + // Depth formats + {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F + {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, + false}, // Z16 + // DepthStencil formats {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, false}, // Z24S8 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, - false}, // S8Z24 - {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F - {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, - false}, // Z16 + false}, // S8Z24 {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, ComponentType::Float, false}, // Z32FS8 }}; @@ -242,10 +246,11 @@ 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::ABGR8UI>, + 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 +265,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>, @@ -281,10 +287,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU MortonCopy<true, PixelFormat::RG8S>, MortonCopy<true, PixelFormat::RG32UI>, MortonCopy<true, PixelFormat::R32UI>, - MortonCopy<true, PixelFormat::Z24S8>, - MortonCopy<true, PixelFormat::S8Z24>, MortonCopy<true, PixelFormat::Z32F>, MortonCopy<true, PixelFormat::Z16>, + MortonCopy<true, PixelFormat::Z24S8>, + MortonCopy<true, PixelFormat::S8Z24>, MortonCopy<true, PixelFormat::Z32FS8>, // clang-format on }; @@ -295,10 +301,11 @@ 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::ABGR8UI>, + 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 +322,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>, @@ -336,10 +344,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU MortonCopy<false, PixelFormat::RG8S>, MortonCopy<false, PixelFormat::RG32UI>, MortonCopy<false, PixelFormat::R32UI>, - MortonCopy<false, PixelFormat::Z24S8>, - MortonCopy<false, PixelFormat::S8Z24>, MortonCopy<false, PixelFormat::Z32F>, MortonCopy<false, PixelFormat::Z16>, + MortonCopy<false, PixelFormat::Z24S8>, + MortonCopy<false, PixelFormat::S8Z24>, MortonCopy<false, PixelFormat::Z32FS8>, // clang-format on }; @@ -461,7 +469,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 +501,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; @@ -784,8 +793,6 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, // Verify surface is compatible for blitting const auto& params{surface->GetSurfaceParams()}; ASSERT(params.type == new_params.type); - ASSERT(params.pixel_format == new_params.pixel_format); - ASSERT(params.component_type == new_params.component_type); // Create a new surface with the new parameters, and blit the previous surface to it Surface new_surface{std::make_shared<CachedSurface>(new_params)}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 36a41522b..fc8b44219 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -25,54 +25,60 @@ struct SurfaceParams { enum class PixelFormat { ABGR8U = 0, ABGR8S = 1, - B5G6R5 = 2, - A2B10G10R10 = 3, - A1B5G5R5 = 4, - R8 = 5, - R8UI = 6, - RGBA16F = 7, - RGBA16U = 8, - RGBA16UI = 9, - R11FG11FB10F = 10, - RGBA32UI = 11, - DXT1 = 12, - DXT23 = 13, - DXT45 = 14, - DXN1 = 15, // This is also known as BC4 - DXN2UNORM = 16, - 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, + ABGR8UI = 2, + B5G6R5U = 3, + A2B10G10R10U = 4, + A1B5G5R5U = 5, + R8U = 6, + R8UI = 7, + RGBA16F = 8, + RGBA16U = 9, + RGBA16UI = 10, + R11FG11FB10F = 11, + RGBA32UI = 12, + DXT1 = 13, + DXT23 = 14, + DXT45 = 15, + DXN1 = 16, // This is also known as BC4 + DXN2UNORM = 17, + DXN2SNORM = 18, + BC7U = 19, + ASTC_2D_4X4 = 20, + G8R8U = 21, + G8R8S = 22, + BGRA8 = 23, + RGBA32F = 24, + RG32F = 25, + R32F = 26, + R16F = 27, + R16U = 28, + R16S = 29, + R16UI = 30, + R16I = 31, + RG16 = 32, + RG16F = 33, + RG16UI = 34, + RG16I = 35, + RG16S = 36, + RGB32F = 37, + SRGBA8 = 38, + RG8U = 39, + RG8S = 40, + RG32UI = 41, + R32UI = 42, MaxColorFormat, - // DepthStencil formats - Z24S8 = 41, - S8Z24 = 42, + // Depth formats Z32F = 43, Z16 = 44, - Z32FS8 = 45, + + MaxDepthFormat, + + // DepthStencil formats + Z24S8 = 45, + S8Z24 = 46, + Z32FS8 = 47, MaxDepthStencilFormat, @@ -112,10 +118,11 @@ struct SurfaceParams { constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ 1, // ABGR8U 1, // ABGR8S - 1, // B5G6R5 - 1, // A2B10G10R10 - 1, // A1B5G5R5 - 1, // R8 + 1, // ABGR8UI + 1, // B5G6R5U + 1, // A2B10G10R10U + 1, // A1B5G5R5U + 1, // R8U 1, // R8UI 1, // RGBA16F 1, // RGBA16U @@ -130,13 +137,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 @@ -151,10 +159,10 @@ struct SurfaceParams { 1, // RG8S 1, // RG32UI 1, // R32UI - 1, // Z24S8 - 1, // S8Z24 1, // Z32F 1, // Z16 + 1, // Z24S8 + 1, // S8Z24 1, // Z32FS8 }}; @@ -169,10 +177,11 @@ struct SurfaceParams { constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ 32, // ABGR8U 32, // ABGR8S - 16, // B5G6R5 - 32, // A2B10G10R10 - 16, // A1B5G5R5 - 8, // R8 + 32, // ABGR8UI + 16, // B5G6R5U + 32, // A2B10G10R10U + 16, // A1B5G5R5U + 8, // R8U 8, // R8UI 64, // RGBA16F 64, // RGBA16U @@ -187,13 +196,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 @@ -208,10 +218,10 @@ struct SurfaceParams { 16, // RG8S 64, // RG32UI 32, // R32UI - 32, // Z24S8 - 32, // S8Z24 32, // Z32F 16, // Z16 + 32, // Z24S8 + 32, // S8Z24 64, // Z32FS8 }}; @@ -250,10 +260,12 @@ struct SurfaceParams { return PixelFormat::ABGR8U; case Tegra::RenderTargetFormat::RGBA8_SNORM: return PixelFormat::ABGR8S; + case Tegra::RenderTargetFormat::RGBA8_UINT: + return PixelFormat::ABGR8UI; 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 +279,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 +303,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: @@ -320,20 +332,40 @@ struct SurfaceParams { return PixelFormat::ABGR8U; case Tegra::Texture::ComponentType::SNORM: return PixelFormat::ABGR8S; + case Tegra::Texture::ComponentType::UINT: + return PixelFormat::ABGR8UI; } LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", 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 +373,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 +421,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 +456,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: @@ -496,6 +558,7 @@ struct SurfaceParams { case Tegra::RenderTargetFormat::R16_UINT: case Tegra::RenderTargetFormat::RG32_UINT: case Tegra::RenderTargetFormat::R32_UINT: + case Tegra::RenderTargetFormat::RGBA8_UINT: return ComponentType::UInt; case Tegra::RenderTargetFormat::RG16_SINT: case Tegra::RenderTargetFormat::R16_SINT: @@ -536,6 +599,10 @@ struct SurfaceParams { return SurfaceType::ColorTexture; } + if (static_cast<size_t>(pixel_format) < static_cast<size_t>(PixelFormat::MaxDepthFormat)) { + return SurfaceType::Depth; + } + if (static_cast<size_t>(pixel_format) < static_cast<size_t>(PixelFormat::MaxDepthStencilFormat)) { return SurfaceType::DepthStencil; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 6834d7085..ac6ccfec7 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; @@ -438,13 +439,12 @@ public: } declarations.AddNewLine(); - // Append the sampler2D array for the used textures. - size_t num_samplers = GetSamplers().size(); - if (num_samplers > 0) { - declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' + - std::to_string(num_samplers) + "];"); - declarations.AddNewLine(); + const auto& samplers = GetSamplers(); + for (const auto& sampler : samplers) { + declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + + ';'); } + declarations.AddNewLine(); } /// Returns a list of constant buffer declarations @@ -456,13 +456,14 @@ public: } /// Returns a list of samplers used in the shader - std::vector<SamplerEntry> GetSamplers() const { + const std::vector<SamplerEntry>& GetSamplers() const { return used_samplers; } /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if /// necessary. - std::string AccessSampler(const Sampler& sampler) { + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array) { size_t offset = static_cast<size_t>(sampler.index.Value()); // If this sampler has already been used, return the existing mapping. @@ -471,12 +472,13 @@ public: [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array); return itr->GetName(); } // Otherwise create a new mapping for this sampler size_t next_index = used_samplers.size(); - SamplerEntry entry{stage, offset, next_index}; + SamplerEntry entry{stage, offset, next_index, type, is_array}; used_samplers.emplace_back(entry); return entry.GetName(); } @@ -540,7 +542,11 @@ private: // vertex shader, and what's the value of the fourth element when inside a Tess Eval // shader. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); - return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))"; + return "vec4(0, 0, uintBitsToFloat(instance_id.x), uintBitsToFloat(gl_VertexID))"; + case Attribute::Index::FrontFacing: + // TODO(Subv): Find out what the values are for the other elements. + ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); + return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))"; default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -633,8 +639,8 @@ private: } /// Generates code representing a texture sampler. - std::string GetSampler(const Sampler& sampler) { - return regs.AccessSampler(sampler); + std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) { + return regs.AccessSampler(sampler, type, is_array); } /** @@ -702,10 +708,11 @@ private: const std::string& op_a, const std::string& op_b) const { using Tegra::Shader::PredCondition; static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = { - {PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="}, - {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"}, - {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="}, - {PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="}, + {PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="}, + {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"}, + {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="}, + {PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="}, + {PredCondition::GreaterThanWithNan, ">"}, }; const auto& comparison{PredicateComparisonStrings.find(condition)}; @@ -714,7 +721,8 @@ private: std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'}; if (condition == PredCondition::LessThanWithNan || - condition == PredCondition::NotEqualWithNan) { + condition == PredCondition::NotEqualWithNan || + condition == PredCondition::GreaterThanWithNan) { predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')'; } @@ -740,6 +748,30 @@ private: return op->second; } + /** + * Transforms the input string GLSL operand into one that applies the abs() function and negates + * the output if necessary. When both abs and neg are true, the negation will be applied after + * taking the absolute value. + * @param operand The input operand to take the abs() of, negate, or both. + * @param abs Whether to apply the abs() function to the input operand. + * @param neg Whether to negate the input operand. + * @returns String corresponding to the operand after being transformed by the abs() and + * negation operations. + */ + static std::string GetOperandAbsNeg(const std::string& operand, bool abs, bool neg) { + std::string result = operand; + + if (abs) { + result = "abs(" + result + ')'; + } + + if (neg) { + result = "-(" + result + ')'; + } + + return result; + } + /* * Returns whether the instruction at the specified offset is a 'sched' instruction. * Sched instructions always appear before a sequence of 3 instructions. @@ -753,28 +785,51 @@ private: } void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a, - const std::string& op_b) { + const std::string& op_b, + Tegra::Shader::PredicateResultMode predicate_mode, + Tegra::Shader::Pred predicate) { + std::string result{}; switch (logic_op) { case LogicOperation::And: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1); + result = '(' + op_a + " & " + op_b + ')'; break; } case LogicOperation::Or: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1); + result = '(' + op_a + " | " + op_b + ')'; break; } case LogicOperation::Xor: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1); + result = '(' + op_a + " ^ " + op_b + ')'; break; } case LogicOperation::PassB: { - regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1); + result = op_b; break; } default: LOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); UNREACHABLE(); } + + if (dest != Tegra::Shader::Register::ZeroIndex) { + regs.SetRegisterToInteger(dest, true, 0, result, 1, 1); + } + + using Tegra::Shader::PredicateResultMode; + // Write the predicate value depending on the predicate mode. + switch (predicate_mode) { + case PredicateResultMode::None: + // Do nothing. + return; + case PredicateResultMode::NotZero: + // Set the predicate to true if the result is not zero. + SetPredicate(static_cast<u64>(predicate), '(' + result + ") != 0"); + break; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented predicate result mode: {}", + static_cast<u32>(predicate_mode)); + UNREACHABLE(); + } } void WriteTexsInstruction(const Instruction& instr, const std::string& coord, @@ -785,29 +840,56 @@ private: ++shader.scope; shader.AddLine(coord); - // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA - // goes into gpr28+0 and gpr28+1 - size_t texs_offset{}; - - size_t src_elem{}; - for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) { - size_t dest_elem{}; - for (unsigned elem = 0; elem < 2; ++elem) { - if (!instr.texs.IsComponentEnabled(src_elem++)) { - // Skip disabled components - continue; - } - regs.SetRegisterToFloat(dest, elem + texs_offset, texture, 1, 4, false, - dest_elem++); + // TEXS has two destination registers and a swizzle. The first two elements in the swizzle + // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 + + size_t written_components = 0; + for (u32 component = 0; component < 4; ++component) { + if (!instr.texs.IsComponentEnabled(component)) { + continue; } - if (!instr.texs.HasTwoDestinations()) { - // Skip the second destination - break; + if (written_components < 2) { + // Write the first two swizzle components to gpr0 and gpr0+1 + regs.SetRegisterToFloat(instr.gpr0, component, texture, 1, 4, false, + written_components % 2); + } else { + ASSERT(instr.texs.HasTwoDestinations()); + // Write the rest of the swizzle components to gpr28 and gpr28+1 + regs.SetRegisterToFloat(instr.gpr28, component, texture, 1, 4, false, + written_components % 2); } - texs_offset += 2; + ++written_components; } + + --shader.scope; + shader.AddLine('}'); + } + + /* + * Emits code to push the input target address to the SSY address stack, incrementing the stack + * top. + */ + void EmitPushToSSYStack(u32 target) { + shader.AddLine('{'); + ++shader.scope; + shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;"); + shader.AddLine("ssy_stack_top++;"); + --shader.scope; + shader.AddLine('}'); + } + + /* + * Emits code to pop an address from the SSY address stack, setting the jump address to the + * popped address and decrementing the stack top. + */ + void EmitPopFromSSYStack() { + shader.AddLine('{'); + ++shader.scope; + shader.AddLine("ssy_stack_top--;"); + shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];"); + shader.AddLine("break;"); --shader.scope; shader.AddLine('}'); } @@ -856,13 +938,6 @@ private: switch (opcode->GetType()) { case OpCode::Type::Arithmetic: { std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - if (instr.alu.abs_a) { - op_a = "abs(" + op_a + ')'; - } - - if (instr.alu.negate_a) { - op_a = "-(" + op_a + ')'; - } std::string op_b; @@ -877,17 +952,10 @@ private: } } - if (instr.alu.abs_b) { - op_b = "abs(" + op_b + ')'; - } - - if (instr.alu.negate_b) { - op_b = "-(" + op_b + ')'; - } - switch (opcode->GetId()) { case OpCode::Id::MOV_C: case OpCode::Id::MOV_R: { + // MOV does not have neither 'abs' nor 'neg' bits. regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); break; } @@ -895,6 +963,8 @@ private: case OpCode::Id::FMUL_C: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { + // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. + op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.saturate_d); break; @@ -902,11 +972,14 @@ private: case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d); break; } case OpCode::Id::MUFU: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); switch (instr.sub_op) { case SubOp::Cos: regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1, @@ -946,6 +1019,9 @@ private: case OpCode::Id::FMNMX_C: case OpCode::Id::FMNMX_R: case OpCode::Id::FMNMX_IMM: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); + std::string condition = GetPredicateCondition(instr.alu.fmnmx.pred, instr.alu.fmnmx.negate_pred != 0); std::string parameters = op_a + ',' + op_b; @@ -959,7 +1035,7 @@ private: case OpCode::Id::RRO_R: case OpCode::Id::RRO_IMM: { // Currently RRO is only implemented as a register move. - // Usage of `abs_b` and `negate_b` here should also be correct. + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); LOG_WARNING(HW_GPU, "RRO instruction is incomplete"); break; @@ -1096,7 +1172,9 @@ private: if (instr.alu.lop32i.invert_b) op_b = "~(" + op_b + ')'; - WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b); + WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b, + Tegra::Shader::PredicateResultMode::None, + Tegra::Shader::Pred::UnusedIndex); break; } default: { @@ -1162,16 +1240,14 @@ private: case OpCode::Id::LOP_C: case OpCode::Id::LOP_R: case OpCode::Id::LOP_IMM: { - ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented"); - ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented"); - if (instr.alu.lop.invert_a) op_a = "~(" + op_a + ')'; if (instr.alu.lop.invert_b) op_b = "~(" + op_b + ')'; - WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b); + WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b, + instr.alu.lop.pred_result_mode, instr.alu.lop.pred48); break; } case OpCode::Id::IMNMX_C: @@ -1236,8 +1312,6 @@ private: break; } case OpCode::Type::Conversion: { - ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented"); - switch (opcode->GetId()) { case OpCode::Id::I2I_R: { ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); @@ -1249,20 +1323,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 +1366,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 +1396,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 +1466,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 +1483,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: { @@ -1386,10 +1508,29 @@ private: break; } case OpCode::Id::TEX: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented"); + std::string coord{}; + + switch (instr.tex.texture_type) { + case Tegra::Shader::TextureType::Texture2D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + case Tegra::Shader::TextureType::Texture3D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } + default: + UNIMPLEMENTED(); + } + + const std::string sampler = + GetSampler(instr.sampler, instr.tex.texture_type, instr.tex.array); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. shader.AddLine("{"); @@ -1411,24 +1552,115 @@ private: break; } case OpCode::Id::TEXS: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + std::string coord{}; + + switch (instr.texs.GetTextureType()) { + case Tegra::Shader::TextureType::Texture2D: { + if (instr.texs.IsArrayTexture()) { + std::string index = regs.GetRegisterAsInteger(instr.gpr8); + std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; + } else { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + } + break; + } + case Tegra::Shader::TextureType::TextureCube: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } + default: + UNIMPLEMENTED(); + } + const std::string sampler = GetSampler(instr.sampler, instr.texs.GetTextureType(), + instr.texs.IsArrayTexture()); const std::string texture = "texture(" + sampler + ", coords)"; WriteTexsInstruction(instr, coord, texture); break; } case OpCode::Id::TLDS: { - const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); - const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");"; + ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D); + ASSERT(instr.tlds.IsArrayTexture() == false); + std::string coord{}; + + switch (instr.tlds.GetTextureType()) { + case Tegra::Shader::TextureType::Texture2D: { + if (instr.tlds.IsArrayTexture()) { + UNIMPLEMENTED(); + } else { + std::string x = regs.GetRegisterAsInteger(instr.gpr8); + std::string y = regs.GetRegisterAsInteger(instr.gpr20); + coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + } + break; + } + default: + UNIMPLEMENTED(); + } + const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(), + instr.tlds.IsArrayTexture()); const std::string texture = "texelFetch(" + sampler + ", coords, 0)"; WriteTexsInstruction(instr, coord, texture); break; } + case OpCode::Id::TLD4: { + ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); + ASSERT(instr.tld4.array == 0); + std::string coord{}; + + switch (instr.tld4.texture_type) { + case Tegra::Shader::TextureType::Texture2D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + default: + UNIMPLEMENTED(); + } + + const std::string sampler = + GetSampler(instr.sampler, instr.tld4.texture_type, instr.tld4.array); + // Add an extra scope and declare the texture coords inside to prevent + // overwriting them in case they are used as outputs of the texs instruction. + shader.AddLine("{"); + ++shader.scope; + shader.AddLine(coord); + const std::string texture = "textureGather(" + sampler + ", coords, " + + std::to_string(instr.tld4.component) + ')'; + + size_t dest_elem{}; + for (size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); + ++dest_elem; + } + --shader.scope; + shader.AddLine("}"); + break; + } + case OpCode::Id::TLD4S: { + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); + // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. + const std::string sampler = + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); + const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + const std::string texture = "textureGather(" + sampler + ", coords, " + + std::to_string(instr.tld4s.component) + ')'; + WriteTexsInstruction(instr, coord, texture); + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -1792,13 +2024,13 @@ private: ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); u32 target = offset + instr.bra.GetBranchTarget(); - shader.AddLine("ssy_target = " + std::to_string(target) + "u;"); + EmitPushToSSYStack(target); break; } case OpCode::Id::SYNC: { // The SYNC opcode jumps to the address previously set by the SSY opcode ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); - shader.AddLine("{ jmp_to = ssy_target; break; }"); + EmitPopFromSSYStack(); break; } case OpCode::Id::DEPBAR: { @@ -1869,7 +2101,13 @@ private: } else { labels.insert(subroutine.begin); shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); - shader.AddLine("uint ssy_target = 0u;"); + + // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems + // unlikely that shaders will use 20 nested SSYs. + constexpr u32 SSY_STACK_SIZE = 20; + shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];"); + shader.AddLine("uint ssy_stack_top = 0u;"); + shader.AddLine("while (true) {"); ++shader.scope; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 129c777d1..57e0e1726 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -38,6 +38,7 @@ out vec4 position; layout (std140) uniform vs_config { vec4 viewport_flip; + uvec4 instance_id; }; void main() { @@ -90,6 +91,7 @@ out vec4 color; layout (std140) uniform fs_config { vec4 viewport_flip; + uvec4 instance_id; }; void main() { diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 4729ce0fc..db48da645 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -11,6 +11,7 @@ #include <vector> #include "common/common_types.h" #include "common/hash.h" +#include "video_core/engines/shader_bytecode.h" namespace GLShader { @@ -72,8 +73,9 @@ class SamplerEntry { using Maxwell = Tegra::Engines::Maxwell3D::Regs; public: - SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index) - : offset(offset), stage(stage), sampler_index(index) {} + SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index, + Tegra::Shader::TextureType type, bool is_array) + : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {} size_t GetOffset() const { return offset; @@ -88,8 +90,41 @@ public: } std::string GetName() const { - return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' + - std::to_string(sampler_index) + ']'; + return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '_' + + std::to_string(sampler_index); + } + + std::string GetTypeString() const { + using Tegra::Shader::TextureType; + std::string glsl_type; + + switch (type) { + case TextureType::Texture1D: + glsl_type = "sampler1D"; + break; + case TextureType::Texture2D: + glsl_type = "sampler2D"; + break; + case TextureType::Texture3D: + glsl_type = "sampler3D"; + break; + case TextureType::TextureCube: + glsl_type = "samplerCube"; + break; + default: + UNIMPLEMENTED(); + } + if (is_array) + glsl_type += "Array"; + return glsl_type; + } + + Tegra::Shader::TextureType GetType() const { + return type; + } + + bool IsArray() const { + return is_array; } static std::string GetArrayName(Maxwell::ShaderStage stage) { @@ -100,11 +135,14 @@ private: static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = { "tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs", }; + /// Offset in TSC memory from which to read the sampler object, as specified by the sampling /// instruction. size_t offset; - Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. - size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. + size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc) + bool is_array; ///< Whether the texture is being sampled as an array texture or not. }; struct ShaderEntries { diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 415d42fda..f0886caac 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -37,11 +37,16 @@ void SetShaderUniformBlockBindings(GLuint shader) { } // namespace Impl void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage) { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& regs = gpu.regs; + const auto& state = gpu.state; // TODO(bunnei): Support more than one viewport viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f; viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; + + // We only assign the instance to the first component of the vector, the rest is just padding. + instance_id[0] = state.current_instance; } } // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 716933a0b..75fa73605 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -24,14 +24,15 @@ void SetShaderUniformBlockBindings(GLuint shader); } // namespace Impl /// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned -// NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at +// NOTE: Always keep a vec4 at the end. The GL spec is not clear whether the alignment at // the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not. // Not following that rule will cause problems on some AMD drivers. struct MaxwellUniformData { void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage); alignas(16) GLvec4 viewport_flip; + alignas(16) GLuvec4 instance_id; }; -static_assert(sizeof(MaxwellUniformData) == 16, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 83ea0cfc0..5d91a0c2f 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; } @@ -132,6 +147,8 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { // GL_CLAMP_TO_BORDER to get the border color of the texture, and then sample the edge to // manually mix them. However the shader part of this is not yet implemented. return GL_CLAMP_TO_BORDER; + case Tegra::Texture::WrapMode::MirrorOnceClampToEdge: + return GL_MIRROR_CLAMP_TO_EDGE; } LOG_CRITICAL(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); UNREACHABLE(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 95f1aa0fe..bf30eda6d 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -425,7 +425,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum switch (severity) { case GL_DEBUG_SEVERITY_HIGH: - LOG_ERROR(Render_OpenGL, format, str_source, str_type, id, message); + LOG_CRITICAL(Render_OpenGL, format, str_source, str_type, id, message); break; case GL_DEBUG_SEVERITY_MEDIUM: LOG_WARNING(Render_OpenGL, format, str_source, str_type, id, message); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 85cb12594..bc4b93033 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 = 0; + 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) @@ -447,7 +509,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign std::vector<u8> icon; const auto res1 = loader->ReadIcon(icon); - u64 program_id; + u64 program_id = 0; const auto res2 = loader->ReadProgramId(program_id); std::string name = " "; @@ -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..11d2331df 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); @@ -368,6 +377,8 @@ bool GMainWindow::SupportsRequiredGLExtensions() { unsupported_ext.append("ARB_vertex_attrib_binding"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) @@ -454,7 +465,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 +623,148 @@ 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); + + if (filename.isEmpty()) { + return; + } + + 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; + + std::array<u8, 0x1000> buffer{}; + const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size()); + + QProgressDialog progress( + tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())), + tr("Cancel"), 0, progress_maximum, this); + progress.setWindowModality(Qt::WindowModal); + + for (size_t i = 0; i < src->GetSize(); i += buffer.size()) { + if (progress.wasCanceled()) { + dest->Resize(0); + return false; + } + + const int progress_value = static_cast<int>(i / buffer.size()); + progress.setValue(progress_value); + + 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, tr("Failed to Install"), + tr("The file you are attempting to install already exists " + "in the cache. Would you like to overwrite it?")) == + QMessageBox::Yes; + }; + + 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; + } + + const QStringList tt_options{tr("System Application"), + tr("System Archive"), + tr("System Application Update"), + tr("Firmware Package (Type A)"), + tr("Firmware Package (Type B)"), + tr("Game"), + tr("Game Update"), + tr("Game DLC"), + tr("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/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index e2945b6cf..351dd9225 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -89,6 +89,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { unsupported_ext.push_back("ARB_vertex_attrib_binding"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) 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)); } } |