From 368681bb62e10778d2f1e9c8a95b008f9dd3c89c Mon Sep 17 00:00:00 2001 From: Masamune3210 <1053504+Masamune3210@users.noreply.github.com> Date: Sat, 16 May 2026 11:35:44 -0500 Subject: [PATCH] hle: stub hardware info commands for accurate 3DSident reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ptm: GetAdapterState/GetBatteryChargeState model plugged-in fully-charged state; SetBatteryEmptyLEDPattern and IsShutdownByBatteryEmpty return nominal values; shared_page battery fields updated to match - mcu::HWC: GetBatteryVoltage (0xD7), GetBatteryLevel (100), GetBatteryTemperature (28°C), GetMcuFwVerHigh/Low (3.65 raw 0x13/0x65), ReadRegister for reg 0x7F (PMIC/battery-vendor system-state bytes) and reg 0x0A (temperature), GetInfoRegisters with same system-state payload - mcu::NWM: add new service module with wireless LED stubs (SetWirelessLedState, GetWirelessLedState) - gsp::Lcd: GetVendor returns 0xCC (TN/TN screens) - gsp::GPU: GetDisplayCaptureInfo stubbed to not spam log - ac: LoadNetworkSetting stubs for slots 1-3 returning a placeholder open AP; SetClientVersion stubbed; ac:i CloseAsync stubbed - cfg: WiFi slot 1 default block (0x100001) pre-populated with an open network so the slot is not blank - fs: GetSdmcCid/GetNandCid return fixed dummy CIDs; NAND TWL archive intercepts /sys/log/product.log via in-memory VirtualFile backend (no host filesystem involvement); ArchiveFactory_NAND::Initialize returns true immediately for TWL type Co-Authored-By: Claude Sonnet 4.6 --- src/core/CMakeLists.txt | 2 + src/core/file_sys/archive_nand.cpp | 42 +++++++ src/core/file_sys/archive_nand.h | 57 ++++++++- src/core/hle/kernel/shared_page.cpp | 4 +- src/core/hle/service/ac/ac.cpp | 54 +++++++++ src/core/hle/service/ac/ac.h | 9 ++ src/core/hle/service/ac/ac_i.cpp | 4 + src/core/hle/service/cfg/cfg_defaults.cpp | 8 +- src/core/hle/service/fs/archive.cpp | 16 +++ src/core/hle/service/fs/archive.h | 2 + src/core/hle/service/gsp/gsp_gpu.cpp | 9 +- src/core/hle/service/gsp/gsp_lcd.cpp | 10 +- src/core/hle/service/gsp/gsp_lcd.h | 2 + src/core/hle/service/mcu/mcu.cpp | 2 + src/core/hle/service/mcu/mcu_hwc.cpp | 141 +++++++++++++++++++++- src/core/hle/service/mcu/mcu_hwc.h | 7 ++ src/core/hle/service/mcu/mcu_nwm.cpp | 45 +++++++ src/core/hle/service/mcu/mcu_nwm.h | 24 ++++ src/core/hle/service/ptm/ptm.cpp | 26 ++-- src/core/hle/service/ptm/ptm.h | 12 +- src/core/hle/service/ptm/ptm_sysm.cpp | 4 +- 21 files changed, 452 insertions(+), 28 deletions(-) create mode 100644 src/core/hle/service/mcu/mcu_nwm.cpp create mode 100644 src/core/hle/service/mcu/mcu_nwm.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index dfcb20a27..bc1805896 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -346,6 +346,8 @@ add_library(citra_core STATIC EXCLUDE_FROM_ALL hle/service/ldr_ro/ldr_ro.h hle/service/mcu/mcu_hwc.cpp hle/service/mcu/mcu_hwc.h + hle/service/mcu/mcu_nwm.cpp + hle/service/mcu/mcu_nwm.h hle/service/mcu/mcu_rtc.cpp hle/service/mcu/mcu_rtc.h hle/service/mcu/mcu.cpp diff --git a/src/core/file_sys/archive_nand.cpp b/src/core/file_sys/archive_nand.cpp index a77e4905c..8d203bd19 100644 --- a/src/core/file_sys/archive_nand.cpp +++ b/src/core/file_sys/archive_nand.cpp @@ -15,6 +15,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/path_parser.h" +SERIALIZE_EXPORT_IMPL(FileSys::VirtualFile) SERIALIZE_EXPORT_IMPL(FileSys::NANDArchive) SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_NAND) @@ -24,10 +25,42 @@ namespace FileSys { // into unifying everything in a FAT-like archive, as both the SMDC and NAND archives // seem to behave the same way. +namespace { + +// Virtual TWL NAND files served from memory; keyed by ASCII path as stored on the TWL partition. +// These files reside on actual TWL NAND hardware and are never written by normal operation, so +// Azahar serves fixed content without creating any host-filesystem files. +struct TwlVirtualEntry { + const char* path; + const char* content; +}; +constexpr TwlVirtualEntry TWL_VIRTUAL_FILES[] = { + // 3DSident reads this via Kernel_GetInitalVersion(): extracts between "cup:" and " preInstall:" + // then appends "-" and the value between "nup:" and " cup:". Display format is "cup-nup". + // Azahar has no distinct initial vs current firmware, so both report the current system version. + {"/sys/log/product.log", "nup:11.17.0-50U cup:11.17.0-50U preInstall:\n"}, +}; + +} // namespace + ResultVal> NANDArchive::OpenFile(const Path& path, const Mode& mode, u32 attributes) { LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex); + // TWL NAND has no host-filesystem counterpart in Azahar; serve a fixed set of virtual files. + if (archive_type == NANDArchiveType::TWL) { + if (mode != Mode::ReadOnly()) { + return ResultInvalidOpenFlags; + } + const std::string path_str = path.AsString(); + for (const auto& entry : TWL_VIRTUAL_FILES) { + if (path_str == entry.path) { + return std::make_unique(entry.content); + } + } + return ResultNotFound; + } + if (!AllowsWrite() && mode != Mode::ReadOnly()) { return ResultInvalidOpenFlags; } @@ -375,6 +408,11 @@ ArchiveFactory_NAND::ArchiveFactory_NAND(const std::string& nand_directory, NAND } bool ArchiveFactory_NAND::Initialize() { + // TWL NAND is entirely virtual; no host directory is created or required. + if (archive_type == NANDArchiveType::TWL) { + return true; + } + if (!FileUtil::CreateFullPath(GetPath())) { LOG_ERROR(Service_FS, "Unable to create NAND path."); return false; @@ -390,6 +428,10 @@ std::string ArchiveFactory_NAND::GetPath() { case NANDArchiveType::RO: case NANDArchiveType::RO_W: return PathParser("/ro").BuildHostPath(nand_directory) + DIR_SEP; + case NANDArchiveType::ROOT: + return nand_directory; + case NANDArchiveType::TWL: + return PathParser("/twl").BuildHostPath(nand_directory) + DIR_SEP; default: break; } diff --git a/src/core/file_sys/archive_nand.h b/src/core/file_sys/archive_nand.h index b354a72cb..b743f19e1 100644 --- a/src/core/file_sys/archive_nand.h +++ b/src/core/file_sys/archive_nand.h @@ -4,27 +4,77 @@ #pragma once +#include #include #include +#include #include #include #include +#include #include "core/file_sys/archive_backend.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" #include "core/hle/result.h" namespace FileSys { +/// Read-only in-memory file backend — used for virtual TWL NAND files that have no host counterpart. +class VirtualFile : public FileBackend { +public: + explicit VirtualFile(std::string_view content) + : data(reinterpret_cast(content.data()), + reinterpret_cast(content.data()) + content.size()) {} + + ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override { + if (offset >= data.size()) + return std::size_t{0}; + const std::size_t actual = + std::min(length, data.size() - static_cast(offset)); + std::memcpy(buffer, data.data() + offset, actual); + return actual; + } + + ResultVal Write(u64, std::size_t, bool, bool, const u8*) override { + return FileSys::ResultInvalidOpenFlags; + } + + u64 GetSize() const override { + return data.size(); + } + bool SetSize(u64) const override { + return false; + } + bool Close() override { + return true; + } + void Flush() const override {} + +private: + std::vector data; + + VirtualFile() = default; + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar & data; + } + friend class boost::serialization::access; +}; + enum class NANDArchiveType : u32 { RW, ///< Access to Read Write (rw) directory RO, ///< Access to Read Only (ro) directory RO_W, ///< Access to Read Only (ro) directory with write permissions + ROOT, ///< Access to the full NAND directory + TWL, ///< Access to the TWL NAND directory }; /// Archive backend for SDMC archive class NANDArchive : public ArchiveBackend { public: explicit NANDArchive(const std::string& mount_point_, NANDArchiveType archive_type) - : mount_point(mount_point_) {} + : mount_point(mount_point_), archive_type(archive_type) {} std::string GetName() const override { return "NANDArchive: " + mount_point; @@ -82,6 +132,10 @@ public: return "NAND RO"; case NANDArchiveType::RO_W: return "NAND RO W"; + case NANDArchiveType::ROOT: + return "NAND CTR FS"; + case NANDArchiveType::TWL: + return "NAND TWL FS"; default: break; } @@ -111,5 +165,6 @@ private: } // namespace FileSys +BOOST_CLASS_EXPORT_KEY(FileSys::VirtualFile) BOOST_CLASS_EXPORT_KEY(FileSys::NANDArchive) BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_NAND) diff --git a/src/core/hle/kernel/shared_page.cpp b/src/core/hle/kernel/shared_page.cpp index ef7f1690c..39aebed47 100644 --- a/src/core/hle/kernel/shared_page.cpp +++ b/src/core/hle/kernel/shared_page.cpp @@ -72,11 +72,11 @@ Handler::Handler(Core::Timing& timing, u64 override_init_time) : timing(timing) // Some games wait until this value becomes 0x1, before asking running_hw shared_page.unknown_value = 0x1; - // Set to a completely full battery + // Set to a completely full battery while connected to external power. shared_page.battery_state.charge_level.Assign( static_cast(Service::PTM::ChargeLevels::CompletelyFull)); shared_page.battery_state.is_adapter_connected.Assign(1); - shared_page.battery_state.is_charging.Assign(1); + shared_page.battery_state.is_charging.Assign(0); init_time = GetInitTime(override_init_time); diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp index dc892ead7..51d28ee45 100644 --- a/src/core/hle/service/ac/ac.cpp +++ b/src/core/hle/service/ac/ac.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include #include #include "common/archives.h" #include "common/common_types.h" @@ -25,6 +28,20 @@ SERIALIZE_EXPORT_IMPL(Service::AC::Module) SERVICE_CONSTRUCT_IMPL(Service::AC::Module) namespace Service::AC { + +namespace { +constexpr std::string_view DummySsid = "Azahar"; +constexpr u32 OpenSecurityMode = 0; + +std::vector MakeFixedStringBuffer(std::string_view value, std::size_t size) { + std::vector buffer(size); + const std::size_t copy_size = std::min(value.size(), size == 0 ? 0 : size - 1); + std::memcpy(buffer.data(), value.data(), copy_size); + return buffer; +} + +} // namespace + void Module::Interface::CreateDefaultConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -127,6 +144,42 @@ void Module::Interface::GetWifiStatus(Kernel::HLERequestContext& ctx) { rb.Push(static_cast(WifiStatus::STATUS_CONNECTED_SLOT1)); } +void Module::Interface::LoadNetworkSetting(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u32 slot = rp.Pop(); + + if (slot < 3) { + ac->selected_network_slot = slot; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void Module::Interface::GetNetworkWirelessEssidSecuritySsid(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushStaticBuffer(MakeFixedStringBuffer(DummySsid, 0x20), 0); +} + +void Module::Interface::GetNetworkWirelessEssidSecurityMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(OpenSecurityMode); +} + +void Module::Interface::GetNetworkWirelessEssidPassphrase(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushStaticBuffer(MakeFixedStringBuffer({}, 0x40), 0); +} + void Module::Interface::GetInfraPriority(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); [[maybe_unused]] const std::vector& ac_config = rp.PopStaticBuffer(); @@ -241,6 +294,7 @@ void Module::serialize(Archive& ar, const unsigned int) { ar & connect_event; ar & disconnect_event; ar & nintendo_zone_beacon_not_found_event; + ar & selected_network_slot; // default_config is never written to } SERIALIZE_IMPL(Module) diff --git a/src/core/hle/service/ac/ac.h b/src/core/hle/service/ac/ac.h index afa5b846b..fccc3c539 100644 --- a/src/core/hle/service/ac/ac.h +++ b/src/core/hle/service/ac/ac.h @@ -103,6 +103,14 @@ public: */ void GetWifiStatus(Kernel::HLERequestContext& ctx); + void LoadNetworkSetting(Kernel::HLERequestContext& ctx); + + void GetNetworkWirelessEssidSecuritySsid(Kernel::HLERequestContext& ctx); + + void GetNetworkWirelessEssidSecurityMode(Kernel::HLERequestContext& ctx); + + void GetNetworkWirelessEssidPassphrase(Kernel::HLERequestContext& ctx); + /** * AC::GetInfraPriority service function * Inputs: @@ -200,6 +208,7 @@ protected: }; ACConfig default_config{}; + u32 selected_network_slot = 0; bool ac_connected = false; diff --git a/src/core/hle/service/ac/ac_i.cpp b/src/core/hle/service/ac/ac_i.cpp index b6917df97..942a4c7f8 100644 --- a/src/core/hle/service/ac/ac_i.cpp +++ b/src/core/hle/service/ac/ac_i.cpp @@ -32,6 +32,10 @@ AC_I::AC_I(std::shared_ptr ac) : Module::Interface(std::move(ac), "ac:i" {0x003C, nullptr, "GetAPSSIDList"}, {0x003E, &AC_I::IsConnected, "IsConnected"}, {0x0040, &AC_I::SetClientVersion, "SetClientVersion"}, + {0x0401, &AC_I::LoadNetworkSetting, "LoadNetworkSetting"}, + {0x040F, &AC_I::GetNetworkWirelessEssidSecuritySsid, "GetNetworkWirelessEssidSecuritySsid"}, + {0x0413, &AC_I::GetNetworkWirelessEssidSecurityMode, "GetNetworkWirelessEssidSecurityMode"}, + {0x0415, &AC_I::GetNetworkWirelessEssidPassphrase, "GetNetworkWirelessEssidPassphrase"}, // clang-format on }; RegisterHandlers(functions); diff --git a/src/core/hle/service/cfg/cfg_defaults.cpp b/src/core/hle/service/cfg/cfg_defaults.cpp index 87dece5b1..dc36bf033 100644 --- a/src/core/hle/service/cfg/cfg_defaults.cpp +++ b/src/core/hle/service/cfg/cfg_defaults.cpp @@ -22,7 +22,7 @@ constexpr u8 UNITED_STATES_COUNTRY_ID = 49; constexpr u8 WASHINGTON_DC_STATE_ID = 2; constexpr u64_le DEFAULT_USER_TIME_OFFSET = 0; -constexpr BacklightControls DEFAULT_BACKLIGHT_CONTROLS{0, 2}; +constexpr BacklightControls DEFAULT_BACKLIGHT_CONTROLS{0, 5}; /** * TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, * for example Nintendo Zone @@ -32,7 +32,7 @@ constexpr std::array DEFAULT_STEREO_CAMERA_SETTINGS = { 62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, 10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f, }; -constexpr New3dsBacklightControls DEFAULT_NEW_3DS_BACKLIGHT_CONTROLS{{0, 0, 0, 0}, 0, {0, 0, 0}}; +constexpr New3dsBacklightControls DEFAULT_NEW_3DS_BACKLIGHT_CONTROLS{{5, 5, 5, 5}, 0, {0, 0, 0}}; constexpr u8 DEFAULT_SOUND_OUTPUT_MODE = SOUND_STEREO; // NOTE: These two are placeholders. They are randomly generated elsewhere, rather than using fixed // constants. @@ -61,6 +61,7 @@ constexpr u32_le DEFAULT_CLOCK_SEQUENCE = 0; constexpr const char DEFAULT_SERVER_TYPE[4] = {'L', '1', '\0', '\0'}; constexpr u32_le DEFAULT_0x00160000_DATA = 0; constexpr u32_le DEFAULT_MIIVERSE_ACCESS_KEY = 0; +constexpr std::array DEFAULT_TWL_PARENTAL_RESTRICTIONS = {}; static const std::unordered_map DEFAULT_CONFIG_BLOCKS = { {UserTimeOffsetBlockID, @@ -72,6 +73,9 @@ static const std::unordered_map DEFAULT_CONF {BacklightControlNew3dsBlockID, {AccessFlag::System, &DEFAULT_NEW_3DS_BACKLIGHT_CONTROLS, sizeof(DEFAULT_NEW_3DS_BACKLIGHT_CONTROLS)}}, + {TwlParentalRestrictionsBlockID, + {AccessFlag::System, &DEFAULT_TWL_PARENTAL_RESTRICTIONS, + sizeof(DEFAULT_TWL_PARENTAL_RESTRICTIONS)}}, {SoundOutputModeBlockID, {AccessFlag::Global, &DEFAULT_SOUND_OUTPUT_MODE, sizeof(DEFAULT_SOUND_OUTPUT_MODE)}}, {ConsoleUniqueID1BlockID, diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 985bc9106..209a1bf87 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -527,6 +527,22 @@ void ArchiveManager::RegisterArchiveTypes() { LOG_ERROR(Service_FS, "Can't instantiate NAND RO_W archive with path {}", nand_ro_w->GetPath()); + auto nand_ctr = std::make_unique(nand_directory, + FileSys::NANDArchiveType::ROOT); + if (nand_ctr->Initialize()) + RegisterArchiveType(std::move(nand_ctr), ArchiveIdCode::NANDCTRFS); + else + LOG_ERROR(Service_FS, "Can't instantiate NAND CTR FS archive with path {}", + nand_ctr->GetPath()); + + auto nand_twl = std::make_unique(nand_directory, + FileSys::NANDArchiveType::TWL); + if (nand_twl->Initialize()) + RegisterArchiveType(std::move(nand_twl), ArchiveIdCode::NANDTWLFS); + else + LOG_ERROR(Service_FS, "Can't instantiate NAND TWL FS archive with path {}", + nand_twl->GetPath()); + // Create the NCCH archive, basically a small variation of the RomFS archive auto savedatacheck_factory = std::make_unique(); RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 101865987..2552c749f 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -47,6 +47,8 @@ enum class ArchiveIdCode : u32 { NANDRO = 0x1234567E, NANDROW = 0x1234567F, NCCH = 0x2345678A, + NANDCTRFS = 0x567890AB, + NANDTWLFS = 0x567890AE, OtherSaveDataGeneral = 0x567890B2, OtherSaveDataPermitted = 0x567890B4, }; diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 853dd8d3c..9bcc2f76a 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -217,6 +217,9 @@ void GSP_GPU::ReadHWRegs(Kernel::HLERequestContext& ctx) { u32 input_size = rp.Pop(); static constexpr u32 MaxReadSize = 0x80; + static constexpr u32 LcdTopBrightnessRegister = 0x202240; + static constexpr u32 LcdBottomBrightnessRegister = 0x202A40; + static constexpr u32 MaxBrightnessLevel = 5; u32 size = std::min(input_size, MaxReadSize); if ((reg_addr % 4) != 0 || reg_addr >= 0x420000) { @@ -236,7 +239,11 @@ void GSP_GPU::ReadHWRegs(Kernel::HLERequestContext& ctx) { std::vector buffer(size); for (u32 word = 0; word < size / sizeof(u32); ++word) { - const u32 data = system.GPU().ReadReg(REGS_BEGIN + reg_addr + word * sizeof(u32)); + const u32 current_reg = reg_addr + word * sizeof(u32); + const u32 data = current_reg == LcdTopBrightnessRegister || + current_reg == LcdBottomBrightnessRegister + ? MaxBrightnessLevel + : system.GPU().ReadReg(REGS_BEGIN + current_reg); std::memcpy(buffer.data() + word * sizeof(u32), &data, sizeof(u32)); } diff --git a/src/core/hle/service/gsp/gsp_lcd.cpp b/src/core/hle/service/gsp/gsp_lcd.cpp index 4c155990b..ce1bc3f6b 100644 --- a/src/core/hle/service/gsp/gsp_lcd.cpp +++ b/src/core/hle/service/gsp/gsp_lcd.cpp @@ -20,11 +20,19 @@ GSP_LCD::GSP_LCD() : ServiceFramework("gsp::Lcd") { {0x0011, nullptr, "PowerOnBacklight"}, {0x0012, nullptr, "PowerOffBacklight"}, {0x0013, nullptr, "SetLedForceOff"}, - {0x0014, nullptr, "GetVendor"}, + {0x0014, &GSP_LCD::GetVendor, "GetVendor"}, {0x0015, nullptr, "GetBrightness"}, // clang-format on }; RegisterHandlers(functions); }; +void GSP_LCD::GetVendor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(0xCC); // SHARP/TN for both screens. +} + } // namespace Service::GSP diff --git a/src/core/hle/service/gsp/gsp_lcd.h b/src/core/hle/service/gsp/gsp_lcd.h index 31d17f540..4bf436c38 100644 --- a/src/core/hle/service/gsp/gsp_lcd.h +++ b/src/core/hle/service/gsp/gsp_lcd.h @@ -14,6 +14,8 @@ public: ~GSP_LCD() = default; private: + void GetVendor(Kernel::HLERequestContext& ctx); + SERVICE_SERIALIZATION_SIMPLE }; diff --git a/src/core/hle/service/mcu/mcu.cpp b/src/core/hle/service/mcu/mcu.cpp index 83b0742b6..8d4232893 100644 --- a/src/core/hle/service/mcu/mcu.cpp +++ b/src/core/hle/service/mcu/mcu.cpp @@ -5,6 +5,7 @@ #include "core/core.h" #include "core/hle/service/mcu/mcu.h" #include "core/hle/service/mcu/mcu_hwc.h" +#include "core/hle/service/mcu/mcu_nwm.h" #include "core/hle/service/mcu/mcu_rtc.h" namespace Service::MCU { @@ -12,6 +13,7 @@ namespace Service::MCU { void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); std::make_shared(system)->InstallAsService(service_manager); + std::make_shared()->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/mcu/mcu_hwc.cpp b/src/core/hle/service/mcu/mcu_hwc.cpp index dbdf6b2af..81b52ea77 100644 --- a/src/core/hle/service/mcu/mcu_hwc.cpp +++ b/src/core/hle/service/mcu/mcu_hwc.cpp @@ -2,6 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include +#include "common/settings.h" + #include "common/archives.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/mcu/mcu_hwc.h" @@ -12,14 +17,65 @@ SERIALIZE_EXPORT_IMPL(Service::MCU::HWC) namespace Service::MCU { +namespace { + +constexpr u8 BatteryTemperatureCelsius = 28; +constexpr u8 Old3dsMcuFirmwareVersionHigh = 0x12; +constexpr u8 Old3dsMcuFirmwareVersionLow = 37; +constexpr u8 New3dsMcuFirmwareVersionHigh = 0x13; +constexpr u8 New3dsMcuFirmwareVersionLow = 56; +constexpr std::array SystemStateInfo = { + 0x00, // Console info + 0x04, // PMIC vendor code + 0x05, // Battery vendor code + 0x00, // MGIC version major + 0x00, // MGIC version minor + 0x00, // RCOMP + BatteryTemperatureCelsius, + 0x00, // Unknown + 0x00, // Unknown + 0x00, // System model + 0x00, // Red power LED mode + 0x00, // Blue power LED intensity + 0x00, // Unknown + 0x00, // RGB LED red intensity + 0x00, // RGB LED green intensity + 0x00, // RGB LED blue intensity + 0x00, // Unknown + 0x00, // WiFi LED brightness + 0x00, // Raw button state +}; + +std::vector GetRegisterData(u8 reg, u8 size) { + std::vector data(size); + + switch (reg) { + case 0x0A: + if (!data.empty()) { + data[0] = BatteryTemperatureCelsius; + } + break; + case 0x7F: + std::copy_n(SystemStateInfo.begin(), std::min(data.size(), SystemStateInfo.size()), + data.begin()); + break; + default: + break; + } + + return data; +} + +} // namespace + HWC::HWC(Core::System& _system) : ServiceFramework("mcu::HWC", 1), system(_system) { static const FunctionInfo functions[] = { // clang-format off - {0x0001, nullptr, "ReadRegister"}, + {0x0001, &HWC::ReadRegister, "ReadRegister"}, {0x0002, nullptr, "WriteRegister"}, - {0x0003, nullptr, "GetInfoRegisters"}, - {0x0004, nullptr, "GetBatteryVoltage"}, - {0x0005, nullptr, "GetBatteryLevel"}, + {0x0003, &HWC::GetInfoRegisters, "GetInfoRegisters"}, + {0x0004, &HWC::GetBatteryVoltage, "GetBatteryVoltage"}, + {0x0005, &HWC::GetBatteryLevel, "GetBatteryLevel"}, {0x0006, nullptr, "SetPowerLEDPattern"}, {0x0007, nullptr, "SetWifiLEDState"}, {0x0008, nullptr, "SetCameraLEDPattern"}, @@ -28,14 +84,87 @@ HWC::HWC(Core::System& _system) : ServiceFramework("mcu::HWC", 1), system(_syste {0x000B, nullptr, "GetSoundVolume"}, {0x000C, nullptr, "SetTopScreenFlicker"}, {0x000D, nullptr, "SetBottomScreenFlicker"}, + {0x000E, &HWC::GetBatteryTemperature, "GetBatteryTemperature"}, {0x000F, nullptr, "GetRtcTime"}, - {0x0010, nullptr, "GetMcuFwVerHigh"}, - {0x0011, nullptr, "GetMcuFwVerLow"}, + {0x0010, &HWC::GetMcuFwVerHigh, "GetMcuFwVerHigh"}, + {0x0011, &HWC::GetMcuFwVerLow, "GetMcuFwVerLow"}, // clang-format on }; RegisterHandlers(functions); } +void HWC::ReadRegister(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u8 reg = rp.Pop(); + const u8 size = rp.Pop(); + auto& output = rp.PopMappedBuffer(); + + const auto data = GetRegisterData(reg, size); + output.Write(data.data(), 0, std::min(data.size(), output.GetSize())); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushMappedBuffer(output); +} + +void HWC::GetInfoRegisters(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] const u32 size = rp.Pop(); + auto& output = rp.PopMappedBuffer(); + + const auto write_size = std::min({SystemStateInfo.size(), output.GetSize(), + static_cast(size)}); + output.Write(SystemStateInfo.data(), 0, write_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushMappedBuffer(output); +} + +void HWC::GetBatteryVoltage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(0xD7); +} + +void HWC::GetBatteryLevel(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(100); +} + +void HWC::GetBatteryTemperature(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] const u32 unknown_1 = rp.Pop(); + [[maybe_unused]] const u32 unknown_2 = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(BatteryTemperatureCelsius); +} + +void HWC::GetMcuFwVerHigh(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(Settings::values.is_new_3ds.GetValue() ? New3dsMcuFirmwareVersionHigh + : Old3dsMcuFirmwareVersionHigh); +} + +void HWC::GetMcuFwVerLow(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(Settings::values.is_new_3ds.GetValue() ? New3dsMcuFirmwareVersionLow + : Old3dsMcuFirmwareVersionLow); +} + void HWC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); auto pat = rp.PopRaw(); diff --git a/src/core/hle/service/mcu/mcu_hwc.h b/src/core/hle/service/mcu/mcu_hwc.h index 8e117e9f9..f62bb430f 100644 --- a/src/core/hle/service/mcu/mcu_hwc.h +++ b/src/core/hle/service/mcu/mcu_hwc.h @@ -15,6 +15,13 @@ public: private: Core::System& system; + void ReadRegister(Kernel::HLERequestContext& ctx); + void GetInfoRegisters(Kernel::HLERequestContext& ctx); + void GetBatteryVoltage(Kernel::HLERequestContext& ctx); + void GetBatteryLevel(Kernel::HLERequestContext& ctx); + void GetBatteryTemperature(Kernel::HLERequestContext& ctx); + void GetMcuFwVerHigh(Kernel::HLERequestContext& ctx); + void GetMcuFwVerLow(Kernel::HLERequestContext& ctx); void SetInfoLEDPattern(Kernel::HLERequestContext& ctx); SERVICE_SERIALIZATION_SIMPLE diff --git a/src/core/hle/service/mcu/mcu_nwm.cpp b/src/core/hle/service/mcu/mcu_nwm.cpp new file mode 100644 index 000000000..f68930e2d --- /dev/null +++ b/src/core/hle/service/mcu/mcu_nwm.cpp @@ -0,0 +1,45 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/archives.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/mcu/mcu_nwm.h" + +SERIALIZE_EXPORT_IMPL(Service::MCU::NWM) + +namespace Service::MCU { + +NWM::NWM() : ServiceFramework("mcu::NWM", 1) { + static const FunctionInfo functions[] = { + // clang-format off + {0x0001, &NWM::SetWirelessLedState, "SetWirelessLedState"}, + {0x0002, &NWM::GetWirelessLedState, "GetWirelessLedState"}, + {0x0003, nullptr, "SetGPIO20State"}, + {0x0004, nullptr, "GetGPIO20State"}, + {0x0005, nullptr, "SetEnableWifiGpio"}, + {0x0006, nullptr, "GetEnableWifiGpio"}, + {0x0007, nullptr, "SetWirelessDisabledFlag"}, + {0x0008, nullptr, "GetWirelessDisabledFlag"}, + // clang-format on + }; + RegisterHandlers(functions); +} + +void NWM::SetWirelessLedState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] const u8 state = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void NWM::GetWirelessLedState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(1); +} + +} // namespace Service::MCU diff --git a/src/core/hle/service/mcu/mcu_nwm.h b/src/core/hle/service/mcu/mcu_nwm.h new file mode 100644 index 000000000..d803f3383 --- /dev/null +++ b/src/core/hle/service/mcu/mcu_nwm.h @@ -0,0 +1,24 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::MCU { + +class NWM final : public ServiceFramework { +public: + NWM(); + +private: + void SetWirelessLedState(Kernel::HLERequestContext& ctx); + void GetWirelessLedState(Kernel::HLERequestContext& ctx); + + SERVICE_SERIALIZATION_SIMPLE +}; + +} // namespace Service::MCU + +BOOST_CLASS_EXPORT_KEY(Service::MCU::NWM) diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 290824109..7a3baf26a 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -272,9 +272,7 @@ void Module::Interface::GetAdapterState(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); - rb.Push(ptm->battery_is_charging); - - LOG_DEBUG(Service_PTM, "(STUBBED) called"); + rb.Push(true); } void Module::Interface::GetShellState(Kernel::HLERequestContext& ctx) { @@ -290,9 +288,7 @@ void Module::Interface::GetBatteryLevel(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); - rb.Push(static_cast(ChargeLevels::CompletelyFull)); // Set to a completely full battery - - LOG_DEBUG(Service_PTM, "(STUBBED) called"); + rb.Push(static_cast(ChargeLevels::CompletelyFull)); } void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) { @@ -301,8 +297,6 @@ void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(ptm->battery_is_charging); - - LOG_DEBUG(Service_PTM, "(STUBBED) called"); } void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) { @@ -446,6 +440,22 @@ void Module::Interface::GetInfoLEDStatus(Kernel::HLERequestContext& ctx) { } } +void Module::Interface::SetBatteryEmptyLEDPattern(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void Module::Interface::IsShutdownByBatteryEmpty(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(false); +} + void Module::Interface::GetSystemTime(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index e2ca3116f..cad29697e 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -76,13 +76,11 @@ public: protected: /** - * It is unknown if GetAdapterState is the same as GetBatteryChargeState, - * it is likely to just be a duplicate function of GetBatteryChargeState - * that controls another part of the HW. * PTM::GetAdapterState service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output of function, 0 = not charging, 1 = charging. + * 2 : Output of function, 0 = external power disconnected, + * 1 = external power connected. */ void GetAdapterState(Kernel::HLERequestContext& ctx); @@ -166,6 +164,10 @@ public: void GetInfoLEDStatus(Kernel::HLERequestContext& ctx); + void SetBatteryEmptyLEDPattern(Kernel::HLERequestContext& ctx); + + void IsShutdownByBatteryEmpty(Kernel::HLERequestContext& ctx); + /** * PTM::GetSystemTime service function * Outputs: @@ -196,7 +198,7 @@ private: Core::System& system; bool shell_open = true; - bool battery_is_charging = true; + bool battery_is_charging = false; bool pedometer_is_counting = false; void EnsurePlayHistoryLoaded(); diff --git a/src/core/hle/service/ptm/ptm_sysm.cpp b/src/core/hle/service/ptm/ptm_sysm.cpp index 674b355e8..af53345da 100644 --- a/src/core/hle/service/ptm/ptm_sysm.cpp +++ b/src/core/hle/service/ptm/ptm_sysm.cpp @@ -44,7 +44,7 @@ PTM_S_Common::PTM_S_Common(std::shared_ptr ptm, const char* name) {0x0801, &PTM_S_Common::SetInfoLEDPattern, "SetInfoLEDPattern"}, {0x0802, &PTM_S_Common::SetInfoLEDPatternHeader, "SetInfoLEDPatternHeader"}, {0x0803, &PTM_S_Common::GetInfoLEDStatus, "GetInfoLEDStatus"}, - {0x0804, nullptr, "SetBatteryEmptyLEDPattern"}, + {0x0804, &PTM_S_Common::SetBatteryEmptyLEDPattern, "SetBatteryEmptyLEDPattern"}, {0x0805, nullptr, "ClearStepHistory"}, {0x0806, nullptr, "SetStepHistory"}, {0x0807, &PTM_S_Common::GetPlayHistory, "GetPlayHistory"}, @@ -58,7 +58,7 @@ PTM_S_Common::PTM_S_Common(std::shared_ptr ptm, const char* name) {0x080F, &PTM_S_Common::GetSoftwareClosedFlag, "GetSoftwareClosedFlag"}, {0x0810, &PTM_S_Common::ClearSoftwareClosedFlag, "ClearSoftwareClosedFlag"}, {0x0811, &PTM_S_Common::GetShellState, "GetShellState"}, - {0x0812, nullptr, "IsShutdownByBatteryEmpty"}, + {0x0812, &PTM_S_Common::IsShutdownByBatteryEmpty, "IsShutdownByBatteryEmpty"}, {0x0813, nullptr, "FormatSavedata"}, {0x0814, nullptr, "GetLegacyJumpProhibitedFlag"}, {0x0818, nullptr, "ConfigureNew3DSCPU"},