diff options
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() | |||
| 440 | add_subdirectory(externals) | 438 | add_subdirectory(externals) |
| 441 | add_subdirectory(src) | 439 | add_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 |
| 444 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu) | 442 | if(ENABLE_QT) |
| 443 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu) | ||
| 444 | else() | ||
| 445 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu-cmd) | ||
| 446 | endif() | ||
| 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 | ||
| 166 | deploy: | ||
| 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 | ||
| 82 | Stream::State AudioRenderer::GetStreamState() const { | ||
| 83 | return stream->GetState(); | ||
| 84 | } | ||
| 85 | |||
| 82 | std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { | 86 | std::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 | ||
| 174 | private: | 175 | private: |
| 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 | ||
| 51 | void Stream::Stop() { | 51 | void Stream::Stop() { |
| 52 | state = State::Stopped; | ||
| 52 | ASSERT_MSG(false, "Unimplemented"); | 53 | ASSERT_MSG(false, "Unimplemented"); |
| 53 | } | 54 | } |
| 54 | 55 | ||
| 56 | Stream::State Stream::GetState() const { | ||
| 57 | return state; | ||
| 58 | } | ||
| 59 | |||
| 55 | s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { | 60 | s64 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 | ||
| 75 | private: | 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 | ||
| 84 | private: | ||
| 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 | ||
| 88 | void SleepCurrentThread(int ms); | 88 | void SleepCurrentThread(int ms); |
| 89 | void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms | 89 | void 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. | ||
| 94 | inline void YieldCPU() { | ||
| 95 | std::this_thread::yield(); | ||
| 96 | } | ||
| 97 | |||
| 98 | void SetCurrentThreadName(const char* name); | 90 | void 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 | ||
| 70 | Cpu::~Cpu() = default; | 70 | Cpu::~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: | |||
| 76 | private: | 76 | private: |
| 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 | ||
| 8 | namespace FileSys { | 9 | namespace FileSys { |
| 9 | 10 | ||
| 10 | BISFactory::BISFactory(VirtualDir nand_root_) | 11 | BISFactory::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 | ||
| 28 | VirtualDir 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. |
| 18 | class BISFactory { | 18 | class BISFactory { |
| 19 | public: | 19 | public: |
| 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 | |||
| 26 | private: | 28 | private: |
| 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 | |||
| 32 | namespace FileSys { | ||
| 33 | |||
| 34 | constexpr u64 FS_MAX_PATH = 0x301; | ||
| 35 | |||
| 36 | constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; | ||
| 37 | constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200; | ||
| 38 | |||
| 39 | // Types for building a RomFS. | ||
| 40 | struct 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 | }; | ||
| 52 | static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); | ||
| 53 | |||
| 54 | struct RomFSDirectoryEntry { | ||
| 55 | u32 parent; | ||
| 56 | u32 sibling; | ||
| 57 | u32 child; | ||
| 58 | u32 file; | ||
| 59 | u32 hash; | ||
| 60 | u32 name_size; | ||
| 61 | }; | ||
| 62 | static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size."); | ||
| 63 | |||
| 64 | struct RomFSFileEntry { | ||
| 65 | u32 parent; | ||
| 66 | u32 sibling; | ||
| 67 | u64 offset; | ||
| 68 | u64 size; | ||
| 69 | u32 hash; | ||
| 70 | u32 name_size; | ||
| 71 | }; | ||
| 72 | static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size."); | ||
| 73 | |||
| 74 | struct RomFSBuildFileContext; | ||
| 75 | |||
| 76 | struct 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 | |||
| 87 | struct 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 | |||
| 99 | static 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 | |||
| 109 | static 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 | |||
| 126 | void 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 | |||
| 176 | bool 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 | |||
| 193 | bool 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 | |||
| 211 | RomFSBuildContext::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 | |||
| 221 | RomFSBuildContext::~RomFSBuildContext() = default; | ||
| 222 | |||
| 223 | std::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 | |||
| 34 | namespace FileSys { | ||
| 35 | |||
| 36 | struct RomFSBuildDirectoryContext; | ||
| 37 | struct RomFSBuildFileContext; | ||
| 38 | struct RomFSDirectoryEntry; | ||
| 39 | struct RomFSFileEntry; | ||
| 40 | |||
| 41 | class RomFSBuildContext { | ||
| 42 | public: | ||
| 43 | explicit RomFSBuildContext(VirtualDir base); | ||
| 44 | ~RomFSBuildContext(); | ||
| 45 | |||
| 46 | // This finalizes the context. | ||
| 47 | std::map<u64, VirtualFile> Build(); | ||
| 48 | |||
| 49 | private: | ||
| 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 | ||
| 34 | constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ | 35 | constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{ |
| 35 | "Update", | 36 | "Update", |
| 37 | "LayeredFS", | ||
| 36 | }; | 38 | }; |
| 37 | 39 | ||
| 38 | std::string FormatPatchTypeName(PatchType type) { | 40 | std::string FormatPatchTypeName(PatchType type) { |
| @@ -66,6 +68,44 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | |||
| 66 | return exefs; | 68 | return exefs; |
| 67 | } | 69 | } |
| 68 | 70 | ||
| 71 | static 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 | |||
| 69 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, | 109 | VirtualFile 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 | ||
| 27 | enum class PatchType { | 27 | enum class PatchType { |
| 28 | Update, | 28 | Update, |
| 29 | LayeredFS, | ||
| 29 | }; | 30 | }; |
| 30 | 31 | ||
| 31 | std::string FormatPatchTypeName(PatchType type); | 32 | std::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 | ||
| 20 | namespace FileSys { | 20 | namespace FileSys { |
| 21 | |||
| 22 | // The size of blocks to use when vfs raw copying into nand. | ||
| 23 | constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000; | ||
| 24 | |||
| 21 | std::string RegisteredCacheEntry::DebugInfo() const { | 25 | std::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 | ||
| 486 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { | 491 | bool 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 | ||
| 28 | using NcaID = std::array<u8, 0x10>; | 28 | using NcaID = std::array<u8, 0x10>; |
| 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; | 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; |
| 30 | using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; | 30 | using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; |
| 31 | 31 | ||
| 32 | enum class InstallResult { | 32 | enum 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 | ||
| 101 | VirtualDir ExtractRomFS(VirtualFile file) { | 103 | VirtualDir 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 | |||
| 132 | VirtualFile 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 | ||
| 13 | namespace FileSys { | 14 | namespace FileSys { |
| 14 | 15 | ||
| 16 | struct RomFSHeader; | ||
| 17 | |||
| 15 | struct IVFCLevel { | 18 | struct 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 | }; |
| 30 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | 33 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); |
| 31 | 34 | ||
| 35 | enum 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 |
| 34 | VirtualDir ExtractRomFS(VirtualFile file); | 42 | VirtualDir ExtractRomFS(VirtualFile file, |
| 43 | RomFSExtractionType type = RomFSExtractionType::Truncated); | ||
| 44 | |||
| 45 | // Converts a VFS filesystem into a RomFS binary | ||
| 46 | // Returns nullptr on failure | ||
| 47 | VirtualFile 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 | ||
| 402 | std::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 | |||
| 402 | std::string VfsDirectory::GetFullPath() const { | 411 | std::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 | ||
| 457 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | 466 | bool 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 | |||
| 484 | bool 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 | ||
| 466 | VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { | 503 | VirtualDir 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 |
| 314 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); | 319 | bool 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. |
| 319 | bool VfsRawCopy(VirtualFile src, VirtualFile dest); | 325 | bool 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. | ||
| 330 | bool 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 | ||
| 10 | namespace FileSys { | 12 | namespace FileSys { |
| 11 | 13 | ||
| 12 | VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { | 14 | static 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 | ||
| 21 | ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) | 26 | ConcatenatedVfsFile::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 | ||
| 35 | ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name) | ||
| 36 | : files(std::move(files_)), name(std::move(name)) { | ||
| 37 | ASSERT(VerifyConcatenationMapContinuity(files)); | ||
| 38 | } | ||
| 39 | |||
| 30 | ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; | 40 | ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; |
| 31 | 41 | ||
| 42 | VirtualFile 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 | |||
| 52 | VirtualFile 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 | |||
| 32 | std::string ConcatenatedVfsFile::GetName() const { | 77 | std::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 | ||
| 64 | std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 109 | std::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 | ||
| 89 | std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 131 | std::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:: | |||
| 93 | bool ConcatenatedVfsFile::Rename(std::string_view name) { | 135 | bool 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 | ||
| 12 | namespace FileSys { | 12 | namespace FileSys { |
| 13 | 13 | ||
| 14 | // Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. | ||
| 15 | VirtualFile 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. |
| 19 | class ConcatenatedVfsFile : public VfsFile { | 16 | class 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 | ||
| 24 | public: | 20 | public: |
| 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 | ||
| 37 | private: | 41 | private: |
| 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 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name) | ||
| 12 | : dirs(std::move(dirs)), name(std::move(name)) {} | ||
| 13 | |||
| 14 | LayeredVfsDirectory::~LayeredVfsDirectory() = default; | ||
| 15 | |||
| 16 | VirtualDir 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 | |||
| 26 | std::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 | |||
| 36 | std::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 | |||
| 48 | std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const { | ||
| 49 | return GetFileRelative(name); | ||
| 50 | } | ||
| 51 | |||
| 52 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { | ||
| 53 | return GetDirectoryRelative(name); | ||
| 54 | } | ||
| 55 | |||
| 56 | std::string LayeredVfsDirectory::GetFullPath() const { | ||
| 57 | return dirs[0]->GetFullPath(); | ||
| 58 | } | ||
| 59 | |||
| 60 | std::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 | |||
| 75 | std::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 | |||
| 92 | bool LayeredVfsDirectory::IsWritable() const { | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | bool LayeredVfsDirectory::IsReadable() const { | ||
| 97 | return true; | ||
| 98 | } | ||
| 99 | |||
| 100 | std::string LayeredVfsDirectory::GetName() const { | ||
| 101 | return name.empty() ? dirs[0]->GetName() : name; | ||
| 102 | } | ||
| 103 | |||
| 104 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const { | ||
| 105 | return dirs[0]->GetParentDirectory(); | ||
| 106 | } | ||
| 107 | |||
| 108 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { | ||
| 109 | return nullptr; | ||
| 110 | } | ||
| 111 | |||
| 112 | std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) { | ||
| 113 | return nullptr; | ||
| 114 | } | ||
| 115 | |||
| 116 | bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) { | ||
| 117 | return false; | ||
| 118 | } | ||
| 119 | |||
| 120 | bool LayeredVfsDirectory::DeleteFile(std::string_view name) { | ||
| 121 | return false; | ||
| 122 | } | ||
| 123 | |||
| 124 | bool LayeredVfsDirectory::Rename(std::string_view name_) { | ||
| 125 | name = name_; | ||
| 126 | return true; | ||
| 127 | } | ||
| 128 | |||
| 129 | bool 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 | |||
| 10 | namespace 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. | ||
| 15 | class LayeredVfsDirectory : public VfsDirectory { | ||
| 16 | LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name); | ||
| 17 | |||
| 18 | public: | ||
| 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 | |||
| 42 | protected: | ||
| 43 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||
| 44 | |||
| 45 | private: | ||
| 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 | ||
| 416 | std::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 | |||
| 416 | bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | 433 | bool 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 | ||
| 102 | protected: | 103 | protected: |
| 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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class StaticVfsFile : public VfsFile { | ||
| 16 | public: | ||
| 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 | |||
| 72 | private: | ||
| 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 | ||
| 9 | namespace FileSys { | 10 | namespace FileSys { |
| 11 | VectorVfsFile::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 | |||
| 14 | VectorVfsFile::~VectorVfsFile() = default; | ||
| 15 | |||
| 16 | std::string VectorVfsFile::GetName() const { | ||
| 17 | return name; | ||
| 18 | } | ||
| 19 | |||
| 20 | size_t VectorVfsFile::GetSize() const { | ||
| 21 | return data.size(); | ||
| 22 | } | ||
| 23 | |||
| 24 | bool VectorVfsFile::Resize(size_t new_size) { | ||
| 25 | data.resize(new_size); | ||
| 26 | return true; | ||
| 27 | } | ||
| 28 | |||
| 29 | std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const { | ||
| 30 | return parent; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool VectorVfsFile::IsWritable() const { | ||
| 34 | return true; | ||
| 35 | } | ||
| 36 | |||
| 37 | bool VectorVfsFile::IsReadable() const { | ||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | std::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 | |||
| 47 | std::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 | |||
| 55 | bool VectorVfsFile::Rename(std::string_view name_) { | ||
| 56 | name = name_; | ||
| 57 | return true; | ||
| 58 | } | ||
| 59 | |||
| 60 | void VectorVfsFile::Assign(std::vector<u8> new_data) { | ||
| 61 | data = std::move(new_data); | ||
| 62 | } | ||
| 63 | |||
| 10 | VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, | 64 | VectorVfsDirectory::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 | ||
| 9 | namespace FileSys { | 9 | namespace FileSys { |
| 10 | 10 | ||
| 11 | // An implementation of VfsFile that is backed by a vector optionally supplied upon construction | ||
| 12 | class VectorVfsFile : public VfsFile { | ||
| 13 | public: | ||
| 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 | |||
| 30 | private: | ||
| 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. |
| 13 | class VectorVfsDirectory : public VfsDirectory { | 38 | class 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 | ||
| 133 | void 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 | */ | ||
| 168 | static 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 | |||
| 186 | VAddr 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 | |||
| 210 | void 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 | |||
| 131 | void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { | 218 | void 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 | ||
| 205 | private: | ||
| 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 | |||
| 208 | private: | ||
| 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 | ||
| 18 | std::mutex Scheduler::scheduler_mutex; | 18 | std::mutex Scheduler::scheduler_mutex; |
| 19 | 19 | ||
| 20 | Scheduler::Scheduler(Core::ARM_Interface* cpu_core) : cpu_core(cpu_core) {} | 20 | Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {} |
| 21 | 21 | ||
| 22 | Scheduler::~Scheduler() { | 22 | Scheduler::~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 | ||
| 20 | class Scheduler final { | 20 | class Scheduler final { |
| 21 | public: | 21 | public: |
| 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 |
| 532 | static void ExitProcess() { | 532 | static 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 | ||
| 74 | void WaitCurrentThread_Sleep() { | 71 | void 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 | */ | ||
| 188 | static 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 | ||
| 63 | class Thread final : public WaitObject { | 63 | class Thread final : public WaitObject { |
| 64 | public: | 64 | public: |
| 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 | ||
| 16 | Module::Interface::~Interface() = default; | 25 | Module::Interface::~Interface() = default; |
| 17 | 26 | ||
| 27 | struct 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 | }; | ||
| 45 | static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size"); | ||
| 46 | |||
| 47 | enum class FatalType : u32 { | ||
| 48 | ErrorReportAndScreen = 0, | ||
| 49 | ErrorReport = 1, | ||
| 50 | ErrorScreen = 2, | ||
| 51 | }; | ||
| 52 | |||
| 53 | static 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 | |||
| 113 | static 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 | |||
| 130 | void 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 | |||
| 18 | void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { | 140 | void 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 | ||
| 26 | void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { | 151 | void 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 | ||
| 9 | Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { | 9 | Fatal_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 | ||
| 346 | FileSys::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 | |||
| 346 | void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { | 355 | void 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(); | |||
| 52 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); | 52 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); |
| 53 | std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); | 53 | std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); |
| 54 | 54 | ||
| 55 | FileSys::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. |
| 57 | void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); | 59 | void 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 | ||
| 7 | namespace Service::HID { | 12 | namespace Service::HID { |
| @@ -9,28 +14,145 @@ namespace Service::HID { | |||
| 9 | IRS::IRS() : ServiceFramework{"irs"} { | 14 | IRS::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 | |||
| 46 | void 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 | |||
| 52 | void 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 | |||
| 58 | void 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 | |||
| 65 | void 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 | |||
| 71 | void 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 | |||
| 77 | void 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 | |||
| 83 | void 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 | |||
| 89 | void 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 | |||
| 97 | void 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 | |||
| 103 | void 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 | |||
| 110 | void 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 | |||
| 116 | void 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 | |||
| 122 | void 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 | |||
| 128 | void 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 | |||
| 134 | void 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 | |||
| 140 | void 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 | |||
| 146 | void 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 | |||
| 152 | void 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 | ||
| 36 | IRS::~IRS() = default; | 158 | IRS::~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 | ||
| 10 | namespace Kernel { | ||
| 11 | class SharedMemory; | ||
| 12 | } | ||
| 13 | |||
| 9 | namespace Service::HID { | 14 | namespace Service::HID { |
| 10 | 15 | ||
| 11 | class IRS final : public ServiceFramework<IRS> { | 16 | class IRS final : public ServiceFramework<IRS> { |
| 12 | public: | 17 | public: |
| 13 | explicit IRS(); | 18 | explicit IRS(); |
| 14 | ~IRS() override; | 19 | ~IRS() override; |
| 20 | |||
| 21 | private: | ||
| 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 | ||
| 17 | class IRS_SYS final : public ServiceFramework<IRS_SYS> { | 44 | class 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 | ||
| 105 | private: | 105 | private: |
| 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 | ||
| 979 | ASSERT_REG_POSITION(macros, 0x45); | 985 | ASSERT_REG_POSITION(macros, 0x45); |
| 986 | ASSERT_REG_POSITION(tfb_enabled, 0x1D1); | ||
| 980 | ASSERT_REG_POSITION(rt, 0x200); | 987 | ASSERT_REG_POSITION(rt, 0x200); |
| 981 | ASSERT_REG_POSITION(viewport_transform[0], 0x280); | 988 | ASSERT_REG_POSITION(viewport_transform[0], 0x280); |
| 982 | ASSERT_REG_POSITION(viewport, 0x300); | 989 | ASSERT_REG_POSITION(viewport, 0x300); |
| @@ -996,6 +1003,7 @@ ASSERT_REG_POSITION(zeta_height, 0x48b); | |||
| 996 | ASSERT_REG_POSITION(depth_test_enable, 0x4B3); | 1003 | ASSERT_REG_POSITION(depth_test_enable, 0x4B3); |
| 997 | ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); | 1004 | ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); |
| 998 | ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); | 1005 | ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); |
| 1006 | ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB); | ||
| 999 | ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); | 1007 | ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); |
| 1000 | ASSERT_REG_POSITION(depth_test_func, 0x4C3); | 1008 | ASSERT_REG_POSITION(depth_test_func, 0x4C3); |
| 1001 | ASSERT_REG_POSITION(blend, 0x4CF); | 1009 | ASSERT_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 | ||
| 7 | namespace Tegra { | 9 | namespace Tegra { |
| 8 | namespace Engines { | 10 | namespace Engines { |
| 9 | 11 | ||
| 10 | void MaxwellCompute::WriteReg(u32 method, u32 value) {} | 12 | void 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 | ||
| 9 | namespace Tegra::Engines { | 13 | namespace Tegra::Engines { |
| 10 | 14 | ||
| 15 | #define MAXWELL_COMPUTE_REG_INDEX(field_name) \ | ||
| 16 | (offsetof(Tegra::Engines::MaxwellCompute::Regs, field_name) / sizeof(u32)) | ||
| 17 | |||
| 11 | class MaxwellCompute final { | 18 | class MaxwellCompute final { |
| 12 | public: | 19 | public: |
| 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 | |||
| 52 | ASSERT_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 | ||
| 318 | enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 }; | 317 | enum class IpaInterpMode : u64 { |
| 319 | enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 }; | 318 | Linear = 0, |
| 319 | Perspective = 1, | ||
| 320 | Flat = 2, | ||
| 321 | Sc = 3, | ||
| 322 | }; | ||
| 323 | |||
| 324 | enum class IpaSampleMode : u64 { | ||
| 325 | Default = 0, | ||
| 326 | Centroid = 1, | ||
| 327 | Offset = 2, | ||
| 328 | }; | ||
| 320 | 329 | ||
| 321 | struct IpaMode { | 330 | struct 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 | ||
| 888 | void 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 | |||
| 899 | void 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 | ||
| 506 | static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { | 509 | static 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 | ||
| 12 | namespace OpenGL { | 13 | namespace 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 | ||
| 88 | GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { | 90 | GLuint 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 | |||
| 12 | namespace OpenGL { | 10 | namespace OpenGL { |
| 13 | 11 | ||
| 14 | using Regs = Tegra::Engines::Maxwell3D::Regs; | ||
| 15 | |||
| 16 | namespace TextureUnits { | 12 | namespace TextureUnits { |
| 17 | 13 | ||
| 18 | struct TextureUnit { | 14 | struct 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 @@ | |||
| 13 | namespace Tegra::Texture { | 13 | namespace 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 | */ |
| 19 | static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { | 21 | template <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 | |||
| 33 | void 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 | |||
| 49 | template <std::size_t N, std::size_t M> | ||
| 50 | struct alignas(64) SwizzleTable { | 22 | struct 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 | ||
| 66 | constexpr auto swizzle_table = SwizzleTable<8, 4>(); | 39 | constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>(); |
| 40 | constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>(); | ||
| 67 | 41 | ||
| 68 | void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_data, | 42 | static 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 | |||
| 71 | static 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 | ||
| 100 | void 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 | |||
| 91 | u32 BytesPerPixel(TextureFormat format) { | 111 | u32 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) { | |||
| 134 | std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, | 154 | std::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 | ||
| 164 | static 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 { | |||
| 29 | class VfsFilesystem; | 29 | class VfsFilesystem; |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | enum class GameListOpenTarget { SaveData }; | 32 | enum class GameListOpenTarget { |
| 33 | SaveData, | ||
| 34 | ModData, | ||
| 35 | }; | ||
| 33 | 36 | ||
| 34 | class GameList : public QWidget { | 37 | class 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. | ||
| 16 | static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( | ||
| 17 | const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { | ||
| 18 | return vfs->CreateDirectory(path, mode); | ||
| 19 | } | ||
| 20 | |||
| 21 | static 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() { | |||
| 362 | void GMainWindow::ConnectWidgetEvents() { | 380 | void 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 | ||
| 759 | static 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 | |||
| 769 | static 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 | |||
| 799 | void 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 | |||
| 868 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | ||
| 869 | QClipboard* clipboard = QGuiApplication::clipboard(); | ||
| 870 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | ||
| 871 | } | ||
| 872 | |||
| 733 | void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | 873 | void 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 |