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