summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--CMakeLists.txt10
-rw-r--r--appveyor.yml10
-rw-r--r--src/audio_core/audio_renderer.cpp4
-rw-r--r--src/audio_core/audio_renderer.h1
-rw-r--r--src/audio_core/stream.cpp5
-rw-r--r--src/audio_core/stream.h15
-rw-r--r--src/audio_core/time_stretch.cpp2
-rw-r--r--src/common/common_paths.h2
-rw-r--r--src/common/file_util.cpp2
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/thread.h8
-rw-r--r--src/core/CMakeLists.txt5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp2
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/core_cpu.cpp8
-rw-r--r--src/core/core_cpu.h2
-rw-r--r--src/core/file_sys/bis_factory.cpp12
-rw-r--r--src/core/file_sys/bis_factory.h5
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp366
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h70
-rw-r--r--src/core/file_sys/patch_manager.cpp49
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp9
-rw-r--r--src/core/file_sys/registered_cache.h2
-rw-r--r--src/core/file_sys/romfs.cpp21
-rw-r--r--src/core/file_sys/romfs.h15
-rw-r--r--src/core/file_sys/vfs.cpp45
-rw-r--r--src/core/file_sys/vfs.h17
-rw-r--r--src/core/file_sys/vfs_concat.cpp77
-rw-r--r--src/core/file_sys/vfs_concat.h18
-rw-r--r--src/core/file_sys/vfs_layered.cpp132
-rw-r--r--src/core/file_sys/vfs_layered.h50
-rw-r--r--src/core/file_sys/vfs_real.cpp17
-rw-r--r--src/core/file_sys/vfs_real.h1
-rw-r--r--src/core/file_sys/vfs_static.h79
-rw-r--r--src/core/file_sys/vfs_vector.cpp54
-rw-r--r--src/core/file_sys/vfs_vector.h25
-rw-r--r--src/core/hle/kernel/process.cpp87
-rw-r--r--src/core/hle/kernel/process.h55
-rw-r--r--src/core/hle/kernel/scheduler.cpp14
-rw-r--r--src/core/hle/kernel/scheduler.h4
-rw-r--r--src/core/hle/kernel/svc.cpp46
-rw-r--r--src/core/hle/kernel/thread.cpp58
-rw-r--r--src/core/hle/kernel/thread.h13
-rw-r--r--src/core/hle/service/audio/audren_u.cpp10
-rw-r--r--src/core/hle/service/fatal/fatal.cpp141
-rw-r--r--src/core/hle/service/fatal/fatal.h1
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp13
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/hle/service/hid/irs.cpp158
-rw-r--r--src/core/hle/service/hid/irs.h27
-rw-r--r--src/core/hle/service/nfp/nfp.cpp1
-rw-r--r--src/core/hle/service/nim/nim.cpp1
-rw-r--r--src/core/hle/service/sm/controller.cpp3
-rw-r--r--src/core/hle/service/ssl/ssl.cpp6
-rw-r--r--src/video_core/engines/maxwell_3d.h12
-rw-r--r--src/video_core/engines/maxwell_compute.cpp19
-rw-r--r--src/video_core/engines/maxwell_compute.h36
-rw-r--r--src/video_core/engines/shader_bytecode.h27
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_state.h4
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp2
-rw-r--r--src/video_core/textures/decoders.cpp117
-rw-r--r--src/video_core/utils.h22
-rw-r--r--src/yuzu/game_list.cpp9
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/main.cpp145
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu_cmd/config.cpp2
79 files changed, 1971 insertions, 286 deletions
diff --git a/.travis.yml b/.travis.yml
index 4d363cbc9..b0fbe3c5f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,7 +24,7 @@ matrix:
24 - os: osx 24 - os: osx
25 env: NAME="macos build" 25 env: NAME="macos build"
26 sudo: false 26 sudo: false
27 osx_image: xcode9.3 27 osx_image: xcode10
28 install: "./.travis/macos/deps.sh" 28 install: "./.travis/macos/deps.sh"
29 script: "./.travis/macos/build.sh" 29 script: "./.travis/macos/build.sh"
30 after_success: "./.travis/macos/upload.sh" 30 after_success: "./.travis/macos/upload.sh"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 30642edd7..cd990188e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -123,8 +123,6 @@ else()
123 # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. 123 # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
124 add_definitions(/DWIN32_LEAN_AND_MEAN) 124 add_definitions(/DWIN32_LEAN_AND_MEAN)
125 125
126 # set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
127 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
128 set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) 126 set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
129 127
130 # Tweak optimization settings 128 # Tweak optimization settings
@@ -440,8 +438,12 @@ enable_testing()
440add_subdirectory(externals) 438add_subdirectory(externals)
441add_subdirectory(src) 439add_subdirectory(src)
442 440
443# Set yuzu project as default StartUp Project in Visual Studio 441# Set yuzu project or yuzu-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not
444set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu) 442if(ENABLE_QT)
443 set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu)
444else()
445 set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu-cmd)
446endif()
445 447
446 448
447# Installation instructions 449# Installation instructions
diff --git a/appveyor.yml b/appveyor.yml
index 2a003ca5e..d475816ab 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -163,3 +163,13 @@ artifacts:
163 name: build 163 name: build
164 type: zip 164 type: zip
165 165
166deploy:
167 provider: GitHub
168 release: $(appveyor_repo_tag_name)
169 auth_token:
170 secure: QqePPnXbkzmXct5c8hZ2X5AbsthbI6cS1Sr+VBzcD8oUOIjfWJJKXVAQGUbQAbb0
171 artifact: update,build
172 draft: false
173 prerelease: false
174 on:
175 appveyor_repo_tag: true
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 83b75e61f..6f0ff953a 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -79,6 +79,10 @@ u32 AudioRenderer::GetMixBufferCount() const {
79 return worker_params.mix_buffer_count; 79 return worker_params.mix_buffer_count;
80} 80}
81 81
82Stream::State AudioRenderer::GetStreamState() const {
83 return stream->GetState();
84}
85
82std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { 86std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
83 // Copy UpdateDataHeader struct 87 // Copy UpdateDataHeader struct
84 UpdateDataHeader config{}; 88 UpdateDataHeader config{};
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index 2c4f5ab75..dfef89e1d 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -170,6 +170,7 @@ public:
170 u32 GetSampleRate() const; 170 u32 GetSampleRate() const;
171 u32 GetSampleCount() const; 171 u32 GetSampleCount() const;
172 u32 GetMixBufferCount() const; 172 u32 GetMixBufferCount() const;
173 Stream::State GetStreamState() const;
173 174
174private: 175private:
175 class VoiceState; 176 class VoiceState;
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 449db2416..742a5e0a0 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -49,9 +49,14 @@ void Stream::Play() {
49} 49}
50 50
51void Stream::Stop() { 51void Stream::Stop() {
52 state = State::Stopped;
52 ASSERT_MSG(false, "Unimplemented"); 53 ASSERT_MSG(false, "Unimplemented");
53} 54}
54 55
56Stream::State Stream::GetState() const {
57 return state;
58}
59
55s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { 60s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
56 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; 61 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
57 return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); 62 return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 27db1112f..aebfeb51d 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -33,6 +33,12 @@ public:
33 Multi51Channel16, 33 Multi51Channel16,
34 }; 34 };
35 35
36 /// Current state of the stream
37 enum class State {
38 Stopped,
39 Playing,
40 };
41
36 /// Callback function type, used to change guest state on a buffer being released 42 /// Callback function type, used to change guest state on a buffer being released
37 using ReleaseCallback = std::function<void()>; 43 using ReleaseCallback = std::function<void()>;
38 44
@@ -72,13 +78,10 @@ public:
72 /// Gets the number of channels 78 /// Gets the number of channels
73 u32 GetNumChannels() const; 79 u32 GetNumChannels() const;
74 80
75private: 81 /// Get the state
76 /// Current state of the stream 82 State GetState() const;
77 enum class State {
78 Stopped,
79 Playing,
80 };
81 83
84private:
82 /// Plays the next queued buffer in the audio stream, starting playback if necessary 85 /// Plays the next queued buffer in the audio stream, starting playback if necessary
83 void PlayNextBuffer(); 86 void PlayNextBuffer();
84 87
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
index fc14151da..d72d67994 100644
--- a/src/audio_core/time_stretch.cpp
+++ b/src/audio_core/time_stretch.cpp
@@ -59,7 +59,7 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
59 m_stretch_ratio = std::max(m_stretch_ratio, 0.05); 59 m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
60 m_sound_touch.setTempo(m_stretch_ratio); 60 m_sound_touch.setTempo(m_stretch_ratio);
61 61
62 LOG_DEBUG(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio, 62 LOG_TRACE(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
63 backlog_fullness); 63 backlog_fullness);
64 64
65 m_sound_touch.putSamples(in, static_cast<u32>(num_in)); 65 m_sound_touch.putSamples(in, static_cast<u32>(num_in));
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index df2ce80b1..4f88de768 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -33,6 +33,8 @@
33#define NAND_DIR "nand" 33#define NAND_DIR "nand"
34#define SYSDATA_DIR "sysdata" 34#define SYSDATA_DIR "sysdata"
35#define KEYS_DIR "keys" 35#define KEYS_DIR "keys"
36#define LOAD_DIR "load"
37#define DUMP_DIR "dump"
36#define LOG_DIR "log" 38#define LOG_DIR "log"
37 39
38// Filenames 40// Filenames
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 21a0b9738..548463787 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
705#endif 705#endif
706 paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); 706 paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
707 paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); 707 paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
708 paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
709 paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
708 paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); 710 paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
709 paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); 711 paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
710 // TODO: Put the logs in a better location for each OS 712 // TODO: Put the logs in a better location for each OS
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 24c1e413c..3d8fe6264 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -29,6 +29,8 @@ enum class UserPath {
29 NANDDir, 29 NANDDir,
30 RootDir, 30 RootDir,
31 SDMCDir, 31 SDMCDir,
32 LoadDir,
33 DumpDir,
32 SysDataDir, 34 SysDataDir,
33 UserDir, 35 UserDir,
34}; 36};
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index efd776db6..9f5918851 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -183,6 +183,7 @@ void FileBackend::Write(const Entry& entry) {
183 SUB(Service, FS) \ 183 SUB(Service, FS) \
184 SUB(Service, GRC) \ 184 SUB(Service, GRC) \
185 SUB(Service, HID) \ 185 SUB(Service, HID) \
186 SUB(Service, IRS) \
186 SUB(Service, LBL) \ 187 SUB(Service, LBL) \
187 SUB(Service, LDN) \ 188 SUB(Service, LDN) \
188 SUB(Service, LDR) \ 189 SUB(Service, LDR) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 4d577524f..abbd056ee 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -70,6 +70,7 @@ enum class Class : ClassType {
70 Service_FS, ///< The FS (Filesystem) service 70 Service_FS, ///< The FS (Filesystem) service
71 Service_GRC, ///< The game recording service 71 Service_GRC, ///< The game recording service
72 Service_HID, ///< The HID (Human interface device) service 72 Service_HID, ///< The HID (Human interface device) service
73 Service_IRS, ///< The IRS service
73 Service_LBL, ///< The LBL (LCD backlight) service 74 Service_LBL, ///< The LBL (LCD backlight) service
74 Service_LDN, ///< The LDN (Local domain network) service 75 Service_LDN, ///< The LDN (Local domain network) service
75 Service_LDR, ///< The loader service 76 Service_LDR, ///< The loader service
diff --git a/src/common/thread.h b/src/common/thread.h
index 12a1c095c..6cbdb96a3 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -87,14 +87,6 @@ private:
87 87
88void SleepCurrentThread(int ms); 88void SleepCurrentThread(int ms);
89void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms 89void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
90
91// Use this function during a spin-wait to make the current thread
92// relax while another thread is working. This may be more efficient
93// than using events because event functions use kernel calls.
94inline void YieldCPU() {
95 std::this_thread::yield();
96}
97
98void SetCurrentThreadName(const char* name); 90void SetCurrentThreadName(const char* name);
99 91
100} // namespace Common 92} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 26f727d96..23fd6e920 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -32,6 +32,8 @@ add_library(core STATIC
32 file_sys/control_metadata.h 32 file_sys/control_metadata.h
33 file_sys/directory.h 33 file_sys/directory.h
34 file_sys/errors.h 34 file_sys/errors.h
35 file_sys/fsmitm_romfsbuild.cpp
36 file_sys/fsmitm_romfsbuild.h
35 file_sys/mode.h 37 file_sys/mode.h
36 file_sys/nca_metadata.cpp 38 file_sys/nca_metadata.cpp
37 file_sys/nca_metadata.h 39 file_sys/nca_metadata.h
@@ -59,10 +61,13 @@ add_library(core STATIC
59 file_sys/vfs.h 61 file_sys/vfs.h
60 file_sys/vfs_concat.cpp 62 file_sys/vfs_concat.cpp
61 file_sys/vfs_concat.h 63 file_sys/vfs_concat.h
64 file_sys/vfs_layered.cpp
65 file_sys/vfs_layered.h
62 file_sys/vfs_offset.cpp 66 file_sys/vfs_offset.cpp
63 file_sys/vfs_offset.h 67 file_sys/vfs_offset.h
64 file_sys/vfs_real.cpp 68 file_sys/vfs_real.cpp
65 file_sys/vfs_real.h 69 file_sys/vfs_real.h
70 file_sys/vfs_static.h
66 file_sys/vfs_vector.cpp 71 file_sys/vfs_vector.cpp
67 file_sys/vfs_vector.h 72 file_sys/vfs_vector.h
68 file_sys/xts_archive.cpp 73 file_sys/xts_archive.cpp
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 7be5a38de..8760d17a8 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -174,7 +174,7 @@ ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
174 std::size_t core_index) 174 std::size_t core_index)
175 : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index}, 175 : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
176 exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} { 176 exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
177 ThreadContext ctx; 177 ThreadContext ctx{};
178 inner_unicorn.SaveContext(ctx); 178 inner_unicorn.SaveContext(ctx);
179 PageTableChanged(); 179 PageTableChanged();
180 LoadContext(ctx); 180 LoadContext(ctx);
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 50f0a42fb..7666354dc 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -64,7 +64,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
64 if (concat.empty()) 64 if (concat.empty())
65 return nullptr; 65 return nullptr;
66 66
67 return FileSys::ConcatenateFiles(concat, dir->GetName()); 67 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
68 } 68 }
69 69
70 return vfs->OpenFile(path, FileSys::Mode::Read); 70 return vfs->OpenFile(path, FileSys::Mode::Read);
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
index 21568ad50..265f8ed9c 100644
--- a/src/core/core_cpu.cpp
+++ b/src/core/core_cpu.cpp
@@ -55,16 +55,16 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
55 55
56 if (Settings::values.use_cpu_jit) { 56 if (Settings::values.use_cpu_jit) {
57#ifdef ARCHITECTURE_x86_64 57#ifdef ARCHITECTURE_x86_64
58 arm_interface = std::make_shared<ARM_Dynarmic>(exclusive_monitor, core_index); 58 arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
59#else 59#else
60 arm_interface = std::make_shared<ARM_Unicorn>(); 60 arm_interface = std::make_unique<ARM_Unicorn>();
61 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); 61 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
62#endif 62#endif
63 } else { 63 } else {
64 arm_interface = std::make_shared<ARM_Unicorn>(); 64 arm_interface = std::make_unique<ARM_Unicorn>();
65 } 65 }
66 66
67 scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get()); 67 scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
68} 68}
69 69
70Cpu::~Cpu() = default; 70Cpu::~Cpu() = default;
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
index 685532965..ee7e04abc 100644
--- a/src/core/core_cpu.h
+++ b/src/core/core_cpu.h
@@ -76,7 +76,7 @@ public:
76private: 76private:
77 void Reschedule(); 77 void Reschedule();
78 78
79 std::shared_ptr<ARM_Interface> arm_interface; 79 std::unique_ptr<ARM_Interface> arm_interface;
80 std::shared_ptr<CpuBarrier> cpu_barrier; 80 std::shared_ptr<CpuBarrier> cpu_barrier;
81 std::shared_ptr<Kernel::Scheduler> scheduler; 81 std::shared_ptr<Kernel::Scheduler> scheduler;
82 82
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 205492897..6102ef476 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -2,13 +2,14 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <fmt/format.h>
5#include "core/file_sys/bis_factory.h" 6#include "core/file_sys/bis_factory.h"
6#include "core/file_sys/registered_cache.h" 7#include "core/file_sys/registered_cache.h"
7 8
8namespace FileSys { 9namespace FileSys {
9 10
10BISFactory::BISFactory(VirtualDir nand_root_) 11BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
11 : nand_root(std::move(nand_root_)), 12 : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
12 sysnand_cache(std::make_shared<RegisteredCache>( 13 sysnand_cache(std::make_shared<RegisteredCache>(
13 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), 14 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
14 usrnand_cache(std::make_shared<RegisteredCache>( 15 usrnand_cache(std::make_shared<RegisteredCache>(
@@ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
24 return usrnand_cache; 25 return usrnand_cache;
25} 26}
26 27
28VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
29 // LayeredFS doesn't work on updates and title id-less homebrew
30 if (title_id == 0 || (title_id & 0x800) > 0)
31 return nullptr;
32 return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
33}
34
27} // namespace FileSys 35} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 9523dd864..c352e0925 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -17,14 +17,17 @@ class RegisteredCache;
17/// registered caches. 17/// registered caches.
18class BISFactory { 18class BISFactory {
19public: 19public:
20 explicit BISFactory(VirtualDir nand_root); 20 explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
21 ~BISFactory(); 21 ~BISFactory();
22 22
23 std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; 23 std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
24 std::shared_ptr<RegisteredCache> GetUserNANDContents() const; 24 std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
25 25
26 VirtualDir GetModificationLoadRoot(u64 title_id) const;
27
26private: 28private:
27 VirtualDir nand_root; 29 VirtualDir nand_root;
30 VirtualDir load_root;
28 31
29 std::shared_ptr<RegisteredCache> sysnand_cache; 32 std::shared_ptr<RegisteredCache> sysnand_cache;
30 std::shared_ptr<RegisteredCache> usrnand_cache; 33 std::shared_ptr<RegisteredCache> usrnand_cache;
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
new file mode 100644
index 000000000..2a913ce82
--- /dev/null
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -0,0 +1,366 @@
1/*
2 * Copyright (c) 2018 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2018 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#include <cstring>
26#include "common/alignment.h"
27#include "common/assert.h"
28#include "core/file_sys/fsmitm_romfsbuild.h"
29#include "core/file_sys/vfs.h"
30#include "core/file_sys/vfs_vector.h"
31
32namespace FileSys {
33
34constexpr u64 FS_MAX_PATH = 0x301;
35
36constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
37constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
38
39// Types for building a RomFS.
40struct RomFSHeader {
41 u64 header_size;
42 u64 dir_hash_table_ofs;
43 u64 dir_hash_table_size;
44 u64 dir_table_ofs;
45 u64 dir_table_size;
46 u64 file_hash_table_ofs;
47 u64 file_hash_table_size;
48 u64 file_table_ofs;
49 u64 file_table_size;
50 u64 file_partition_ofs;
51};
52static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
53
54struct RomFSDirectoryEntry {
55 u32 parent;
56 u32 sibling;
57 u32 child;
58 u32 file;
59 u32 hash;
60 u32 name_size;
61};
62static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
63
64struct RomFSFileEntry {
65 u32 parent;
66 u32 sibling;
67 u64 offset;
68 u64 size;
69 u32 hash;
70 u32 name_size;
71};
72static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
73
74struct RomFSBuildFileContext;
75
76struct RomFSBuildDirectoryContext {
77 std::string path;
78 u32 cur_path_ofs = 0;
79 u32 path_len = 0;
80 u32 entry_offset = 0;
81 std::shared_ptr<RomFSBuildDirectoryContext> parent;
82 std::shared_ptr<RomFSBuildDirectoryContext> child;
83 std::shared_ptr<RomFSBuildDirectoryContext> sibling;
84 std::shared_ptr<RomFSBuildFileContext> file;
85};
86
87struct RomFSBuildFileContext {
88 std::string path;
89 u32 cur_path_ofs = 0;
90 u32 path_len = 0;
91 u32 entry_offset = 0;
92 u64 offset = 0;
93 u64 size = 0;
94 std::shared_ptr<RomFSBuildDirectoryContext> parent;
95 std::shared_ptr<RomFSBuildFileContext> sibling;
96 VirtualFile source;
97};
98
99static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
100 u32 hash = parent ^ 123456789;
101 for (u32 i = 0; i < path_len; i++) {
102 hash = (hash >> 5) | (hash << 27);
103 hash ^= path[start + i];
104 }
105
106 return hash;
107}
108
109static u64 romfs_get_hash_table_count(u64 num_entries) {
110 if (num_entries < 3) {
111 return 3;
112 }
113
114 if (num_entries < 19) {
115 return num_entries | 1;
116 }
117
118 u64 count = num_entries;
119 while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
120 count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
121 count++;
122 }
123 return count;
124}
125
126void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
127 std::shared_ptr<RomFSBuildDirectoryContext> parent) {
128 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
129
130 VirtualDir dir;
131
132 if (parent->path_len == 0)
133 dir = root_romfs;
134 else
135 dir = root_romfs->GetDirectoryRelative(parent->path);
136
137 const auto entries = dir->GetEntries();
138
139 for (const auto& kv : entries) {
140 if (kv.second == VfsEntryType::Directory) {
141 const auto child = std::make_shared<RomFSBuildDirectoryContext>();
142 // Set child's path.
143 child->cur_path_ofs = parent->path_len + 1;
144 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
145 child->path = parent->path + "/" + kv.first;
146
147 // Sanity check on path_len
148 ASSERT(child->path_len < FS_MAX_PATH);
149
150 if (AddDirectory(parent, child)) {
151 child_dirs.push_back(child);
152 }
153 } else {
154 const auto child = std::make_shared<RomFSBuildFileContext>();
155 // Set child's path.
156 child->cur_path_ofs = parent->path_len + 1;
157 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
158 child->path = parent->path + "/" + kv.first;
159
160 // Sanity check on path_len
161 ASSERT(child->path_len < FS_MAX_PATH);
162
163 child->source = root_romfs->GetFileRelative(child->path);
164
165 child->size = child->source->GetSize();
166
167 AddFile(parent, child);
168 }
169 }
170
171 for (auto& child : child_dirs) {
172 this->VisitDirectory(root_romfs, child);
173 }
174}
175
176bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
177 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
178 // Check whether it's already in the known directories.
179 const auto existing = directories.find(dir_ctx->path);
180 if (existing != directories.end())
181 return false;
182
183 // Add a new directory.
184 num_dirs++;
185 dir_table_size +=
186 sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
187 dir_ctx->parent = parent_dir_ctx;
188 directories.emplace(dir_ctx->path, dir_ctx);
189
190 return true;
191}
192
193bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
194 std::shared_ptr<RomFSBuildFileContext> file_ctx) {
195 // Check whether it's already in the known files.
196 const auto existing = files.find(file_ctx->path);
197 if (existing != files.end()) {
198 return false;
199 }
200
201 // Add a new file.
202 num_files++;
203 file_table_size +=
204 sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
205 file_ctx->parent = parent_dir_ctx;
206 files.emplace(file_ctx->path, file_ctx);
207
208 return true;
209}
210
211RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
212 root = std::make_shared<RomFSBuildDirectoryContext>();
213 root->path = "\0";
214 directories.emplace(root->path, root);
215 num_dirs = 1;
216 dir_table_size = 0x18;
217
218 VisitDirectory(base, root);
219}
220
221RomFSBuildContext::~RomFSBuildContext() = default;
222
223std::map<u64, VirtualFile> RomFSBuildContext::Build() {
224 const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
225 const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
226 dir_hash_table_size = 4 * dir_hash_table_entry_count;
227 file_hash_table_size = 4 * file_hash_table_entry_count;
228
229 // Assign metadata pointers
230 RomFSHeader header{};
231
232 std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
233 std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
234
235 std::vector<u8> dir_table(dir_table_size);
236 std::vector<u8> file_table(file_table_size);
237
238 std::shared_ptr<RomFSBuildFileContext> cur_file;
239
240 // Determine file offsets.
241 u32 entry_offset = 0;
242 std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
243 for (const auto& it : files) {
244 cur_file = it.second;
245 file_partition_size = Common::AlignUp(file_partition_size, 16);
246 cur_file->offset = file_partition_size;
247 file_partition_size += cur_file->size;
248 cur_file->entry_offset = entry_offset;
249 entry_offset += sizeof(RomFSFileEntry) +
250 Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4);
251 prev_file = cur_file;
252 }
253 // Assign deferred parent/sibling ownership.
254 for (auto it = files.rbegin(); it != files.rend(); ++it) {
255 cur_file = it->second;
256 cur_file->sibling = cur_file->parent->file;
257 cur_file->parent->file = cur_file;
258 }
259
260 std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
261
262 // Determine directory offsets.
263 entry_offset = 0;
264 for (const auto& it : directories) {
265 cur_dir = it.second;
266 cur_dir->entry_offset = entry_offset;
267 entry_offset += sizeof(RomFSDirectoryEntry) +
268 Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4);
269 }
270 // Assign deferred parent/sibling ownership.
271 for (auto it = directories.rbegin(); it->second != root; ++it) {
272 cur_dir = it->second;
273 cur_dir->sibling = cur_dir->parent->child;
274 cur_dir->parent->child = cur_dir;
275 }
276
277 std::map<u64, VirtualFile> out;
278
279 // Populate file tables.
280 for (const auto& it : files) {
281 cur_file = it.second;
282 RomFSFileEntry cur_entry{};
283
284 cur_entry.parent = cur_file->parent->entry_offset;
285 cur_entry.sibling =
286 cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
287 cur_entry.offset = cur_file->offset;
288 cur_entry.size = cur_file->size;
289
290 const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
291 const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
292 cur_file->cur_path_ofs, name_size);
293 cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
294 file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
295
296 cur_entry.name_size = name_size;
297
298 out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
299 std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
300 std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
301 Common::AlignUp(cur_entry.name_size, 4));
302 std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
303 cur_file->path.data() + cur_file->cur_path_ofs, name_size);
304 }
305
306 // Populate dir tables.
307 for (const auto& it : directories) {
308 cur_dir = it.second;
309 RomFSDirectoryEntry cur_entry{};
310
311 cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
312 cur_entry.sibling =
313 cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
314 cur_entry.child =
315 cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
316 cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
317
318 const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
319 const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
320 cur_dir->path, cur_dir->cur_path_ofs, name_size);
321 cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
322 dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
323
324 cur_entry.name_size = name_size;
325
326 std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
327 sizeof(RomFSDirectoryEntry));
328 std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
329 Common::AlignUp(cur_entry.name_size, 4));
330 std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
331 cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
332 }
333
334 // Set header fields.
335 header.header_size = sizeof(RomFSHeader);
336 header.file_hash_table_size = file_hash_table_size;
337 header.file_table_size = file_table_size;
338 header.dir_hash_table_size = dir_hash_table_size;
339 header.dir_table_size = dir_table_size;
340 header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
341 header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4);
342 header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
343 header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
344 header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
345
346 std::vector<u8> header_data(sizeof(RomFSHeader));
347 std::memcpy(header_data.data(), &header, header_data.size());
348 out.emplace(0, std::make_shared<VectorVfsFile>(std::move(header_data)));
349
350 std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
351 dir_table_size);
352 std::size_t index = 0;
353 std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
354 index += dir_hash_table.size() * sizeof(u32);
355 std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
356 index += dir_table.size();
357 std::memcpy(metadata.data() + index, file_hash_table.data(),
358 file_hash_table.size() * sizeof(u32));
359 index += file_hash_table.size() * sizeof(u32);
360 std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
361 out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(std::move(metadata)));
362
363 return out;
364}
365
366} // namespace FileSys
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
new file mode 100644
index 000000000..b0c3c123b
--- /dev/null
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -0,0 +1,70 @@
1/*
2 * Copyright (c) 2018 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2018 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include <map>
28#include <memory>
29#include <string>
30#include <boost/detail/container_fwd.hpp>
31#include "common/common_types.h"
32#include "core/file_sys/vfs.h"
33
34namespace FileSys {
35
36struct RomFSBuildDirectoryContext;
37struct RomFSBuildFileContext;
38struct RomFSDirectoryEntry;
39struct RomFSFileEntry;
40
41class RomFSBuildContext {
42public:
43 explicit RomFSBuildContext(VirtualDir base);
44 ~RomFSBuildContext();
45
46 // This finalizes the context.
47 std::map<u64, VirtualFile> Build();
48
49private:
50 VirtualDir base;
51 std::shared_ptr<RomFSBuildDirectoryContext> root;
52 std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
53 std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
54 u64 num_dirs = 0;
55 u64 num_files = 0;
56 u64 dir_table_size = 0;
57 u64 file_table_size = 0;
58 u64 dir_hash_table_size = 0;
59 u64 file_hash_table_size = 0;
60 u64 file_partition_size = 0;
61
62 void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
63
64 bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
65 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
66 bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
67 std::shared_ptr<RomFSBuildFileContext> file_ctx);
68};
69
70} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index aebc69d52..4b3b5e665 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -11,6 +11,7 @@
11#include "core/file_sys/patch_manager.h" 11#include "core/file_sys/patch_manager.h"
12#include "core/file_sys/registered_cache.h" 12#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/romfs.h" 13#include "core/file_sys/romfs.h"
14#include "core/file_sys/vfs_layered.h"
14#include "core/hle/service/filesystem/filesystem.h" 15#include "core/hle/service/filesystem/filesystem.h"
15#include "core/loader/loader.h" 16#include "core/loader/loader.h"
16 17
@@ -31,8 +32,9 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
31 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); 32 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
32} 33}
33 34
34constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ 35constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{
35 "Update", 36 "Update",
37 "LayeredFS",
36}; 38};
37 39
38std::string FormatPatchTypeName(PatchType type) { 40std::string FormatPatchTypeName(PatchType type) {
@@ -66,6 +68,44 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
66 return exefs; 68 return exefs;
67} 69}
68 70
71static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
72 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
73 if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
74 return;
75 }
76
77 auto extracted = ExtractRomFS(romfs);
78 if (extracted == nullptr) {
79 return;
80 }
81
82 auto patch_dirs = load_dir->GetSubdirectories();
83 std::sort(patch_dirs.begin(), patch_dirs.end(),
84 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
85
86 std::vector<VirtualDir> layers;
87 layers.reserve(patch_dirs.size() + 1);
88 for (const auto& subdir : patch_dirs) {
89 auto romfs_dir = subdir->GetSubdirectory("romfs");
90 if (romfs_dir != nullptr)
91 layers.push_back(std::move(romfs_dir));
92 }
93 layers.push_back(std::move(extracted));
94
95 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
96 if (layered == nullptr) {
97 return;
98 }
99
100 auto packed = CreateRomFS(std::move(layered));
101 if (packed == nullptr) {
102 return;
103 }
104
105 LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
106 romfs = std::move(packed);
107}
108
69VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, 109VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
70 ContentRecordType type) const { 110 ContentRecordType type) const {
71 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, 111 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
@@ -89,6 +129,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
89 } 129 }
90 } 130 }
91 131
132 // LayeredFS
133 ApplyLayeredFS(romfs, title_id, type);
134
92 return romfs; 135 return romfs;
93} 136}
94 137
@@ -114,6 +157,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
114 } 157 }
115 } 158 }
116 159
160 const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
161 if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
162 out.insert_or_assign(PatchType::LayeredFS, "");
163
117 return out; 164 return out;
118} 165}
119 166
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 209cab1dc..464f17515 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -26,6 +26,7 @@ std::string FormatTitleVersion(u32 version,
26 26
27enum class PatchType { 27enum class PatchType {
28 Update, 28 Update,
29 LayeredFS,
29}; 30};
30 31
31std::string FormatPatchTypeName(PatchType type); 32std::string FormatPatchTypeName(PatchType type);
@@ -42,6 +43,7 @@ public:
42 43
43 // Currently tracked RomFS patches: 44 // Currently tracked RomFS patches:
44 // - Game Updates 45 // - Game Updates
46 // - LayeredFS
45 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 47 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
46 ContentRecordType type = ContentRecordType::Program) const; 48 ContentRecordType type = ContentRecordType::Program) const;
47 49
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index dad7ae10b..e9b040689 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -18,6 +18,10 @@
18#include "core/loader/loader.h" 18#include "core/loader/loader.h"
19 19
20namespace FileSys { 20namespace FileSys {
21
22// The size of blocks to use when vfs raw copying into nand.
23constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
24
21std::string RegisteredCacheEntry::DebugInfo() const { 25std::string RegisteredCacheEntry::DebugInfo() const {
22 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); 26 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
23} 27}
@@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
121 if (concat.empty()) 125 if (concat.empty())
122 return nullptr; 126 return nullptr;
123 127
124 file = FileSys::ConcatenateFiles(concat); 128 file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
125 } 129 }
126 130
127 return file; 131 return file;
@@ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
480 auto out = dir->CreateFileRelative(path); 484 auto out = dir->CreateFileRelative(path);
481 if (out == nullptr) 485 if (out == nullptr)
482 return InstallResult::ErrorCopyFailed; 486 return InstallResult::ErrorCopyFailed;
483 return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; 487 return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
488 : InstallResult::ErrorCopyFailed;
484} 489}
485 490
486bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { 491bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index f487b0cf0..c0cd59fc5 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -27,7 +27,7 @@ struct ContentRecord;
27 27
28using NcaID = std::array<u8, 0x10>; 28using NcaID = std::array<u8, 0x10>;
29using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; 29using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
30using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; 30using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
31 31
32enum class InstallResult { 32enum class InstallResult {
33 Success, 33 Success,
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 9f6e41cdf..5910f7046 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -4,8 +4,10 @@
4 4
5#include "common/common_types.h" 5#include "common/common_types.h"
6#include "common/swap.h" 6#include "common/swap.h"
7#include "core/file_sys/fsmitm_romfsbuild.h"
7#include "core/file_sys/romfs.h" 8#include "core/file_sys/romfs.h"
8#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/file_sys/vfs_concat.h"
9#include "core/file_sys/vfs_offset.h" 11#include "core/file_sys/vfs_offset.h"
10#include "core/file_sys/vfs_vector.h" 12#include "core/file_sys/vfs_vector.h"
11 13
@@ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
98 } 100 }
99} 101}
100 102
101VirtualDir ExtractRomFS(VirtualFile file) { 103VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
102 RomFSHeader header{}; 104 RomFSHeader header{};
103 if (file->ReadObject(&header) != sizeof(RomFSHeader)) 105 if (file->ReadObject(&header) != sizeof(RomFSHeader))
104 return nullptr; 106 return nullptr;
@@ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) {
117 119
118 VirtualDir out = std::move(root); 120 VirtualDir out = std::move(root);
119 121
120 while (out->GetSubdirectory("") != nullptr) 122 while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
121 out = out->GetSubdirectory(""); 123 if (out->GetSubdirectories().front()->GetName() == "data" &&
124 type == RomFSExtractionType::Truncated)
125 break;
126 out = out->GetSubdirectories().front();
127 }
122 128
123 return out; 129 return out;
124} 130}
131
132VirtualFile CreateRomFS(VirtualDir dir) {
133 if (dir == nullptr)
134 return nullptr;
135
136 RomFSBuildContext ctx{dir};
137 return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
138}
139
125} // namespace FileSys 140} // namespace FileSys
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index e54a7d7a9..ecd1eb725 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <map>
8#include "common/common_funcs.h" 9#include "common/common_funcs.h"
9#include "common/common_types.h" 10#include "common/common_types.h"
10#include "common/swap.h" 11#include "common/swap.h"
@@ -12,6 +13,8 @@
12 13
13namespace FileSys { 14namespace FileSys {
14 15
16struct RomFSHeader;
17
15struct IVFCLevel { 18struct IVFCLevel {
16 u64_le offset; 19 u64_le offset;
17 u64_le size; 20 u64_le size;
@@ -29,8 +32,18 @@ struct IVFCHeader {
29}; 32};
30static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); 33static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
31 34
35enum class RomFSExtractionType {
36 Full, // Includes data directory
37 Truncated, // Traverses into data directory
38};
39
32// Converts a RomFS binary blob to VFS Filesystem 40// Converts a RomFS binary blob to VFS Filesystem
33// Returns nullptr on failure 41// Returns nullptr on failure
34VirtualDir ExtractRomFS(VirtualFile file); 42VirtualDir ExtractRomFS(VirtualFile file,
43 RomFSExtractionType type = RomFSExtractionType::Truncated);
44
45// Converts a VFS filesystem into a RomFS binary
46// Returns nullptr on failure
47VirtualFile CreateRomFS(VirtualDir dir);
35 48
36} // namespace FileSys 49} // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index d7b52abfd..bfe50da73 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
399 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); 399 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
400} 400}
401 401
402std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
403 std::map<std::string, VfsEntryType, std::less<>> out;
404 for (const auto& dir : GetSubdirectories())
405 out.emplace(dir->GetName(), VfsEntryType::Directory);
406 for (const auto& file : GetFiles())
407 out.emplace(file->GetName(), VfsEntryType::File);
408 return out;
409}
410
402std::string VfsDirectory::GetFullPath() const { 411std::string VfsDirectory::GetFullPath() const {
403 if (IsRoot()) 412 if (IsRoot())
404 return GetName(); 413 return GetName();
@@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t
454 return true; 463 return true;
455} 464}
456 465
457bool VfsRawCopy(VirtualFile src, VirtualFile dest) { 466bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
458 if (src == nullptr || dest == nullptr) 467 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
459 return false; 468 return false;
460 if (!dest->Resize(src->GetSize())) 469 if (!dest->Resize(src->GetSize()))
461 return false; 470 return false;
462 std::vector<u8> data = src->ReadAllBytes(); 471
463 return dest->WriteBytes(data, 0) == data.size(); 472 std::vector<u8> temp(std::min(block_size, src->GetSize()));
473 for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
474 const auto read = std::min(block_size, src->GetSize() - i);
475 const auto block = src->Read(temp.data(), read, i);
476
477 if (dest->Write(temp.data(), read, i) != read)
478 return false;
479 }
480
481 return true;
482}
483
484bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
485 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
486 return false;
487
488 for (const auto& file : src->GetFiles()) {
489 const auto out = dest->CreateFile(file->GetName());
490 if (!VfsRawCopy(file, out, block_size))
491 return false;
492 }
493
494 for (const auto& dir : src->GetSubdirectories()) {
495 const auto out = dest->CreateSubdirectory(dir->GetName());
496 if (!VfsRawCopyD(dir, out, block_size))
497 return false;
498 }
499
500 return true;
464} 501}
465 502
466VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { 503VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 74489b452..270291631 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9#include <string_view> 10#include <string_view>
@@ -265,6 +266,10 @@ public:
265 // dest. 266 // dest.
266 virtual bool Copy(std::string_view src, std::string_view dest); 267 virtual bool Copy(std::string_view src, std::string_view dest);
267 268
269 // Gets all of the entries directly in the directory (files and dirs), returning a map between
270 // item name -> type.
271 virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
272
268 // Interprets the file with name file instead as a directory of type directory. 273 // Interprets the file with name file instead as a directory of type directory.
269 // The directory must have a constructor that takes a single argument of type 274 // The directory must have a constructor that takes a single argument of type
270 // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a 275 // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
@@ -310,13 +315,19 @@ public:
310 bool Rename(std::string_view name) override; 315 bool Rename(std::string_view name) override;
311}; 316};
312 317
313// Compare the two files, byte-for-byte, in increments specificed by block_size 318// Compare the two files, byte-for-byte, in increments specified by block_size
314bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); 319bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
320 std::size_t block_size = 0x1000);
315 321
316// A method that copies the raw data between two different implementations of VirtualFile. If you 322// A method that copies the raw data between two different implementations of VirtualFile. If you
317// are using the same implementation, it is probably better to use the Copy method in the parent 323// are using the same implementation, it is probably better to use the Copy method in the parent
318// directory of src/dest. 324// directory of src/dest.
319bool VfsRawCopy(VirtualFile src, VirtualFile dest); 325bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
326
327// A method that performs a similar function to VfsRawCopy above, but instead copies entire
328// directories. It suffers the same performance penalties as above and an implementation-specific
329// Copy should always be preferred.
330bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
320 331
321// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not 332// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
322// it attempts to create it and returns the new dir or nullptr on failure. 333// it attempts to create it and returns the new dir or nullptr on failure.
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index dc7a279a9..16d801c0c 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -5,17 +5,22 @@
5#include <algorithm> 5#include <algorithm>
6#include <utility> 6#include <utility>
7 7
8#include "common/assert.h"
8#include "core/file_sys/vfs_concat.h" 9#include "core/file_sys/vfs_concat.h"
10#include "core/file_sys/vfs_static.h"
9 11
10namespace FileSys { 12namespace FileSys {
11 13
12VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { 14static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
13 if (files.empty()) 15 const auto last_valid = --map.end();
14 return nullptr; 16 for (auto iter = map.begin(); iter != last_valid;) {
15 if (files.size() == 1) 17 const auto old = iter++;
16 return files[0]; 18 if (old->first + old->second->GetSize() != iter->first) {
19 return false;
20 }
21 }
17 22
18 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); 23 return map.begin()->first == 0;
19} 24}
20 25
21ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) 26ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
@@ -27,8 +32,48 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
27 } 32 }
28} 33}
29 34
35ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
36 : files(std::move(files_)), name(std::move(name)) {
37 ASSERT(VerifyConcatenationMapContinuity(files));
38}
39
30ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; 40ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
31 41
42VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files,
43 std::string name) {
44 if (files.empty())
45 return nullptr;
46 if (files.size() == 1)
47 return files[0];
48
49 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
50}
51
52VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
53 std::map<u64, VirtualFile> files,
54 std::string name) {
55 if (files.empty())
56 return nullptr;
57 if (files.size() == 1)
58 return files.begin()->second;
59
60 const auto last_valid = --files.end();
61 for (auto iter = files.begin(); iter != last_valid;) {
62 const auto old = iter++;
63 if (old->first + old->second->GetSize() != iter->first) {
64 files.emplace(old->first + old->second->GetSize(),
65 std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first -
66 old->second->GetSize()));
67 }
68 }
69
70 // Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
71 if (files.begin()->first != 0)
72 files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
73
74 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
75}
76
32std::string ConcatenatedVfsFile::GetName() const { 77std::string ConcatenatedVfsFile::GetName() const {
33 if (files.empty()) 78 if (files.empty())
34 return ""; 79 return "";
@@ -62,7 +107,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
62} 107}
63 108
64std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 109std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
65 auto entry = files.end(); 110 auto entry = --files.end();
66 for (auto iter = files.begin(); iter != files.end(); ++iter) { 111 for (auto iter = files.begin(); iter != files.end(); ++iter) {
67 if (iter->first > offset) { 112 if (iter->first > offset) {
68 entry = --iter; 113 entry = --iter;
@@ -70,20 +115,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
70 } 115 }
71 } 116 }
72 117
73 // Check if the entry should be the last one. The loop above will make it end(). 118 if (entry->first + entry->second->GetSize() <= offset)
74 if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
75 --entry;
76
77 if (entry == files.end())
78 return 0; 119 return 0;
79 120
80 const auto remaining = entry->second->GetSize() + offset - entry->first; 121 const auto read_in =
81 if (length > remaining) { 122 std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
82 return entry->second->Read(data, remaining, offset - entry->first) + 123 if (length > read_in) {
83 Read(data + remaining, length - remaining, offset + remaining); 124 return entry->second->Read(data, read_in, offset - entry->first) +
125 Read(data + read_in, length - read_in, offset + read_in);
84 } 126 }
85 127
86 return entry->second->Read(data, length, offset - entry->first); 128 return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
87} 129}
88 130
89std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 131std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
@@ -93,4 +135,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
93bool ConcatenatedVfsFile::Rename(std::string_view name) { 135bool ConcatenatedVfsFile::Rename(std::string_view name) {
94 return false; 136 return false;
95} 137}
138
96} // namespace FileSys 139} // namespace FileSys
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 717d04bdc..c90f9d5d1 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -4,26 +4,30 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map>
7#include <memory> 8#include <memory>
8#include <string_view> 9#include <string_view>
9#include <boost/container/flat_map.hpp>
10#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
11 11
12namespace FileSys { 12namespace FileSys {
13 13
14// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
15VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
16
17// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently 14// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
18// read-only. 15// read-only.
19class ConcatenatedVfsFile : public VfsFile { 16class ConcatenatedVfsFile : public VfsFile {
20 friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
21
22 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); 17 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
18 ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
23 19
24public: 20public:
25 ~ConcatenatedVfsFile() override; 21 ~ConcatenatedVfsFile() override;
26 22
23 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
24 static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name);
25
26 /// Convenience function that turns a map of offsets to files into a concatenated file, filling
27 /// gaps with a given filler byte.
28 static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files,
29 std::string name);
30
27 std::string GetName() const override; 31 std::string GetName() const override;
28 std::size_t GetSize() const override; 32 std::size_t GetSize() const override;
29 bool Resize(std::size_t new_size) override; 33 bool Resize(std::size_t new_size) override;
@@ -36,7 +40,7 @@ public:
36 40
37private: 41private:
38 // Maps starting offset to file -- more efficient. 42 // Maps starting offset to file -- more efficient.
39 boost::container::flat_map<u64, VirtualFile> files; 43 std::map<u64, VirtualFile> files;
40 std::string name; 44 std::string name;
41}; 45};
42 46
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
new file mode 100644
index 000000000..bfee01725
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -0,0 +1,132 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <utility>
7#include "core/file_sys/vfs_layered.h"
8
9namespace FileSys {
10
11LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
12 : dirs(std::move(dirs)), name(std::move(name)) {}
13
14LayeredVfsDirectory::~LayeredVfsDirectory() = default;
15
16VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
17 std::string name) {
18 if (dirs.empty())
19 return nullptr;
20 if (dirs.size() == 1)
21 return dirs[0];
22
23 return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
24}
25
26std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
27 for (const auto& layer : dirs) {
28 const auto file = layer->GetFileRelative(path);
29 if (file != nullptr)
30 return file;
31 }
32
33 return nullptr;
34}
35
36std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
37 std::string_view path) const {
38 std::vector<VirtualDir> out;
39 for (const auto& layer : dirs) {
40 auto dir = layer->GetDirectoryRelative(path);
41 if (dir != nullptr)
42 out.push_back(std::move(dir));
43 }
44
45 return MakeLayeredDirectory(std::move(out));
46}
47
48std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
49 return GetFileRelative(name);
50}
51
52std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
53 return GetDirectoryRelative(name);
54}
55
56std::string LayeredVfsDirectory::GetFullPath() const {
57 return dirs[0]->GetFullPath();
58}
59
60std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
61 std::vector<VirtualFile> out;
62 for (const auto& layer : dirs) {
63 for (const auto& file : layer->GetFiles()) {
64 if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
65 return comp->GetName() == file->GetName();
66 }) == out.end()) {
67 out.push_back(file);
68 }
69 }
70 }
71
72 return out;
73}
74
75std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
76 std::vector<std::string> names;
77 for (const auto& layer : dirs) {
78 for (const auto& sd : layer->GetSubdirectories()) {
79 if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
80 names.push_back(sd->GetName());
81 }
82 }
83
84 std::vector<VirtualDir> out;
85 out.reserve(names.size());
86 for (const auto& subdir : names)
87 out.push_back(GetSubdirectory(subdir));
88
89 return out;
90}
91
92bool LayeredVfsDirectory::IsWritable() const {
93 return false;
94}
95
96bool LayeredVfsDirectory::IsReadable() const {
97 return true;
98}
99
100std::string LayeredVfsDirectory::GetName() const {
101 return name.empty() ? dirs[0]->GetName() : name;
102}
103
104std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
105 return dirs[0]->GetParentDirectory();
106}
107
108std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
109 return nullptr;
110}
111
112std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
113 return nullptr;
114}
115
116bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
117 return false;
118}
119
120bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
121 return false;
122}
123
124bool LayeredVfsDirectory::Rename(std::string_view name_) {
125 name = name_;
126 return true;
127}
128
129bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
130 return false;
131}
132} // namespace FileSys
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
new file mode 100644
index 000000000..d85310f57
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.h
@@ -0,0 +1,50 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/file_sys/vfs.h"
9
10namespace FileSys {
11
12// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
13// one and falling back to the one after. The highest priority directory (overwrites all others)
14// should be element 0 in the dirs vector.
15class LayeredVfsDirectory : public VfsDirectory {
16 LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
17
18public:
19 ~LayeredVfsDirectory() override;
20
21 /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
22 static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
23
24 std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
25 std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
26 std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
27 std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
28 std::string GetFullPath() const override;
29
30 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
31 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
32 bool IsWritable() const override;
33 bool IsReadable() const override;
34 std::string GetName() const override;
35 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
36 std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
37 std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
38 bool DeleteSubdirectory(std::string_view name) override;
39 bool DeleteFile(std::string_view name) override;
40 bool Rename(std::string_view name) override;
41
42protected:
43 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
44
45private:
46 std::vector<VirtualDir> dirs;
47 std::string name;
48};
49
50} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 5e242e20f..9defad04c 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const {
413 return out; 413 return out;
414} 414}
415 415
416std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
417 if (perms == Mode::Append)
418 return {};
419
420 std::map<std::string, VfsEntryType, std::less<>> out;
421 FileUtil::ForeachDirectoryEntry(
422 nullptr, path,
423 [&out](u64* entries_out, const std::string& directory, const std::string& filename) {
424 const std::string full_path = directory + DIR_SEP + filename;
425 out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
426 : VfsEntryType::File);
427 return true;
428 });
429
430 return out;
431}
432
416bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 433bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
417 return false; 434 return false;
418} 435}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 681c43e82..5b61db90d 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -98,6 +98,7 @@ public:
98 bool DeleteFile(std::string_view name) override; 98 bool DeleteFile(std::string_view name) override;
99 bool Rename(std::string_view name) override; 99 bool Rename(std::string_view name) override;
100 std::string GetFullPath() const override; 100 std::string GetFullPath() const override;
101 std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
101 102
102protected: 103protected:
103 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 104 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
new file mode 100644
index 000000000..44fab51d1
--- /dev/null
+++ b/src/core/file_sys/vfs_static.h
@@ -0,0 +1,79 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <algorithm>
8#include <memory>
9#include <string_view>
10
11#include "core/file_sys/vfs.h"
12
13namespace FileSys {
14
15class StaticVfsFile : public VfsFile {
16public:
17 explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "",
18 VirtualDir parent = nullptr)
19 : value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {}
20
21 std::string GetName() const override {
22 return name;
23 }
24
25 std::size_t GetSize() const override {
26 return size;
27 }
28
29 bool Resize(std::size_t new_size) override {
30 size = new_size;
31 return true;
32 }
33
34 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
35 return parent;
36 }
37
38 bool IsWritable() const override {
39 return false;
40 }
41
42 bool IsReadable() const override {
43 return true;
44 }
45
46 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
47 const auto read = std::min(length, size - offset);
48 std::fill(data, data + read, value);
49 return read;
50 }
51
52 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
53 return 0;
54 }
55
56 boost::optional<u8> ReadByte(std::size_t offset) const override {
57 if (offset < size)
58 return value;
59 return boost::none;
60 }
61
62 std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
63 const auto read = std::min(length, size - offset);
64 return std::vector<u8>(read, value);
65 }
66
67 bool Rename(std::string_view new_name) override {
68 name = new_name;
69 return true;
70 }
71
72private:
73 u8 value;
74 std::size_t size;
75 std::string name;
76 VirtualDir parent;
77};
78
79} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index ec7f735b5..389c7e003 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -3,10 +3,64 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <cstring>
6#include <utility> 7#include <utility>
7#include "core/file_sys/vfs_vector.h" 8#include "core/file_sys/vfs_vector.h"
8 9
9namespace FileSys { 10namespace FileSys {
11VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
12 : data(std::move(initial_data)), parent(std::move(parent)), name(std::move(name)) {}
13
14VectorVfsFile::~VectorVfsFile() = default;
15
16std::string VectorVfsFile::GetName() const {
17 return name;
18}
19
20size_t VectorVfsFile::GetSize() const {
21 return data.size();
22}
23
24bool VectorVfsFile::Resize(size_t new_size) {
25 data.resize(new_size);
26 return true;
27}
28
29std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
30 return parent;
31}
32
33bool VectorVfsFile::IsWritable() const {
34 return true;
35}
36
37bool VectorVfsFile::IsReadable() const {
38 return true;
39}
40
41std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
42 const auto read = std::min(length, data.size() - offset);
43 std::memcpy(data_, data.data() + offset, read);
44 return read;
45}
46
47std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
48 if (offset + length > data.size())
49 data.resize(offset + length);
50 const auto write = std::min(length, data.size() - offset);
51 std::memcpy(data.data(), data_, write);
52 return write;
53}
54
55bool VectorVfsFile::Rename(std::string_view name_) {
56 name = name_;
57 return true;
58}
59
60void VectorVfsFile::Assign(std::vector<u8> new_data) {
61 data = std::move(new_data);
62}
63
10VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, 64VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
11 std::vector<VirtualDir> dirs_, std::string name_, 65 std::vector<VirtualDir> dirs_, std::string name_,
12 VirtualDir parent_) 66 VirtualDir parent_)
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index cba44a7a6..48a414c98 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -8,6 +8,31 @@
8 8
9namespace FileSys { 9namespace FileSys {
10 10
11// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
12class VectorVfsFile : public VfsFile {
13public:
14 explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
15 VirtualDir parent = nullptr);
16 ~VectorVfsFile() override;
17
18 std::string GetName() const override;
19 std::size_t GetSize() const override;
20 bool Resize(std::size_t new_size) override;
21 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
22 bool IsWritable() const override;
23 bool IsReadable() const override;
24 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
25 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
26 bool Rename(std::string_view name) override;
27
28 virtual void Assign(std::vector<u8> new_data);
29
30private:
31 std::vector<u8> data;
32 VirtualDir parent;
33 std::string name;
34};
35
11// An implementation of VfsDirectory that maintains two vectors for subdirectories and files. 36// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
12// Vector data is supplied upon construction. 37// Vector data is supplied upon construction.
13class VectorVfsDirectory : public VfsDirectory { 38class VectorVfsDirectory : public VfsDirectory {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 914bbe0a1..121f741fd 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -7,10 +7,12 @@
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/common_funcs.h" 8#include "common/common_funcs.h"
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "core/core.h"
10#include "core/hle/kernel/errors.h" 11#include "core/hle/kernel/errors.h"
11#include "core/hle/kernel/kernel.h" 12#include "core/hle/kernel/kernel.h"
12#include "core/hle/kernel/process.h" 13#include "core/hle/kernel/process.h"
13#include "core/hle/kernel/resource_limit.h" 14#include "core/hle/kernel/resource_limit.h"
15#include "core/hle/kernel/scheduler.h"
14#include "core/hle/kernel/thread.h" 16#include "core/hle/kernel/thread.h"
15#include "core/hle/kernel/vm_manager.h" 17#include "core/hle/kernel/vm_manager.h"
16#include "core/memory.h" 18#include "core/memory.h"
@@ -128,6 +130,91 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
128 Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this); 130 Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this);
129} 131}
130 132
133void Process::PrepareForTermination() {
134 status = ProcessStatus::Exited;
135
136 const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
137 for (auto& thread : thread_list) {
138 if (thread->owner_process != this)
139 continue;
140
141 if (thread == GetCurrentThread())
142 continue;
143
144 // TODO(Subv): When are the other running/ready threads terminated?
145 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
146 thread->status == ThreadStatus::WaitSynchAll,
147 "Exiting processes with non-waiting threads is currently unimplemented");
148
149 thread->Stop();
150 }
151 };
152
153 auto& system = Core::System::GetInstance();
154 stop_threads(system.Scheduler(0)->GetThreadList());
155 stop_threads(system.Scheduler(1)->GetThreadList());
156 stop_threads(system.Scheduler(2)->GetThreadList());
157 stop_threads(system.Scheduler(3)->GetThreadList());
158}
159
160/**
161 * Finds a free location for the TLS section of a thread.
162 * @param tls_slots The TLS page array of the thread's owner process.
163 * Returns a tuple of (page, slot, alloc_needed) where:
164 * page: The index of the first allocated TLS page that has free slots.
165 * slot: The index of the first free slot in the indicated page.
166 * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
167 */
168static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot(
169 const std::vector<std::bitset<8>>& tls_slots) {
170 // Iterate over all the allocated pages, and try to find one where not all slots are used.
171 for (std::size_t page = 0; page < tls_slots.size(); ++page) {
172 const auto& page_tls_slots = tls_slots[page];
173 if (!page_tls_slots.all()) {
174 // We found a page with at least one free slot, find which slot it is
175 for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
176 if (!page_tls_slots.test(slot)) {
177 return std::make_tuple(page, slot, false);
178 }
179 }
180 }
181 }
182
183 return std::make_tuple(0, 0, true);
184}
185
186VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) {
187 auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots);
188
189 if (needs_allocation) {
190 tls_slots.emplace_back(0); // The page is completely available at the start
191 available_page = tls_slots.size() - 1;
192 available_slot = 0; // Use the first slot in the new page
193
194 // Allocate some memory from the end of the linear heap for this region.
195 auto& tls_memory = thread.GetTLSMemory();
196 tls_memory->insert(tls_memory->end(), Memory::PAGE_SIZE, 0);
197
198 vm_manager.RefreshMemoryBlockMappings(tls_memory.get());
199
200 vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
201 tls_memory, 0, Memory::PAGE_SIZE, MemoryState::ThreadLocal);
202 }
203
204 tls_slots[available_page].set(available_slot);
205
206 return Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
207 available_slot * Memory::TLS_ENTRY_SIZE;
208}
209
210void Process::FreeTLSSlot(VAddr tls_address) {
211 const VAddr tls_base = tls_address - Memory::TLS_AREA_VADDR;
212 const VAddr tls_page = tls_base / Memory::PAGE_SIZE;
213 const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
214
215 tls_slots[tls_page].reset(tls_slot);
216}
217
131void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { 218void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
132 const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, 219 const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
133 MemoryState memory_state) { 220 MemoryState memory_state) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 81538f70c..04d74e572 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -131,6 +131,16 @@ public:
131 return HANDLE_TYPE; 131 return HANDLE_TYPE;
132 } 132 }
133 133
134 /// Gets the current status of the process
135 ProcessStatus GetStatus() const {
136 return status;
137 }
138
139 /// Gets the unique ID that identifies this particular process.
140 u32 GetProcessID() const {
141 return process_id;
142 }
143
134 /// Title ID corresponding to the process 144 /// Title ID corresponding to the process
135 u64 program_id; 145 u64 program_id;
136 146
@@ -154,11 +164,6 @@ public:
154 u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK; 164 u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK;
155 u32 allowed_thread_priority_mask = 0xFFFFFFFF; 165 u32 allowed_thread_priority_mask = 0xFFFFFFFF;
156 u32 is_virtual_address_memory_enabled = 0; 166 u32 is_virtual_address_memory_enabled = 0;
157 /// Current status of the process
158 ProcessStatus status;
159
160 /// The ID of this process
161 u32 process_id = 0;
162 167
163 /** 168 /**
164 * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them 169 * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
@@ -171,13 +176,42 @@ public:
171 */ 176 */
172 void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size); 177 void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size);
173 178
179 /**
180 * Prepares a process for termination by stopping all of its threads
181 * and clearing any other resources.
182 */
183 void PrepareForTermination();
184
174 void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr); 185 void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr);
175 186
176 /////////////////////////////////////////////////////////////////////////////////////////////// 187 ///////////////////////////////////////////////////////////////////////////////////////////////
177 // Memory Management 188 // Memory Management
178 189
190 // Marks the next available region as used and returns the address of the slot.
191 VAddr MarkNextAvailableTLSSlotAsUsed(Thread& thread);
192
193 // Frees a used TLS slot identified by the given address
194 void FreeTLSSlot(VAddr tls_address);
195
196 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
197 ResultCode HeapFree(VAddr target, u32 size);
198
199 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size);
200
201 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
202
179 VMManager vm_manager; 203 VMManager vm_manager;
180 204
205private:
206 explicit Process(KernelCore& kernel);
207 ~Process() override;
208
209 /// Current status of the process
210 ProcessStatus status;
211
212 /// The ID of this process
213 u32 process_id = 0;
214
181 // Memory used to back the allocations in the regular heap. A single vector is used to cover 215 // Memory used to back the allocations in the regular heap. A single vector is used to cover
182 // the entire virtual address space extents that bound the allocations, including any holes. 216 // the entire virtual address space extents that bound the allocations, including any holes.
183 // This makes deallocation and reallocation of holes fast and keeps process memory contiguous 217 // This makes deallocation and reallocation of holes fast and keeps process memory contiguous
@@ -197,17 +231,6 @@ public:
197 std::vector<std::bitset<8>> tls_slots; 231 std::vector<std::bitset<8>> tls_slots;
198 232
199 std::string name; 233 std::string name;
200
201 ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
202 ResultCode HeapFree(VAddr target, u32 size);
203
204 ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size);
205
206 ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
207
208private:
209 explicit Process(KernelCore& kernel);
210 ~Process() override;
211}; 234};
212 235
213} // namespace Kernel 236} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 69c812f16..9faf903cf 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -17,7 +17,7 @@ namespace Kernel {
17 17
18std::mutex Scheduler::scheduler_mutex; 18std::mutex Scheduler::scheduler_mutex;
19 19
20Scheduler::Scheduler(Core::ARM_Interface* cpu_core) : cpu_core(cpu_core) {} 20Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {}
21 21
22Scheduler::~Scheduler() { 22Scheduler::~Scheduler() {
23 for (auto& thread : thread_list) { 23 for (auto& thread : thread_list) {
@@ -59,9 +59,9 @@ void Scheduler::SwitchContext(Thread* new_thread) {
59 // Save context for previous thread 59 // Save context for previous thread
60 if (previous_thread) { 60 if (previous_thread) {
61 previous_thread->last_running_ticks = CoreTiming::GetTicks(); 61 previous_thread->last_running_ticks = CoreTiming::GetTicks();
62 cpu_core->SaveContext(previous_thread->context); 62 cpu_core.SaveContext(previous_thread->context);
63 // Save the TPIDR_EL0 system register in case it was modified. 63 // Save the TPIDR_EL0 system register in case it was modified.
64 previous_thread->tpidr_el0 = cpu_core->GetTPIDR_EL0(); 64 previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0();
65 65
66 if (previous_thread->status == ThreadStatus::Running) { 66 if (previous_thread->status == ThreadStatus::Running) {
67 // This is only the case when a reschedule is triggered without the current thread 67 // This is only the case when a reschedule is triggered without the current thread
@@ -91,10 +91,10 @@ void Scheduler::SwitchContext(Thread* new_thread) {
91 SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); 91 SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
92 } 92 }
93 93
94 cpu_core->LoadContext(new_thread->context); 94 cpu_core.LoadContext(new_thread->context);
95 cpu_core->SetTlsAddress(new_thread->GetTLSAddress()); 95 cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
96 cpu_core->SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); 96 cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
97 cpu_core->ClearExclusiveState(); 97 cpu_core.ClearExclusiveState();
98 } else { 98 } else {
99 current_thread = nullptr; 99 current_thread = nullptr;
100 // Note: We do not reset the current process and current page table when idling because 100 // Note: We do not reset the current process and current page table when idling because
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 744990c9b..2c94641ec 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -19,7 +19,7 @@ namespace Kernel {
19 19
20class Scheduler final { 20class Scheduler final {
21public: 21public:
22 explicit Scheduler(Core::ARM_Interface* cpu_core); 22 explicit Scheduler(Core::ARM_Interface& cpu_core);
23 ~Scheduler(); 23 ~Scheduler();
24 24
25 /// Returns whether there are any threads that are ready to run. 25 /// Returns whether there are any threads that are ready to run.
@@ -72,7 +72,7 @@ private:
72 72
73 SharedPtr<Thread> current_thread = nullptr; 73 SharedPtr<Thread> current_thread = nullptr;
74 74
75 Core::ARM_Interface* cpu_core; 75 Core::ARM_Interface& cpu_core;
76 76
77 static std::mutex scheduler_mutex; 77 static std::mutex scheduler_mutex;
78}; 78};
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 371fc439e..c9d212a4c 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -169,7 +169,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
169 return ERR_INVALID_HANDLE; 169 return ERR_INVALID_HANDLE;
170 } 170 }
171 171
172 *process_id = process->process_id; 172 *process_id = process->GetProcessID();
173 return RESULT_SUCCESS; 173 return RESULT_SUCCESS;
174} 174}
175 175
@@ -530,35 +530,13 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd
530 530
531/// Exits the current process 531/// Exits the current process
532static void ExitProcess() { 532static void ExitProcess() {
533 LOG_INFO(Kernel_SVC, "Process {} exiting", Core::CurrentProcess()->process_id); 533 auto& current_process = Core::CurrentProcess();
534 534
535 ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running, 535 LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
536 ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
536 "Process has already exited"); 537 "Process has already exited");
537 538
538 Core::CurrentProcess()->status = ProcessStatus::Exited; 539 current_process->PrepareForTermination();
539
540 auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) {
541 for (auto& thread : thread_list) {
542 if (thread->owner_process != Core::CurrentProcess())
543 continue;
544
545 if (thread == GetCurrentThread())
546 continue;
547
548 // TODO(Subv): When are the other running/ready threads terminated?
549 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
550 thread->status == ThreadStatus::WaitSynchAll,
551 "Exiting processes with non-waiting threads is currently unimplemented");
552
553 thread->Stop();
554 }
555 };
556
557 auto& system = Core::System::GetInstance();
558 stop_threads(system.Scheduler(0)->GetThreadList());
559 stop_threads(system.Scheduler(1)->GetThreadList());
560 stop_threads(system.Scheduler(2)->GetThreadList());
561 stop_threads(system.Scheduler(3)->GetThreadList());
562 540
563 // Kill the current thread 541 // Kill the current thread
564 GetCurrentThread()->Stop(); 542 GetCurrentThread()->Stop();
@@ -1039,7 +1017,7 @@ static const FunctionDef SVC_Table[] = {
1039 {0x2B, nullptr, "FlushDataCache"}, 1017 {0x2B, nullptr, "FlushDataCache"},
1040 {0x2C, nullptr, "MapPhysicalMemory"}, 1018 {0x2C, nullptr, "MapPhysicalMemory"},
1041 {0x2D, nullptr, "UnmapPhysicalMemory"}, 1019 {0x2D, nullptr, "UnmapPhysicalMemory"},
1042 {0x2E, nullptr, "GetNextThreadInfo"}, 1020 {0x2E, nullptr, "GetFutureThreadInfo"},
1043 {0x2F, nullptr, "GetLastThreadInfo"}, 1021 {0x2F, nullptr, "GetLastThreadInfo"},
1044 {0x30, nullptr, "GetResourceLimitLimitValue"}, 1022 {0x30, nullptr, "GetResourceLimitLimitValue"},
1045 {0x31, nullptr, "GetResourceLimitCurrentValue"}, 1023 {0x31, nullptr, "GetResourceLimitCurrentValue"},
@@ -1065,11 +1043,11 @@ static const FunctionDef SVC_Table[] = {
1065 {0x45, nullptr, "CreateEvent"}, 1043 {0x45, nullptr, "CreateEvent"},
1066 {0x46, nullptr, "Unknown"}, 1044 {0x46, nullptr, "Unknown"},
1067 {0x47, nullptr, "Unknown"}, 1045 {0x47, nullptr, "Unknown"},
1068 {0x48, nullptr, "AllocateUnsafeMemory"}, 1046 {0x48, nullptr, "MapPhysicalMemoryUnsafe"},
1069 {0x49, nullptr, "FreeUnsafeMemory"}, 1047 {0x49, nullptr, "UnmapPhysicalMemoryUnsafe"},
1070 {0x4A, nullptr, "SetUnsafeAllocationLimit"}, 1048 {0x4A, nullptr, "SetUnsafeLimit"},
1071 {0x4B, nullptr, "CreateJitMemory"}, 1049 {0x4B, nullptr, "CreateCodeMemory"},
1072 {0x4C, nullptr, "MapJitMemory"}, 1050 {0x4C, nullptr, "ControlCodeMemory"},
1073 {0x4D, nullptr, "SleepSystem"}, 1051 {0x4D, nullptr, "SleepSystem"},
1074 {0x4E, nullptr, "ReadWriteRegister"}, 1052 {0x4E, nullptr, "ReadWriteRegister"},
1075 {0x4F, nullptr, "SetProcessActivity"}, 1053 {0x4F, nullptr, "SetProcessActivity"},
@@ -1104,7 +1082,7 @@ static const FunctionDef SVC_Table[] = {
1104 {0x6C, nullptr, "SetHardwareBreakPoint"}, 1082 {0x6C, nullptr, "SetHardwareBreakPoint"},
1105 {0x6D, nullptr, "GetDebugThreadParam"}, 1083 {0x6D, nullptr, "GetDebugThreadParam"},
1106 {0x6E, nullptr, "Unknown"}, 1084 {0x6E, nullptr, "Unknown"},
1107 {0x6F, nullptr, "GetMemoryInfo"}, 1085 {0x6F, nullptr, "GetSystemInfo"},
1108 {0x70, nullptr, "CreatePort"}, 1086 {0x70, nullptr, "CreatePort"},
1109 {0x71, nullptr, "ManageNamedPort"}, 1087 {0x71, nullptr, "ManageNamedPort"},
1110 {0x72, nullptr, "ConnectToPort"}, 1088 {0x72, nullptr, "ConnectToPort"},
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index c2d7535c9..315f65338 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -65,10 +65,7 @@ void Thread::Stop() {
65 wait_objects.clear(); 65 wait_objects.clear();
66 66
67 // Mark the TLS slot in the thread's page as free. 67 // Mark the TLS slot in the thread's page as free.
68 const u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; 68 owner_process->FreeTLSSlot(tls_address);
69 const u64 tls_slot =
70 ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
71 Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot);
72} 69}
73 70
74void WaitCurrentThread_Sleep() { 71void WaitCurrentThread_Sleep() {
@@ -178,32 +175,6 @@ void Thread::ResumeFromWait() {
178} 175}
179 176
180/** 177/**
181 * Finds a free location for the TLS section of a thread.
182 * @param tls_slots The TLS page array of the thread's owner process.
183 * Returns a tuple of (page, slot, alloc_needed) where:
184 * page: The index of the first allocated TLS page that has free slots.
185 * slot: The index of the first free slot in the indicated page.
186 * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
187 */
188static std::tuple<std::size_t, std::size_t, bool> GetFreeThreadLocalSlot(
189 const std::vector<std::bitset<8>>& tls_slots) {
190 // Iterate over all the allocated pages, and try to find one where not all slots are used.
191 for (std::size_t page = 0; page < tls_slots.size(); ++page) {
192 const auto& page_tls_slots = tls_slots[page];
193 if (!page_tls_slots.all()) {
194 // We found a page with at least one free slot, find which slot it is
195 for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
196 if (!page_tls_slots.test(slot)) {
197 return std::make_tuple(page, slot, false);
198 }
199 }
200 }
201 }
202
203 return std::make_tuple(0, 0, true);
204}
205
206/**
207 * Resets a thread context, making it ready to be scheduled and run by the CPU 178 * Resets a thread context, making it ready to be scheduled and run by the CPU
208 * @param context Thread context to reset 179 * @param context Thread context to reset
209 * @param stack_top Address of the top of the stack 180 * @param stack_top Address of the top of the stack
@@ -264,32 +235,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
264 thread->owner_process = owner_process; 235 thread->owner_process = owner_process;
265 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id); 236 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id);
266 thread->scheduler->AddThread(thread, priority); 237 thread->scheduler->AddThread(thread, priority);
267 238 thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
268 // Find the next available TLS index, and mark it as used
269 auto& tls_slots = owner_process->tls_slots;
270
271 auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots);
272 if (needs_allocation) {
273 tls_slots.emplace_back(0); // The page is completely available at the start
274 available_page = tls_slots.size() - 1;
275 available_slot = 0; // Use the first slot in the new page
276
277 // Allocate some memory from the end of the linear heap for this region.
278 const std::size_t offset = thread->tls_memory->size();
279 thread->tls_memory->insert(thread->tls_memory->end(), Memory::PAGE_SIZE, 0);
280
281 auto& vm_manager = owner_process->vm_manager;
282 vm_manager.RefreshMemoryBlockMappings(thread->tls_memory.get());
283
284 vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
285 thread->tls_memory, 0, Memory::PAGE_SIZE,
286 MemoryState::ThreadLocal);
287 }
288
289 // Mark the slot as used
290 tls_slots[available_page].set(available_slot);
291 thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
292 available_slot * Memory::TLS_ENTRY_SIZE;
293 239
294 // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used 240 // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
295 // to initialize the context 241 // to initialize the context
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 91e9b79ec..4250144c3 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -62,6 +62,9 @@ enum class ThreadWakeupReason {
62 62
63class Thread final : public WaitObject { 63class Thread final : public WaitObject {
64public: 64public:
65 using TLSMemory = std::vector<u8>;
66 using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
67
65 /** 68 /**
66 * Creates and returns a new thread. The new thread is immediately scheduled 69 * Creates and returns a new thread. The new thread is immediately scheduled
67 * @param kernel The kernel instance this thread will be created under. 70 * @param kernel The kernel instance this thread will be created under.
@@ -134,6 +137,14 @@ public:
134 return thread_id; 137 return thread_id;
135 } 138 }
136 139
140 TLSMemoryPtr& GetTLSMemory() {
141 return tls_memory;
142 }
143
144 const TLSMemoryPtr& GetTLSMemory() const {
145 return tls_memory;
146 }
147
137 /** 148 /**
138 * Resumes a thread from waiting 149 * Resumes a thread from waiting
139 */ 150 */
@@ -269,7 +280,7 @@ private:
269 explicit Thread(KernelCore& kernel); 280 explicit Thread(KernelCore& kernel);
270 ~Thread() override; 281 ~Thread() override;
271 282
272 std::shared_ptr<std::vector<u8>> tls_memory = std::make_shared<std::vector<u8>>(); 283 TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
273}; 284};
274 285
275/** 286/**
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 06ac6372d..6073f4ecd 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -10,6 +10,7 @@
10#include "common/alignment.h" 10#include "common/alignment.h"
11#include "common/common_funcs.h" 11#include "common/common_funcs.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "core/core.h"
13#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
14#include "core/hle/kernel/event.h" 15#include "core/hle/kernel/event.h"
15#include "core/hle/kernel/hle_ipc.h" 16#include "core/hle/kernel/hle_ipc.h"
@@ -25,7 +26,7 @@ public:
25 {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"}, 26 {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
26 {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"}, 27 {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
27 {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"}, 28 {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
28 {3, nullptr, "GetAudioRendererState"}, 29 {3, &IAudioRenderer::GetAudioRendererState, "GetAudioRendererState"},
29 {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, 30 {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
30 {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, 31 {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
31 {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, 32 {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
@@ -62,6 +63,13 @@ private:
62 LOG_DEBUG(Service_Audio, "called"); 63 LOG_DEBUG(Service_Audio, "called");
63 } 64 }
64 65
66 void GetAudioRendererState(Kernel::HLERequestContext& ctx) {
67 IPC::ResponseBuilder rb{ctx, 3};
68 rb.Push(RESULT_SUCCESS);
69 rb.Push<u32>(static_cast<u32>(renderer->GetStreamState()));
70 LOG_DEBUG(Service_Audio, "called");
71 }
72
65 void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) { 73 void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
66 IPC::ResponseBuilder rb{ctx, 3}; 74 IPC::ResponseBuilder rb{ctx, 3};
67 rb.Push(RESULT_SUCCESS); 75 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index b436ce4e6..2212b2cdd 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -2,8 +2,17 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array>
6#include <cstring>
7#include <ctime>
8#include <fmt/time.h>
9#include "common/file_util.h"
5#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/scm_rev.h"
12#include "common/swap.h"
13#include "core/core.h"
6#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
15#include "core/hle/kernel/process.h"
7#include "core/hle/service/fatal/fatal.h" 16#include "core/hle/service/fatal/fatal.h"
8#include "core/hle/service/fatal/fatal_p.h" 17#include "core/hle/service/fatal/fatal_p.h"
9#include "core/hle/service/fatal/fatal_u.h" 18#include "core/hle/service/fatal/fatal_u.h"
@@ -15,16 +24,142 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
15 24
16Module::Interface::~Interface() = default; 25Module::Interface::~Interface() = default;
17 26
27struct FatalInfo {
28 std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or
29 // not(find a game which has non zero valeus)
30 u64_le unk0{};
31 u64_le unk1{};
32 u64_le unk2{};
33 u64_le unk3{};
34 u64_le unk4{};
35 u64_le unk5{};
36 u64_le unk6{};
37
38 std::array<u64_le, 32> backtrace{};
39 u64_le unk7{};
40 u64_le unk8{};
41 u32_le backtrace_size{};
42 u32_le unk9{};
43 u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding?
44};
45static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size");
46
47enum class FatalType : u32 {
48 ErrorReportAndScreen = 0,
49 ErrorReport = 1,
50 ErrorScreen = 2,
51};
52
53static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
54 const auto title_id = Core::CurrentProcess()->program_id;
55 std::string crash_report =
56 fmt::format("Yuzu {}-{} crash report\n"
57 "Title ID: {:016x}\n"
58 "Result: 0x{:X} ({:04}-{:04d})\n"
59 "\n",
60 Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
61 2000 + static_cast<u32>(error_code.module.Value()),
62 static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7);
63 if (info.backtrace_size != 0x0) {
64 crash_report += "Registers:\n";
65 // TODO(ogniK): This is just a guess, find a game which actually has non zero values
66 for (size_t i = 0; i < info.registers.size(); i++) {
67 crash_report +=
68 fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]);
69 }
70 crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0);
71 crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1);
72 crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2);
73 crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3);
74 crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4);
75 crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5);
76 crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6);
77 crash_report += "\nBacktrace:\n";
78 for (size_t i = 0; i < info.backtrace_size; i++) {
79 crash_report +=
80 fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]);
81 }
82 crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7);
83 crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8);
84 crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9);
85 crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10);
86 }
87
88 LOG_ERROR(Service_Fatal, "{}", crash_report);
89
90 const std::string crashreport_dir =
91 FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs";
92
93 if (!FileUtil::CreateFullPath(crashreport_dir)) {
94 LOG_ERROR(
95 Service_Fatal,
96 "Unable to create crash report directory. Possible log directory permissions issue.");
97 return;
98 }
99
100 const std::time_t t = std::time(nullptr);
101 const std::string crashreport_filename =
102 fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t));
103
104 auto file = FileUtil::IOFile(crashreport_filename, "wb");
105 if (file.IsOpen()) {
106 file.WriteString(crash_report);
107 LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename);
108 } else {
109 LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename);
110 }
111}
112
113static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
114 LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type));
115 switch (fatal_type) {
116 case FatalType::ErrorReportAndScreen:
117 GenerateErrorReport(error_code, info);
118 [[fallthrough]];
119 case FatalType::ErrorScreen:
120 // Since we have no fatal:u error screen. We should just kill execution instead
121 ASSERT(false);
122 break;
123 // Should not throw a fatal screen but should generate an error report
124 case FatalType::ErrorReport:
125 GenerateErrorReport(error_code, info);
126 break;
127 };
128}
129
130void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
131 LOG_ERROR(Service_Fatal, "called");
132 IPC::RequestParser rp{ctx};
133 auto error_code = rp.Pop<ResultCode>();
134
135 ThrowFatalError(error_code, FatalType::ErrorScreen, {});
136 IPC::ResponseBuilder rb{ctx, 2};
137 rb.Push(RESULT_SUCCESS);
138}
139
18void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { 140void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
141 LOG_ERROR(Service_Fatal, "called");
19 IPC::RequestParser rp(ctx); 142 IPC::RequestParser rp(ctx);
20 u32 error_code = rp.Pop<u32>(); 143 auto error_code = rp.Pop<ResultCode>();
21 LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code); 144 auto fatal_type = rp.PopEnum<FatalType>();
145
146 ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy
22 IPC::ResponseBuilder rb{ctx, 2}; 147 IPC::ResponseBuilder rb{ctx, 2};
23 rb.Push(RESULT_SUCCESS); 148 rb.Push(RESULT_SUCCESS);
24} 149}
25 150
26void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { 151void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
27 LOG_WARNING(Service_Fatal, "(STUBBED) called"); 152 LOG_ERROR(Service_Fatal, "called");
153 IPC::RequestParser rp(ctx);
154 auto error_code = rp.Pop<ResultCode>();
155 auto fatal_type = rp.PopEnum<FatalType>();
156 auto fatal_info = ctx.ReadBuffer();
157 FatalInfo info{};
158
159 ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!");
160 std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo));
161
162 ThrowFatalError(error_code, fatal_type, info);
28 IPC::ResponseBuilder rb{ctx, 2}; 163 IPC::ResponseBuilder rb{ctx, 2};
29 rb.Push(RESULT_SUCCESS); 164 rb.Push(RESULT_SUCCESS);
30} 165}
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
index 4d9a5be52..09371ff7f 100644
--- a/src/core/hle/service/fatal/fatal.h
+++ b/src/core/hle/service/fatal/fatal.h
@@ -15,6 +15,7 @@ public:
15 explicit Interface(std::shared_ptr<Module> module, const char* name); 15 explicit Interface(std::shared_ptr<Module> module, const char* name);
16 ~Interface() override; 16 ~Interface() override;
17 17
18 void ThrowFatal(Kernel::HLERequestContext& ctx);
18 void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); 19 void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx);
19 void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); 20 void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx);
20 21
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
index befc307cf..1572a2051 100644
--- a/src/core/hle/service/fatal/fatal_u.cpp
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -8,7 +8,7 @@ namespace Service::Fatal {
8 8
9Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { 9Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
10 static const FunctionInfo functions[] = { 10 static const FunctionInfo functions[] = {
11 {0, nullptr, "ThrowFatal"}, 11 {0, &Fatal_U::ThrowFatal, "ThrowFatal"},
12 {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, 12 {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
13 {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"}, 13 {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"},
14 }; 14 };
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index d349ee686..aed2abb71 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
343 return sdmc_factory->GetSDMCContents(); 343 return sdmc_factory->GetSDMCContents();
344} 344}
345 345
346FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
347 LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id);
348
349 if (bis_factory == nullptr)
350 return nullptr;
351
352 return bis_factory->GetModificationLoadRoot(title_id);
353}
354
346void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { 355void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
347 if (overwrite) { 356 if (overwrite) {
348 bis_factory = nullptr; 357 bis_factory = nullptr;
@@ -354,9 +363,11 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
354 FileSys::Mode::ReadWrite); 363 FileSys::Mode::ReadWrite);
355 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 364 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
356 FileSys::Mode::ReadWrite); 365 FileSys::Mode::ReadWrite);
366 auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
367 FileSys::Mode::ReadWrite);
357 368
358 if (bis_factory == nullptr) 369 if (bis_factory == nullptr)
359 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); 370 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
360 if (save_data_factory == nullptr) 371 if (save_data_factory == nullptr)
361 save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); 372 save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
362 if (sdmc_factory == nullptr) 373 if (sdmc_factory == nullptr)
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index aab65a2b8..7039a2247 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -52,6 +52,8 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
52std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); 52std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
53std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); 53std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
54 54
55FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
56
55// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function 57// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
56// above is called. 58// above is called.
57void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); 59void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index e587ad0d8..872e3c344 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -2,6 +2,11 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/swap.h"
6#include "core/core.h"
7#include "core/core_timing.h"
8#include "core/hle/ipc_helpers.h"
9#include "core/hle/kernel/shared_memory.h"
5#include "core/hle/service/hid/irs.h" 10#include "core/hle/service/hid/irs.h"
6 11
7namespace Service::HID { 12namespace Service::HID {
@@ -9,28 +14,145 @@ namespace Service::HID {
9IRS::IRS() : ServiceFramework{"irs"} { 14IRS::IRS() : ServiceFramework{"irs"} {
10 // clang-format off 15 // clang-format off
11 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
12 {302, nullptr, "ActivateIrsensor"}, 17 {302, &IRS::ActivateIrsensor, "ActivateIrsensor"},
13 {303, nullptr, "DeactivateIrsensor"}, 18 {303, &IRS::DeactivateIrsensor, "DeactivateIrsensor"},
14 {304, nullptr, "GetIrsensorSharedMemoryHandle"}, 19 {304, &IRS::GetIrsensorSharedMemoryHandle, "GetIrsensorSharedMemoryHandle"},
15 {305, nullptr, "StopImageProcessor"}, 20 {305, &IRS::StopImageProcessor, "StopImageProcessor"},
16 {306, nullptr, "RunMomentProcessor"}, 21 {306, &IRS::RunMomentProcessor, "RunMomentProcessor"},
17 {307, nullptr, "RunClusteringProcessor"}, 22 {307, &IRS::RunClusteringProcessor, "RunClusteringProcessor"},
18 {308, nullptr, "RunImageTransferProcessor"}, 23 {308, &IRS::RunImageTransferProcessor, "RunImageTransferProcessor"},
19 {309, nullptr, "GetImageTransferProcessorState"}, 24 {309, &IRS::GetImageTransferProcessorState, "GetImageTransferProcessorState"},
20 {310, nullptr, "RunTeraPluginProcessor"}, 25 {310, &IRS::RunTeraPluginProcessor, "RunTeraPluginProcessor"},
21 {311, nullptr, "GetNpadIrCameraHandle"}, 26 {311, &IRS::GetNpadIrCameraHandle, "GetNpadIrCameraHandle"},
22 {312, nullptr, "RunPointingProcessor"}, 27 {312, &IRS::RunPointingProcessor, "RunPointingProcessor"},
23 {313, nullptr, "SuspendImageProcessor"}, 28 {313, &IRS::SuspendImageProcessor, "SuspendImageProcessor"},
24 {314, nullptr, "CheckFirmwareVersion"}, 29 {314, &IRS::CheckFirmwareVersion, "CheckFirmwareVersion"},
25 {315, nullptr, "SetFunctionLevel"}, 30 {315, &IRS::SetFunctionLevel, "SetFunctionLevel"},
26 {316, nullptr, "RunImageTransferExProcessor"}, 31 {316, &IRS::RunImageTransferExProcessor, "RunImageTransferExProcessor"},
27 {317, nullptr, "RunIrLedProcessor"}, 32 {317, &IRS::RunIrLedProcessor, "RunIrLedProcessor"},
28 {318, nullptr, "StopImageProcessorAsync"}, 33 {318, &IRS::StopImageProcessorAsync, "StopImageProcessorAsync"},
29 {319, nullptr, "ActivateIrsensorWithFunctionLevel"}, 34 {319, &IRS::ActivateIrsensorWithFunctionLevel, "ActivateIrsensorWithFunctionLevel"},
30 }; 35 };
31 // clang-format on 36 // clang-format on
32 37
33 RegisterHandlers(functions); 38 RegisterHandlers(functions);
39
40 auto& kernel = Core::System::GetInstance().Kernel();
41 shared_mem = Kernel::SharedMemory::Create(
42 kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite,
43 Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory");
44}
45
46void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
47 IPC::ResponseBuilder rb{ctx, 2};
48 rb.Push(RESULT_SUCCESS);
49 LOG_WARNING(Service_IRS, "(STUBBED) called");
50}
51
52void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
53 IPC::ResponseBuilder rb{ctx, 2};
54 rb.Push(RESULT_SUCCESS);
55 LOG_WARNING(Service_IRS, "(STUBBED) called");
56}
57
58void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
59 IPC::ResponseBuilder rb{ctx, 2, 1};
60 rb.Push(RESULT_SUCCESS);
61 rb.PushCopyObjects(shared_mem);
62 LOG_DEBUG(Service_IRS, "called");
63}
64
65void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
66 IPC::ResponseBuilder rb{ctx, 2};
67 rb.Push(RESULT_SUCCESS);
68 LOG_WARNING(Service_IRS, "(STUBBED) called");
69}
70
71void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
72 IPC::ResponseBuilder rb{ctx, 2};
73 rb.Push(RESULT_SUCCESS);
74 LOG_WARNING(Service_IRS, "(STUBBED) called");
75}
76
77void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
78 IPC::ResponseBuilder rb{ctx, 2};
79 rb.Push(RESULT_SUCCESS);
80 LOG_WARNING(Service_IRS, "(STUBBED) called");
81}
82
83void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
84 IPC::ResponseBuilder rb{ctx, 2};
85 rb.Push(RESULT_SUCCESS);
86 LOG_WARNING(Service_IRS, "(STUBBED) called");
87}
88
89void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
90 IPC::ResponseBuilder rb{ctx, 5};
91 rb.Push(RESULT_SUCCESS);
92 rb.PushRaw<u64>(CoreTiming::GetTicks());
93 rb.PushRaw<u32>(0);
94 LOG_WARNING(Service_IRS, "(STUBBED) called");
95}
96
97void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
98 IPC::ResponseBuilder rb{ctx, 2};
99 rb.Push(RESULT_SUCCESS);
100 LOG_WARNING(Service_IRS, "(STUBBED) called");
101}
102
103void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
104 IPC::ResponseBuilder rb{ctx, 3};
105 rb.Push(RESULT_SUCCESS);
106 rb.PushRaw<u32>(device_handle);
107 LOG_WARNING(Service_IRS, "(STUBBED) called");
108}
109
110void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
111 IPC::ResponseBuilder rb{ctx, 2};
112 rb.Push(RESULT_SUCCESS);
113 LOG_WARNING(Service_IRS, "(STUBBED) called");
114}
115
116void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(RESULT_SUCCESS);
119 LOG_WARNING(Service_IRS, "(STUBBED) called");
120}
121
122void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
123 IPC::ResponseBuilder rb{ctx, 2};
124 rb.Push(RESULT_SUCCESS);
125 LOG_WARNING(Service_IRS, "(STUBBED) called");
126}
127
128void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) {
129 IPC::ResponseBuilder rb{ctx, 2};
130 rb.Push(RESULT_SUCCESS);
131 LOG_WARNING(Service_IRS, "(STUBBED) called");
132}
133
134void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
135 IPC::ResponseBuilder rb{ctx, 2};
136 rb.Push(RESULT_SUCCESS);
137 LOG_WARNING(Service_IRS, "(STUBBED) called");
138}
139
140void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
141 IPC::ResponseBuilder rb{ctx, 2};
142 rb.Push(RESULT_SUCCESS);
143 LOG_WARNING(Service_IRS, "(STUBBED) called");
144}
145
146void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
147 IPC::ResponseBuilder rb{ctx, 2};
148 rb.Push(RESULT_SUCCESS);
149 LOG_WARNING(Service_IRS, "(STUBBED) called");
150}
151
152void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
153 IPC::ResponseBuilder rb{ctx, 2};
154 rb.Push(RESULT_SUCCESS);
155 LOG_WARNING(Service_IRS, "(STUBBED) called");
34} 156}
35 157
36IRS::~IRS() = default; 158IRS::~IRS() = default;
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 6fb16b45d..12de6bfb3 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -4,14 +4,41 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "core/hle/kernel/object.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8 9
10namespace Kernel {
11class SharedMemory;
12}
13
9namespace Service::HID { 14namespace Service::HID {
10 15
11class IRS final : public ServiceFramework<IRS> { 16class IRS final : public ServiceFramework<IRS> {
12public: 17public:
13 explicit IRS(); 18 explicit IRS();
14 ~IRS() override; 19 ~IRS() override;
20
21private:
22 void ActivateIrsensor(Kernel::HLERequestContext& ctx);
23 void DeactivateIrsensor(Kernel::HLERequestContext& ctx);
24 void GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx);
25 void StopImageProcessor(Kernel::HLERequestContext& ctx);
26 void RunMomentProcessor(Kernel::HLERequestContext& ctx);
27 void RunClusteringProcessor(Kernel::HLERequestContext& ctx);
28 void RunImageTransferProcessor(Kernel::HLERequestContext& ctx);
29 void GetImageTransferProcessorState(Kernel::HLERequestContext& ctx);
30 void RunTeraPluginProcessor(Kernel::HLERequestContext& ctx);
31 void GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx);
32 void RunPointingProcessor(Kernel::HLERequestContext& ctx);
33 void SuspendImageProcessor(Kernel::HLERequestContext& ctx);
34 void CheckFirmwareVersion(Kernel::HLERequestContext& ctx);
35 void SetFunctionLevel(Kernel::HLERequestContext& ctx);
36 void RunImageTransferExProcessor(Kernel::HLERequestContext& ctx);
37 void RunIrLedProcessor(Kernel::HLERequestContext& ctx);
38 void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
39 void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
40 Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
41 const u32 device_handle{0xABCD};
15}; 42};
16 43
17class IRS_SYS final : public ServiceFramework<IRS_SYS> { 44class IRS_SYS final : public ServiceFramework<IRS_SYS> {
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index f8d2127d9..8c07a05c2 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/core.h"
6#include "core/hle/ipc_helpers.h" 7#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/event.h" 8#include "core/hle/kernel/event.h"
8#include "core/hle/service/hid/hid.h" 9#include "core/hle/service/hid/hid.h"
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index c1737defa..261ad539c 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -4,6 +4,7 @@
4 4
5#include <chrono> 5#include <chrono>
6#include <ctime> 6#include <ctime>
7#include "core/core.h"
7#include "core/hle/ipc_helpers.h" 8#include "core/hle/ipc_helpers.h"
8#include "core/hle/kernel/event.h" 9#include "core/hle/kernel/event.h"
9#include "core/hle/service/nim/nim.h" 10#include "core/hle/service/nim/nim.h"
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index cdf328a26..98f6e4111 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -2,8 +2,11 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h"
5#include "common/logging/log.h" 6#include "common/logging/log.h"
6#include "core/hle/ipc_helpers.h" 7#include "core/hle/ipc_helpers.h"
8#include "core/hle/kernel/client_session.h"
9#include "core/hle/kernel/server_session.h"
7#include "core/hle/kernel/session.h" 10#include "core/hle/kernel/session.h"
8#include "core/hle/service/sm/controller.h" 11#include "core/hle/service/sm/controller.h"
9 12
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index fe0a318ee..bc4f7a437 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -103,6 +103,7 @@ public:
103 } 103 }
104 104
105private: 105private:
106 u32 ssl_version{};
106 void CreateContext(Kernel::HLERequestContext& ctx) { 107 void CreateContext(Kernel::HLERequestContext& ctx) {
107 LOG_WARNING(Service_SSL, "(STUBBED) called"); 108 LOG_WARNING(Service_SSL, "(STUBBED) called");
108 109
@@ -112,10 +113,9 @@ private:
112 } 113 }
113 114
114 void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { 115 void SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
115 LOG_WARNING(Service_SSL, "(STUBBED) called"); 116 LOG_DEBUG(Service_SSL, "called");
116 IPC::RequestParser rp{ctx}; 117 IPC::RequestParser rp{ctx};
117 u32 unk1 = rp.Pop<u32>(); // Probably minor/major? 118 ssl_version = rp.Pop<u32>();
118 u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does
119 119
120 IPC::ResponseBuilder rb{ctx, 2}; 120 IPC::ResponseBuilder rb{ctx, 2};
121 rb.Push(RESULT_SUCCESS); 121 rb.Push(RESULT_SUCCESS);
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index b81b0723d..16cdfc7e2 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -461,7 +461,11 @@ public:
461 u32 entry; 461 u32 entry;
462 } macros; 462 } macros;
463 463
464 INSERT_PADDING_WORDS(0x1B8); 464 INSERT_PADDING_WORDS(0x189);
465
466 u32 tfb_enabled;
467
468 INSERT_PADDING_WORDS(0x2E);
465 469
466 RenderTargetConfig rt[NumRenderTargets]; 470 RenderTargetConfig rt[NumRenderTargets];
467 471
@@ -594,7 +598,9 @@ public:
594 598
595 u32 depth_write_enabled; 599 u32 depth_write_enabled;
596 600
597 INSERT_PADDING_WORDS(0x7); 601 u32 alpha_test_enabled;
602
603 INSERT_PADDING_WORDS(0x6);
598 604
599 u32 d3d_cull_mode; 605 u32 d3d_cull_mode;
600 606
@@ -977,6 +983,7 @@ private:
977 "Field " #field_name " has invalid position") 983 "Field " #field_name " has invalid position")
978 984
979ASSERT_REG_POSITION(macros, 0x45); 985ASSERT_REG_POSITION(macros, 0x45);
986ASSERT_REG_POSITION(tfb_enabled, 0x1D1);
980ASSERT_REG_POSITION(rt, 0x200); 987ASSERT_REG_POSITION(rt, 0x200);
981ASSERT_REG_POSITION(viewport_transform[0], 0x280); 988ASSERT_REG_POSITION(viewport_transform[0], 0x280);
982ASSERT_REG_POSITION(viewport, 0x300); 989ASSERT_REG_POSITION(viewport, 0x300);
@@ -996,6 +1003,7 @@ ASSERT_REG_POSITION(zeta_height, 0x48b);
996ASSERT_REG_POSITION(depth_test_enable, 0x4B3); 1003ASSERT_REG_POSITION(depth_test_enable, 0x4B3);
997ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); 1004ASSERT_REG_POSITION(independent_blend_enable, 0x4B9);
998ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); 1005ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
1006ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB);
999ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); 1007ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
1000ASSERT_REG_POSITION(depth_test_func, 0x4C3); 1008ASSERT_REG_POSITION(depth_test_func, 0x4C3);
1001ASSERT_REG_POSITION(blend, 0x4CF); 1009ASSERT_REG_POSITION(blend, 0x4CF);
diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp
index e4e5f9e5e..59e28b22d 100644
--- a/src/video_core/engines/maxwell_compute.cpp
+++ b/src/video_core/engines/maxwell_compute.cpp
@@ -2,12 +2,29 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/logging/log.h"
6#include "core/core.h"
5#include "video_core/engines/maxwell_compute.h" 7#include "video_core/engines/maxwell_compute.h"
6 8
7namespace Tegra { 9namespace Tegra {
8namespace Engines { 10namespace Engines {
9 11
10void MaxwellCompute::WriteReg(u32 method, u32 value) {} 12void MaxwellCompute::WriteReg(u32 method, u32 value) {
13 ASSERT_MSG(method < Regs::NUM_REGS,
14 "Invalid MaxwellCompute register, increase the size of the Regs structure");
15
16 regs.reg_array[method] = value;
17
18 switch (method) {
19 case MAXWELL_COMPUTE_REG_INDEX(compute): {
20 LOG_CRITICAL(HW_GPU, "Compute shaders are not implemented");
21 UNREACHABLE();
22 break;
23 }
24 default:
25 break;
26 }
27}
11 28
12} // namespace Engines 29} // namespace Engines
13} // namespace Tegra 30} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h
index 2b3e4ced6..6ea934fb9 100644
--- a/src/video_core/engines/maxwell_compute.h
+++ b/src/video_core/engines/maxwell_compute.h
@@ -4,17 +4,53 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
8#include "common/assert.h"
9#include "common/bit_field.h"
10#include "common/common_funcs.h"
7#include "common/common_types.h" 11#include "common/common_types.h"
8 12
9namespace Tegra::Engines { 13namespace Tegra::Engines {
10 14
15#define MAXWELL_COMPUTE_REG_INDEX(field_name) \
16 (offsetof(Tegra::Engines::MaxwellCompute::Regs, field_name) / sizeof(u32))
17
11class MaxwellCompute final { 18class MaxwellCompute final {
12public: 19public:
13 MaxwellCompute() = default; 20 MaxwellCompute() = default;
14 ~MaxwellCompute() = default; 21 ~MaxwellCompute() = default;
15 22
23 struct Regs {
24 static constexpr std::size_t NUM_REGS = 0xCF8;
25
26 union {
27 struct {
28 INSERT_PADDING_WORDS(0x281);
29
30 union {
31 u32 compute_end;
32 BitField<0, 1, u32> unknown;
33 } compute;
34
35 INSERT_PADDING_WORDS(0xA76);
36 };
37 std::array<u32, NUM_REGS> reg_array;
38 };
39 } regs{};
40
41 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32),
42 "MaxwellCompute Regs has wrong size");
43
16 /// Write the value to the register identified by method. 44 /// Write the value to the register identified by method.
17 void WriteReg(u32 method, u32 value); 45 void WriteReg(u32 method, u32 value);
18}; 46};
19 47
48#define ASSERT_REG_POSITION(field_name, position) \
49 static_assert(offsetof(MaxwellCompute::Regs, field_name) == position * 4, \
50 "Field " #field_name " has invalid position")
51
52ASSERT_REG_POSITION(compute, 0x281);
53
54#undef ASSERT_REG_POSITION
55
20} // namespace Tegra::Engines 56} // namespace Tegra::Engines
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 7e1de0fa1..b1f137b9c 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -5,9 +5,8 @@
5#pragma once 5#pragma once
6 6
7#include <bitset> 7#include <bitset>
8#include <cstring>
9#include <map>
10#include <string> 8#include <string>
9#include <tuple>
11#include <vector> 10#include <vector>
12 11
13#include <boost/optional.hpp> 12#include <boost/optional.hpp>
@@ -315,17 +314,29 @@ enum class TextureMiscMode : u64 {
315 PTP, 314 PTP,
316}; 315};
317 316
318enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 }; 317enum class IpaInterpMode : u64 {
319enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 }; 318 Linear = 0,
319 Perspective = 1,
320 Flat = 2,
321 Sc = 3,
322};
323
324enum class IpaSampleMode : u64 {
325 Default = 0,
326 Centroid = 1,
327 Offset = 2,
328};
320 329
321struct IpaMode { 330struct IpaMode {
322 IpaInterpMode interpolation_mode; 331 IpaInterpMode interpolation_mode;
323 IpaSampleMode sampling_mode; 332 IpaSampleMode sampling_mode;
324 inline bool operator==(const IpaMode& a) { 333
325 return (a.interpolation_mode == interpolation_mode) && (a.sampling_mode == sampling_mode); 334 bool operator==(const IpaMode& a) const {
335 return std::tie(interpolation_mode, sampling_mode) ==
336 std::tie(a.interpolation_mode, a.sampling_mode);
326 } 337 }
327 inline bool operator!=(const IpaMode& a) { 338 bool operator!=(const IpaMode& a) const {
328 return !((*this) == a); 339 return !operator==(a);
329 } 340 }
330}; 341};
331 342
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 70fb54507..44850d193 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -450,6 +450,8 @@ void RasterizerOpenGL::DrawArrays() {
450 SyncBlendState(); 450 SyncBlendState();
451 SyncLogicOpState(); 451 SyncLogicOpState();
452 SyncCullMode(); 452 SyncCullMode();
453 SyncAlphaTest();
454 SyncTransformFeedback();
453 455
454 // TODO(bunnei): Sync framebuffer_scale uniform here 456 // TODO(bunnei): Sync framebuffer_scale uniform here
455 // TODO(bunnei): Sync scissorbox uniform(s) here 457 // TODO(bunnei): Sync scissorbox uniform(s) here
@@ -883,4 +885,24 @@ void RasterizerOpenGL::SyncLogicOpState() {
883 state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); 885 state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
884} 886}
885 887
888void RasterizerOpenGL::SyncAlphaTest() {
889 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
890
891 // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be
892 // implemented with a test+discard in fragment shaders.
893 if (regs.alpha_test_enabled != 0) {
894 LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented");
895 UNREACHABLE();
896 }
897}
898
899void RasterizerOpenGL::SyncTransformFeedback() {
900 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
901
902 if (regs.tfb_enabled != 0) {
903 LOG_CRITICAL(Render_OpenGL, "Transform feedbacks are not implemented");
904 UNREACHABLE();
905 }
906}
907
886} // namespace OpenGL 908} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index bf9560bdc..c3f1e14bf 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -158,6 +158,12 @@ private:
158 /// Syncs the LogicOp state to match the guest state 158 /// Syncs the LogicOp state to match the guest state
159 void SyncLogicOpState(); 159 void SyncLogicOpState();
160 160
161 /// Syncs the alpha test state to match the guest state
162 void SyncAlphaTest();
163
164 /// Syncs the transform feedback state to match the guest state
165 void SyncTransformFeedback();
166
161 bool has_ARB_direct_state_access = false; 167 bool has_ARB_direct_state_access = false;
162 bool has_ARB_multi_bind = false; 168 bool has_ARB_multi_bind = false;
163 bool has_ARB_separate_shader_objects = false; 169 bool has_ARB_separate_shader_objects = false;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 86682d7cb..24a540258 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -141,8 +141,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
141 {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, 141 {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
142 true}, // BC7U 142 true}, // BC7U
143 {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, 143 {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8,
144 ComponentType::UNorm, true}, // BC6H_UF16 144 ComponentType::Float, true}, // BC6H_UF16
145 {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, 145 {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float,
146 true}, // BC6H_SF16 146 true}, // BC6H_SF16
147 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 147 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
148 {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U 148 {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U
@@ -501,6 +501,9 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
501 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR); 501 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
502 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 502 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
503 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 503 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
504
505 VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
506 SurfaceParams::SurfaceTargetName(params.target));
504} 507}
505 508
506static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { 509static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index d7a4bc37f..80c5f324b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -137,6 +137,27 @@ struct SurfaceParams {
137 } 137 }
138 } 138 }
139 139
140 static std::string SurfaceTargetName(SurfaceTarget target) {
141 switch (target) {
142 case SurfaceTarget::Texture1D:
143 return "Texture1D";
144 case SurfaceTarget::Texture2D:
145 return "Texture2D";
146 case SurfaceTarget::Texture3D:
147 return "Texture3D";
148 case SurfaceTarget::Texture1DArray:
149 return "Texture1DArray";
150 case SurfaceTarget::Texture2DArray:
151 return "Texture2DArray";
152 case SurfaceTarget::TextureCubemap:
153 return "TextureCubemap";
154 default:
155 LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
156 UNREACHABLE();
157 return fmt::format("TextureUnknown({})", static_cast<u32>(target));
158 }
159 }
160
140 /** 161 /**
141 * Gets the compression factor for the specified PixelFormat. This applies to just the 162 * Gets the compression factor for the specified PixelFormat. This applies to just the
142 * "compressed width" and "compressed height", not the overall compression factor of a 163 * "compressed width" and "compressed height", not the overall compression factor of a
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 894fe6eae..7cd8f91e4 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -8,6 +8,7 @@
8#include "video_core/engines/maxwell_3d.h" 8#include "video_core/engines/maxwell_3d.h"
9#include "video_core/renderer_opengl/gl_shader_cache.h" 9#include "video_core/renderer_opengl/gl_shader_cache.h"
10#include "video_core/renderer_opengl/gl_shader_manager.h" 10#include "video_core/renderer_opengl/gl_shader_manager.h"
11#include "video_core/utils.h"
11 12
12namespace OpenGL { 13namespace OpenGL {
13 14
@@ -83,6 +84,7 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
83 shader.Create(program_result.first.c_str(), gl_type); 84 shader.Create(program_result.first.c_str(), gl_type);
84 program.Create(true, shader.handle); 85 program.Create(true, shader.handle);
85 SetShaderUniformBlockBindings(program.handle); 86 SetShaderUniformBlockBindings(program.handle);
87 VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
86} 88}
87 89
88GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { 90GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index af99132ba..e5173e20a 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -4,6 +4,7 @@
4 4
5#include <iterator> 5#include <iterator>
6#include <glad/glad.h> 6#include <glad/glad.h>
7#include "common/assert.h"
7#include "common/logging/log.h" 8#include "common/logging/log.h"
8#include "video_core/renderer_opengl/gl_state.h" 9#include "video_core/renderer_opengl/gl_state.h"
9 10
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index e3e24b9e7..9a93029d8 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -7,12 +7,8 @@
7#include <array> 7#include <array>
8#include <glad/glad.h> 8#include <glad/glad.h>
9 9
10#include "video_core/engines/maxwell_3d.h"
11
12namespace OpenGL { 10namespace OpenGL {
13 11
14using Regs = Tegra::Engines::Maxwell3D::Regs;
15
16namespace TextureUnits { 12namespace TextureUnits {
17 13
18struct TextureUnit { 14struct TextureUnit {
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
index 664f3ca20..e409228cc 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -74,7 +74,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a
74 } 74 }
75 } 75 }
76 76
77 if (invalidate | !persistent) { 77 if (invalidate || !persistent) {
78 GLbitfield flags = GL_MAP_WRITE_BIT | (persistent ? GL_MAP_PERSISTENT_BIT : 0) | 78 GLbitfield flags = GL_MAP_WRITE_BIT | (persistent ? GL_MAP_PERSISTENT_BIT : 0) |
79 (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) | 79 (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) |
80 (invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT); 80 (invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT);
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 20ba6d4f6..3d5476e5d 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -13,47 +13,20 @@
13namespace Tegra::Texture { 13namespace Tegra::Texture {
14 14
15/** 15/**
16 * This table represents the internal swizzle of a gob,
17 * in format 16 bytes x 2 sector packing.
16 * Calculates the offset of an (x, y) position within a swizzled texture. 18 * Calculates the offset of an (x, y) position within a swizzled texture.
17 * Taken from the Tegra X1 TRM. 19 * Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188
18 */ 20 */
19static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { 21template <std::size_t N, std::size_t M, u32 Align>
20 // Round up to the next gob
21 const u32 image_width_in_gobs{(image_width * bytes_per_pixel + 63) / 64};
22
23 u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
24 (x * bytes_per_pixel / 64) * 512 * block_height +
25 (y % (8 * block_height) / 8) * 512;
26 x *= bytes_per_pixel;
27 u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
28 (y % 2) * 16 + (x % 16);
29
30 return address;
31}
32
33void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
34 u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
35 u8* data_ptrs[2];
36 for (unsigned y = 0; y < height; ++y) {
37 for (unsigned x = 0; x < width; ++x) {
38 u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height);
39 u32 pixel_index = (x + y * width) * out_bytes_per_pixel;
40
41 data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
42 data_ptrs[!unswizzle] = &unswizzled_data[pixel_index];
43
44 std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
45 }
46 }
47}
48
49template <std::size_t N, std::size_t M>
50struct alignas(64) SwizzleTable { 22struct alignas(64) SwizzleTable {
23 static_assert(M * Align == 64, "Swizzle Table does not align to GOB");
51 constexpr SwizzleTable() { 24 constexpr SwizzleTable() {
52 for (u32 y = 0; y < N; ++y) { 25 for (u32 y = 0; y < N; ++y) {
53 for (u32 x = 0; x < M; ++x) { 26 for (u32 x = 0; x < M; ++x) {
54 const u32 x2 = x * 16; 27 const u32 x2 = x * Align;
55 values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 + 28 values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 +
56 ((x2 % 32) / 16) * 32 + (y % 2) * 16); 29 ((x2 % 32) / 16) * 32 + (y % 2) * 16 + (x2 % 16));
57 } 30 }
58 } 31 }
59 } 32 }
@@ -63,24 +36,60 @@ struct alignas(64) SwizzleTable {
63 std::array<std::array<u16, M>, N> values{}; 36 std::array<std::array<u16, M>, N> values{};
64}; 37};
65 38
66constexpr auto swizzle_table = SwizzleTable<8, 4>(); 39constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>();
40constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>();
67 41
68void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_data, 42static void LegacySwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
69 u8* unswizzled_data, bool unswizzle, u32 block_height) { 43 u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
44 u32 block_height) {
45 std::array<u8*, 2> data_ptrs;
46 const std::size_t stride = width * bytes_per_pixel;
47 const std::size_t gobs_in_x = 64;
48 const std::size_t gobs_in_y = 8;
49 const std::size_t gobs_size = gobs_in_x * gobs_in_y;
50 const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
51 for (std::size_t y = 0; y < height; ++y) {
52 const std::size_t gob_y_address =
53 (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
54 (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
55 const auto& table = legacy_swizzle_table[y % gobs_in_y];
56 for (std::size_t x = 0; x < width; ++x) {
57 const std::size_t gob_address =
58 gob_y_address + (x * bytes_per_pixel / gobs_in_x) * gobs_size * block_height;
59 const std::size_t x2 = x * bytes_per_pixel;
60 const std::size_t swizzle_offset = gob_address + table[x2 % gobs_in_x];
61 const std::size_t pixel_index = (x + y * width) * out_bytes_per_pixel;
62
63 data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
64 data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
65
66 std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
67 }
68 }
69}
70
71static void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
72 u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
73 u32 block_height) {
70 std::array<u8*, 2> data_ptrs; 74 std::array<u8*, 2> data_ptrs;
71 const std::size_t stride{width * bytes_per_pixel}; 75 const std::size_t stride{width * bytes_per_pixel};
72 const std::size_t image_width_in_gobs{(stride + 63) / 64}; 76 const std::size_t gobs_in_x = 64;
77 const std::size_t gobs_in_y = 8;
78 const std::size_t gobs_size = gobs_in_x * gobs_in_y;
79 const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
73 const std::size_t copy_size{16}; 80 const std::size_t copy_size{16};
74 for (std::size_t y = 0; y < height; ++y) { 81 for (std::size_t y = 0; y < height; ++y) {
75 const std::size_t initial_gob = 82 const std::size_t initial_gob =
76 (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + 83 (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
77 (y % (8 * block_height) / 8) * 512; 84 (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
78 const std::size_t pixel_base{y * width * bytes_per_pixel}; 85 const std::size_t pixel_base{y * width * out_bytes_per_pixel};
79 const auto& table = swizzle_table[y % 8]; 86 const auto& table = fast_swizzle_table[y % gobs_in_y];
80 for (std::size_t xb = 0; xb < stride; xb += copy_size) { 87 for (std::size_t xb = 0; xb < stride; xb += copy_size) {
81 const std::size_t gob_address{initial_gob + (xb / 64) * 512 * block_height}; 88 const std::size_t gob_address{initial_gob +
89 (xb / gobs_in_x) * gobs_size * block_height};
82 const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]}; 90 const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]};
83 const std::size_t pixel_index{xb + pixel_base}; 91 const std::size_t out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
92 const std::size_t pixel_index{out_x + pixel_base};
84 data_ptrs[unswizzle] = swizzled_data + swizzle_offset; 93 data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
85 data_ptrs[!unswizzle] = unswizzled_data + pixel_index; 94 data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
86 std::memcpy(data_ptrs[0], data_ptrs[1], copy_size); 95 std::memcpy(data_ptrs[0], data_ptrs[1], copy_size);
@@ -88,6 +97,17 @@ void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_da
88 } 97 }
89} 98}
90 99
100void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
101 u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
102 if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) {
103 FastSwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
104 unswizzled_data, unswizzle, block_height);
105 } else {
106 LegacySwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
107 unswizzled_data, unswizzle, block_height);
108 }
109}
110
91u32 BytesPerPixel(TextureFormat format) { 111u32 BytesPerPixel(TextureFormat format) {
92 switch (format) { 112 switch (format) {
93 case TextureFormat::DXT1: 113 case TextureFormat::DXT1:
@@ -134,13 +154,8 @@ u32 BytesPerPixel(TextureFormat format) {
134std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, 154std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
135 u32 height, u32 block_height) { 155 u32 height, u32 block_height) {
136 std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); 156 std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
137 if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) { 157 CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel,
138 FastSwizzleData(width / tile_size, height / tile_size, bytes_per_pixel, 158 Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
139 Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
140 } else {
141 CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel,
142 Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
143 }
144 return unswizzled_data; 159 return unswizzled_data;
145} 160}
146 161
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index e0a14d48f..681919ae3 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -161,4 +161,26 @@ static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixe
161 } 161 }
162} 162}
163 163
164static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr,
165 std::string extra_info = "") {
166 if (!GLAD_GL_KHR_debug) {
167 return; // We don't need to throw an error as this is just for debugging
168 }
169 const std::string nice_addr = fmt::format("0x{:016x}", addr);
170 std::string object_label;
171
172 switch (identifier) {
173 case GL_TEXTURE:
174 object_label = extra_info + "@" + nice_addr;
175 break;
176 case GL_PROGRAM:
177 object_label = "ShaderProgram@" + nice_addr;
178 break;
179 default:
180 object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
181 break;
182 }
183 glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
184}
185
164} // namespace VideoCore 186} // namespace VideoCore
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index ffe0b84c1..67890455a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
318 int row = item_model->itemFromIndex(item)->row(); 318 int row = item_model->itemFromIndex(item)->row();
319 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); 319 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
320 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); 320 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
321 std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
321 322
322 QMenu context_menu; 323 QMenu context_menu;
323 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 324 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
325 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
326 context_menu.addSeparator();
327 QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
328 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
324 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 329 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
325 330
326 open_save_location->setEnabled(program_id != 0); 331 open_save_location->setEnabled(program_id != 0);
@@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
329 334
330 connect(open_save_location, &QAction::triggered, 335 connect(open_save_location, &QAction::triggered,
331 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); 336 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
337 connect(open_lfs_location, &QAction::triggered,
338 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
339 connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
340 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
332 connect(navigate_to_gamedb_entry, &QAction::triggered, 341 connect(navigate_to_gamedb_entry, &QAction::triggered,
333 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); 342 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
334 343
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index ba7921bdb..05e115e19 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -29,7 +29,10 @@ namespace FileSys {
29class VfsFilesystem; 29class VfsFilesystem;
30} 30}
31 31
32enum class GameListOpenTarget { SaveData }; 32enum class GameListOpenTarget {
33 SaveData,
34 ModData,
35};
33 36
34class GameList : public QWidget { 37class GameList : public QWidget {
35 Q_OBJECT 38 Q_OBJECT
@@ -63,6 +66,8 @@ signals:
63 void GameChosen(QString game_path); 66 void GameChosen(QString game_path);
64 void ShouldCancelWorker(); 67 void ShouldCancelWorker();
65 void OpenFolderRequested(u64 program_id, GameListOpenTarget target); 68 void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
69 void DumpRomFSRequested(u64 program_id, const std::string& game_path);
70 void CopyTIDRequested(u64 program_id);
66 void NavigateToGamedbEntryRequested(u64 program_id, 71 void NavigateToGamedbEntryRequested(u64 program_id,
67 const CompatibilityList& compatibility_list); 72 const CompatibilityList& compatibility_list);
68 73
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 7c0c47346..3db0e90da 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -69,7 +69,7 @@ public:
69 if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { 69 if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) {
70 picture = GetDefaultIcon(size); 70 picture = GetDefaultIcon(size);
71 } 71 }
72 picture = picture.scaled(size, size); 72 picture = picture.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
73 73
74 setData(picture, Qt::DecorationRole); 74 setData(picture, Qt::DecorationRole);
75 } 75 }
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 45bb1d1d1..d74489935 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -7,6 +7,22 @@
7#include <memory> 7#include <memory>
8#include <thread> 8#include <thread>
9 9
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "core/file_sys/vfs.h"
12#include "core/file_sys/vfs_real.h"
13
14// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
15// defines.
16static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(
17 const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) {
18 return vfs->CreateDirectory(path, mode);
19}
20
21static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir,
22 const std::string& path) {
23 return dir->CreateFile(path);
24}
25
10#include <fmt/ostream.h> 26#include <fmt/ostream.h>
11#include <glad/glad.h> 27#include <glad/glad.h>
12 28
@@ -30,16 +46,18 @@
30#include "common/telemetry.h" 46#include "common/telemetry.h"
31#include "core/core.h" 47#include "core/core.h"
32#include "core/crypto/key_manager.h" 48#include "core/crypto/key_manager.h"
49#include "core/file_sys/bis_factory.h"
33#include "core/file_sys/card_image.h" 50#include "core/file_sys/card_image.h"
34#include "core/file_sys/content_archive.h" 51#include "core/file_sys/content_archive.h"
35#include "core/file_sys/control_metadata.h" 52#include "core/file_sys/control_metadata.h"
36#include "core/file_sys/patch_manager.h" 53#include "core/file_sys/patch_manager.h"
37#include "core/file_sys/registered_cache.h" 54#include "core/file_sys/registered_cache.h"
55#include "core/file_sys/romfs.h"
38#include "core/file_sys/savedata_factory.h" 56#include "core/file_sys/savedata_factory.h"
39#include "core/file_sys/submission_package.h" 57#include "core/file_sys/submission_package.h"
40#include "core/file_sys/vfs_real.h"
41#include "core/hle/kernel/process.h" 58#include "core/hle/kernel/process.h"
42#include "core/hle/service/filesystem/filesystem.h" 59#include "core/hle/service/filesystem/filesystem.h"
60#include "core/hle/service/filesystem/fsp_ldr.h"
43#include "core/loader/loader.h" 61#include "core/loader/loader.h"
44#include "core/perf_stats.h" 62#include "core/perf_stats.h"
45#include "core/settings.h" 63#include "core/settings.h"
@@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() {
362void GMainWindow::ConnectWidgetEvents() { 380void GMainWindow::ConnectWidgetEvents() {
363 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 381 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
364 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); 382 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
383 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
384 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
365 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 385 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
366 &GMainWindow::OnGameListNavigateToGamedbEntry); 386 &GMainWindow::OnGameListNavigateToGamedbEntry);
367 387
@@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
713 program_id, user_id, 0); 733 program_id, user_id, 0);
714 break; 734 break;
715 } 735 }
736 case GameListOpenTarget::ModData: {
737 open_target = "Mod Data";
738 const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
739 path = fmt::format("{}{:016X}", load_dir, program_id);
740 break;
741 }
716 default: 742 default:
717 UNIMPLEMENTED(); 743 UNIMPLEMENTED();
718 } 744 }
@@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
730 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); 756 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
731} 757}
732 758
759static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
760 std::size_t out = 0;
761
762 for (const auto& subdir : dir->GetSubdirectories()) {
763 out += 1 + CalculateRomFSEntrySize(subdir, full);
764 }
765
766 return out + (full ? dir->GetFiles().size() : 0);
767}
768
769static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src,
770 const FileSys::VirtualDir& dest, std::size_t block_size, bool full) {
771 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
772 return false;
773 if (dialog.wasCanceled())
774 return false;
775
776 if (full) {
777 for (const auto& file : src->GetFiles()) {
778 const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
779 if (!FileSys::VfsRawCopy(file, out, block_size))
780 return false;
781 dialog.setValue(dialog.value() + 1);
782 if (dialog.wasCanceled())
783 return false;
784 }
785 }
786
787 for (const auto& dir : src->GetSubdirectories()) {
788 const auto out = dest->CreateSubdirectory(dir->GetName());
789 if (!RomFSRawCopy(dialog, dir, out, block_size, full))
790 return false;
791 dialog.setValue(dialog.value() + 1);
792 if (dialog.wasCanceled())
793 return false;
794 }
795
796 return true;
797}
798
799void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
800 const auto path = fmt::format("{}{:016X}/romfs",
801 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
802
803 const auto failed = [this, &path] {
804 QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
805 tr("There was an error copying the RomFS files or the user "
806 "cancelled the operation."));
807 vfs->DeleteDirectory(path);
808 };
809
810 const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
811 if (loader == nullptr) {
812 failed();
813 return;
814 }
815
816 FileSys::VirtualFile file;
817 if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
818 failed();
819 return;
820 }
821
822 const auto romfs =
823 loader->IsRomFSUpdatable()
824 ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
825 : file;
826
827 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
828 if (extracted == nullptr) {
829 failed();
830 return;
831 }
832
833 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
834
835 if (out == nullptr) {
836 failed();
837 return;
838 }
839
840 bool ok;
841 const auto res = QInputDialog::getItem(
842 this, tr("Select RomFS Dump Mode"),
843 tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
844 "files into the new directory while <br>skeleton will only create the directory "
845 "structure."),
846 {"Full", "Skeleton"}, 0, false, &ok);
847 if (!ok)
848 failed();
849
850 const auto full = res == "Full";
851 const auto entry_size = CalculateRomFSEntrySize(extracted, full);
852
853 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this);
854 progress.setWindowModality(Qt::WindowModal);
855 progress.setMinimumDuration(100);
856
857 if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) {
858 progress.close();
859 QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
860 tr("The operation completed successfully."));
861 QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
862 } else {
863 progress.close();
864 failed();
865 }
866}
867
868void GMainWindow::OnGameListCopyTID(u64 program_id) {
869 QClipboard* clipboard = QGuiApplication::clipboard();
870 clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
871}
872
733void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, 873void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
734 const CompatibilityList& compatibility_list) { 874 const CompatibilityList& compatibility_list) {
735 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 875 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() {
790 return; 930 return;
791 } 931 }
792 932
793 const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { 933 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
934 const FileSys::VirtualFile& dest, std::size_t block_size) {
794 if (src == nullptr || dest == nullptr) 935 if (src == nullptr || dest == nullptr)
795 return false; 936 return false;
796 if (!dest->Resize(src->GetSize())) 937 if (!dest->Resize(src->GetSize()))
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 552e3e61c..8ee9242b1 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -138,6 +138,8 @@ private slots:
138 /// Called whenever a user selects a game in the game list widget. 138 /// Called whenever a user selects a game in the game list widget.
139 void OnGameListLoadFile(QString game_path); 139 void OnGameListLoadFile(QString game_path);
140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); 140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
141 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
142 void OnGameListCopyTID(u64 program_id);
141 void OnGameListNavigateToGamedbEntry(u64 program_id, 143 void OnGameListNavigateToGamedbEntry(u64 program_id,
142 const CompatibilityList& compatibility_list); 144 const CompatibilityList& compatibility_list);
143 void OnMenuLoadFile(); 145 void OnMenuLoadFile();
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 7ec1f5110..a478b0a56 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -120,7 +120,7 @@ void Config::ReadValues() {
120 sdl2_config->Get("Data Storage", "nand_directory", 120 sdl2_config->Get("Data Storage", "nand_directory",
121 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 121 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
122 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, 122 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
123 sdl2_config->Get("Data Storage", "nand_directory", 123 sdl2_config->Get("Data Storage", "sdmc_directory",
124 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 124 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
125 125
126 // System 126 // System