diff options
Diffstat (limited to 'src')
46 files changed, 851 insertions, 164 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 6f0ff953a..23e5d3f10 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -30,7 +30,7 @@ public: return info; } - VoiceInfo& Info() { + VoiceInfo& GetInfo() { return info; } @@ -51,9 +51,30 @@ private: VoiceInfo info{}; }; +class AudioRenderer::EffectState { +public: + const EffectOutStatus& GetOutStatus() const { + return out_status; + } + + const EffectInStatus& GetInfo() const { + return info; + } + + EffectInStatus& GetInfo() { + return info; + } + + void UpdateState(); + +private: + EffectOutStatus out_status{}; + EffectInStatus info{}; +}; AudioRenderer::AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event) - : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) { + : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), + effects(params.effect_count) { audio_out = std::make_unique<AudioCore::AudioOut>(); stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer", @@ -96,11 +117,29 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ memory_pool_count * sizeof(MemoryPoolInfo)); // Copy VoiceInfo structs - std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size + - config.voice_resource_size}; + std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size + + config.memory_pools_size + config.voice_resource_size}; for (auto& voice : voices) { - std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo)); - offset += sizeof(VoiceInfo); + std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo)); + voice_offset += sizeof(VoiceInfo); + } + + std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size + + config.memory_pools_size + config.voice_resource_size + + config.voices_size}; + for (auto& effect : effects) { + std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus)); + effect_offset += sizeof(EffectInStatus); + } + + // Update memory pool state + std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); + for (std::size_t index = 0; index < memory_pool.size(); ++index) { + if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { + memory_pool[index].state = MemoryPoolStates::Attached; + } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { + memory_pool[index].state = MemoryPoolStates::Detached; + } } // Update voices @@ -114,14 +153,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ } } - // Update memory pool state - std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); - for (std::size_t index = 0; index < memory_pool.size(); ++index) { - if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { - memory_pool[index].state = MemoryPoolStates::Attached; - } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { - memory_pool[index].state = MemoryPoolStates::Detached; - } + for (auto& effect : effects) { + effect.UpdateState(); } // Release previous buffers and queue next ones for playback @@ -144,6 +177,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ voice_out_status_offset += sizeof(VoiceOutStatus); } + std::size_t effect_out_status_offset{ + sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + + response_data.voice_resource_size}; + for (const auto& effect : effects) { + std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(), + sizeof(EffectOutStatus)); + effect_out_status_offset += sizeof(EffectOutStatus); + } return output_params; } @@ -244,11 +285,29 @@ void AudioRenderer::VoiceState::RefreshBuffer() { break; } - samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE); + samples = + Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE); is_refresh_pending = false; } +void AudioRenderer::EffectState::UpdateState() { + if (info.is_new) { + out_status.state = EffectStatus::New; + } else { + if (info.type == Effect::Aux) { + ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0, + "Aux buffers tried to update"); + } + } +} + static constexpr s16 ClampToS16(s32 value) { return static_cast<s16>(std::clamp(value, -32768, 32767)); } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index dfef89e1d..046417da3 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -28,6 +28,16 @@ enum class PlayState : u8 { Paused = 2, }; +enum class Effect : u8 { + None = 0, + Aux = 2, +}; + +enum class EffectStatus : u8 { + None = 0, + New = 1, +}; + struct AudioRendererParameter { u32_le sample_rate; u32_le sample_count; @@ -128,6 +138,43 @@ struct VoiceOutStatus { }; static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); +struct AuxInfo { + std::array<u8, 24> input_mix_buffers; + std::array<u8, 24> output_mix_buffers; + u32_le mix_buffer_count; + u32_le sample_rate; // Stored in the aux buffer currently + u32_le sampe_count; + u64_le send_buffer_info; + u64_le send_buffer_base; + + u64_le return_buffer_info; + u64_le return_buffer_base; +}; +static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); + +struct EffectInStatus { + Effect type; + u8 is_new; + u8 is_enabled; + INSERT_PADDING_BYTES(1); + u32_le mix_id; + u64_le buffer_base; + u64_le buffer_sz; + s32_le priority; + INSERT_PADDING_BYTES(4); + union { + std::array<u8, 0xa0> raw; + AuxInfo aux_info; + }; +}; +static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); + +struct EffectOutStatus { + EffectStatus state; + INSERT_PADDING_BYTES(0xf); +}; +static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); + struct UpdateDataHeader { UpdateDataHeader() {} @@ -173,11 +220,13 @@ public: Stream::State GetStreamState() const; private: + class EffectState; class VoiceState; AudioRendererParameter worker_params; Kernel::SharedPtr<Kernel::Event> buffer_event; std::vector<VoiceState> voices; + std::vector<EffectState> effects; std::unique_ptr<AudioOut> audio_out; AudioCore::StreamPtr stream; }; diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 589ae5cbf..5b63f9e81 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) { return 0; } +std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { + std::vector<u8> out(str.size() / 2); + if (little_endian) { + for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2) + out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } else { + for (std::size_t i = 0; i < str.size(); i += 2) + out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } + return out; +} + +std::string HexVectorToString(const std::vector<u8>& vector, bool upper) { + std::string out; + for (u8 c : vector) + out += fmt::format(upper ? "{:02X}" : "{:02x}", c); + return out; +} + std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { if (len != 32) { LOG_ERROR(Common, diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 863a5ccd9..68f003cb6 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <string> +#include <vector> #include <fmt/format.h> #include "common/common_types.h" @@ -14,6 +15,8 @@ namespace Common { u8 ToHexNibble(char c1); +std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); + template <std::size_t Size, bool le = false> std::array<u8, Size> HexStringToArray(std::string_view str) { std::array<u8, Size> out{}; @@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) { return out; } +std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true); + template <std::size_t Size> std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { std::string out; diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7e978cf7a..0762321a9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -129,7 +129,7 @@ public: }; std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); auto** const page_table = current_process->VMManager().page_table.pointers.data(); Dynarmic::A64::UserConfig config; diff --git a/src/core/core.cpp b/src/core/core.cpp index b6acfb3e4..e2fb9e038 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -136,7 +136,8 @@ struct System::Impl { if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); - kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main")); + auto main_process = Kernel::Process::Create(kernel, "main"); + kernel.MakeCurrentProcess(main_process.get()); cpu_barrier = std::make_shared<CpuBarrier>(); cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size()); @@ -361,11 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_ind return impl->cpu_cores[core_index]->Scheduler(); } -Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() { +Kernel::Process* System::CurrentProcess() { return impl->kernel.CurrentProcess(); } -const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const { +const Kernel::Process* System::CurrentProcess() const { return impl->kernel.CurrentProcess(); } diff --git a/src/core/core.h b/src/core/core.h index f9a3e97e3..ea4d53914 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -174,11 +174,11 @@ public: /// Gets the scheduler for the CPU core with the specified index const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index); - /// Provides a reference to the current process - Kernel::SharedPtr<Kernel::Process>& CurrentProcess(); + /// Provides a pointer to the current process + Kernel::Process* CurrentProcess(); - /// Provides a constant reference to the current process. - const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const; + /// Provides a constant pointer to the current process. + const Kernel::Process* CurrentProcess() const; /// Provides a reference to the kernel instance. Kernel::KernelCore& Kernel(); @@ -246,7 +246,7 @@ inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } -inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { +inline Kernel::Process* CurrentProcess() { return System::GetInstance().CurrentProcess(); } diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 2a913ce82..47b7526c7 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -26,6 +26,7 @@ #include "common/alignment.h" #include "common/assert.h" #include "core/file_sys/fsmitm_romfsbuild.h" +#include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_vector.h" @@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) { return count; } -void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, +void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext, std::shared_ptr<RomFSBuildDirectoryContext> parent) { std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; @@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); child->path = parent->path + "/" + kv.first; + if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr) + continue; + // Sanity check on path_len ASSERT(child->path_len < FS_MAX_PATH); @@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); child->path = parent->path + "/" + kv.first; + if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr) + continue; + // Sanity check on path_len ASSERT(child->path_len < FS_MAX_PATH); child->source = root_romfs->GetFileRelative(child->path); + if (ext != nullptr) { + const auto ips = ext->GetFileRelative(child->path + ".ips"); + + if (ips != nullptr) { + auto patched = PatchIPS(child->source, ips); + if (patched != nullptr) + child->source = std::move(patched); + } + } + child->size = child->source->GetSize(); AddFile(parent, child); @@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, } for (auto& child : child_dirs) { - this->VisitDirectory(root_romfs, child); + this->VisitDirectory(root_romfs, ext, child); } } @@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare return true; } -RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) { +RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_) + : base(std::move(base_)), ext(std::move(ext_)) { root = std::make_shared<RomFSBuildDirectoryContext>(); root->path = "\0"; directories.emplace(root->path, root); num_dirs = 1; dir_table_size = 0x18; - VisitDirectory(base, root); + VisitDirectory(base, ext, root); } RomFSBuildContext::~RomFSBuildContext() = default; diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h index b0c3c123b..3d377b0af 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.h +++ b/src/core/file_sys/fsmitm_romfsbuild.h @@ -40,7 +40,7 @@ struct RomFSFileEntry; class RomFSBuildContext { public: - explicit RomFSBuildContext(VirtualDir base); + explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr); ~RomFSBuildContext(); // This finalizes the context. @@ -48,6 +48,7 @@ public: private: VirtualDir base; + VirtualDir ext; std::shared_ptr<RomFSBuildDirectoryContext> root; std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories; std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files; @@ -59,7 +60,8 @@ private: u64 file_hash_table_size = 0; u64 file_partition_size = 0; - void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent); + void VisitDirectory(VirtualDir filesys, VirtualDir ext, + std::shared_ptr<RomFSBuildDirectoryContext> parent); bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx); diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index df933ee36..554eae9bc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -2,7 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/assert.h" +#include <algorithm> +#include <cstring> +#include <map> +#include <sstream> +#include <string> +#include <utility> + +#include "common/hex_util.h" +#include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs_vector.h" @@ -15,16 +23,48 @@ enum class IPSFileType { Error, }; +constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{ + {"\\a", "\a"}, + {"\\b", "\b"}, + {"\\f", "\f"}, + {"\\n", "\n"}, + {"\\r", "\r"}, + {"\\t", "\t"}, + {"\\v", "\v"}, + {"\\\\", "\\"}, + {"\\\'", "\'"}, + {"\\\"", "\""}, + {"\\\?", "\?"}, +}}; + static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { - if (magic.size() != 5) + if (magic.size() != 5) { return IPSFileType::Error; - if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'}) + } + + constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}}; + if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) { return IPSFileType::IPS; - if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'}) + } + + constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}}; + if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) { return IPSFileType::IPS32; + } + return IPSFileType::Error; } +static bool IsEOF(IPSFileType type, const std::vector<u8>& data) { + constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}}; + if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) { + return true; + } + + constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}}; + return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin()); +} + VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { if (in == nullptr || ips == nullptr) return nullptr; @@ -39,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { u64 offset = 5; // After header while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) { offset += temp.size(); - if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} || - type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) { + if (IsEOF(type, temp)) { break; } @@ -68,21 +107,232 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return nullptr; if (real_offset + rle_size > in_data.size()) - rle_size = in_data.size() - real_offset; + rle_size = static_cast<u16>(in_data.size() - real_offset); std::memset(in_data.data() + real_offset, data.get(), rle_size); } else { // Standard Patch auto read = data_size; if (real_offset + read > in_data.size()) - read = in_data.size() - real_offset; + read = static_cast<u16>(in_data.size() - real_offset); if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) return nullptr; offset += data_size; } } - if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'}) + if (!IsEOF(type, temp)) { return nullptr; - return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); + } + + return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), + in->GetContainingDirectory()); +} + +struct IPSwitchCompiler::IPSwitchPatch { + std::string name; + bool enabled; + std::map<u32, std::vector<u8>> records; +}; + +IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { + Parse(); +} + +IPSwitchCompiler::~IPSwitchCompiler() = default; + +std::array<u8, 32> IPSwitchCompiler::GetBuildID() const { + return nso_build_id; +} + +bool IPSwitchCompiler::IsValid() const { + return valid; +} + +static bool StartsWith(std::string_view base, std::string_view check) { + return base.size() >= check.size() && base.substr(0, check.size()) == check; +} + +static std::string EscapeStringSequences(std::string in) { + for (const auto& seq : ESCAPE_CHARACTER_MAP) { + for (auto index = in.find(seq.first); index != std::string::npos; + index = in.find(seq.first, index)) { + in.replace(index, std::strlen(seq.first), seq.second); + index += std::strlen(seq.second); + } + } + + return in; +} + +void IPSwitchCompiler::ParseFlag(const std::string& line) { + if (StartsWith(line, "@flag offset_shift ")) { + // Offset Shift Flag + offset_shift = std::stoll(line.substr(19), nullptr, 0); + } else if (StartsWith(line, "@little-endian")) { + // Set values to read as little endian + is_little_endian = true; + } else if (StartsWith(line, "@big-endian")) { + // Set values to read as big endian + is_little_endian = false; + } else if (StartsWith(line, "@flag print_values")) { + // Force printing of applied values + print_values = true; + } +} + +void IPSwitchCompiler::Parse() { + const auto bytes = patch_text->ReadAllBytes(); + std::stringstream s; + s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()); + + std::vector<std::string> lines; + std::string stream_line; + while (std::getline(s, stream_line)) { + // Remove a trailing \r + if (!stream_line.empty() && stream_line.back() == '\r') + stream_line.pop_back(); + lines.push_back(std::move(stream_line)); + } + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + // Remove midline comments + std::size_t comment_index = std::string::npos; + bool within_string = false; + for (std::size_t k = 0; k < line.size(); ++k) { + if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) { + within_string = !within_string; + } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) { + comment_index = k; + break; + } + } + + if (!StartsWith(line, "//") && comment_index != std::string::npos) { + last_comment = line.substr(comment_index + 2); + line = line.substr(0, comment_index); + } + + if (StartsWith(line, "@stop")) { + // Force stop + break; + } else if (StartsWith(line, "@nsobid-")) { + // NSO Build ID Specifier + auto raw_build_id = line.substr(8); + if (raw_build_id.size() != 0x40) + raw_build_id.resize(0x40, '0'); + nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); + } else if (StartsWith(line, "#")) { + // Mandatory Comment + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}", + patch_text->GetName(), line.substr(1)); + } else if (StartsWith(line, "//")) { + // Normal Comment + last_comment = line.substr(2); + if (last_comment.find_first_not_of(' ') == std::string::npos) + continue; + if (last_comment.find_first_not_of(' ') != 0) + last_comment = last_comment.substr(last_comment.find_first_not_of(' ')); + } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) { + // Start of patch + const auto enabled = StartsWith(line, "@enabled"); + if (i == 0) + return; + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})", + patch_text->GetName(), last_comment, line.substr(1)); + + IPSwitchPatch patch{last_comment, enabled, {}}; + + // Read rest of patch + while (true) { + if (i + 1 >= lines.size()) + break; + const auto patch_line = lines[++i]; + + // Start of new patch + if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { + --i; + break; + } + + // Check for a flag + if (StartsWith(patch_line, "@")) { + ParseFlag(patch_line); + continue; + } + + // 11 - 8 hex digit offset + space + minimum two digit overwrite val + if (patch_line.length() < 11) + break; + auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); + offset += static_cast<unsigned long>(offset_shift); + + std::vector<u8> replace; + // 9 - first char of replacement val + if (patch_line[9] == '\"') { + // string replacement + auto end_index = patch_line.find('\"', 10); + if (end_index == std::string::npos || end_index < 10) + return; + while (patch_line[end_index - 1] == '\\') { + end_index = patch_line.find('\"', end_index + 1); + if (end_index == std::string::npos || end_index < 10) + return; + } + + auto value = patch_line.substr(10, end_index - 10); + value = EscapeStringSequences(value); + replace.reserve(value.size()); + std::copy(value.begin(), value.end(), std::back_inserter(replace)); + } else { + // hex replacement + const auto value = patch_line.substr(9); + replace.reserve(value.size() / 2); + replace = Common::HexStringToVector(value, is_little_endian); + } + + if (print_values) { + LOG_INFO(Loader, + "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " + "with byte string '{}'", + patch_text->GetName(), offset, Common::HexVectorToString(replace)); + } + + patch.records.insert_or_assign(offset, std::move(replace)); + } + + patches.push_back(std::move(patch)); + } else if (StartsWith(line, "@")) { + ParseFlag(line); + } + } + + valid = true; +} + +VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { + if (in == nullptr || !valid) + return nullptr; + + auto in_data = in->ReadAllBytes(); + + for (const auto& patch : patches) { + if (!patch.enabled) + continue; + + for (const auto& record : patch.records) { + if (record.first >= in_data.size()) + continue; + auto replace_size = record.second.size(); + if (record.first + replace_size > in_data.size()) + replace_size = in_data.size() - record.first; + for (std::size_t i = 0; i < replace_size; ++i) + in_data[i + record.first] = record.second[i]; + } + } + + return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), + in->GetContainingDirectory()); } } // namespace FileSys diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h index 81c163494..450b2f71e 100644 --- a/src/core/file_sys/ips_layer.h +++ b/src/core/file_sys/ips_layer.h @@ -4,12 +4,41 @@ #pragma once +#include <array> #include <memory> +#include <vector> +#include "common/common_types.h" #include "core/file_sys/vfs.h" namespace FileSys { VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); +class IPSwitchCompiler { +public: + explicit IPSwitchCompiler(VirtualFile patch_text); + ~IPSwitchCompiler(); + + std::array<u8, 0x20> GetBuildID() const; + bool IsValid() const; + VirtualFile Apply(const VirtualFile& in) const; + +private: + struct IPSwitchPatch; + + void ParseFlag(const std::string& flag); + void Parse(); + + bool valid = false; + + VirtualFile patch_text; + std::vector<IPSwitchPatch> patches; + std::array<u8, 0x20> nso_build_id{}; + bool is_little_endian = false; + s64 offset_shift = 0; + bool print_values = false; + std::string last_comment = ""; +}; + } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 1ac00ebb0..019caebe9 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { return exefs; } -static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs, - const std::string& build_id) { - std::vector<VirtualFile> ips; - ips.reserve(patch_dirs.size()); +static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) { + std::vector<VirtualFile> out; + out.reserve(patch_dirs.size()); for (const auto& subdir : patch_dirs) { auto exefs_dir = subdir->GetSubdirectory("exefs"); if (exefs_dir != nullptr) { for (const auto& file : exefs_dir->GetFiles()) { - if (file->GetExtension() != "ips") - continue; - auto name = file->GetName(); - const auto p1 = name.substr(0, name.find('.')); - const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); - - if (build_id == this_build_id) - ips.push_back(file); + if (file->GetExtension() == "ips") { + auto name = file->GetName(); + const auto p1 = name.substr(0, name.find('.')); + const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); + + if (build_id == this_build_id) + out.push_back(file); + } else if (file->GetExtension() == "pchtxt") { + IPSwitchCompiler compiler{file}; + if (!compiler.IsValid()) + continue; + + auto this_build_id = Common::HexArrayToString(compiler.GetBuildID()); + this_build_id = + this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); + + if (build_id == this_build_id) + out.push_back(file); + } } } } - return ips; + return out; } std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { @@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - const auto ips = CollectIPSPatches(patch_dirs, build_id); + const auto patches = CollectPatches(patch_dirs, build_id); auto out = nso; - for (const auto& ips_file : ips) { - LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"", - ips_file->GetContainingDirectory()->GetParentDirectory()->GetName()); - const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file); - if (patched != nullptr) - out = patched->ReadAllBytes(); + for (const auto& patch_file : patches) { + if (patch_file->GetExtension() == "ips") { + LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"", + patch_file->GetContainingDirectory()->GetParentDirectory()->GetName()); + const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file); + if (patched != nullptr) + out = patched->ReadAllBytes(); + } else if (patch_file->GetExtension() == "pchtxt") { + LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"", + patch_file->GetContainingDirectory()->GetParentDirectory()->GetName()); + const IPSwitchCompiler compiler{patch_file}; + const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out)); + if (patched != nullptr) + out = patched->ReadAllBytes(); + } } if (out.size() < 0x100) @@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - return !CollectIPSPatches(patch_dirs, build_id).empty(); + return !CollectPatches(patch_dirs, build_id).empty(); } static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { @@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); std::vector<VirtualDir> layers; + std::vector<VirtualDir> layers_ext; layers.reserve(patch_dirs.size() + 1); + layers_ext.reserve(patch_dirs.size() + 1); for (const auto& subdir : patch_dirs) { auto romfs_dir = subdir->GetSubdirectory("romfs"); if (romfs_dir != nullptr) layers.push_back(std::move(romfs_dir)); + + auto ext_dir = subdir->GetSubdirectory("romfs_ext"); + if (ext_dir != nullptr) + layers_ext.push_back(std::move(ext_dir)); } layers.push_back(std::move(extracted)); @@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } - auto packed = CreateRomFS(std::move(layered)); + auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); + + auto packed = CreateRomFS(std::move(layered), std::move(layered_ext)); if (packed == nullptr) { return; } @@ -263,8 +291,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; - if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs"))) - AppendCommaIfNotEmpty(types, "IPS"); + + const auto exefs_dir = mod->GetSubdirectory("exefs"); + if (IsDirValidAndNonEmpty(exefs_dir)) { + bool ips = false; + bool ipswitch = false; + + for (const auto& file : exefs_dir->GetFiles()) { + if (file->GetExtension() == "ips") + ips = true; + else if (file->GetExtension() == "pchtxt") + ipswitch = true; + } + + if (ips) + AppendCommaIfNotEmpty(types, "IPS"); + if (ipswitch) + AppendCommaIfNotEmpty(types, "IPSwitch"); + } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); @@ -301,23 +345,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto& installed{Service::FileSystem::GetUnionContents()}; const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) return {}; - return ParseControlNCA(base_control_nca); + return ParseControlNCA(*base_control_nca); } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( - const std::shared_ptr<NCA>& nca) const { - const auto base_romfs = nca->GetRomFS(); +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { + const auto base_romfs = nca.GetRomFS(); if (base_romfs == nullptr) return {}; - const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control); + const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); if (romfs == nullptr) return {}; @@ -329,7 +372,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( if (nacp_file == nullptr) nacp_file = extracted->GetFile("Control.nacp"); - const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file); + auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); VirtualFile icon_file; for (const auto& language : FileSys::LANGUAGE_NAMES) { @@ -338,6 +381,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( break; } - return {nacp, icon_file}; + return {std::move(nacp), icon_file}; } } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 2ae9322a1..7d168837f 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -36,6 +36,7 @@ public: // Currently tracked NSO patches: // - IPS + // - IPSwitch std::vector<u8> PatchNSO(const std::vector<u8>& nso) const; // Checks to see if PatchNSO() will have any effect given the NSO's build ID. @@ -56,11 +57,10 @@ public: // Given title_id of the program, attempts to get the control data of the update and parse it, // falling back to the base control data. - std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const; + std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA - std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA( - const std::shared_ptr<NCA>& nca) const; + std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; private: u64 title_id; diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index 5910f7046..81e1f66ac 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { return out; } -VirtualFile CreateRomFS(VirtualDir dir) { +VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) { if (dir == nullptr) return nullptr; - RomFSBuildContext ctx{dir}; + RomFSBuildContext ctx{dir, ext}; return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName()); } diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index ecd1eb725..0ec404731 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file, // Converts a VFS filesystem into a RomFS binary // Returns nullptr on failure -VirtualFile CreateRomFS(VirtualDir dir); +VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr); } // namespace FileSys diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 98eb74298..bd680adfe 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -116,7 +116,7 @@ struct KernelCore::Impl { next_thread_id = 1; process_list.clear(); - current_process.reset(); + current_process = nullptr; handle_table.Clear(); resource_limits.fill(nullptr); @@ -207,7 +207,7 @@ struct KernelCore::Impl { // Lists all processes that exist in the current session. std::vector<SharedPtr<Process>> process_list; - SharedPtr<Process> current_process; + Process* current_process = nullptr; Kernel::HandleTable handle_table; std::array<SharedPtr<ResourceLimit>, 4> resource_limits; @@ -266,15 +266,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) { impl->process_list.push_back(std::move(process)); } -void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) { - impl->current_process = std::move(process); +void KernelCore::MakeCurrentProcess(Process* process) { + impl->current_process = process; } -SharedPtr<Process>& KernelCore::CurrentProcess() { +Process* KernelCore::CurrentProcess() { return impl->current_process; } -const SharedPtr<Process>& KernelCore::CurrentProcess() const { +const Process* KernelCore::CurrentProcess() const { return impl->current_process; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index c0771ecf0..41554821f 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -66,13 +66,13 @@ public: void AppendNewProcess(SharedPtr<Process> process); /// Makes the given process the new current process. - void MakeCurrentProcess(SharedPtr<Process> process); + void MakeCurrentProcess(Process* process); - /// Retrieves a reference to the current process. - SharedPtr<Process>& CurrentProcess(); + /// Retrieves a pointer to the current process. + Process* CurrentProcess(); - /// Retrieves a const reference to the current process. - const SharedPtr<Process>& CurrentProcess() const; + /// Retrieves a const pointer to the current process. + const Process* CurrentProcess() const; /// Adds a port to the named port table void AddNamedPort(std::string name, SharedPtr<ClientPort> port); diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index cfd6e1bad..1342c597e 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -9,7 +9,7 @@ #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" -#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" @@ -78,16 +78,16 @@ void Scheduler::SwitchContext(Thread* new_thread) { // Cancel any outstanding wakeup events for this thread new_thread->CancelWakeupTimer(); - auto previous_process = Core::CurrentProcess(); + auto* const previous_process = Core::CurrentProcess(); current_thread = new_thread; ready_queue.remove(new_thread->GetPriority(), new_thread); new_thread->SetStatus(ThreadStatus::Running); - const auto thread_owner_process = current_thread->GetOwnerProcess(); + auto* const thread_owner_process = current_thread->GetOwnerProcess(); if (previous_process != thread_owner_process) { - Core::CurrentProcess() = thread_owner_process; + Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process); SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 6c4af7e47..3afcce3fe 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -301,13 +301,28 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return Mutex::Release(mutex_addr); } +struct BreakReason { + union { + u64 raw; + BitField<31, 1, u64> dont_kill_application; + }; +}; + /// Break program execution static void Break(u64 reason, u64 info1, u64 info2) { - LOG_CRITICAL( - Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - ASSERT(false); + BreakReason break_reason{reason}; + if (break_reason.dont_kill_application) { + LOG_ERROR( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + } else { + LOG_CRITICAL( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + ASSERT(false); + } } /// Used to output a message on a debug hardware unit - does nothing on a retail unit @@ -326,7 +341,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, info_sub_id, handle); - const auto& current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); const auto& vm_manager = current_process->VMManager(); switch (static_cast<GetInfoType>(info_id)) { @@ -424,7 +439,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { return ERR_INVALID_HANDLE; } - const auto current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); if (thread->GetOwnerProcess() != current_process) { return ERR_INVALID_HANDLE; } @@ -516,7 +531,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_HANDLE; } - return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, + return shared_memory->Map(Core::CurrentProcess(), addr, permissions_type, MemoryPermission::DontCare); } @@ -535,7 +550,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); - return shared_memory->Unmap(Core::CurrentProcess().get(), addr); + return shared_memory->Unmap(Core::CurrentProcess(), addr); } /// Query process memory @@ -573,7 +588,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd /// Exits the current process static void ExitProcess() { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, @@ -621,7 +636,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V auto& kernel = Core::System::GetInstance().Kernel(); CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, - Core::CurrentProcess())); + *Core::CurrentProcess())); const auto new_guest_handle = kernel.HandleTable().Create(thread); if (new_guest_handle.Failed()) { return new_guest_handle.Code(); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 8e514cf9a..33aed8c23 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -194,7 +194,7 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, - VAddr stack_top, SharedPtr<Process> owner_process) { + VAddr stack_top, Process& owner_process) { // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); @@ -208,7 +208,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name // TODO(yuriks): Other checks, returning 0xD9001BEA - if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { + if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) { LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); // TODO (bunnei): Find the correct error code to use here return ResultCode(-1); @@ -232,7 +232,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name thread->wait_handle = 0; thread->name = std::move(name); thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); - thread->owner_process = owner_process; + thread->owner_process = &owner_process; thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get(); thread->scheduler->AddThread(thread, priority); thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); @@ -264,7 +264,7 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri // Initialize new "main" thread const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0, - stack_top, &owner_process); + stack_top, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c6ffbd28c..f4d7bd235 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -89,7 +89,7 @@ public: static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, VAddr stack_top, - SharedPtr<Process> owner_process); + Process& owner_process); std::string GetName() const override { return name; @@ -262,11 +262,11 @@ public: return processor_id; } - SharedPtr<Process>& GetOwnerProcess() { + Process* GetOwnerProcess() { return owner_process; } - const SharedPtr<Process>& GetOwnerProcess() const { + const Process* GetOwnerProcess() const { return owner_process; } @@ -386,7 +386,7 @@ private: u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register. /// Process that owns this thread - SharedPtr<Process> owner_process; + Process* owner_process; /// Objects that the thread is waiting on, in the same order as they were /// passed to WaitSynchronization1/N. diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 79580bcd9..0ecfb5af1 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -61,13 +61,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs AOC_U::~AOC_U() = default; void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [¤t](u64 tid) { - return (tid & DLC_BASE_TITLE_ID_MASK) == current; - })); + rb.Push<u32>(static_cast<u32>( + std::count_if(add_on_content.begin(), add_on_content.end(), + [¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; }))); } void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { @@ -91,7 +91,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { return; } - count = std::min<size_t>(out.size() - offset, count); + count = static_cast<u32>(std::min<size_t>(out.size() - offset, count)); std::rotate(out.begin(), out.begin() + offset, out.end()); out.resize(count); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index d8b8037a8..7555bbe7d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -157,7 +157,12 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset); const auto itr = buffer_mappings.find(params.offset); - ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping"); + if (itr == buffer_mappings.end()) { + LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset); + // Hardware tests shows that unmapping an already unmapped buffer always returns successful + // and doesn't fail. + return 0; + } auto& system_instance = Core::System::GetInstance(); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 9a86e5824..951fd8257 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <cinttypes> +#include <cstring> #include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" @@ -140,7 +141,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) const FileSys::VirtualFile module_file = dir->GetFile(module); if (module_file != nullptr) { const VAddr load_addr = next_load_addr; - next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm); + next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, + std::strcmp(module, "rtld") == 0, pm); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); // Register module with GDBStub GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index c10f826a4..25dd3f04e 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -18,7 +18,9 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nro.h" +#include "core/loader/nso.h" #include "core/memory.h" +#include "core/settings.h" namespace Loader { @@ -150,6 +152,19 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size); } + if (!Settings::values.program_args.empty()) { + const auto arg_data = Settings::values.program_args; + codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + NSOArgumentHeader args_header{ + NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; + const auto end_offset = program_image.size(); + program_image.resize(static_cast<u32>(program_image.size()) + + NSO_ARGUMENT_DATA_ALLOCATION_SIZE); + std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); + std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), + arg_data.size()); + } + // Read MOD header ModHeader mod_header{}; // Default .bss to NRO header bss size if MOD0 section doesn't exist diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 2186b02af..28c6dd9b7 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -17,6 +17,7 @@ #include "core/hle/kernel/vm_manager.h" #include "core/loader/nso.h" #include "core/memory.h" +#include "core/settings.h" namespace Loader { @@ -94,6 +95,7 @@ static constexpr u32 PageAlignSize(u32 size) { } VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, + bool should_pass_arguments, boost::optional<FileSys::PatchManager> pm) { if (file == nullptr) return {}; @@ -125,6 +127,19 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); } + if (should_pass_arguments && !Settings::values.program_args.empty()) { + const auto arg_data = Settings::values.program_args; + codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + NSOArgumentHeader args_header{ + NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; + const auto end_offset = program_image.size(); + program_image.resize(static_cast<u32>(program_image.size()) + + NSO_ARGUMENT_DATA_ALLOCATION_SIZE); + std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); + std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), + arg_data.size()); + } + // MOD header pointer is at .text offset + 4 u32 module_offset; std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); @@ -172,7 +187,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) { // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - LoadModule(file, base_address); + LoadModule(file, base_address, true); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 05353d4d9..70ab3b718 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -11,6 +11,15 @@ namespace Loader { +constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; + +struct NSOArgumentHeader { + u32_le allocated_size; + u32_le actual_size; + INSERT_PADDING_BYTES(0x18); +}; +static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size."); + /// Loads an NSO file class AppLoader_NSO final : public AppLoader, Linker { public: @@ -27,7 +36,7 @@ public: return IdentifyType(file); } - static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, + static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments, boost::optional<FileSys::PatchManager> pm = boost::none); ResultStatus Load(Kernel::Process& process) override; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 5534ce01c..13e57848d 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -35,7 +35,7 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_NSP::~AppLoader_NSP() = default; diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index b006594a6..db91cd01e 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader> secondary_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; u64 title_id; }; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index ee5452eb9..7a619acb4 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -30,7 +30,7 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_XCI::~AppLoader_XCI() = default; diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 770ed1437..46f8dfc9e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader_NCA> nca_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; }; } // namespace Loader diff --git a/src/core/settings.h b/src/core/settings.h index 1808f5937..83b9a04c8 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -155,6 +155,7 @@ struct Values { // Debugging bool use_gdbstub; u16 gdbstub_port; + std::string program_args; // WebService bool enable_telemetry; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index f29fff1e7..7b04792b5 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -2,12 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> + +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/entropy.h> + #include "common/assert.h" #include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" -#include <mbedtls/ctr_drbg.h> -#include <mbedtls/entropy.h> #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -28,11 +32,11 @@ static u64 GenerateTelemetryId() { mbedtls_entropy_context entropy; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_context ctr_drbg; - std::string personalization = "yuzu Telemetry ID"; + constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}}; mbedtls_ctr_drbg_init(&ctr_drbg); ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - reinterpret_cast<const unsigned char*>(personalization.c_str()), + reinterpret_cast<const unsigned char*>(personalization.data()), personalization.size()) == 0); ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id), sizeof(u64)) == 0); diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index cec271df0..2a4845797 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -5,6 +5,7 @@ #pragma once #include <memory> +#include <string> #include "common/telemetry.h" namespace Core { @@ -30,8 +31,6 @@ public: field_collection.AddField(type, name, std::move(value)); } - static void FinalizeAsyncJob(); - private: Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields @@ -53,7 +52,6 @@ u64 RegenerateTelemetryId(); * Verifies the username and token. * @param username yuzu username to use for authentication. * @param token yuzu token to use for authentication. - * @param func A function that gets exectued when the verification is finished * @returns Future with bool indicating whether the verification succeeded */ bool VerifyLogin(const std::string& username, const std::string& token); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index c0a57e71f..37e15bad0 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -15,7 +15,8 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { - Core::CurrentProcess() = Kernel::Process::Create(kernel, ""); + auto process = Kernel::Process::Create(kernel, ""); + kernel.MakeCurrentProcess(process.get()); page_table = &Core::CurrentProcess()->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 740a1006a..c82a0dcfa 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -2429,8 +2429,7 @@ private: ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), "NDV is not implemented"); - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const bool is_array = instr.tmml.array != 0; auto texture_type = instr.tmml.texture_type.Value(); const std::string sampler = @@ -2441,13 +2440,11 @@ private: std::string coord; switch (texture_type) { case Tegra::Shader::TextureType::Texture1D: { - std::string x = regs.GetRegisterAsFloat(instr.gpr8); coord = "float coords = " + x + ';'; break; } case Tegra::Shader::TextureType::Texture2D: { - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; break; } @@ -2457,8 +2454,7 @@ private: UNREACHABLE(); // Fallback to interpreting as a 2D texture for now - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; texture_type = Tegra::Shader::TextureType::Texture2D; } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 4e4c108ab..e8ab23326 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -110,6 +110,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); + setAttribute(Qt::WA_AcceptTouchEvents); InputCommon::Init(); InputCommon::StartJoystickEventHandler(); @@ -190,11 +191,17 @@ QByteArray GRenderWindow::saveGeometry() { return geometry; } -qreal GRenderWindow::windowPixelRatio() { +qreal GRenderWindow::windowPixelRatio() const { // windowHandle() might not be accessible until the window is displayed to screen. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; } +std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { + const qreal pixel_ratio = windowPixelRatio(); + return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), + static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; +} + void GRenderWindow::closeEvent(QCloseEvent* event) { emit Closed(); QWidget::closeEvent(event); @@ -209,31 +216,81 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchBeginEvent + auto pos = event->pos(); if (event->button() == Qt::LeftButton) { - qreal pixelRatio = windowPixelRatio(); - this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), - static_cast<unsigned>(pos.y() * pixelRatio)); + const auto [x, y] = ScaleTouch(pos); + this->TouchPressed(x, y); } else if (event->button() == Qt::RightButton) { InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); } } void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchUpdateEvent + auto pos = event->pos(); - qreal pixelRatio = windowPixelRatio(); - this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), - std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); + const auto [x, y] = ScaleTouch(pos); + this->TouchMoved(x, y); InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchEndEvent + if (event->button() == Qt::LeftButton) this->TouchReleased(); else if (event->button() == Qt::RightButton) InputCommon::GetMotionEmu()->EndTilt(); } +void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { + // TouchBegin always has exactly one touch point, so take the .first() + const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); + this->TouchPressed(x, y); +} + +void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { + QPointF pos; + int active_points = 0; + + // average all active touch points + for (const auto tp : event->touchPoints()) { + if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { + active_points++; + pos += tp.pos(); + } + } + + pos /= active_points; + + const auto [x, y] = ScaleTouch(pos); + this->TouchMoved(x, y); +} + +void GRenderWindow::TouchEndEvent() { + this->TouchReleased(); +} + +bool GRenderWindow::event(QEvent* event) { + if (event->type() == QEvent::TouchBegin) { + TouchBeginEvent(static_cast<QTouchEvent*>(event)); + return true; + } else if (event->type() == QEvent::TouchUpdate) { + TouchUpdateEvent(static_cast<QTouchEvent*>(event)); + return true; + } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) { + TouchEndEvent(); + return true; + } + + return QWidget::event(event); +} + void GRenderWindow::focusOutEvent(QFocusEvent* event) { QWidget::focusOutEvent(event); InputCommon::GetKeyboard()->ReleaseAllKeys(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index f133bfadf..873985564 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -15,6 +15,7 @@ class QKeyEvent; class QScreen; +class QTouchEvent; class GGLWidgetInternal; class GMainWindow; @@ -119,7 +120,7 @@ public: void restoreGeometry(const QByteArray& geometry); // overridden QByteArray saveGeometry(); // overridden - qreal windowPixelRatio(); + qreal windowPixelRatio() const; void closeEvent(QCloseEvent* event) override; @@ -130,6 +131,8 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; void OnClientAreaResized(unsigned width, unsigned height); @@ -148,6 +151,11 @@ signals: void Closed(); private: + std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const; + void TouchBeginEvent(const QTouchEvent* event); + void TouchUpdateEvent(const QTouchEvent* event); + void TouchEndEvent(); + void OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) override; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 650dd03c0..7fec15991 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -134,6 +134,7 @@ void Config::ReadValues() { qt_config->beginGroup("Debugging"); Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); + Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString(); qt_config->endGroup(); qt_config->beginGroup("WebService"); @@ -269,6 +270,7 @@ void Config::SaveValues() { qt_config->beginGroup("Debugging"); qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); + qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args)); qt_config->endGroup(); qt_config->beginGroup("WebService"); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 45d84f19a..9e765fc93 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -33,6 +33,7 @@ void ConfigureDebug::setConfiguration() { ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); + ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); } void ConfigureDebug::applyConfiguration() { @@ -40,6 +41,7 @@ void ConfigureDebug::applyConfiguration() { Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); + Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 5ae7276bd..ff4987604 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -107,6 +107,29 @@ </widget> </item> <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Homebrew</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Arguments String</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="homebrew_args_edit"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index d2b3de683..8f99a1c78 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -27,9 +27,8 @@ #include "yuzu/ui_settings.h" namespace { -void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, - const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon, - std::string& name) { +void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, + std::vector<u8>& icon, std::string& name) { auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); if (icon_file != nullptr) icon = icon_file->ReadAllBytes(); @@ -110,7 +109,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { const FileSys::PatchManager patch{program_id}; const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); if (control != nullptr) - GetMetadataFromControlNCA(patch, control, icon, name); + GetMetadataFromControlNCA(patch, *control, icon, name); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -197,8 +196,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign res2 == Loader::ResultStatus::Success) { // Use from metadata pool. if (nca_control_map.find(program_id) != nca_control_map.end()) { - const auto nca = nca_control_map[program_id]; - GetMetadataFromControlNCA(patch, nca, icon, name); + const auto& nca = nca_control_map[program_id]; + GetMetadataFromControlNCA(patch, *nca, icon, name); } } diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 9d934e220..2470f4640 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -138,6 +138,7 @@ void Config::ReadValues() { Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); + Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); // Web Service Settings::values.enable_telemetry = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 155095095..a9ad92a80 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -40,6 +40,35 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } } +std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const { + int w, h; + SDL_GetWindowSize(render_window, &w, &h); + + touch_x *= w; + touch_y *= h; + + return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)), + static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))}; +} + +void EmuWindow_SDL2::OnFingerDown(float x, float y) { + // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind + // This isn't critical because the best we can do when we have that is to average them, like the + // 3DS does + + const auto [px, py] = TouchToPixelPos(x, y); + TouchPressed(px, py); +} + +void EmuWindow_SDL2::OnFingerMotion(float x, float y) { + const auto [px, py] = TouchToPixelPos(x, y); + TouchMoved(px, py); +} + +void EmuWindow_SDL2::OnFingerUp() { + TouchReleased(); +} + void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { InputCommon::GetKeyboard()->PressKey(key); @@ -219,11 +248,26 @@ void EmuWindow_SDL2::PollEvents() { OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state); break; case SDL_MOUSEMOTION: - OnMouseMotion(event.motion.x, event.motion.y); + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) + OnMouseMotion(event.motion.x, event.motion.y); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y); + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) { + OnMouseButton(event.button.button, event.button.state, event.button.x, + event.button.y); + } + break; + case SDL_FINGERDOWN: + OnFingerDown(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERMOTION: + OnFingerMotion(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERUP: + OnFingerUp(); break; case SDL_QUIT: is_open = false; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index d34902109..b0d4116cc 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -40,6 +40,18 @@ private: /// Called by PollEvents when a mouse button is pressed or released void OnMouseButton(u32 button, u8 state, s32 x, s32 y); + /// Translates pixel position (0..1) to pixel positions + std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const; + + /// Called by PollEvents when a finger starts touching the touchscreen + void OnFingerDown(float x, float y); + + /// Called by PollEvents when a finger moves while touching the touchscreen + void OnFingerMotion(float x, float y); + + /// Called by PollEvents when a finger stops touching the touchscreen + void OnFingerUp(); + /// Called by PollEvents when any event that may cause the window to be resized occurs void OnResize(); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 1d951ca3f..27aba95f6 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -56,9 +56,10 @@ static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] <filename>\n" "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" - "-f, --fullscreen Start in fullscreen mode\n" + "-f, --fullscreen Start in fullscreen mode\n" "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n"; + "-v, --version Output version information and exit\n" + "-p, --program Pass following string as arguments to executable\n"; } static void PrintVersion() { @@ -103,15 +104,13 @@ int main(int argc, char** argv) { bool fullscreen = false; static struct option long_options[] = { - {"gdbport", required_argument, 0, 'g'}, - {"fullscreen", no_argument, 0, 'f'}, - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, - {0, 0, 0, 0}, + {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, + {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0}, }; while (optind < argc) { - char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index); + char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index); if (arg != -1) { switch (arg) { case 'g': @@ -135,6 +134,10 @@ int main(int argc, char** argv) { case 'v': PrintVersion(); return 0; + case 'p': + Settings::values.program_args = argv[optind]; + ++optind; + break; } } else { #ifdef _WIN32 |