diff options
Diffstat (limited to '')
72 files changed, 1952 insertions, 897 deletions
diff --git a/src/core/memory_hook.cpp b/src/common/memory_hook.cpp index c61c6c1fb..3986986d6 100644 --- a/src/core/memory_hook.cpp +++ b/src/common/memory_hook.cpp @@ -2,10 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/memory_hook.h" +#include "common/memory_hook.h" -namespace Memory { +namespace Common { MemoryHook::~MemoryHook() = default; -} // namespace Memory +} // namespace Common diff --git a/src/core/memory_hook.h b/src/common/memory_hook.h index 940777107..adaa4c2c5 100644 --- a/src/core/memory_hook.h +++ b/src/common/memory_hook.h @@ -9,7 +9,7 @@ #include "common/common_types.h" -namespace Memory { +namespace Common { /** * Memory hooks have two purposes: @@ -44,4 +44,4 @@ public: }; using MemoryHookPointer = std::shared_ptr<MemoryHook>; -} // namespace Memory +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e8aefb6b4..9e23afe85 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,6 +31,8 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h + file_sys/cheat_engine.cpp + file_sys/cheat_engine.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -68,6 +70,8 @@ add_library(core STATIC file_sys/system_archive/ng_word.h file_sys/system_archive/system_archive.cpp file_sys/system_archive/system_archive.h + file_sys/system_archive/system_version.cpp + file_sys/system_archive/system_version.h file_sys/vfs.cpp file_sys/vfs.h file_sys/vfs_concat.cpp @@ -107,6 +111,8 @@ add_library(core STATIC hle/kernel/client_port.h hle/kernel/client_session.cpp hle/kernel/client_session.h + hle/kernel/code_set.cpp + hle/kernel/code_set.h hle/kernel/errors.h hle/kernel/handle_table.cpp hle/kernel/handle_table.h @@ -421,8 +427,6 @@ add_library(core STATIC loader/deconstructed_rom_directory.h loader/elf.cpp loader/elf.h - loader/linker.cpp - loader/linker.h loader/loader.cpp loader/loader.h loader/nax.cpp @@ -439,8 +443,6 @@ add_library(core STATIC loader/xci.h memory.cpp memory.h - memory_hook.cpp - memory_hook.h memory_setup.h perf_stats.cpp perf_stats.h diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 9b7ca4030..4fdc12f11 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -12,6 +12,7 @@ #include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" +#include "core/core_timing_util.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/svc.h" @@ -119,7 +120,7 @@ public: return std::max(parent.core_timing.GetDowncount(), 0); } u64 GetCNTPCT() override { - return parent.core_timing.GetTicks(); + return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks()); } ARM_Dynarmic& parent; @@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { config.tpidr_el0 = &cb->tpidr_el0; config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; - config.cntfrq_el0 = 19200000; // Value from fusee. + config.cntfrq_el0 = Timing::CNTFREQ; // Unpredictable instructions config.define_unpredictable_behaviour = true; diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 6cc458296..aada1e862 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -12,7 +12,7 @@ #include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" -namespace Memory { +namespace Common { struct PageTable; } @@ -70,7 +70,7 @@ private: Timing::CoreTiming& core_timing; DynarmicExclusiveMonitor& exclusive_monitor; - Memory::PageTable* current_page_table = nullptr; + Common::PageTable* current_page_table = nullptr; }; class DynarmicExclusiveMonitor final : public ExclusiveMonitor { diff --git a/src/core/core.cpp b/src/core/core.cpp index 89b3fb418..4fe77c25b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -32,6 +32,7 @@ #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "file_sys/cheat_engine.h" #include "frontend/applets/profile_select.h" #include "frontend/applets/software_keyboard.h" #include "frontend/applets/web_browser.h" @@ -205,6 +206,7 @@ struct System::Impl { GDBStub::Shutdown(); Service::Shutdown(); service_manager.reset(); + cheat_engine.reset(); telemetry_session.reset(); gpu_core.reset(); @@ -255,6 +257,8 @@ struct System::Impl { CpuCoreManager cpu_core_manager; bool is_powered_on = false; + std::unique_ptr<FileSys::CheatEngine> cheat_engine; + /// Frontend applets std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; @@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { return impl->debug_context.get(); } +void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, + const std::string& build_id, VAddr code_region_start, + VAddr code_region_end) { + impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id, + code_region_start, code_region_end); +} + void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { impl->virtual_filesystem = std::move(vfs); } diff --git a/src/core/core.h b/src/core/core.h index ba76a41d8..4d83b93cc 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -20,6 +20,7 @@ class WebBrowserApplet; } // namespace Core::Frontend namespace FileSys { +class CheatList; class VfsFilesystem; } // namespace FileSys @@ -253,6 +254,9 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, + VAddr code_region_start, VAddr code_region_end); + void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); const Frontend::ProfileSelectApplet& GetProfileSelector() const; diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp index 88ff70233..7942f30d6 100644 --- a/src/core/core_timing_util.cpp +++ b/src/core/core_timing_util.cpp @@ -7,6 +7,7 @@ #include <cinttypes> #include <limits> #include "common/logging/log.h" +#include "common/uint128.h" namespace Core::Timing { @@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) { return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000; } +u64 CpuCyclesToClockCycles(u64 ticks) { + const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ); + return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first; +} + } // namespace Core::Timing diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h index 513cfac1b..679aa3123 100644 --- a/src/core/core_timing_util.h +++ b/src/core/core_timing_util.h @@ -11,6 +11,7 @@ namespace Core::Timing { // The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz // The exact value used is of course unverified. constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked +constexpr u64 CNTFREQ = 19200000; // Value from fusee. inline s64 msToCycles(int ms) { // since ms is int there is no way to overflow @@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) { return cycles * 1000 / BASE_CLOCK_RATE; } +u64 CpuCyclesToClockCycles(u64 ticks); + } // namespace Core::Timing diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp new file mode 100644 index 000000000..247fbc864 --- /dev/null +++ b/src/core/file_sys/cheat_engine.cpp @@ -0,0 +1,490 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <locale> +#include "common/hex_util.h" +#include "common/microprofile.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/file_sys/cheat_engine.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace FileSys { + +constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); +constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; + +u64 Cheat::Address() const { + u64 out; + std::memcpy(&out, raw.data(), sizeof(u64)); + return Common::swap64(out) & 0xFFFFFFFFFF; +} + +u64 Cheat::ValueWidth(u64 offset) const { + return Value(offset, width); +} + +u64 Cheat::Value(u64 offset, u64 width) const { + u64 out; + std::memcpy(&out, raw.data() + offset, sizeof(u64)); + out = Common::swap64(out); + if (width == 8) + return out; + return out & ((1ull << (width * CHAR_BIT)) - 1); +} + +u32 Cheat::KeypadValue() const { + u32 out; + std::memcpy(&out, raw.data(), sizeof(u32)); + return Common::swap32(out) & 0x0FFFFFFF; +} + +void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, + VAddr heap_end, MemoryWriter writer, MemoryReader reader) { + this->main_region_begin = main_begin; + this->main_region_end = main_end; + this->heap_region_begin = heap_begin; + this->heap_region_end = heap_end; + this->writer = writer; + this->reader = reader; +} + +MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); + +void CheatList::Execute() { + MICROPROFILE_SCOPE(Cheat_Engine); + + std::fill(scratch.begin(), scratch.end(), 0); + in_standard = false; + for (std::size_t i = 0; i < master_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); + current_block = i; + ExecuteBlock(master_list[i].second); + } + + in_standard = true; + for (std::size_t i = 0; i < standard_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); + current_block = i; + ExecuteBlock(standard_list[i].second); + } +} + +CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) + : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} + +bool CheatList::EvaluateConditional(const Cheat& cheat) const { + using ComparisonFunction = bool (*)(u64, u64); + constexpr std::array<ComparisonFunction, 6> comparison_functions{ + [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, + [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, + [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, + }; + + if (cheat.type == CodeType::ConditionalInput) { + const auto applet_resource = + system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); + if (applet_resource == nullptr) { + LOG_WARNING( + Common_Filesystem, + "Attempted to evaluate input conditional, but applet resource is not initialized!"); + return false; + } + + const auto press_state = + applet_resource + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) + .GetAndResetPressState(); + return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; + } + + ASSERT(cheat.type == CodeType::Conditional); + + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); + auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; + const auto addr = cheat.Address() + offset; + + return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); +} + +void CheatList::ProcessBlockPairs(const Block& block) { + block_pairs.clear(); + + u64 scope = 0; + std::map<u64, u64> pairs; + + for (std::size_t i = 0; i < block.size(); ++i) { + const auto& cheat = block[i]; + + switch (cheat.type) { + case CodeType::Conditional: + case CodeType::ConditionalInput: + pairs.insert_or_assign(scope, i); + ++scope; + break; + case CodeType::EndConditional: { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + break; + } + case CodeType::Loop: { + if (cheat.end_of_loop) { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + } else { + pairs.insert_or_assign(scope, i); + ++scope; + } + break; + } + } + } +} + +void CheatList::WriteImmediate(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + const auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = cheat.Address() + offset + register_3; + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, + cheat.Value(8, cheat.width)); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); +} + +void CheatList::BeginConditional(const Cheat& cheat) { + if (EvaluateConditional(cheat)) { + return; + } + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; +} + +void CheatList::EndConditional(const Cheat& cheat) { + LOG_DEBUG(Common_Filesystem, "Ending conditional block."); +} + +void CheatList::Loop(const Cheat& cheat) { + if (cheat.end_of_loop.Value()) + ASSERT(!cheat.end_of_loop.Value()); + + auto& register_3 = scratch.at(cheat.register_3); + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + ASSERT(iter->first < iter->second); + + const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); + for (s32 i = initial_value; i >= 0; --i) { + register_3 = static_cast<u64>(i); + for (std::size_t c = iter->first + 1; c < iter->second; ++c) { + current_index = c; + ExecuteSingleCheat( + (in_standard ? standard_list : master_list)[current_block].second[c]); + } + } + + current_index = iter->second; +} + +void CheatList::LoadImmediate(const Cheat& cheat) { + auto& register_3 = scratch.at(cheat.register_3); + + LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, + cheat.Value(4, 8)); + register_3 = cheat.Value(4, 8); +} + +void CheatList::LoadIndexed(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); + LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", + cheat.register_3, addr); + register_3 = reader(cheat.width, SanitizeAddress(addr)); +} + +void CheatList::StoreIndexed(const Cheat& cheat) { + const auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = + register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", + cheat.Value(4, cheat.width), addr); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); +} + +void CheatList::RegisterArithmetic(const Cheat& cheat) { + using ArithmeticFunction = u64 (*)(u64, u64); + constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ + [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, + [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, + [](u64 a, u64 b) { return a >> b; }, + }; + + using ArithmeticOverflowCheck = bool (*)(u64, u64); + constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b + [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b + [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b + }; + + static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), + "Missing or have extra arithmetic overflow checks compared to functions!"); + + auto& register_3 = scratch.at(cheat.register_3); + + ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); + auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; + auto* overflow_function = + arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; + LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", + cheat.register_3, cheat.ValueWidth(4)); + + if (overflow_function(register_3, cheat.ValueWidth(4))) { + LOG_WARNING(Common_Filesystem, + "overflow will occur when performing arithmetic operation={:02X} with operands " + "a={:016X}, b={:016X}!", + static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); + } + + register_3 = function(register_3, cheat.ValueWidth(4)); +} + +void CheatList::BeginConditionalInput(const Cheat& cheat) { + if (EvaluateConditional(cheat)) + return; + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; +} + +VAddr CheatList::SanitizeAddress(VAddr in) const { + if ((in < main_region_begin || in >= main_region_end) && + (in < heap_region_begin || in >= heap_region_end)) { + LOG_ERROR(Common_Filesystem, + "Cheat attempting to access memory at invalid address={:016X}, if this persists, " + "the cheat may be incorrect. However, this may be normal early in execution if " + "the game has not properly set up yet.", + in); + return 0; ///< Invalid addresses will hard crash + } + + return in; +} + +void CheatList::ExecuteSingleCheat(const Cheat& cheat) { + using CheatOperationFunction = void (CheatList::*)(const Cheat&); + constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ + &CheatList::WriteImmediate, &CheatList::BeginConditional, + &CheatList::EndConditional, &CheatList::Loop, + &CheatList::LoadImmediate, &CheatList::LoadIndexed, + &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, + &CheatList::BeginConditionalInput, + }; + + const auto index = static_cast<u8>(cheat.type.Value()); + ASSERT(index < sizeof(cheat_operation_functions)); + const auto op = cheat_operation_functions[index]; + (this->*op)(cheat); +} + +void CheatList::ExecuteBlock(const Block& block) { + encountered_loops.clear(); + + ProcessBlockPairs(block); + for (std::size_t i = 0; i < block.size(); ++i) { + current_index = i; + ExecuteSingleCheat(block[i]); + i = current_index; + } +} + +CheatParser::~CheatParser() = default; + +CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const { + return {system, std::move(master), std::move(standard)}; +} + +TextCheatParser::~TextCheatParser() = default; + +CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { + std::stringstream ss; + ss.write(reinterpret_cast<const char*>(data.data()), data.size()); + + std::vector<std::string> lines; + std::string stream_line; + while (std::getline(ss, 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)); + } + + CheatList::ProgramSegment master_list; + CheatList::ProgramSegment standard_list; + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + const auto master = line[0] == '{'; + const auto begin = master ? line.find('{') : line.find('['); + const auto end = master ? line.rfind('}') : line.rfind(']'); + + ASSERT(begin != std::string::npos && end != std::string::npos); + + const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; + CheatList::Block block{}; + + while (i < lines.size() - 1) { + line = lines[++i]; + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + --i; + break; + } + + if (line.size() < 8) + continue; + + Cheat out{}; + out.raw = ParseSingleLineCheat(line); + block.push_back(out); + } + + (master ? master_list : standard_list).emplace_back(patch_name, block); + } + } + + return MakeCheatList(system, master_list, standard_list); +} + +std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { + std::array<u8, 16> out{}; + + if (line.size() < 8) + return out; + + const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); + std::memcpy(out.data(), word1.data(), sizeof(u32)); + + if (line.size() < 17 || line[8] != ' ') + return out; + + const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); + std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); + + if (line.size() < 26 || line[17] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); + if (type == CodeType::Loop || type == CodeType::LoadImmediate || + type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { + std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); + std::memset(out.data() + 4, 0, sizeof(u32)); + } + + return out; + } + + const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); + std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); + + if (line.size() < 35 || line[26] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); + if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { + std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); + std::memset(out.data() + 8, 0, sizeof(u32)); + } + + return out; + } + + const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); + std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); + + return out; +} + +u64 MemoryReadImpl(u32 width, VAddr addr) { + switch (width) { + case 1: + return Memory::Read8(addr); + case 2: + return Memory::Read16(addr); + case 4: + return Memory::Read32(addr); + case 8: + return Memory::Read64(addr); + default: + UNREACHABLE(); + return 0; + } +} + +void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { + switch (width) { + case 1: + Memory::Write8(addr, static_cast<u8>(value)); + break; + case 2: + Memory::Write16(addr, static_cast<u16>(value)); + break; + case 4: + Memory::Write32(addr, static_cast<u32>(value)); + break; + case 8: + Memory::Write64(addr, value); + break; + default: + UNREACHABLE(); + } +} + +CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, + const std::string& build_id, VAddr code_region_start, + VAddr code_region_end) + : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { + event = core_timing.RegisterEvent( + "CheatEngine::FrameCallback::" + build_id, + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); + + const auto& vm_manager = system.CurrentProcess()->VMManager(); + for (auto& list : this->cheats) { + list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), + code_region_end, vm_manager.GetHeapRegionEndAddress(), + &MemoryWriteImpl, &MemoryReadImpl); + } +} + +CheatEngine::~CheatEngine() { + core_timing.UnscheduleEvent(event, 0); +} + +void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { + for (auto& list : cheats) { + list.Execute(); + } + + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); +} + +} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h new file mode 100644 index 000000000..ac22a82cb --- /dev/null +++ b/src/core/file_sys/cheat_engine.h @@ -0,0 +1,234 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <set> +#include <vector> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Core { +class System; +} + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace FileSys { + +enum class CodeType : u32 { + // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Writes a T sized value Y to the address A added to the value of register R in memory domain M + WriteImmediate = 0, + + // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Compares the T sized value Y to the value at address A in memory domain M using the + // conditional function C. If success, continues execution. If failure, jumps to the matching + // EndConditional statement. + Conditional = 1, + + // 20000000 + // Terminates a Conditional or ConditionalInput block. + EndConditional = 2, + + // 300R0000 VVVVVVVV + // Starts looping V times, storing the current count in register R. + // Loop block is terminated with a matching 310R0000. + Loop = 3, + + // 400R0000 VVVVVVVV VVVVVVVV + // Sets the value of register R to the value V. + LoadImmediate = 4, + + // 5TMRI0AA AAAAAAAA + // Sets the value of register R to the value of width T at address A in memory domain M, with + // the current value of R added to the address if I == 1. + LoadIndexed = 5, + + // 6T0RIFG0 VVVVVVVV VVVVVVVV + // Writes the value V of width T to the memory address stored in register R. Adds the value of + // register G to the final calculation if F is nonzero. Increments the value of register R by T + // after operation if I is nonzero. + StoreIndexed = 6, + + // 7T0RA000 VVVVVVVV + // Performs the arithmetic operation A on the value in register R and the value V of width T, + // storing the result in register R. + RegisterArithmetic = 7, + + // 8KKKKKKK + // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, + // execution continues. If none are, execution skips to the next EndConditional command. + ConditionalInput = 8, +}; + +enum class MemoryType : u32 { + // Addressed relative to start of main NSO + MainNSO = 0, + + // Addressed relative to start of heap + Heap = 1, +}; + +enum class ArithmeticOp : u32 { + Add = 0, + Sub = 1, + Mult = 2, + LShift = 3, + RShift = 4, +}; + +enum class ComparisonOp : u32 { + GreaterThan = 1, + GreaterThanEqual = 2, + LessThan = 3, + LessThanEqual = 4, + Equal = 5, + Inequal = 6, +}; + +union Cheat { + std::array<u8, 16> raw; + + BitField<4, 4, CodeType> type; + BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. + BitField<0, 4, u32> end_of_loop; + BitField<12, 4, MemoryType> memory_type; + BitField<8, 4, u32> register_3; + BitField<8, 4, ComparisonOp> comparison_op; + BitField<20, 4, u32> load_from_register; + BitField<20, 4, u32> increment_register; + BitField<20, 4, ArithmeticOp> arithmetic_op; + BitField<16, 4, u32> add_additional_register; + BitField<28, 4, u32> register_6; + + u64 Address() const; + u64 ValueWidth(u64 offset) const; + u64 Value(u64 offset, u64 width) const; + u32 KeypadValue() const; +}; + +class CheatParser; + +// Represents a full collection of cheats for a game. The Execute function should be called every +// interval that all cheats should be executed. Clients should not directly instantiate this class +// (hence private constructor), they should instead receive an instance from CheatParser, which +// guarantees the list is always in an acceptable state. +class CheatList { +public: + friend class CheatParser; + + using Block = std::vector<Cheat>; + using ProgramSegment = std::vector<std::pair<std::string, Block>>; + + // (width in bytes, address, value) + using MemoryWriter = void (*)(u32, VAddr, u64); + // (width in bytes, address) -> value + using MemoryReader = u64 (*)(u32, VAddr); + + void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, + MemoryWriter writer, MemoryReader reader); + + void Execute(); + +private: + CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); + + void ProcessBlockPairs(const Block& block); + void ExecuteSingleCheat(const Cheat& cheat); + + void ExecuteBlock(const Block& block); + + bool EvaluateConditional(const Cheat& cheat) const; + + // Individual cheat operations + void WriteImmediate(const Cheat& cheat); + void BeginConditional(const Cheat& cheat); + void EndConditional(const Cheat& cheat); + void Loop(const Cheat& cheat); + void LoadImmediate(const Cheat& cheat); + void LoadIndexed(const Cheat& cheat); + void StoreIndexed(const Cheat& cheat); + void RegisterArithmetic(const Cheat& cheat); + void BeginConditionalInput(const Cheat& cheat); + + VAddr SanitizeAddress(VAddr in) const; + + // Master Codes are defined as codes that cannot be disabled and are run prior to all + // others. + ProgramSegment master_list; + // All other codes + ProgramSegment standard_list; + + bool in_standard = false; + + // 16 (0x0-0xF) scratch registers that can be used by cheats + std::array<u64, 16> scratch{}; + + MemoryWriter writer = nullptr; + MemoryReader reader = nullptr; + + u64 main_region_begin{}; + u64 heap_region_begin{}; + u64 main_region_end{}; + u64 heap_region_end{}; + + u64 current_block{}; + // The current index of the cheat within the current Block + u64 current_index{}; + + // The 'stack' of the program. When a conditional or loop statement is encountered, its index is + // pushed onto this queue. When a end block is encountered, the condition is checked. + std::map<u64, u64> block_pairs; + + std::set<u64> encountered_loops; + + const Core::System* system; +}; + +// Intermediary class that parses a text file or other disk format for storing cheats into a +// CheatList object, that can be used for execution. +class CheatParser { +public: + virtual ~CheatParser(); + + virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; + +protected: + CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const; +}; + +// CheatParser implementation that parses text files +class TextCheatParser final : public CheatParser { +public: + ~TextCheatParser() override; + + CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; + +private: + std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; +}; + +// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming +class CheatEngine final { +public: + CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, + VAddr code_region_start, VAddr code_region_end); + ~CheatEngine(); + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + + std::vector<CheatList> cheats; + + Core::Timing::EventType* event; + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 5d4d05c82..15b9e6624 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -24,13 +24,26 @@ namespace FileSys { union NCASectionHeader; +/// Describes the type of content within an NCA archive. enum class NCAContentType : u8 { + /// Executable-related data Program = 0, + + /// Metadata. Meta = 1, + + /// Access control data. Control = 2, + + /// Information related to the game manual + /// e.g. Legal information, etc. Manual = 3, + + /// System data. Data = 4, - Data_Unknown5 = 5, ///< Seems to be used on some system archives + + /// Data that can be accessed by applications. + PublicData = 5, }; enum class NCASectionCryptoType : u8 { diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index e4a4ee4ab..bb4654366 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -11,6 +11,9 @@ namespace FileSys { constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1}; constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002}; constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001}; +constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005}; +constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223}; +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 61706966e..e11217708 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -7,6 +7,7 @@ #include <cstddef> #include <cstring> +#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "core/file_sys/content_archive.h" @@ -19,6 +20,7 @@ #include "core/file_sys/vfs_vector.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#include "core/loader/nso.h" #include "core/settings.h" namespace FileSys { @@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", }; -struct NSOBuildHeader { - u32_le magic; - INSERT_PADDING_BYTES(0x3C); - std::array<u8, 0x20> build_id; - INSERT_PADDING_BYTES(0xA0); -}; -static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size."); - std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { std::array<u8, sizeof(u32)> bytes{}; bytes[0] = version % SINGLE_BYTE_MODULUS; @@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD } std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { - if (nso.size() < 0x100) + if (nso.size() < sizeof(Loader::NSOHeader)) { return nso; + } - NSOBuildHeader header; - std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader)); + Loader::NSOHeader header; + std::memcpy(&header, nso.data(), sizeof(header)); - if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { return nso; + } const auto build_id_raw = Common::HexArrayToString(header.build_id); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); @@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { } } - if (out.size() < 0x100) + if (out.size() < sizeof(Loader::NSOHeader)) { return nso; - std::memcpy(out.data(), &header, sizeof(NSOBuildHeader)); + } + + std::memcpy(out.data(), &header, sizeof(header)); return out; } @@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } +static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, + const std::array<u8, 0x20>& build_id_, + const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexArrayToString(build_id_, upper); + const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); + const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); + + if (file == nullptr) { + LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + std::vector<u8> data(file->GetSize()); + if (file->Read(data.data(), data.size()) != data.size()) { + LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + TextCheatParser parser; + return parser.Parse(system, data); +} + +std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, + const std::array<u8, 32>& build_id_) const { + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + 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(); }); + + std::vector<CheatList> out; + out.reserve(patch_dirs.size()); + for (const auto& subdir : patch_dirs) { + auto cheats_dir = subdir->GetSubdirectory("cheats"); + if (cheats_dir != nullptr) { + auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); + if (res.has_value()) { + out.push_back(std::move(*res)); + continue; + } + + res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); + if (res.has_value()) + out.push_back(std::move(*res)); + } + } + + return out; +} + static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || @@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); + if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats"))) + AppendCommaIfNotEmpty(types, "Cheats"); if (types.empty()) continue; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index b8a1652fd..de2672c76 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,14 @@ #include <memory> #include <string> #include "common/common_types.h" +#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" +namespace Core { +class System; +} + namespace FileSys { class NCA; @@ -45,6 +50,10 @@ public: // Used to prevent expensive copies in NSO loader. bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; + // Creates a CheatList object with all + std::vector<CheatList> CreateCheatList(const Core::System& system, + const std::array<u8, 0x20>& build_id) const; + // Currently tracked RomFS patches: // - Game Updates // - LayeredFS diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 128199063..1c6bacace 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { case NCAContentType::Control: return ContentRecordType::Control; case NCAContentType::Data: - case NCAContentType::Data_Unknown5: + case NCAContentType::PublicData: return ContentRecordType::Data; case NCAContentType::Manual: // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index e3e79f40a..c9722ed77 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -6,6 +6,7 @@ #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/ng_word.h" #include "core/file_sys/system_archive/system_archive.h" +#include "core/file_sys/system_archive/system_version.h" namespace FileSys::SystemArchive { @@ -30,7 +31,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI {0x0100000000000806, "NgWord", &NgWord1}, {0x0100000000000807, "SsidList", nullptr}, {0x0100000000000808, "Dictionary", nullptr}, - {0x0100000000000809, "SystemVersion", nullptr}, + {0x0100000000000809, "SystemVersion", &SystemVersion}, {0x010000000000080A, "AvatarImage", nullptr}, {0x010000000000080B, "LocalNews", nullptr}, {0x010000000000080C, "Eula", nullptr}, diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp new file mode 100644 index 000000000..6e22f97b0 --- /dev/null +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -0,0 +1,52 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/system_archive/system_version.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys::SystemArchive { + +namespace SystemVersionData { + +// This section should reflect the best system version to describe yuzu's HLE api. +// TODO(DarkLordZach): Update when HLE gets better. + +constexpr u8 VERSION_MAJOR = 5; +constexpr u8 VERSION_MINOR = 1; +constexpr u8 VERSION_MICRO = 0; + +constexpr u8 REVISION_MAJOR = 3; +constexpr u8 REVISION_MINOR = 0; + +constexpr char PLATFORM_STRING[] = "NX"; +constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd"; +constexpr char DISPLAY_VERSION[] = "5.1.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0"; + +} // namespace SystemVersionData + +std::string GetLongDisplayVersion() { + return SystemVersionData::DISPLAY_TITLE; +} + +VirtualDir SystemVersion() { + VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file"); + file->WriteObject(SystemVersionData::VERSION_MAJOR, 0); + file->WriteObject(SystemVersionData::VERSION_MINOR, 1); + file->WriteObject(SystemVersionData::VERSION_MICRO, 2); + file->WriteObject(SystemVersionData::REVISION_MAJOR, 4); + file->WriteObject(SystemVersionData::REVISION_MINOR, 5); + file->WriteArray(SystemVersionData::PLATFORM_STRING, + std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8); + file->WriteArray(SystemVersionData::VERSION_HASH, + std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28); + file->WriteArray(SystemVersionData::DISPLAY_VERSION, + std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68); + file->WriteArray(SystemVersionData::DISPLAY_TITLE, + std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80); + return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file}, + std::vector<VirtualDir>{}, "data"); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h new file mode 100644 index 000000000..deed79b26 --- /dev/null +++ b/src/core/file_sys/system_archive/system_version.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +std::string GetLongDisplayVersion(); + +VirtualDir SystemVersion(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 455d1f346..fae54bcc7 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -39,10 +39,10 @@ struct CommandHeader { union { u32_le raw_low; BitField<0, 16, CommandType> type; - BitField<16, 4, u32_le> num_buf_x_descriptors; - BitField<20, 4, u32_le> num_buf_a_descriptors; - BitField<24, 4, u32_le> num_buf_b_descriptors; - BitField<28, 4, u32_le> num_buf_w_descriptors; + BitField<16, 4, u32> num_buf_x_descriptors; + BitField<20, 4, u32> num_buf_a_descriptors; + BitField<24, 4, u32> num_buf_b_descriptors; + BitField<28, 4, u32> num_buf_w_descriptors; }; enum class BufferDescriptorCFlag : u32 { @@ -53,28 +53,28 @@ struct CommandHeader { union { u32_le raw_high; - BitField<0, 10, u32_le> data_size; + BitField<0, 10, u32> data_size; BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags; - BitField<31, 1, u32_le> enable_handle_descriptor; + BitField<31, 1, u32> enable_handle_descriptor; }; }; static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect"); union HandleDescriptorHeader { u32_le raw_high; - BitField<0, 1, u32_le> send_current_pid; - BitField<1, 4, u32_le> num_handles_to_copy; - BitField<5, 4, u32_le> num_handles_to_move; + BitField<0, 1, u32> send_current_pid; + BitField<1, 4, u32> num_handles_to_copy; + BitField<5, 4, u32> num_handles_to_move; }; static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect"); struct BufferDescriptorX { union { - BitField<0, 6, u32_le> counter_bits_0_5; - BitField<6, 3, u32_le> address_bits_36_38; - BitField<9, 3, u32_le> counter_bits_9_11; - BitField<12, 4, u32_le> address_bits_32_35; - BitField<16, 16, u32_le> size; + BitField<0, 6, u32> counter_bits_0_5; + BitField<6, 3, u32> address_bits_36_38; + BitField<9, 3, u32> counter_bits_9_11; + BitField<12, 4, u32> address_bits_32_35; + BitField<16, 16, u32> size; }; u32_le address_bits_0_31; @@ -103,10 +103,10 @@ struct BufferDescriptorABW { u32_le address_bits_0_31; union { - BitField<0, 2, u32_le> flags; - BitField<2, 3, u32_le> address_bits_36_38; - BitField<24, 4, u32_le> size_bits_32_35; - BitField<28, 4, u32_le> address_bits_32_35; + BitField<0, 2, u32> flags; + BitField<2, 3, u32> address_bits_36_38; + BitField<24, 4, u32> size_bits_32_35; + BitField<28, 4, u32> address_bits_32_35; }; VAddr Address() const { @@ -128,8 +128,8 @@ struct BufferDescriptorC { u32_le address_bits_0_31; union { - BitField<0, 16, u32_le> address_bits_32_47; - BitField<16, 16, u32_le> size; + BitField<0, 16, u32> address_bits_32_47; + BitField<16, 16, u32> size; }; VAddr Address() const { @@ -167,8 +167,8 @@ struct DomainMessageHeader { struct { union { BitField<0, 8, CommandType> command; - BitField<8, 8, u32_le> input_object_count; - BitField<16, 16, u32_le> size; + BitField<8, 8, u32> input_object_count; + BitField<16, 16, u32> size; }; u32_le object_id; INSERT_PADDING_WORDS(2); diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 079283830..68406eb63 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -19,9 +19,12 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/result.h" namespace IPC { +constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301}; + class RequestHelperBase { protected: Kernel::HLERequestContext* context = nullptr; @@ -272,6 +275,20 @@ inline void ResponseBuilder::Push(u64 value) { } template <> +inline void ResponseBuilder::Push(float value) { + u32 integral; + std::memcpy(&integral, &value, sizeof(u32)); + Push(integral); +} + +template <> +inline void ResponseBuilder::Push(double value) { + u64 integral; + std::memcpy(&integral, &value, sizeof(u64)); + Push(integral); +} + +template <> inline void ResponseBuilder::Push(bool value) { Push(static_cast<u8>(value)); } @@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() { return cmdbuf[index++]; } +template <> +inline s32 RequestParser::Pop() { + return static_cast<s32>(Pop<u32>()); +} + template <typename T> void RequestParser::PopRaw(T& value) { std::memcpy(&value, cmdbuf + index, sizeof(T)); @@ -393,11 +415,37 @@ inline u64 RequestParser::Pop() { } template <> +inline s8 RequestParser::Pop() { + return static_cast<s8>(Pop<u8>()); +} + +template <> +inline s16 RequestParser::Pop() { + return static_cast<s16>(Pop<u16>()); +} + +template <> inline s64 RequestParser::Pop() { return static_cast<s64>(Pop<u64>()); } template <> +inline float RequestParser::Pop() { + const u32 value = Pop<u32>(); + float real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +template <> +inline double RequestParser::Pop() { + const u64 value = Pop<u64>(); + float real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +template <> inline bool RequestParser::Pop() { return Pop<u8>() != 0; } diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index d4c91d529..aa432658e 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { // Create a new session pair, let the created sessions inherit the parent port's HLE handler. auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this); - if (server_port->hle_handler) - server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); - else - server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions)); + if (server_port->HasHLEHandler()) { + server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); + } else { + server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions)); + } // Wake the threads waiting on the ServerPort server_port->WakeupAllWaitingThreads(); diff --git a/src/core/hle/kernel/code_set.cpp b/src/core/hle/kernel/code_set.cpp new file mode 100644 index 000000000..1f434e9af --- /dev/null +++ b/src/core/hle/kernel/code_set.cpp @@ -0,0 +1,12 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/code_set.h" + +namespace Kernel { + +CodeSet::CodeSet() = default; +CodeSet::~CodeSet() = default; + +} // namespace Kernel diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h new file mode 100644 index 000000000..834fd23d2 --- /dev/null +++ b/src/core/hle/kernel/code_set.h @@ -0,0 +1,90 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> +#include <vector> + +#include "common/common_types.h" + +namespace Kernel { + +/** + * Represents executable data that may be loaded into a kernel process. + * + * A code set consists of three basic segments: + * - A code (AKA text) segment, + * - A read-only data segment (rodata) + * - A data segment + * + * The code segment is the portion of the object file that contains + * executable instructions. + * + * The read-only data segment in the portion of the object file that + * contains (as one would expect) read-only data, such as fixed constant + * values and data structures. + * + * The data segment is similar to the read-only data segment -- it contains + * variables and data structures that have predefined values, however, + * entities within this segment can be modified. + */ +struct CodeSet final { + /// A single segment within a code set. + struct Segment final { + /// The byte offset that this segment is located at. + std::size_t offset = 0; + + /// The address to map this segment to. + VAddr addr = 0; + + /// The size of this segment in bytes. + u32 size = 0; + }; + + explicit CodeSet(); + ~CodeSet(); + + CodeSet(const CodeSet&) = delete; + CodeSet& operator=(const CodeSet&) = delete; + + CodeSet(CodeSet&&) = default; + CodeSet& operator=(CodeSet&&) = default; + + Segment& CodeSegment() { + return segments[0]; + } + + const Segment& CodeSegment() const { + return segments[0]; + } + + Segment& RODataSegment() { + return segments[1]; + } + + const Segment& RODataSegment() const { + return segments[1]; + } + + Segment& DataSegment() { + return segments[2]; + } + + const Segment& DataSegment() const { + return segments[2]; + } + + /// The overall data that backs this code set. + std::shared_ptr<std::vector<u8>> memory; + + /// The segments that comprise this code set. + std::array<Segment, 3> segments; + + /// The entry point address for this code set. + VAddr entrypoint = 0; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 0743670ad..98e87313b 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <map> #include <utility> #include <vector> @@ -10,8 +9,11 @@ #include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" #include "core/memory.h" @@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t } } -ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle, +Mutex::Mutex(Core::System& system) : system{system} {} +Mutex::~Mutex() = default; + +ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, Handle requesting_thread_handle) { // The mutex address must be 4-byte aligned if ((address % sizeof(u32)) != 0) { return ERR_INVALID_ADDRESS; } + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + Thread* const current_thread = system.CurrentScheduler().GetCurrentThread(); SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle); SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle); // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another // thread. - ASSERT(requesting_thread == GetCurrentThread()); + ASSERT(requesting_thread == current_thread); - u32 addr_value = Memory::Read32(address); + const u32 addr_value = Memory::Read32(address); // If the mutex isn't being held, just return success. if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) { return RESULT_SUCCESS; } - if (holding_thread == nullptr) + if (holding_thread == nullptr) { return ERR_INVALID_HANDLE; + } // Wait until the mutex is released - GetCurrentThread()->SetMutexWaitAddress(address); - GetCurrentThread()->SetWaitHandle(requesting_thread_handle); + current_thread->SetMutexWaitAddress(address); + current_thread->SetWaitHandle(requesting_thread_handle); - GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex); - GetCurrentThread()->InvalidateWakeupCallback(); + current_thread->SetStatus(ThreadStatus::WaitMutex); + current_thread->InvalidateWakeupCallback(); // Update the lock holder thread's priority to prevent priority inversion. - holding_thread->AddMutexWaiter(GetCurrentThread()); + holding_thread->AddMutexWaiter(current_thread); - Core::System::GetInstance().PrepareReschedule(); + system.PrepareReschedule(); return RESULT_SUCCESS; } @@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) { return ERR_INVALID_ADDRESS; } - auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); + auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); + auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address); // There are no more threads waiting for the mutex, release it completely. if (thread == nullptr) { @@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) { } // Transfer the ownership of the mutex from the previous owner to the new one. - TransferMutexOwnership(address, GetCurrentThread(), thread); + TransferMutexOwnership(address, current_thread, thread); u32 mutex_value = thread->GetWaitHandle(); diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 81e62d497..b904de2e8 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -5,32 +5,34 @@ #pragma once #include "common/common_types.h" -#include "core/hle/kernel/object.h" union ResultCode; -namespace Kernel { +namespace Core { +class System; +} -class HandleTable; -class Thread; +namespace Kernel { class Mutex final { public: + explicit Mutex(Core::System& system); + ~Mutex(); + /// Flag that indicates that a mutex still has threads waiting for it. static constexpr u32 MutexHasWaitersFlag = 0x40000000; /// Mask of the bits in a mutex address value that contain the mutex owner. static constexpr u32 MutexOwnerMask = 0xBFFFFFFF; /// Attempts to acquire a mutex at the specified address. - static ResultCode TryAcquire(HandleTable& handle_table, VAddr address, - Handle holding_thread_handle, Handle requesting_thread_handle); + ResultCode TryAcquire(VAddr address, Handle holding_thread_handle, + Handle requesting_thread_handle); /// Releases the mutex at the specified address. - static ResultCode Release(VAddr address); + ResultCode Release(VAddr address); private: - Mutex() = default; - ~Mutex() = default; + Core::System& system; }; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 7e8ba978c..0d782e4ba 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -31,7 +32,7 @@ namespace { */ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) { // Setup page table so we can write to memory - SetCurrentPageTable(&owner_process.VMManager().page_table); + Memory::SetCurrentPageTable(&owner_process.VMManager().page_table); // Initialize new "main" thread const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); @@ -50,9 +51,6 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi } } // Anonymous namespace -CodeSet::CodeSet() = default; -CodeSet::~CodeSet() = default; - SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) { auto& kernel = system.Kernel(); @@ -133,7 +131,7 @@ void Process::PrepareForTermination() { if (thread->GetOwnerProcess() != this) continue; - if (thread == GetCurrentThread()) + if (thread == system.CurrentScheduler().GetCurrentThread()) continue; // TODO(Subv): When are the other running/ready threads terminated? @@ -145,7 +143,6 @@ void Process::PrepareForTermination() { } }; - const auto& system = Core::System::GetInstance(); stop_threads(system.Scheduler(0).GetThreadList()); stop_threads(system.Scheduler(1).GetThreadList()); stop_threads(system.Scheduler(2).GetThreadList()); @@ -213,7 +210,7 @@ void Process::FreeTLSSlot(VAddr tls_address) { } void Process::LoadModule(CodeSet module_, VAddr base_addr) { - const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, + const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { const auto vma = vm_manager .MapMemoryBlock(segment.addr + base_addr, module_.memory, @@ -223,18 +220,17 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) { }; // Map CodeSet segments - MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic); - MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable); - MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable); + MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code); + MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData); + MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData); // Clear instruction cache in CPU JIT - Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + system.InvalidateCpuInstructionCaches(); } -Process::Process(Core::System& system) : WaitObject{system.Kernel()}, address_arbiter{system} {} +Process::Process(Core::System& system) + : WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {} + Process::~Process() = default; void Process::Acquire(Thread* thread) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 2a132c894..1bd7bf5c1 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -7,13 +7,13 @@ #include <array> #include <bitset> #include <cstddef> -#include <memory> #include <string> #include <vector> #include <boost/container/static_vector.hpp> #include "common/common_types.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process_capability.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/kernel/wait_object.h" @@ -33,6 +33,8 @@ class KernelCore; class ResourceLimit; class Thread; +struct CodeSet; + struct AddressMapping { // Address and size must be page-aligned VAddr address; @@ -65,46 +67,6 @@ enum class ProcessStatus { DebugBreak, }; -struct CodeSet final { - struct Segment { - std::size_t offset = 0; - VAddr addr = 0; - u32 size = 0; - }; - - explicit CodeSet(); - ~CodeSet(); - - Segment& CodeSegment() { - return segments[0]; - } - - const Segment& CodeSegment() const { - return segments[0]; - } - - Segment& RODataSegment() { - return segments[1]; - } - - const Segment& RODataSegment() const { - return segments[1]; - } - - Segment& DataSegment() { - return segments[2]; - } - - const Segment& DataSegment() const { - return segments[2]; - } - - std::shared_ptr<std::vector<u8>> memory; - - std::array<Segment, 3> segments; - VAddr entrypoint = 0; -}; - class Process final : public WaitObject { public: enum : u64 { @@ -165,6 +127,16 @@ public: return address_arbiter; } + /// Gets a reference to the process' mutex lock. + Mutex& GetMutex() { + return mutex; + } + + /// Gets a const reference to the process' mutex lock + const Mutex& GetMutex() const { + return mutex; + } + /// Gets the current status of the process ProcessStatus GetStatus() const { return status; @@ -266,7 +238,7 @@ public: void FreeTLSSlot(VAddr tls_address); private: - explicit Process(Core::System& kernel); + explicit Process(Core::System& system); ~Process() override; /// Checks if the specified thread should wait until this process is available. @@ -327,9 +299,18 @@ private: /// Per-process address arbiter. AddressArbiter address_arbiter; + /// The per-process mutex lock instance used for handling various + /// forms of services, such as lock arbitration, and condition + /// variable related facilities. + Mutex mutex; + /// Random values for svcGetInfo RandomEntropy std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy; + /// System context + Core::System& system; + + /// Name of this process std::string name; }; diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 5fccfd9f4..cc189cc64 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -96,7 +96,7 @@ void Scheduler::SwitchContext(Thread* new_thread) { auto* const thread_owner_process = current_thread->GetOwnerProcess(); if (previous_process != thread_owner_process) { system.Kernel().MakeCurrentProcess(thread_owner_process); - SetCurrentPageTable(&thread_owner_process->VMManager().page_table); + Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table); } cpu_core.LoadContext(new_thread->GetContext()); @@ -199,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) { ASSERT(thread->GetPriority() < THREADPRIO_COUNT); // Yield this thread -- sleep for zero time and force reschedule to different thread - WaitCurrentThread_Sleep(); - GetCurrentThread()->WakeAfterDelay(0); + GetCurrentThread()->Sleep(0); } void Scheduler::YieldWithLoadBalancing(Thread* thread) { @@ -215,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) { ASSERT(priority < THREADPRIO_COUNT); // Sleep for zero time to be able to force reschedule to different thread - WaitCurrentThread_Sleep(); - GetCurrentThread()->WakeAfterDelay(0); + GetCurrentThread()->Sleep(0); Thread* suggested_thread = nullptr; diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index d6ceeb2da..0e1515c89 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -26,6 +26,10 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() { return MakeResult(std::move(session)); } +void ServerPort::AppendPendingSession(SharedPtr<ServerSession> pending_session) { + pending_sessions.push_back(std::move(pending_session)); +} + bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.empty(); diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index e52f8245f..9bc667cf2 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -22,6 +22,8 @@ class SessionRequestHandler; class ServerPort final : public WaitObject { public: + using HLEHandler = std::shared_ptr<SessionRequestHandler>; + /** * Creates a pair of ServerPort and an associated ClientPort. * @@ -51,22 +53,27 @@ public: */ ResultVal<SharedPtr<ServerSession>> Accept(); + /// Whether or not this server port has an HLE handler available. + bool HasHLEHandler() const { + return hle_handler != nullptr; + } + + /// Gets the HLE handler for this port. + HLEHandler GetHLEHandler() const { + return hle_handler; + } + /** * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port * will inherit a reference to this handler. */ - void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { + void SetHleHandler(HLEHandler hle_handler_) { hle_handler = std::move(hle_handler_); } - std::string name; ///< Name of port (optional) - - /// ServerSessions waiting to be accepted by the port - std::vector<SharedPtr<ServerSession>> pending_sessions; - - /// This session's HLE request handler template (optional) - /// ServerSessions created from this port inherit a reference to this handler. - std::shared_ptr<SessionRequestHandler> hle_handler; + /// Appends a ServerSession to the collection of ServerSessions + /// waiting to be accepted by this port. + void AppendPendingSession(SharedPtr<ServerSession> pending_session); bool ShouldWait(Thread* thread) const override; void Acquire(Thread* thread) override; @@ -74,6 +81,16 @@ public: private: explicit ServerPort(KernelCore& kernel); ~ServerPort() override; + + /// ServerSessions waiting to be accepted by the port + std::vector<SharedPtr<ServerSession>> pending_sessions; + + /// This session's HLE request handler template (optional) + /// ServerSessions created from this port inherit a reference to this handler. + HLEHandler hle_handler; + + /// Name of the port (optional) + std::string name; }; } // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index bc89c4880..e5e7f99e1 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -552,9 +552,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, return ERR_INVALID_ADDRESS; } - auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle, - requesting_thread_handle); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle, + requesting_thread_handle); } /// Unlock a mutex @@ -572,7 +572,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return ERR_INVALID_ADDRESS; } - return Mutex::Release(mutex_addr); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + return current_process->GetMutex().Release(mutex_addr); } enum class BreakType : u32 { @@ -1285,10 +1286,14 @@ static ResultCode StartThread(Handle thread_handle) { /// Called when a thread exits static void ExitThread() { - LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC()); + auto& system = Core::System::GetInstance(); - ExitCurrentThread(); - Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); + + auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); + current_thread->Stop(); + system.CurrentScheduler().RemoveThread(current_thread); + system.PrepareReschedule(); } /// Sleep the current thread @@ -1301,32 +1306,32 @@ static void SleepThread(s64 nanoseconds) { YieldAndWaitForLoadBalancing = -2, }; + auto& system = Core::System::GetInstance(); + auto& scheduler = system.CurrentScheduler(); + auto* const current_thread = scheduler.GetCurrentThread(); + if (nanoseconds <= 0) { - auto& scheduler{Core::System::GetInstance().CurrentScheduler()}; switch (static_cast<SleepType>(nanoseconds)) { case SleepType::YieldWithoutLoadBalancing: - scheduler.YieldWithoutLoadBalancing(GetCurrentThread()); + scheduler.YieldWithoutLoadBalancing(current_thread); break; case SleepType::YieldWithLoadBalancing: - scheduler.YieldWithLoadBalancing(GetCurrentThread()); + scheduler.YieldWithLoadBalancing(current_thread); break; case SleepType::YieldAndWaitForLoadBalancing: - scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread()); + scheduler.YieldAndWaitForLoadBalancing(current_thread); break; default: UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } else { - // Sleep current thread and check for next thread to schedule - WaitCurrentThread_Sleep(); - - // Create an event to wake the thread up after the specified nanosecond delay has passed - GetCurrentThread()->WakeAfterDelay(nanoseconds); + current_thread->Sleep(nanoseconds); } // Reschedule all CPU cores - for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) - Core::System::GetInstance().CpuCore(i).PrepareReschedule(); + for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) { + system.CpuCore(i).PrepareReschedule(); + } } /// Wait process wide key atomic @@ -1337,11 +1342,15 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}", mutex_addr, condition_variable_addr, thread_handle, nano_seconds); - const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + const auto& handle_table = current_process->GetHandleTable(); SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); ASSERT(thread); - CASCADE_CODE(Mutex::Release(mutex_addr)); + const auto release_result = current_process->GetMutex().Release(mutex_addr); + if (release_result.IsError()) { + return release_result; + } SharedPtr<Thread> current_thread = GetCurrentThread(); current_thread->SetCondVarWaitAddress(condition_variable_addr); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index eb54d6651..3b22e8e0d 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -7,8 +7,6 @@ #include <optional> #include <vector> -#include <boost/range/algorithm_ext/erase.hpp> - #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -68,17 +66,6 @@ void Thread::Stop() { owner_process->FreeTLSSlot(tls_address); } -void WaitCurrentThread_Sleep() { - Thread* thread = GetCurrentThread(); - thread->SetStatus(ThreadStatus::WaitSleep); -} - -void ExitCurrentThread() { - Thread* thread = GetCurrentThread(); - thread->Stop(); - Core::System::GetInstance().CurrentScheduler().RemoveThread(thread); -} - void Thread::WakeAfterDelay(s64 nanoseconds) { // Don't schedule a wakeup if the thread wants to wait forever if (nanoseconds == -1) @@ -269,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { if (thread->lock_owner == this) { // If the thread is already waiting for this thread to release the mutex, ensure that the // waiters list is consistent and return without doing anything. - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr != wait_mutex_threads.end()); + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter != wait_mutex_threads.end()); return; } @@ -278,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { ASSERT(thread->lock_owner == nullptr); // Ensure that the thread is not already in the list of mutex waiters - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr == wait_mutex_threads.end()); - + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter == wait_mutex_threads.end()); + + // Keep the list in an ordered fashion + const auto insertion_point = std::find_if( + wait_mutex_threads.begin(), wait_mutex_threads.end(), + [&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); }); + wait_mutex_threads.insert(insertion_point, thread); thread->lock_owner = this; - wait_mutex_threads.emplace_back(std::move(thread)); + UpdatePriority(); } @@ -290,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) { ASSERT(thread->lock_owner == this); // Ensure that the thread is in the list of mutex waiters - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr != wait_mutex_threads.end()); + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter != wait_mutex_threads.end()); + + wait_mutex_threads.erase(iter); - boost::remove_erase(wait_mutex_threads, thread); thread->lock_owner = nullptr; UpdatePriority(); } void Thread::UpdatePriority() { - // Find the highest priority among all the threads that are waiting for this thread's lock + // If any of the threads waiting on the mutex have a higher priority + // (taking into account priority inheritance), then this thread inherits + // that thread's priority. u32 new_priority = nominal_priority; - for (const auto& thread : wait_mutex_threads) { - if (thread->nominal_priority < new_priority) - new_priority = thread->nominal_priority; + if (!wait_mutex_threads.empty()) { + if (wait_mutex_threads.front()->current_priority < new_priority) { + new_priority = wait_mutex_threads.front()->current_priority; + } } - if (new_priority == current_priority) + if (new_priority == current_priority) { return; + } scheduler->SetThreadPriority(this, new_priority); - current_priority = new_priority; + if (!lock_owner) { + return; + } + + // Ensure that the thread is within the correct location in the waiting list. + auto old_owner = lock_owner; + lock_owner->RemoveMutexWaiter(this); + old_owner->AddMutexWaiter(this); + // Recursively update the priority of the thread that depends on the priority of this one. - if (lock_owner) - lock_owner->UpdatePriority(); + lock_owner->UpdatePriority(); } void Thread::ChangeCore(u32 core, u64 mask) { @@ -391,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) { } } +void Thread::Sleep(s64 nanoseconds) { + // Sleep current thread and check for next thread to schedule + SetStatus(ThreadStatus::WaitSleep); + + // Create an event to wake the thread up after the specified nanosecond delay has passed + WakeAfterDelay(nanoseconds); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c48b21aba..faad5f391 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -383,6 +383,9 @@ public: void SetActivity(ThreadActivity value); + /// Sleeps this thread for the given amount of nanoseconds. + void Sleep(s64 nanoseconds); + private: explicit Thread(KernelCore& kernel); ~Thread() override; @@ -398,8 +401,14 @@ private: VAddr entry_point = 0; VAddr stack_top = 0; - u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application - u32 current_priority = 0; ///< Current thread priority, can be temporarily changed + /// Nominal thread priority, as set by the emulated application. + /// The nominal priority is the thread priority without priority + /// inheritance taken into account. + u32 nominal_priority = 0; + + /// Current thread priority. This may change over the course of the + /// thread's lifetime in order to facilitate priority inheritance. + u32 current_priority = 0; u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks. u64 last_running_ticks = 0; ///< CPU tick when thread was last running @@ -460,14 +469,4 @@ private: */ Thread* GetCurrentThread(); -/** - * Waits the current thread on a sleep - */ -void WaitCurrentThread_Sleep(); - -/** - * Stops the current thread and removes it from the thread_list - */ -void ExitCurrentThread(); - } // namespace Kernel diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 05c59af34..22bf55ce7 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -7,29 +7,29 @@ #include <utility> #include "common/assert.h" #include "common/logging/log.h" +#include "common/memory_hook.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" -#include "core/memory_hook.h" #include "core/memory_setup.h" namespace Kernel { namespace { const char* GetMemoryStateName(MemoryState state) { static constexpr const char* names[] = { - "Unmapped", "Io", - "Normal", "CodeStatic", - "CodeMutable", "Heap", - "Shared", "Unknown1", - "ModuleCodeStatic", "ModuleCodeMutable", - "IpcBuffer0", "Stack", - "ThreadLocal", "TransferMemoryIsolated", - "TransferMemory", "ProcessMemory", - "Inaccessible", "IpcBuffer1", - "IpcBuffer3", "KernelStack", + "Unmapped", "Io", + "Normal", "Code", + "CodeData", "Heap", + "Shared", "Unknown1", + "ModuleCode", "ModuleCodeData", + "IpcBuffer0", "Stack", + "ThreadLocal", "TransferMemoryIsolated", + "TransferMemory", "ProcessMemory", + "Inaccessible", "IpcBuffer1", + "IpcBuffer3", "KernelStack", }; return names[ToSvcMemoryState(state)]; @@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const { ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state, - Memory::MemoryHookPointer mmio_handler) { + Common::MemoryHookPointer mmio_handler) { // This is the appropriately sized VMA that will turn into our allocation. CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size)); VirtualMemoryArea& final_vma = vma_handle->second; @@ -624,7 +624,7 @@ void VMManager::ClearPageTable() { std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr); page_table.special_regions.clear(); std::fill(page_table.attributes.begin(), page_table.attributes.end(), - Memory::PageType::Unmapped); + Common::PageType::Unmapped); } VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask, diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 88e0b3c02..7cdff6094 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -9,9 +9,10 @@ #include <tuple> #include <vector> #include "common/common_types.h" +#include "common/memory_hook.h" +#include "common/page_table.h" #include "core/hle/result.h" #include "core/memory.h" -#include "core/memory_hook.h" namespace FileSys { enum class ProgramAddressSpaceType : u8; @@ -164,12 +165,12 @@ enum class MemoryState : u32 { Unmapped = 0x00, Io = 0x01 | FlagMapped, Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed, - CodeStatic = 0x03 | CodeFlags | FlagMapProcess, - CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory, + Code = 0x03 | CodeFlags | FlagMapProcess, + CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory, Heap = 0x05 | DataFlags | FlagCodeMemory, Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated, - ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess, - ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory, + ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess, + ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory, IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated | IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned, @@ -290,7 +291,7 @@ struct VirtualMemoryArea { // Settings for type = MMIO /// Physical address of the register area this VMA maps to. PAddr paddr = 0; - Memory::MemoryHookPointer mmio_handler = nullptr; + Common::MemoryHookPointer mmio_handler = nullptr; /// Tests if this area can be merged to the right with `next`. bool CanBeMergedWith(const VirtualMemoryArea& next) const; @@ -368,7 +369,7 @@ public: * @param mmio_handler The handler that will implement read and write for this MMIO region. */ ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state, - Memory::MemoryHookPointer mmio_handler); + Common::MemoryHookPointer mmio_handler); /// Unmaps a range of addresses, splitting VMAs as necessary. ResultCode UnmapRange(VAddr target, u64 size); @@ -509,7 +510,7 @@ public: /// Each VMManager has its own page table, which is set as the main one when the owning process /// is scheduled. - Memory::PageTable page_table; + Common::PageTable page_table{Memory::PAGE_BITS}; private: using VMAIter = VMAMap::iterator; @@ -616,6 +617,9 @@ private: VAddr new_map_region_base = 0; VAddr new_map_region_end = 0; + VAddr main_code_region_base = 0; + VAddr main_code_region_end = 0; + VAddr tls_io_region_base = 0; VAddr tls_io_region_end = 0; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 1ed144481..ab84f5ddc 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -13,14 +13,6 @@ // All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes /** - * Detailed description of the error. Code 0 always means success. - */ -enum class ErrorDescription : u32 { - Success = 0, - RemoteProcessDead = 301, -}; - -/** * Identifies the module which caused the error. Error codes can be propagated through a call * chain, meaning that this doesn't always correspond to the module where the API call made is * contained. @@ -120,7 +112,7 @@ enum class ErrorModule : u32 { ShopN = 811, }; -/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields. +/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields. union ResultCode { u32 raw; @@ -133,17 +125,9 @@ union ResultCode { constexpr explicit ResultCode(u32 raw) : raw(raw) {} - constexpr ResultCode(ErrorModule module, ErrorDescription description) - : ResultCode(module, static_cast<u32>(description)) {} - constexpr ResultCode(ErrorModule module_, u32 description_) : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} - constexpr ResultCode& operator=(const ResultCode& o) { - raw = o.raw; - return *this; - } - constexpr bool IsSuccess() const { return raw == 0; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3f009d2b7..9c44e27c6 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,10 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> #include <cinttypes> #include <cstring> -#include <stack> #include "audio_core/audio_renderer.h" #include "core/core.h" #include "core/file_sys/savedata_factory.h" @@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) } IAudioController::IAudioController() : ServiceFramework("IAudioController") { + // clang-format off static const FunctionInfo functions[] = { {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"}, - {1, &IAudioController::GetMainAppletExpectedMasterVolume, - "GetMainAppletExpectedMasterVolume"}, - {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, - "GetLibraryAppletExpectedMasterVolume"}, - {3, nullptr, "ChangeMainAppletMasterVolume"}, - {4, nullptr, "SetTransparentVolumeRate"}, + {1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"}, + {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"}, + {3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"}, + {4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"}, }; + // clang-format on + RegisterHandlers(functions); } IAudioController::~IAudioController() = default; void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const float main_applet_volume_tmp = rp.Pop<float>(); + const float library_applet_volume_tmp = rp.Pop<float>(); + + LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}", + main_applet_volume_tmp, library_applet_volume_tmp); + + // Ensure the volume values remain within the 0-100% range + main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume); + library_applet_volume = + std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(volume); + rb.Push(main_applet_volume); } void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(volume); + rb.Push(library_applet_volume); +} + +void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) { + struct Parameters { + float volume; + s64 fade_time_ns; + }; + static_assert(sizeof(Parameters) == 16); + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw<Parameters>(); + + LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume, + parameters.fade_time_ns); + + main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume); + fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns}; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const float transparent_volume_rate_tmp = rp.Pop<float>(); + + LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp); + + // Clamp volume range to 0-100%. + transparent_volume_rate = + std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { @@ -169,7 +215,21 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController" IDisplayController::~IDisplayController() = default; -IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {} +IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "NotifyMessageToHomeMenuForDebug"}, + {1, nullptr, "OpenMainApplication"}, + {10, nullptr, "EmulateButtonEvent"}, + {20, nullptr, "InvalidateTransitionLayer"}, + {30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"}, + {40, nullptr, "GetAppletResourceUsageInfo"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + IDebugFunctions::~IDebugFunctions() = default; ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index b6113cfdd..565dd8e9e 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -4,6 +4,7 @@ #pragma once +#include <chrono> #include <memory> #include <queue> #include "core/hle/kernel/writable_event.h" @@ -81,8 +82,21 @@ private: void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx); void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); + void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx); + void SetTransparentAudioRate(Kernel::HLERequestContext& ctx); - u32 volume{100}; + static constexpr float min_allowed_volume = 0.0f; + static constexpr float max_allowed_volume = 1.0f; + + float main_applet_volume{0.25f}; + float library_applet_volume{max_allowed_volume}; + float transparent_volume_rate{min_allowed_volume}; + + // Volume transition fade time in nanoseconds. + // e.g. If the main applet volume was 0% and was changed to 50% + // with a fade of 50ns, then over the course of 50ns, + // the volume will gradually fade up to 50% + std::chrono::nanoseconds fade_time_ns{0}; }; class IDisplayController final : public ServiceFramework<IDisplayController> { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 377e12cfa..cb4a1160d 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -8,6 +8,7 @@ #include <vector> #include <opus.h> +#include <opus_multistream.h> #include "common/assert.h" #include "common/logging/log.h" @@ -18,12 +19,12 @@ namespace Service::Audio { namespace { struct OpusDeleter { - void operator()(void* ptr) const { - operator delete(ptr); + void operator()(OpusMSDecoder* ptr) const { + opus_multistream_decoder_destroy(ptr); } }; -using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>; +using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; struct OpusPacketHeader { // Packet size in bytes. @@ -33,7 +34,7 @@ struct OpusPacketHeader { }; static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); -class OpusDecoderStateBase { +class OpusDecoderState { public: /// Describes extra behavior that may be asked of the decoding context. enum class ExtraBehavior { @@ -49,22 +50,13 @@ public: Enabled, }; - virtual ~OpusDecoderStateBase() = default; - - // Decodes interleaved Opus packets. Optionally allows reporting time taken to - // perform the decoding, as well as any relevant extra behavior. - virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) = 0; -}; - -// Represents the decoder state for a non-multistream decoder. -class OpusDecoderState final : public OpusDecoderStateBase { -public: explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count) : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {} + // Decodes interleaved Opus packets. Optionally allows reporting time taken to + // perform the decoding, as well as any relevant extra behavior. void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) override { + ExtraBehavior extra_behavior) { if (perf_time == PerfTime::Disabled) { DecodeInterleavedHelper(ctx, nullptr, extra_behavior); } else { @@ -135,7 +127,7 @@ private: const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); const auto out_sample_count = - opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); + opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); if (out_sample_count < 0) { LOG_ERROR(Audio, "Incorrect sample count received from opus_decode, " @@ -158,7 +150,7 @@ private: void ResetDecoderContext() { ASSERT(decoder != nullptr); - opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); } OpusDecoderPtr decoder; @@ -168,7 +160,7 @@ private: class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { public: - explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state) + explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state) : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { // clang-format off static const FunctionInfo functions[] = { @@ -190,35 +182,51 @@ private: void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Audio, "called"); - decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled, - OpusDecoderStateBase::ExtraBehavior::None); + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, + OpusDecoderState::ExtraBehavior::None); } void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Audio, "called"); - decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, - OpusDecoderStateBase::ExtraBehavior::None); + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, + OpusDecoderState::ExtraBehavior::None); } void DecodeInterleaved(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Audio, "called"); IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop<bool>() - ? OpusDecoderStateBase::ExtraBehavior::ResetContext - : OpusDecoderStateBase::ExtraBehavior::None; + const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext + : OpusDecoderState::ExtraBehavior::None; - decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled, - extra_behavior); + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); } - std::unique_ptr<OpusDecoderStateBase> decoder_state; + OpusDecoderState decoder_state; }; std::size_t WorkerBufferSize(u32 channel_count) { ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - return opus_decoder_get_size(static_cast<int>(channel_count)); + constexpr int num_streams = 1; + const int num_stereo_streams = channel_count == 2 ? 1 : 0; + return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); +} + +// Creates the mapping table that maps the input channels to the particular +// output channels. In the stereo case, we map the left and right input channels +// to the left and right output channels respectively. +// +// However, in the monophonic case, we only map the one available channel +// to the sole output channel. We specify 255 for the would-be right channel +// as this is a special value defined by Opus to indicate to the decoder to +// ignore that channel. +std::array<u8, 2> CreateMappingTable(u32 channel_count) { + if (channel_count == 2) { + return {{0, 1}}; + } + + return {{0, 255}}; } } // Anonymous namespace @@ -259,9 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { const std::size_t worker_sz = WorkerBufferSize(channel_count); ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))}; - if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { - LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); + const int num_stereo_streams = channel_count == 2 ? 1 : 0; + const auto mapping_table = CreateMappingTable(channel_count); + + int error = 0; + OpusDecoderPtr decoder{ + opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, + num_stereo_streams, mapping_table.data(), &error)}; + if (error != OPUS_OK || decoder == nullptr) { + LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code rb.Push(ResultCode(-1)); @@ -271,7 +285,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IHardwareOpusDecoderManager>( - std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count)); + OpusDecoderState{std::move(decoder), sample_rate, channel_count}); } HwOpus::HwOpus() : ServiceFramework("hwopus") { diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 54959edd8..f03fb629c 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { FSP_SRV::~FSP_SRV() = default; void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + current_process_id = rp.Pop<u64>(); + + LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 3a5f4e200..d7572ba7a 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -32,6 +32,7 @@ private: void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); FileSys::VirtualFile romfs; + u64 current_process_id = 0; }; } // namespace Service::FileSystem diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 929035034..e584b92ec 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -41,20 +41,20 @@ private: struct PadState { union { u32_le raw{}; - BitField<0, 1, u32_le> a; - BitField<1, 1, u32_le> b; - BitField<2, 1, u32_le> x; - BitField<3, 1, u32_le> y; - BitField<4, 1, u32_le> l; - BitField<5, 1, u32_le> r; - BitField<6, 1, u32_le> zl; - BitField<7, 1, u32_le> zr; - BitField<8, 1, u32_le> plus; - BitField<9, 1, u32_le> minus; - BitField<10, 1, u32_le> d_left; - BitField<11, 1, u32_le> d_up; - BitField<12, 1, u32_le> d_right; - BitField<13, 1, u32_le> d_down; + BitField<0, 1, u32> a; + BitField<1, 1, u32> b; + BitField<2, 1, u32> x; + BitField<3, 1, u32> y; + BitField<4, 1, u32> l; + BitField<5, 1, u32> r; + BitField<6, 1, u32> zl; + BitField<7, 1, u32> zr; + BitField<8, 1, u32> plus; + BitField<9, 1, u32> minus; + BitField<10, 1, u32> d_left; + BitField<11, 1, u32> d_up; + BitField<12, 1, u32> d_right; + BitField<13, 1, u32> d_down; }; }; static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size"); @@ -62,7 +62,7 @@ private: struct Attributes { union { u32_le raw{}; - BitField<0, 1, u32_le> connected; + BitField<0, 1, u32> connected; }; }; static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 18c7a94e6..4ff50b3cd 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -39,13 +39,13 @@ public: union { u32_le raw{}; - BitField<0, 1, u32_le> pro_controller; - BitField<1, 1, u32_le> handheld; - BitField<2, 1, u32_le> joycon_dual; - BitField<3, 1, u32_le> joycon_left; - BitField<4, 1, u32_le> joycon_right; + BitField<0, 1, u32> pro_controller; + BitField<1, 1, u32> handheld; + BitField<2, 1, u32> joycon_dual; + BitField<3, 1, u32> joycon_left; + BitField<4, 1, u32> joycon_right; - BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible + BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible }; }; static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size"); @@ -150,43 +150,43 @@ private: union { u64_le raw{}; // Button states - BitField<0, 1, u64_le> a; - BitField<1, 1, u64_le> b; - BitField<2, 1, u64_le> x; - BitField<3, 1, u64_le> y; - BitField<4, 1, u64_le> l_stick; - BitField<5, 1, u64_le> r_stick; - BitField<6, 1, u64_le> l; - BitField<7, 1, u64_le> r; - BitField<8, 1, u64_le> zl; - BitField<9, 1, u64_le> zr; - BitField<10, 1, u64_le> plus; - BitField<11, 1, u64_le> minus; + BitField<0, 1, u64> a; + BitField<1, 1, u64> b; + BitField<2, 1, u64> x; + BitField<3, 1, u64> y; + BitField<4, 1, u64> l_stick; + BitField<5, 1, u64> r_stick; + BitField<6, 1, u64> l; + BitField<7, 1, u64> r; + BitField<8, 1, u64> zl; + BitField<9, 1, u64> zr; + BitField<10, 1, u64> plus; + BitField<11, 1, u64> minus; // D-Pad - BitField<12, 1, u64_le> d_left; - BitField<13, 1, u64_le> d_up; - BitField<14, 1, u64_le> d_right; - BitField<15, 1, u64_le> d_down; + BitField<12, 1, u64> d_left; + BitField<13, 1, u64> d_up; + BitField<14, 1, u64> d_right; + BitField<15, 1, u64> d_down; // Left JoyStick - BitField<16, 1, u64_le> l_stick_left; - BitField<17, 1, u64_le> l_stick_up; - BitField<18, 1, u64_le> l_stick_right; - BitField<19, 1, u64_le> l_stick_down; + BitField<16, 1, u64> l_stick_left; + BitField<17, 1, u64> l_stick_up; + BitField<18, 1, u64> l_stick_right; + BitField<19, 1, u64> l_stick_down; // Right JoyStick - BitField<20, 1, u64_le> r_stick_left; - BitField<21, 1, u64_le> r_stick_up; - BitField<22, 1, u64_le> r_stick_right; - BitField<23, 1, u64_le> r_stick_down; + BitField<20, 1, u64> r_stick_left; + BitField<21, 1, u64> r_stick_up; + BitField<22, 1, u64> r_stick_right; + BitField<23, 1, u64> r_stick_down; // Not always active? - BitField<24, 1, u64_le> left_sl; - BitField<25, 1, u64_le> left_sr; + BitField<24, 1, u64> left_sl; + BitField<25, 1, u64> left_sr; - BitField<26, 1, u64_le> right_sl; - BitField<27, 1, u64_le> right_sr; + BitField<26, 1, u64> right_sl; + BitField<27, 1, u64> right_sr; }; }; static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); @@ -200,12 +200,12 @@ private: struct ConnectionState { union { u32_le raw{}; - BitField<0, 1, u32_le> IsConnected; - BitField<1, 1, u32_le> IsWired; - BitField<2, 1, u32_le> IsLeftJoyConnected; - BitField<3, 1, u32_le> IsLeftJoyWired; - BitField<4, 1, u32_le> IsRightJoyConnected; - BitField<5, 1, u32_le> IsRightJoyWired; + BitField<0, 1, u32> IsConnected; + BitField<1, 1, u32> IsWired; + BitField<2, 1, u32> IsLeftJoyConnected; + BitField<3, 1, u32> IsLeftJoyWired; + BitField<4, 1, u32> IsRightJoyConnected; + BitField<5, 1, u32> IsRightJoyWired; }; }; static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); @@ -240,23 +240,23 @@ private: struct NPadProperties { union { s64_le raw{}; - BitField<11, 1, s64_le> is_vertical; - BitField<12, 1, s64_le> is_horizontal; - BitField<13, 1, s64_le> use_plus; - BitField<14, 1, s64_le> use_minus; + BitField<11, 1, s64> is_vertical; + BitField<12, 1, s64> is_horizontal; + BitField<13, 1, s64> use_plus; + BitField<14, 1, s64> use_minus; }; }; struct NPadDevice { union { u32_le raw{}; - BitField<0, 1, s32_le> pro_controller; - BitField<1, 1, s32_le> handheld; - BitField<2, 1, s32_le> handheld_left; - BitField<3, 1, s32_le> handheld_right; - BitField<4, 1, s32_le> joycon_left; - BitField<5, 1, s32_le> joycon_right; - BitField<6, 1, s32_le> pokeball; + BitField<0, 1, s32> pro_controller; + BitField<1, 1, s32> handheld; + BitField<2, 1, s32> handheld_left; + BitField<3, 1, s32> handheld_right; + BitField<4, 1, s32> joycon_left; + BitField<5, 1, s32> joycon_right; + BitField<6, 1, s32> pokeball; }; }; diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 012b6e0dd..76fc340e9 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -33,8 +33,8 @@ private: struct Attributes { union { u32 raw{}; - BitField<0, 1, u32_le> start_touch; - BitField<1, 1, u32_le> end_touch; + BitField<0, 1, u32> start_touch; + BitField<1, 1, u32> end_touch; }; }; static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7cc58db4c..498602de5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,6 +4,9 @@ #pragma once +#include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/service.h" + #include "controllers/controller_base.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 9df7ac50f..d65693fc7 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -319,15 +319,14 @@ public: } ASSERT(vm_manager - .MirrorMemory(*map_address, nro_addr, nro_size, - Kernel::MemoryState::ModuleCodeStatic) + .MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess()); if (bss_size > 0) { ASSERT(vm_manager .MirrorMemory(*map_address + nro_size, bss_addr, bss_size, - Kernel::MemoryState::ModuleCodeStatic) + Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess()); } @@ -388,8 +387,7 @@ public: const auto& nro_size = iter->second.size; ASSERT(vm_manager - .MirrorMemory(heap_addr, mapped_addr, nro_size, - Kernel::MemoryState::ModuleCodeStatic) + .MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess()); diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 1f462e087..2a61593e2 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -42,7 +42,7 @@ private: union { BitField<0, 16, Flags> flags; BitField<16, 8, Severity> severity; - BitField<24, 8, u32_le> verbosity; + BitField<24, 8, u32> verbosity; }; u32_le payload_size; diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 0f02a1a18..4f6042b00 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -19,11 +19,11 @@ public: virtual ~nvdevice() = default; union Ioctl { u32_le raw; - BitField<0, 8, u32_le> cmd; - BitField<8, 8, u32_le> group; - BitField<16, 14, u32_le> length; - BitField<30, 1, u32_le> is_in; - BitField<31, 1, u32_le> is_out; + BitField<0, 8, u32> cmd; + BitField<8, 8, u32> group; + BitField<16, 14, u32> length; + BitField<30, 1, u32> is_in; + BitField<31, 1, u32> is_out; }; /** 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 a34b9e753..af62d33d2 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -10,6 +10,7 @@ #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" #include "core/hle/service/nvdrv/devices/nvmap.h" +#include "core/memory.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" @@ -88,7 +89,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) for (const auto& entry : entries) { LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", entry.offset, entry.nvmap_handle, entry.pages); - Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10; + GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10; auto object = nvmap_dev->GetObject(entry.nvmap_handle); if (!object) { LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle); @@ -101,7 +102,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) u64 size = static_cast<u64>(entry.pages) << 0x10; ASSERT(size <= object->size); - Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size); + GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size); ASSERT(returned == offset); } std::memcpy(output.data(), entries.data(), output.size()); @@ -172,16 +173,8 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou return 0; } - auto& system_instance = Core::System::GetInstance(); - - // Remove this memory region from the rasterizer cache. - auto& gpu = system_instance.GPU(); - auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset); - ASSERT(cpu_addr); - gpu.FlushAndInvalidateRegion(*cpu_addr, itr->second.size); - - params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size); - + params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset, + itr->second.size); buffer_mappings.erase(itr->second.offset); std::memcpy(output.data(), ¶ms, output.size()); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 576fd6407..00806b0ed 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -11,7 +11,6 @@ #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" @@ -168,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co case IPC::CommandType::Close: { IPC::ResponseBuilder rb{context, 2}; rb.Push(RESULT_SUCCESS); - return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead); + return IPC::ERR_REMOTE_PROCESS_DEAD; } case IPC::CommandType::ControlWithContext: case IPC::CommandType::Control: { diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index c9b4da5b0..ecee554bf 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -2,13 +2,88 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/assert.h" #include "common/logging/log.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/system_archive/system_version.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/set/set_sys.h" namespace Service::Set { +namespace { +constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05; + +enum class GetFirmwareVersionType { + Version1, + Version2, +}; + +void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionType type) { + LOG_WARNING(Service_SET, "called - Using hardcoded firmware version '{}'", + FileSys::SystemArchive::GetLongDisplayVersion()); + + ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100, + "FirmwareVersion output buffer must be 0x100 bytes in size!"); + + // Instead of using the normal procedure of checking for the real system archive and if it + // doesn't exist, synthesizing one, I feel that that would lead to strange bugs because a + // used is using a really old or really new SystemVersion title. The synthesized one ensures + // consistence (currently reports as 5.1.0-0.0) + const auto archive = FileSys::SystemArchive::SystemVersion(); + + const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) { + LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", + desc.c_str()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(code); + }; + + if (archive == nullptr) { + early_exit_failure("The system version archive couldn't be synthesized.", + FileSys::ERROR_FAILED_MOUNT_ARCHIVE); + return; + } + + const auto ver_file = archive->GetFile("file"); + if (ver_file == nullptr) { + early_exit_failure("The system version archive didn't contain the file 'file'.", + FileSys::ERROR_INVALID_ARGUMENT); + return; + } + + auto data = ver_file->ReadAllBytes(); + if (data.size() != 0x100) { + early_exit_failure("The system version file 'file' was not the correct size.", + FileSys::ERROR_OUT_OF_BOUNDS); + return; + } + + // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will + // zero out the REVISION_MINOR field. + if (type == GetFirmwareVersionType::Version1) { + data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0; + } + + ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} +} // Anonymous namespace + +void SET_SYS::GetFirmwareVersion(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called"); + GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1); +} + +void SET_SYS::GetFirmwareVersion2(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called"); + GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2); +} + void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); @@ -33,8 +108,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") { {0, nullptr, "SetLanguageCode"}, {1, nullptr, "SetNetworkSettings"}, {2, nullptr, "GetNetworkSettings"}, - {3, nullptr, "GetFirmwareVersion"}, - {4, nullptr, "GetFirmwareVersion2"}, + {3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"}, + {4, &SET_SYS::GetFirmwareVersion2, "GetFirmwareVersion2"}, {5, nullptr, "GetFirmwareVersionDigest"}, {7, nullptr, "GetLockScreenFlag"}, {8, nullptr, "SetLockScreenFlag"}, diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h index f602f3c77..13ee2cf46 100644 --- a/src/core/hle/service/set/set_sys.h +++ b/src/core/hle/service/set/set_sys.h @@ -20,6 +20,8 @@ private: BasicBlack = 1, }; + void GetFirmwareVersion(Kernel::HLERequestContext& ctx); + void GetFirmwareVersion2(Kernel::HLERequestContext& ctx); void GetColorSetId(Kernel::HLERequestContext& ctx); void SetColorSetId(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index bef25433e..b9d6381b4 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -67,7 +67,7 @@ public: if (port == nullptr) { return nullptr; } - return std::static_pointer_cast<T>(port->hle_handler); + return std::static_pointer_cast<T>(port->GetHLEHandler()); } void InvokeControlRequest(Kernel::HLERequestContext& context); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index a975767bb..566cd6006 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -24,6 +24,7 @@ #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/service.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" #include "core/hle/service/vi/vi_s.h" @@ -33,6 +34,7 @@ namespace Service::VI { constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; +constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5}; constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6}; constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7}; @@ -1203,26 +1205,40 @@ IApplicationDisplayService::IApplicationDisplayService( RegisterHandlers(functions); } -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {} +static bool IsValidServiceAccess(Permission permission, Policy policy) { + if (permission == Permission::User) { + return policy == Policy::User; + } + + if (permission == Permission::System || permission == Permission::Manager) { + return policy == Policy::User || policy == Policy::Compositor; + } -Module::Interface::~Interface() = default; + return false; +} -void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); +void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, + Permission permission) { + IPC::RequestParser rp{ctx}; + const auto policy = rp.PopEnum<Policy>(); + + if (!IsValidServiceAccess(permission, policy)) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_PERMISSION_DENIED); + return; + } IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); + rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger)); } void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) { - auto module = std::make_shared<Module>(); - std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index e3963502a..6b66f8b81 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -4,12 +4,21 @@ #pragma once -#include "core/hle/service/service.h" +#include <memory> +#include "common/common_types.h" + +namespace Kernel { +class HLERequestContext; +} namespace Service::NVFlinger { class NVFlinger; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::VI { enum class DisplayResolution : u32 { @@ -19,22 +28,25 @@ enum class DisplayResolution : u32 { UndockedHeight = 720, }; -class Module final { -public: - class Interface : public ServiceFramework<Interface> { - public: - explicit Interface(std::shared_ptr<Module> module, const char* name, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~Interface() override; - - void GetDisplayService(Kernel::HLERequestContext& ctx); +/// Permission level for a particular VI service instance +enum class Permission { + User, + System, + Manager, +}; - protected: - std::shared_ptr<Module> module; - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; - }; +/// A policy type that may be requested via GetDisplayService and +/// GetDisplayServiceWithProxyNameExchange +enum class Policy { + User, + Compositor, }; +namespace detail { +void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission); +} // namespace detail + /// Registers all VI services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 207c06b16..06070087f 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" namespace Service::VI { -VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) { +VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_M::~VI_M() = default; +void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index 487d58d50..290e06689 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_M final : public Module::Interface { +class VI_M final : public ServiceFramework<VI_M> { public: - explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_M() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 920e6a1f6..57c596cc4 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_s.h" namespace Service::VI { -VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) { +VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {1, &VI_S::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_S::~VI_S() = default; +void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index bbc31148f..47804dc0b 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_S final : public Module::Interface { +class VI_S final : public ServiceFramework<VI_S> { public: - explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_S() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index d81e410d6..9d5ceb608 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_u.h" namespace Service::VI { -VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) { +VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {0, &VI_U::GetDisplayService, "GetDisplayService"}, }; @@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_U::~VI_U() = default; +void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index b92f28c92..19bdb73b0 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_U final : public Module::Interface { +class VI_U final : public ServiceFramework<VI_U> { public: - explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_U() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 6057c7f26..8b1920f22 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/elf.h" diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp deleted file mode 100644 index 57ca8c3ee..000000000 --- a/src/core/loader/linker.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <vector> - -#include "common/common_funcs.h" -#include "common/logging/log.h" -#include "common/swap.h" -#include "core/loader/linker.h" -#include "core/memory.h" - -namespace Loader { - -enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 }; - -enum DynamicType : u32 { - DT_NULL = 0, - DT_PLTRELSZ = 2, - DT_STRTAB = 5, - DT_SYMTAB = 6, - DT_RELA = 7, - DT_RELASZ = 8, - DT_STRSZ = 10, - DT_JMPREL = 23, -}; - -struct Elf64_Rela { - u64_le offset; - RelocationType type; - u32_le symbol; - s64_le addend; -}; -static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size."); - -struct Elf64_Dyn { - u64_le tag; - u64_le value; -}; -static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size."); - -struct Elf64_Sym { - u32_le name; - INSERT_PADDING_BYTES(0x2); - u16_le shndx; - u64_le value; - u64_le size; -}; -static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size."); - -void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols, - u64 relocation_offset, u64 size, VAddr load_base) { - for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) { - Elf64_Rela rela; - std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela)); - - const Symbol& symbol = symbols[rela.symbol]; - switch (rela.type) { - case RelocationType::RELATIVE: { - const u64 value = load_base + rela.addend; - if (!symbol.name.empty()) { - exports[symbol.name] = value; - } - std::memcpy(&program_image[rela.offset], &value, sizeof(u64)); - break; - } - case RelocationType::JUMP_SLOT: - case RelocationType::GLOB_DAT: - if (!symbol.value) { - imports[symbol.name] = {rela.offset + load_base, 0}; - } else { - exports[symbol.name] = symbol.value; - std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64)); - } - break; - case RelocationType::ABS64: - if (!symbol.value) { - imports[symbol.name] = {rela.offset + load_base, rela.addend}; - } else { - const u64 value = symbol.value + rela.addend; - exports[symbol.name] = value; - std::memcpy(&program_image[rela.offset], &value, sizeof(u64)); - } - break; - default: - LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type)); - break; - } - } -} - -void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) { - std::map<u64, u64> dynamic; - while (dynamic_section_offset < program_image.size()) { - Elf64_Dyn dyn; - std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn)); - dynamic_section_offset += sizeof(Elf64_Dyn); - - if (dyn.tag == DT_NULL) { - break; - } - dynamic[dyn.tag] = dyn.value; - } - - u64 offset = dynamic[DT_SYMTAB]; - std::vector<Symbol> symbols; - while (offset < program_image.size()) { - Elf64_Sym sym; - std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym)); - offset += sizeof(Elf64_Sym); - - if (sym.name >= dynamic[DT_STRSZ]) { - break; - } - - std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]); - if (sym.value) { - exports[name] = load_base + sym.value; - symbols.emplace_back(std::move(name), load_base + sym.value); - } else { - symbols.emplace_back(std::move(name), 0); - } - } - - if (dynamic.find(DT_RELA) != dynamic.end()) { - WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base); - } - - if (dynamic.find(DT_JMPREL) != dynamic.end()) { - WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ], - load_base); - } -} - -void Linker::ResolveImports() { - // Resolve imports - for (const auto& import : imports) { - const auto& search = exports.find(import.first); - if (search != exports.end()) { - Memory::Write64(import.second.ea, search->second + import.second.addend); - } else { - LOG_ERROR(Loader, "Unresolved import: {}", import.first); - } - } -} - -} // namespace Loader diff --git a/src/core/loader/linker.h b/src/core/loader/linker.h deleted file mode 100644 index 107625837..000000000 --- a/src/core/loader/linker.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <string> -#include "common/common_types.h" - -namespace Loader { - -class Linker { -protected: - struct Symbol { - Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {} - std::string name; - u64 value; - }; - - struct Import { - VAddr ea; - s64 addend; - }; - - void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols, - u64 relocation_offset, u64 size, VAddr load_base); - void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base); - - void ResolveImports(); - - std::map<std::string, Import> imports; - std::map<std::string, VAddr> exports; -}; - -} // namespace Loader diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 4fad0c0dd..5de02a94b 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_offset.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/filesystem/filesystem.h" diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 013d629c0..85b0ed644 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -4,10 +4,10 @@ #pragma once +#include <memory> #include <string> #include <vector> #include "common/common_types.h" -#include "core/loader/linker.h" #include "core/loader/loader.h" namespace FileSys { @@ -21,7 +21,7 @@ class Process; namespace Loader { /// Loads an NRO file -class AppLoader_NRO final : public AppLoader, Linker { +class AppLoader_NRO final : public AppLoader { public: explicit AppLoader_NRO(FileSys::VirtualFile file); ~AppLoader_NRO() override; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 6ded0b707..714d85a59 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -7,10 +7,13 @@ #include <lz4.h> #include "common/common_funcs.h" #include "common/file_util.h" +#include "common/hex_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nso.h" @@ -18,36 +21,8 @@ #include "core/settings.h" namespace Loader { - -struct NsoSegmentHeader { - u32_le offset; - u32_le location; - u32_le size; - union { - u32_le alignment; - u32_le bss_size; - }; -}; -static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); - -struct NsoHeader { - u32_le magic; - u32_le version; - INSERT_PADDING_WORDS(1); - u8 flags; - std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order) - std::array<u8, 0x20> build_id; - std::array<u32_le, 3> segments_compressed_size; - - bool IsSegmentCompressed(size_t segment_num) const { - ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); - return ((flags >> segment_num) & 1); - } -}; -static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); -static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable."); - -struct ModHeader { +namespace { +struct MODHeader { u32_le magic; u32_le dynamic_offset; u32_le bss_start_offset; @@ -56,25 +31,10 @@ struct ModHeader { u32_le eh_frame_hdr_end_offset; u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base }; -static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); - -AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} +static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size."); -FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { - u32 magic = 0; - if (file->ReadObject(&magic) != sizeof(magic)) { - return FileType::Error; - } - - if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { - return FileType::Error; - } - - return FileType::NSO; -} - -static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, - const NsoSegmentHeader& header) { +std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, + const NSOSegmentHeader& header) { std::vector<u8> uncompressed_data(header.size); const int bytes_uncompressed = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()), @@ -88,23 +48,47 @@ static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, return uncompressed_data; } -static constexpr u32 PageAlignSize(u32 size) { +constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } +} // Anonymous namespace + +bool NSOHeader::IsSegmentCompressed(size_t segment_num) const { + ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); + return ((flags >> segment_num) & 1) != 0; +} + +AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} + +FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { + u32 magic = 0; + if (file->ReadObject(&magic) != sizeof(magic)) { + return FileType::Error; + } + + if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { + return FileType::Error; + } + + return FileType::NSO; +} std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments, std::optional<FileSys::PatchManager> pm) { - if (file.GetSize() < sizeof(NsoHeader)) + if (file.GetSize() < sizeof(NSOHeader)) { return {}; + } - NsoHeader nso_header{}; - if (sizeof(NsoHeader) != file.ReadObject(&nso_header)) + NSOHeader nso_header{}; + if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { return {}; + } - if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { return {}; + } // Build program image Kernel::CodeSet codeset; @@ -140,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); // Read MOD header - ModHeader mod_header{}; + MODHeader mod_header{}; // Default .bss to size in segment header if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; - std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); + std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader)); const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; if (has_mod_header) { // Resize program image to include .bss section and page align each section @@ -155,13 +139,25 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply patches if necessary if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { - std::vector<u8> pi_header(program_image.size() + 0x100); - std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader)); - std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); + std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); + pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), + reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); + pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), + program_image.end()); pi_header = pm->PatchNSO(pi_header); - std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); + std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); + } + + // Apply cheats if they exist and the program has a valid title ID + if (pm) { + auto& system = Core::System::GetInstance(); + const auto cheats = pm->CreateCheatList(system, nso_header.build_id); + if (!cheats.empty()) { + system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id), + load_base, load_base + program_image.size()); + } } // Load codeset for current process diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 135b6ea5a..4674c3724 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -4,10 +4,12 @@ #pragma once +#include <array> #include <optional> +#include <type_traits> #include "common/common_types.h" +#include "common/swap.h" #include "core/file_sys/patch_manager.h" -#include "core/loader/linker.h" #include "core/loader/loader.h" namespace Kernel { @@ -16,6 +18,43 @@ class Process; namespace Loader { +struct NSOSegmentHeader { + u32_le offset; + u32_le location; + u32_le size; + union { + u32_le alignment; + u32_le bss_size; + }; +}; +static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); + +struct NSOHeader { + using SHA256Hash = std::array<u8, 0x20>; + + struct RODataRelativeExtent { + u32_le data_offset; + u32_le size; + }; + + u32_le magic; + u32_le version; + u32 reserved; + u32_le flags; + std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order) + std::array<u8, 0x20> build_id; + std::array<u32_le, 3> segments_compressed_size; + std::array<u8, 0x1C> padding; + RODataRelativeExtent api_info_extent; + RODataRelativeExtent dynstr_extent; + RODataRelativeExtent dynsyn_extent; + std::array<SHA256Hash, 3> segment_hashes; + + bool IsSegmentCompressed(size_t segment_num) const; +}; +static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); +static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); + constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; struct NSOArgumentHeader { @@ -26,7 +65,7 @@ struct NSOArgumentHeader { static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size."); /// Loads an NSO file -class AppLoader_NSO final : public AppLoader, Linker { +class AppLoader_NSO final : public AppLoader { public: explicit AppLoader_NSO(FileSys::VirtualFile file); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 6591c45d2..332c1037c 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/page_table.h" #include "common/swap.h" #include "core/arm/arm_interface.h" #include "core/core.h" @@ -18,13 +19,14 @@ #include "core/hle/lock.h" #include "core/memory.h" #include "core/memory_setup.h" +#include "video_core/gpu.h" #include "video_core/renderer_base.h" namespace Memory { -static PageTable* current_page_table = nullptr; +static Common::PageTable* current_page_table = nullptr; -void SetCurrentPageTable(PageTable* page_table) { +void SetCurrentPageTable(Common::PageTable* page_table) { current_page_table = page_table; auto& system = Core::System::GetInstance(); @@ -36,39 +38,20 @@ void SetCurrentPageTable(PageTable* page_table) { } } -PageTable* GetCurrentPageTable() { +Common::PageTable* GetCurrentPageTable() { return current_page_table; } -PageTable::PageTable() = default; - -PageTable::PageTable(std::size_t address_space_width_in_bits) { - Resize(address_space_width_in_bits); -} - -PageTable::~PageTable() = default; - -void PageTable::Resize(std::size_t address_space_width_in_bits) { - const std::size_t num_page_table_entries = 1ULL << (address_space_width_in_bits - PAGE_BITS); - - pointers.resize(num_page_table_entries); - attributes.resize(num_page_table_entries); - - // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the - // vector size is subsequently decreased (via resize), the vector might not automatically - // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for - // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use. - - pointers.shrink_to_fit(); - attributes.shrink_to_fit(); -} - -static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) { +static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory, + Common::PageType type) { LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE, (base + size) * PAGE_SIZE); - RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, - FlushMode::FlushAndInvalidate); + // During boot, current_page_table might not be set yet, in which case we need not flush + if (Core::System::GetInstance().IsPoweredOn()) { + Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS, + size * PAGE_SIZE); + } VAddr end = base + size; ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", @@ -88,41 +71,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa } } -void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) { +void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); } -void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) { +void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer mmio_handler) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special); auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)}; - page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)}; + page_table.special_regions.add( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } -void UnmapRegion(PageTable& page_table, VAddr base, u64 size) { +void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Unmapped); auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); page_table.special_regions.erase(interval); } -void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) { +void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook) { auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)}; - page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)}; + page_table.special_regions.add( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } -void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) { +void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook) { auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)}; - page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)}; + page_table.special_regions.subtract( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } /** @@ -171,19 +160,19 @@ T Read(const VAddr vaddr) { return value; } - PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); return 0; - case PageType::Memory: + case Common::PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); break; - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); - + case Common::PageType::RasterizerCachedMemory: { + auto host_ptr{GetPointerFromVMA(vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T)); T value; - std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); + std::memcpy(&value, host_ptr, sizeof(T)); return value; } default: @@ -201,18 +190,19 @@ void Write(const VAddr vaddr, const T data) { return; } - PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, static_cast<u32>(data), vaddr); return; - case PageType::Memory: + case Common::PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); break; - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate); - std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); + case Common::PageType::RasterizerCachedMemory: { + auto host_ptr{GetPointerFromVMA(vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T)); + std::memcpy(host_ptr, &data, sizeof(T)); break; } default: @@ -227,10 +217,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { if (page_pointer) return true; - if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) + if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory) return true; - if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) + if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special) return false; return false; @@ -250,7 +240,8 @@ u8* GetPointer(const VAddr vaddr) { return page_pointer + (vaddr & PAGE_MASK); } - if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) { + if (current_page_table->attributes[vaddr >> PAGE_BITS] == + Common::PageType::RasterizerCachedMemory) { return GetPointerFromVMA(vaddr); } @@ -284,20 +275,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { - PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; if (cached) { // Switch page type to cached if now cached switch (page_type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address // space, for example, a system module need not have a VRAM mapping. break; - case PageType::Memory: - page_type = PageType::RasterizerCachedMemory; + case Common::PageType::Memory: + page_type = Common::PageType::RasterizerCachedMemory; current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; break; - case PageType::RasterizerCachedMemory: + case Common::PageType::RasterizerCachedMemory: // There can be more than one GPU region mapped per CPU region, so it's common that // this area is already marked as cached. break; @@ -307,23 +298,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { } else { // Switch page type to uncached if now uncached switch (page_type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address // space, for example, a system module need not have a VRAM mapping. break; - case PageType::Memory: + case Common::PageType::Memory: // There can be more than one GPU region mapped per CPU region, so it's common that // this area is already unmarked as cached. break; - case PageType::RasterizerCachedMemory: { + case Common::PageType::RasterizerCachedMemory: { u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); if (pointer == nullptr) { // It's possible that this function has been called while updating the pagetable // after unmapping a VMA. In that case the underlying VMA will no longer exist, // and we should just leave the pagetable entry blank. - page_type = PageType::Unmapped; + page_type = Common::PageType::Unmapped; } else { - page_type = PageType::Memory; + page_type = Common::PageType::Memory; current_page_table->pointers[vaddr >> PAGE_BITS] = pointer; } break; @@ -335,47 +326,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { } } -void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) { - auto& system_instance = Core::System::GetInstance(); - - // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be - // null here - if (!system_instance.IsPoweredOn()) { - return; - } - - const VAddr end = start + size; - - const auto CheckRegion = [&](VAddr region_start, VAddr region_end) { - if (start >= region_end || end <= region_start) { - // No overlap with region - return; - } - - const VAddr overlap_start = std::max(start, region_start); - const VAddr overlap_end = std::min(end, region_end); - const VAddr overlap_size = overlap_end - overlap_start; - - auto& gpu = system_instance.GPU(); - switch (mode) { - case FlushMode::Flush: - gpu.FlushRegion(overlap_start, overlap_size); - break; - case FlushMode::Invalidate: - gpu.InvalidateRegion(overlap_start, overlap_size); - break; - case FlushMode::FlushAndInvalidate: - gpu.FlushAndInvalidateRegion(overlap_start, overlap_size); - break; - } - }; - - const auto& vm_manager = Core::CurrentProcess()->VMManager(); - - CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress()); - CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress()); -} - u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -406,24 +356,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); const u8* src_ptr = page_table.pointers[page_index] + page_offset; std::memcpy(dest_buffer, src_ptr, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Flush); - std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount); + std::memcpy(dest_buffer, host_ptr, copy_amount); break; } default: @@ -470,23 +420,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memcpy(dest_ptr, src_buffer, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Invalidate); - std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount); + std::memcpy(host_ptr, src_buffer, copy_amount); break; } default: @@ -516,23 +466,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std: const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memset(dest_ptr, 0, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Invalidate); - std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount); + std::memset(host_ptr, 0, copy_amount); break; } default: @@ -558,23 +508,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); ZeroBlock(process, dest_addr, copy_amount); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); const u8* src_ptr = page_table.pointers[page_index] + page_offset; WriteBlock(process, dest_addr, src_ptr, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Flush); - WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount); + WriteBlock(process, dest_addr, host_ptr, copy_amount); break; } default: diff --git a/src/core/memory.h b/src/core/memory.h index 1acf5ce8c..1d38cdca8 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -6,11 +6,11 @@ #include <cstddef> #include <string> -#include <tuple> -#include <vector> -#include <boost/icl/interval_map.hpp> #include "common/common_types.h" -#include "core/memory_hook.h" + +namespace Common { +struct PageTable; +} namespace Kernel { class Process; @@ -26,71 +26,6 @@ constexpr std::size_t PAGE_BITS = 12; constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS; constexpr u64 PAGE_MASK = PAGE_SIZE - 1; -enum class PageType : u8 { - /// Page is unmapped and should cause an access error. - Unmapped, - /// Page is mapped to regular memory. This is the only type you can get pointers to. - Memory, - /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and - /// invalidation - RasterizerCachedMemory, - /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. - Special, -}; - -struct SpecialRegion { - enum class Type { - DebugHook, - IODevice, - } type; - - MemoryHookPointer handler; - - bool operator<(const SpecialRegion& other) const { - return std::tie(type, handler) < std::tie(other.type, other.handler); - } - - bool operator==(const SpecialRegion& other) const { - return std::tie(type, handler) == std::tie(other.type, other.handler); - } -}; - -/** - * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely - * mimics the way a real CPU page table works. - */ -struct PageTable { - explicit PageTable(); - explicit PageTable(std::size_t address_space_width_in_bits); - ~PageTable(); - - /** - * Resizes the page table to be able to accomodate enough pages within - * a given address space. - * - * @param address_space_width_in_bits The address size width in bits. - */ - void Resize(std::size_t address_space_width_in_bits); - - /** - * Vector of memory pointers backing each page. An entry can only be non-null if the - * corresponding entry in the `attributes` vector is of type `Memory`. - */ - std::vector<u8*> pointers; - - /** - * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is - * of type `Special`. - */ - boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions; - - /** - * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then - * the corresponding entry in `pointers` MUST be set to null. - */ - std::vector<PageType> attributes; -}; - /// Virtual user-space memory regions enum : VAddr { /// Read-only page containing kernel and system configuration values. @@ -116,8 +51,8 @@ enum : VAddr { }; /// Currently active page table -void SetCurrentPageTable(PageTable* page_table); -PageTable* GetCurrentPageTable(); +void SetCurrentPageTable(Common::PageTable* page_table); +Common::PageTable* GetCurrentPageTable(); /// Determines if the given VAddr is valid for the specified process. bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr); @@ -161,10 +96,4 @@ enum class FlushMode { */ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached); -/** - * Flushes and invalidates any externally cached rasterizer resources touching the given virtual - * address region. - */ -void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode); - } // namespace Memory diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 9a1a4f4be..5225ee8e2 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -5,7 +5,11 @@ #pragma once #include "common/common_types.h" -#include "core/memory_hook.h" +#include "common/memory_hook.h" + +namespace Common { +struct PageTable; +} namespace Memory { @@ -17,7 +21,7 @@ namespace Memory { * @param size The amount of bytes to map. Must be page-aligned. * @param target Buffer with the memory backing the mapping. Must be of length at least `size`. */ -void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target); +void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target); /** * Maps a region of the emulated process address space as a IO region. @@ -26,11 +30,14 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target); * @param size The amount of bytes to map. Must be page-aligned. * @param mmio_handler The handler that backs the mapping. */ -void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler); +void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer mmio_handler); -void UnmapRegion(PageTable& page_table, VAddr base, u64 size); +void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size); -void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook); -void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook); +void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook); +void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook); } // namespace Memory |