code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/psc/time/steady_clock.h"

namespace Service::PSC::Time {

SteadyClock::SteadyClock(Core::System& system_, std::shared_ptr<TimeManager> manager,
                         bool can_write_steady_clock, bool can_write_uninitialized_clock)
    : ServiceFramework{system_, "ISteadyClock"}, m_system{system},
      m_clock_core{manager->m_standard_steady_clock},
      m_can_write_steady_clock{can_write_steady_clock}, m_can_write_uninitialized_clock{
                                                            can_write_uninitialized_clock} {
    // clang-format off
         static const FunctionInfo functions[] = {
        {0, D<&SteadyClock::GetCurrentTimePoint>, "GetCurrentTimePoint"},
        {2, D<&SteadyClock::GetTestOffset>, "GetTestOffset"},
        {3, D<&SteadyClock::SetTestOffset>, "SetTestOffset"},
        {100, D<&SteadyClock::GetRtcValue>, "GetRtcValue"},
        {101, D<&SteadyClock::IsRtcResetDetected>, "IsRtcResetDetected"},
        {102, D<&SteadyClock::GetSetupResultValue>, "GetSetupResultValue"},
        {200, D<&SteadyClock::GetInternalOffset>, "GetInternalOffset"},
    };
    // clang-format on
    RegisterHandlers(functions);
}

Result SteadyClock::GetCurrentTimePoint(Out<SteadyClockTimePoint> out_time_point) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_time_point={}", *out_time_point);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.GetCurrentTimePoint(*out_time_point));
}

Result SteadyClock::GetTestOffset(Out<s64> out_test_offset) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_test_offset={}", *out_test_offset);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    *out_test_offset = m_clock_core.GetTestOffset();
    R_SUCCEED();
}

Result SteadyClock::SetTestOffset(s64 test_offset) {
    LOG_DEBUG(Service_Time, "called. test_offset={}", test_offset);

    R_UNLESS(m_can_write_steady_clock, ResultPermissionDenied);
    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    m_clock_core.SetTestOffset(test_offset);
    R_SUCCEED();
}

Result SteadyClock::GetRtcValue(Out<s64> out_rtc_value) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_rtc_value={}", *out_rtc_value);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.GetRtcValue(*out_rtc_value));
}

Result SteadyClock::IsRtcResetDetected(Out<bool> out_is_detected) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_is_detected={}", *out_is_detected);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    *out_is_detected = m_clock_core.IsResetDetected();
    R_SUCCEED();
}

Result SteadyClock::GetSetupResultValue(Out<Result> out_result) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_result=0x{:X}", out_result->raw);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    *out_result = m_clock_core.GetSetupResultValue();
    R_SUCCEED();
}

Result SteadyClock::GetInternalOffset(Out<s64> out_internal_offset) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_internal_offset={}", *out_internal_offset);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    *out_internal_offset = m_clock_core.GetInternalOffset();
    R_SUCCEED();
}

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/psc/time/common.h"
#include "core/hle/service/psc/time/manager.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"

namespace Core {
class System;
}

namespace Service::PSC::Time {

class SteadyClock final : public ServiceFramework<SteadyClock> {
public:
    explicit SteadyClock(Core::System& system, std::shared_ptr<TimeManager> manager,
                         bool can_write_steady_clock, bool can_write_uninitialized_clock);

    ~SteadyClock() override = default;

    Result GetCurrentTimePoint(Out<SteadyClockTimePoint> out_time_point);
    Result GetTestOffset(Out<s64> out_test_offset);
    Result SetTestOffset(s64 test_offset);
    Result GetRtcValue(Out<s64> out_rtc_value);
    Result IsRtcResetDetected(Out<bool> out_is_detected);
    Result GetSetupResultValue(Out<Result> out_result);
    Result GetInternalOffset(Out<s64> out_internal_offset);

private:
    Core::System& m_system;

    StandardSteadyClockCore& m_clock_core;
    bool m_can_write_steady_clock;
    bool m_can_write_uninitialized_clock;
};

} // namespace Service::PSC::Time

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/psc/time/system_clock.h"

namespace Service::PSC::Time {

SystemClock::SystemClock(Core::System& system_, SystemClockCore& clock_core, bool can_write_clock,
                         bool can_write_uninitialized_clock)
    : ServiceFramework{system_, "ISystemClock"}, m_system{system}, m_clock_core{clock_core},
      m_can_write_clock{can_write_clock}, m_can_write_uninitialized_clock{
                                              can_write_uninitialized_clock} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0, D<&SystemClock::GetCurrentTime>, "GetCurrentTime"},
        {1, D<&SystemClock::SetCurrentTime>, "SetCurrentTime"},
        {2, D<&SystemClock::GetSystemClockContext>, "GetSystemClockContext"},
        {3, D<&SystemClock::SetSystemClockContext>, "SetSystemClockContext"},
        {4, D<&SystemClock::GetOperationEventReadableHandle>, "GetOperationEventReadableHandle"},
    };
    // clang-format on
    RegisterHandlers(functions);
}

Result SystemClock::GetCurrentTime(Out<s64> out_time) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_time={}", *out_time);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.GetCurrentTime(out_time.Get()));
}

Result SystemClock::SetCurrentTime(s64 time) {
    LOG_DEBUG(Service_Time, "called. time={}", time);

    R_UNLESS(m_can_write_clock, ResultPermissionDenied);
    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.SetCurrentTime(time));
}

Result SystemClock::GetSystemClockContext(Out<SystemClockContext> out_context) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_context={}", *out_context);
    };

    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.GetContext(*out_context));
}

Result SystemClock::SetSystemClockContext(const SystemClockContext& context) {
    LOG_DEBUG(Service_Time, "called. context={}", context);

    R_UNLESS(m_can_write_clock, ResultPermissionDenied);
    R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
             ResultClockUninitialized);

    R_RETURN(m_clock_core.SetContextAndWrite(context));
}

Result SystemClock::GetOperationEventReadableHandle(
    OutCopyHandle<Kernel::KReadableEvent> out_event) {
    LOG_DEBUG(Service_Time, "called.");

    if (!m_operation_event) {
        m_operation_event = std::make_unique<OperationEvent>(m_system);
        R_UNLESS(m_operation_event != nullptr, ResultFailed);

        m_clock_core.LinkOperationEvent(*m_operation_event);
    }

    *out_event = &m_operation_event->m_event->GetReadableEvent();
    R_SUCCEED();
}

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/psc/time/common.h"
#include "core/hle/service/psc/time/manager.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"

namespace Core {
class System;
}

namespace Service::PSC::Time {

class SystemClock final : public ServiceFramework<SystemClock> {
public:
    explicit SystemClock(Core::System& system, SystemClockCore& system_clock_core,
                         bool can_write_clock, bool can_write_uninitialized_clock);

    ~SystemClock() override = default;

    Result GetCurrentTime(Out<s64> out_time);
    Result SetCurrentTime(s64 time);
    Result GetSystemClockContext(Out<SystemClockContext> out_context);
    Result SetSystemClockContext(const SystemClockContext& context);
    Result GetOperationEventReadableHandle(OutCopyHandle<Kernel::KReadableEvent> out_event);

private:
    Core::System& m_system;

    SystemClockCore& m_clock_core;
    bool m_can_write_clock;
    bool m_can_write_uninitialized_clock;
    std::unique_ptr<OperationEvent> m_operation_event{};
};

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/psc/time/time_zone.h"

namespace Service::PSC::Time {
namespace {
constexpr Result ValidateRule(const Tz::Rule& rule) {
    if (rule.typecnt > static_cast<s32>(Tz::TZ_MAX_TYPES) ||
        rule.timecnt > static_cast<s32>(Tz::TZ_MAX_TIMES) ||
        rule.charcnt > static_cast<s32>(Tz::TZ_MAX_CHARS)) {
        R_RETURN(ResultTimeZoneOutOfRange);
    }

    for (s32 i = 0; i < rule.timecnt; i++) {
        if (rule.types[i] >= rule.typecnt) {
            R_RETURN(ResultTimeZoneOutOfRange);
        }
    }

    for (s32 i = 0; i < rule.typecnt; i++) {
        if (rule.ttis[i].tt_desigidx >= static_cast<s32>(rule.chars.size())) {
            R_RETURN(ResultTimeZoneOutOfRange);
        }
    }
    R_SUCCEED();
}

constexpr bool GetTimeZoneTime(s64& out_time, const Tz::Rule& rule, s64 time, s32 index,
                               s32 index_offset) {
    s32 found_idx{};
    s32 expected_index{index + index_offset};
    s64 time_to_find{time + rule.ttis[rule.types[index]].tt_utoff -
                     rule.ttis[rule.types[expected_index]].tt_utoff};

    if (rule.timecnt > 1 && rule.ats[0] <= time_to_find) {
        s32 low{1};
        s32 high{rule.timecnt};

        while (low < high) {
            auto mid{(low + high) / 2};
            if (rule.ats[mid] <= time_to_find) {
                low = mid + 1;
            } else if (rule.ats[mid] > time_to_find) {
                high = mid;
            }
        }
        found_idx = low - 1;
    }

    if (found_idx == expected_index) {
        out_time = time_to_find;
    }
    return found_idx == expected_index;
}
} // namespace

void TimeZone::SetTimePoint(const SteadyClockTimePoint& time_point) {
    std::scoped_lock l{m_mutex};
    m_steady_clock_time_point = time_point;
}

void TimeZone::SetTotalLocationNameCount(u32 count) {
    std::scoped_lock l{m_mutex};
    m_total_location_name_count = count;
}

void TimeZone::SetRuleVersion(const RuleVersion& rule_version) {
    std::scoped_lock l{m_mutex};
    m_rule_version = rule_version;
}

Result TimeZone::GetLocationName(LocationName& out_name) {
    std::scoped_lock l{m_mutex};
    R_UNLESS(m_initialized, ResultClockUninitialized);
    out_name = m_location;
    R_SUCCEED();
}

Result TimeZone::GetTotalLocationCount(u32& out_count) {
    std::scoped_lock l{m_mutex};
    if (!m_initialized) {
        return ResultClockUninitialized;
    }

    out_count = m_total_location_name_count;
    R_SUCCEED();
}

Result TimeZone::GetRuleVersion(RuleVersion& out_rule_version) {
    std::scoped_lock l{m_mutex};
    if (!m_initialized) {
        return ResultClockUninitialized;
    }
    out_rule_version = m_rule_version;
    R_SUCCEED();
}

Result TimeZone::GetTimePoint(SteadyClockTimePoint& out_time_point) {
    std::scoped_lock l{m_mutex};
    if (!m_initialized) {
        return ResultClockUninitialized;
    }
    out_time_point = m_steady_clock_time_point;
    R_SUCCEED();
}

Result TimeZone::ToCalendarTime(CalendarTime& out_calendar_time,
                                CalendarAdditionalInfo& out_additional_info, s64 time,
                                const Tz::Rule& rule) {
    std::scoped_lock l{m_mutex};
    R_RETURN(ToCalendarTimeImpl(out_calendar_time, out_additional_info, time, rule));
}

Result TimeZone::ToCalendarTimeWithMyRule(CalendarTime& calendar_time,
                                          CalendarAdditionalInfo& calendar_additional, s64 time) {
    // This is checked outside the mutex. Bug?
    if (!m_initialized) {
        return ResultClockUninitialized;
    }

    std::scoped_lock l{m_mutex};
    R_RETURN(ToCalendarTimeImpl(calendar_time, calendar_additional, time, m_my_rule));
}

Result TimeZone::ParseBinary(const LocationName& name, std::span<const u8> binary) {
    std::scoped_lock l{m_mutex};

    Tz::Rule tmp_rule{};
    R_TRY(ParseBinaryImpl(tmp_rule, binary));

    m_my_rule = tmp_rule;
    m_location = name;

    R_SUCCEED();
}

Result TimeZone::ParseBinaryInto(Tz::Rule& out_rule, std::span<const u8> binary) {
    std::scoped_lock l{m_mutex};
    R_RETURN(ParseBinaryImpl(out_rule, binary));
}

Result TimeZone::ToPosixTime(u32& out_count, std::span<s64> out_times, size_t out_times_max_count,
                             const CalendarTime& calendar, const Tz::Rule& rule) {
    std::scoped_lock l{m_mutex};

    auto res = ToPosixTimeImpl(out_count, out_times, out_times_max_count, calendar, rule, -1);

    if (res != ResultSuccess) {
        if (res == ResultTimeZoneNotFound) {
            res = ResultSuccess;
            out_count = 0;
        }
    } else if (out_count == 2 && out_times[0] > out_times[1]) {
        std::swap(out_times[0], out_times[1]);
    }
    R_RETURN(res);
}

Result TimeZone::ToPosixTimeWithMyRule(u32& out_count, std::span<s64> out_times,
                                       size_t out_times_max_count, const CalendarTime& calendar) {
    std::scoped_lock l{m_mutex};

    auto res = ToPosixTimeImpl(out_count, out_times, out_times_max_count, calendar, m_my_rule, -1);

    if (res != ResultSuccess) {
        if (res == ResultTimeZoneNotFound) {
            res = ResultSuccess;
            out_count = 0;
        }
    } else if (out_count == 2 && out_times[0] > out_times[1]) {
        std::swap(out_times[0], out_times[1]);
    }
    R_RETURN(res);
}

Result TimeZone::ParseBinaryImpl(Tz::Rule& out_rule, std::span<const u8> binary) {
    if (Tz::ParseTimeZoneBinary(out_rule, binary)) {
        R_RETURN(ResultTimeZoneParseFailed);
    }
    R_SUCCEED();
}

Result TimeZone::ToCalendarTimeImpl(CalendarTime& out_calendar_time,
                                    CalendarAdditionalInfo& out_additional_info, s64 time,
                                    const Tz::Rule& rule) {
    R_TRY(ValidateRule(rule));

    Tz::CalendarTimeInternal calendar_internal{};
    time_t time_tmp{static_cast<time_t>(time)};
    if (Tz::localtime_rz(&calendar_internal, &rule, &time_tmp)) {
        R_RETURN(ResultOverflow);
    }

    out_calendar_time.year = static_cast<s16>(calendar_internal.tm_year + 1900);
    out_calendar_time.month = static_cast<s8>(calendar_internal.tm_mon + 1);
    out_calendar_time.day = static_cast<s8>(calendar_internal.tm_mday);
    out_calendar_time.hour = static_cast<s8>(calendar_internal.tm_hour);
    out_calendar_time.minute = static_cast<s8>(calendar_internal.tm_min);
    out_calendar_time.second = static_cast<s8>(calendar_internal.tm_sec);

    out_additional_info.day_of_week = calendar_internal.tm_wday;
    out_additional_info.day_of_year = calendar_internal.tm_yday;

    std::memcpy(out_additional_info.name.data(), calendar_internal.tm_zone.data(),
                out_additional_info.name.size());
    out_additional_info.name[out_additional_info.name.size() - 1] = '\0';

    out_additional_info.is_dst = calendar_internal.tm_isdst;
    out_additional_info.ut_offset = calendar_internal.tm_utoff;

    R_SUCCEED();
}

Result TimeZone::ToPosixTimeImpl(u32& out_count, std::span<s64> out_times,
                                 size_t out_times_max_count, const CalendarTime& calendar,
                                 const Tz::Rule& rule, s32 is_dst) {
    R_TRY(ValidateRule(rule));

    CalendarTime local_calendar{calendar};

    local_calendar.month -= 1;
    local_calendar.year -= 1900;

    Tz::CalendarTimeInternal internal{
        .tm_sec = local_calendar.second,
        .tm_min = local_calendar.minute,
        .tm_hour = local_calendar.hour,
        .tm_mday = local_calendar.day,
        .tm_mon = local_calendar.month,
        .tm_year = local_calendar.year,
        .tm_wday = 0,
        .tm_yday = 0,
        .tm_isdst = is_dst,
        .tm_zone = {},
        .tm_utoff = 0,
        .time_index = 0,
    };
    time_t time_tmp{};
    auto res = Tz::mktime_tzname(&time_tmp, &rule, &internal);
    s64 time = static_cast<s64>(time_tmp);

    if (res == 1) {
        R_RETURN(ResultOverflow);
    } else if (res == 2) {
        R_RETURN(ResultTimeZoneNotFound);
    }

    if (internal.tm_sec != local_calendar.second || internal.tm_min != local_calendar.minute ||
        internal.tm_hour != local_calendar.hour || internal.tm_mday != local_calendar.day ||
        internal.tm_mon != local_calendar.month || internal.tm_year != local_calendar.year) {
        R_RETURN(ResultTimeZoneNotFound);
    }

    if (res != 0) {
        ASSERT(false);
    }

    out_times[0] = time;
    if (out_times_max_count < 2) {
        out_count = 1;
        R_SUCCEED();
    }

    s64 time2{};
    if (internal.time_index > 0 && GetTimeZoneTime(time2, rule, time, internal.time_index, -1)) {
        out_times[1] = time2;
        out_count = 2;
        R_SUCCEED();
    }

    if (((internal.time_index + 1) < rule.timecnt) &&
        GetTimeZoneTime(time2, rule, time, internal.time_index, 1)) {
        out_times[1] = time2;
        out_count = 2;
        R_SUCCEED();
    }

    out_count = 1;
    R_SUCCEED();
}

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <mutex>
#include <span>

#include <tz/tz.h>
#include "core/hle/service/psc/time/common.h"

namespace Service::PSC::Time {

class TimeZone {
public:
    TimeZone() = default;

    bool IsInitialized() const {
        return m_initialized;
    }

    void SetInitialized() {
        m_initialized = true;
    }

    void SetTimePoint(const SteadyClockTimePoint& time_point);
    void SetTotalLocationNameCount(u32 count);
    void SetRuleVersion(const RuleVersion& rule_version);
    Result GetLocationName(LocationName& out_name);
    Result GetTotalLocationCount(u32& out_count);
    Result GetRuleVersion(RuleVersion& out_rule_version);
    Result GetTimePoint(SteadyClockTimePoint& out_time_point);

    Result ToCalendarTime(CalendarTime& out_calendar_time,
                          CalendarAdditionalInfo& out_additional_info, s64 time,
                          const Tz::Rule& rule);
    Result ToCalendarTimeWithMyRule(CalendarTime& calendar_time,
                                    CalendarAdditionalInfo& calendar_additional, s64 time);
    Result ParseBinary(const LocationName& name, std::span<const u8> binary);
    Result ParseBinaryInto(Tz::Rule& out_rule, std::span<const u8> binary);
    Result ToPosixTime(u32& out_count, std::span<s64> out_times, size_t out_times_max_count,
                       const CalendarTime& calendar, const Tz::Rule& rule);
    Result ToPosixTimeWithMyRule(u32& out_count, std::span<s64> out_times,
                                 size_t out_times_max_count, const CalendarTime& calendar);

private:
    Result ParseBinaryImpl(Tz::Rule& out_rule, std::span<const u8> binary);
    Result ToCalendarTimeImpl(CalendarTime& out_calendar_time,
                              CalendarAdditionalInfo& out_additional_info, s64 time,
                              const Tz::Rule& rule);
    Result ToPosixTimeImpl(u32& out_count, std::span<s64> out_times, size_t out_times_max_count,
                           const CalendarTime& calendar, const Tz::Rule& rule, s32 is_dst);

    bool m_initialized{};
    std::recursive_mutex m_mutex;
    LocationName m_location{};
    Tz::Rule m_my_rule{};
    SteadyClockTimePoint m_steady_clock_time_point{};
    u32 m_total_location_name_count{};
    RuleVersion m_rule_version{};
};

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <tz/tz.h>

#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/psc/time/time_zone_service.h"

namespace Service::PSC::Time {

TimeZoneService::TimeZoneService(Core::System& system_, StandardSteadyClockCore& clock_core,
                                 TimeZone& time_zone, bool can_write_timezone_device_location)
    : ServiceFramework{system_, "ITimeZoneService"}, m_system{system}, m_clock_core{clock_core},
      m_time_zone{time_zone}, m_can_write_timezone_device_location{
                                  can_write_timezone_device_location} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0,   D<&TimeZoneService::GetDeviceLocationName>, "GetDeviceLocationName"},
        {1,   D<&TimeZoneService::SetDeviceLocationName>, "SetDeviceLocationName"},
        {2,   D<&TimeZoneService::GetTotalLocationNameCount>, "GetTotalLocationNameCount"},
        {3,   D<&TimeZoneService::LoadLocationNameList>, "LoadLocationNameList"},
        {4,   D<&TimeZoneService::LoadTimeZoneRule>, "LoadTimeZoneRule"},
        {5,   D<&TimeZoneService::GetTimeZoneRuleVersion>, "GetTimeZoneRuleVersion"},
        {6,   D<&TimeZoneService::GetDeviceLocationNameAndUpdatedTime>, "GetDeviceLocationNameAndUpdatedTime"},
        {7,   D<&TimeZoneService::SetDeviceLocationNameWithTimeZoneRule>, "SetDeviceLocationNameWithTimeZoneRule"},
        {8,   D<&TimeZoneService::ParseTimeZoneBinary>, "ParseTimeZoneBinary"},
        {20,  D<&TimeZoneService::GetDeviceLocationNameOperationEventReadableHandle>, "GetDeviceLocationNameOperationEventReadableHandle"},
        {100, D<&TimeZoneService::ToCalendarTime>, "ToCalendarTime"},
        {101, D<&TimeZoneService::ToCalendarTimeWithMyRule>, "ToCalendarTimeWithMyRule"},
        {201, D<&TimeZoneService::ToPosixTime>, "ToPosixTime"},
        {202, D<&TimeZoneService::ToPosixTimeWithMyRule>, "ToPosixTimeWithMyRule"},
    };
    // clang-format on
    RegisterHandlers(functions);
}

Result TimeZoneService::GetDeviceLocationName(Out<LocationName> out_location_name) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_location_name={}", *out_location_name);
    };

    R_RETURN(m_time_zone.GetLocationName(*out_location_name));
}

Result TimeZoneService::SetDeviceLocationName(const LocationName& location_name) {
    LOG_DEBUG(Service_Time, "called. This function is not implemented!");

    R_UNLESS(m_can_write_timezone_device_location, ResultPermissionDenied);
    R_RETURN(ResultNotImplemented);
}

Result TimeZoneService::GetTotalLocationNameCount(Out<u32> out_count) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_count={}", *out_count);
    };

    R_RETURN(m_time_zone.GetTotalLocationCount(*out_count));
}

Result TimeZoneService::LoadLocationNameList(
    Out<u32> out_count, OutArray<LocationName, BufferAttr_HipcMapAlias> out_names, u32 index) {
    LOG_DEBUG(Service_Time, "called. This function is not implemented!");

    R_RETURN(ResultNotImplemented);
}

Result TimeZoneService::LoadTimeZoneRule(OutRule out_rule, const LocationName& location_name) {
    LOG_DEBUG(Service_Time, "called. This function is not implemented!");

    R_RETURN(ResultNotImplemented);
}

Result TimeZoneService::GetTimeZoneRuleVersion(Out<RuleVersion> out_rule_version) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_rule_version={}", *out_rule_version);
    };

    R_RETURN(m_time_zone.GetRuleVersion(*out_rule_version));
}

Result TimeZoneService::GetDeviceLocationNameAndUpdatedTime(
    Out<LocationName> out_location_name, Out<SteadyClockTimePoint> out_time_point) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. out_location_name={} out_time_point={}",
                  *out_location_name, *out_time_point);
    };

    R_TRY(m_time_zone.GetLocationName(*out_location_name));
    R_RETURN(m_time_zone.GetTimePoint(*out_time_point));
}

Result TimeZoneService::SetDeviceLocationNameWithTimeZoneRule(
    const LocationName& location_name, InBuffer<BufferAttr_HipcAutoSelect> binary) {
    LOG_DEBUG(Service_Time, "called. location_name={}", location_name);

    R_UNLESS(m_can_write_timezone_device_location, ResultPermissionDenied);
    R_TRY(m_time_zone.ParseBinary(location_name, binary));

    SteadyClockTimePoint time_point{};
    R_TRY(m_clock_core.GetCurrentTimePoint(time_point));

    m_time_zone.SetTimePoint(time_point);
    R_SUCCEED();
}

Result TimeZoneService::ParseTimeZoneBinary(OutRule out_rule,
                                            InBuffer<BufferAttr_HipcAutoSelect> binary) {
    LOG_DEBUG(Service_Time, "called.");

    R_RETURN(m_time_zone.ParseBinaryInto(*out_rule, binary));
}

Result TimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(
    OutCopyHandle<Kernel::KReadableEvent> out_event) {
    LOG_DEBUG(Service_Time, "called. This function is not implemented!");

    R_RETURN(ResultNotImplemented);
}

Result TimeZoneService::ToCalendarTime(Out<CalendarTime> out_calendar_time,
                                       Out<CalendarAdditionalInfo> out_additional_info, s64 time,
                                       InRule rule) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
                  *out_calendar_time, *out_additional_info);
    };

    R_RETURN(
        m_time_zone.ToCalendarTime(*out_calendar_time, *out_additional_info, time, *rule.Get()));
}

Result TimeZoneService::ToCalendarTimeWithMyRule(Out<CalendarTime> out_calendar_time,
                                                 Out<CalendarAdditionalInfo> out_additional_info,
                                                 s64 time) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time, "called. time={} out_calendar_time={} out_additional_info={}", time,
                  *out_calendar_time, *out_additional_info);
    };

    R_RETURN(m_time_zone.ToCalendarTimeWithMyRule(*out_calendar_time, *out_additional_info, time));
}

Result TimeZoneService::ToPosixTime(Out<u32> out_count,
                                    OutArray<s64, BufferAttr_HipcPointer> out_times,
                                    const CalendarTime& calendar_time, InRule rule) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time,
                  "called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
                  calendar_time, *out_count, out_times[0], out_times[1]);
    };

    R_RETURN(
        m_time_zone.ToPosixTime(*out_count, out_times, out_times.size(), calendar_time, *rule));
}

Result TimeZoneService::ToPosixTimeWithMyRule(Out<u32> out_count,
                                              OutArray<s64, BufferAttr_HipcPointer> out_times,
                                              const CalendarTime& calendar_time) {
    SCOPE_EXIT {
        LOG_DEBUG(Service_Time,
                  "called. calendar_time={} out_count={} out_times[0]={} out_times[1]={} ",
                  calendar_time, *out_count, out_times[0], out_times[1]);
    };

    R_RETURN(
        m_time_zone.ToPosixTimeWithMyRule(*out_count, out_times, out_times.size(), calendar_time));
}

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/psc/time/common.h"
#include "core/hle/service/psc/time/manager.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"

namespace Core {
class System;
}

namespace Tz {
struct Rule;
}

namespace Service::PSC::Time {

class TimeZoneService final : public ServiceFramework<TimeZoneService> {
    using InRule = InLargeData<Tz::Rule, BufferAttr_HipcMapAlias>;
    using OutRule = OutLargeData<Tz::Rule, BufferAttr_HipcMapAlias>;

public:
    explicit TimeZoneService(Core::System& system, StandardSteadyClockCore& clock_core,
                             TimeZone& time_zone, bool can_write_timezone_device_location);

    ~TimeZoneService() override = default;

    Result GetDeviceLocationName(Out<LocationName> out_location_name);
    Result SetDeviceLocationName(const LocationName& location_name);
    Result GetTotalLocationNameCount(Out<u32> out_count);
    Result LoadLocationNameList(Out<u32> out_count,
                                OutArray<LocationName, BufferAttr_HipcMapAlias> out_names,
                                u32 index);
    Result LoadTimeZoneRule(OutRule out_rule, const LocationName& location_name);
    Result GetTimeZoneRuleVersion(Out<RuleVersion> out_rule_version);
    Result GetDeviceLocationNameAndUpdatedTime(Out<LocationName> location_name,
                                               Out<SteadyClockTimePoint> out_time_point);
    Result SetDeviceLocationNameWithTimeZoneRule(const LocationName& location_name,
                                                 InBuffer<BufferAttr_HipcAutoSelect> binary);
    Result ParseTimeZoneBinary(OutRule out_rule, InBuffer<BufferAttr_HipcAutoSelect> binary);
    Result GetDeviceLocationNameOperationEventReadableHandle(
        OutCopyHandle<Kernel::KReadableEvent> out_event);
    Result ToCalendarTime(Out<CalendarTime> out_calendar_time,
                          Out<CalendarAdditionalInfo> out_additional_info, s64 time, InRule rule);
    Result ToCalendarTimeWithMyRule(Out<CalendarTime> out_calendar_time,
                                    Out<CalendarAdditionalInfo> out_additional_info, s64 time);
    Result ToPosixTime(Out<u32> out_count, OutArray<s64, BufferAttr_HipcPointer> out_times,
                       const CalendarTime& calendar_time, InRule rule);
    Result ToPosixTimeWithMyRule(Out<u32> out_count,
                                 OutArray<s64, BufferAttr_HipcPointer> out_times,
                                 const CalendarTime& calendar_time);

private:
    Core::System& m_system;

    StandardSteadyClockCore& m_clock_core;
    TimeZone& m_time_zone;
    bool m_can_write_timezone_device_location;
};

} // namespace Service::PSC::Time


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <memory>

#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/ptm/psm.h"

namespace Service::PTM {

class IPsmSession final : public ServiceFramework<IPsmSession> {
public:
    explicit IPsmSession(Core::System& system_)
        : ServiceFramework{system_, "IPsmSession"}, service_context{system_, "IPsmSession"} {
        // clang-format off
        static const FunctionInfo functions[] = {
            {0, &IPsmSession::BindStateChangeEvent, "BindStateChangeEvent"},
            {1, &IPsmSession::UnbindStateChangeEvent, "UnbindStateChangeEvent"},
            {2, &IPsmSession::SetChargerTypeChangeEventEnabled, "SetChargerTypeChangeEventEnabled"},
            {3, &IPsmSession::SetPowerSupplyChangeEventEnabled, "SetPowerSupplyChangeEventEnabled"},
            {4, &IPsmSession::SetBatteryVoltageStateChangeEventEnabled, "SetBatteryVoltageStateChangeEventEnabled"},
        };
        // clang-format on

        RegisterHandlers(functions);

        state_change_event = service_context.CreateEvent("IPsmSession::state_change_event");
    }

    ~IPsmSession() override {
        service_context.CloseEvent(state_change_event);
    }

    void SignalChargerTypeChanged() {
        if (should_signal && should_signal_charger_type) {
            state_change_event->Signal();
        }
    }

    void SignalPowerSupplyChanged() {
        if (should_signal && should_signal_power_supply) {
            state_change_event->Signal();
        }
    }

    void SignalBatteryVoltageStateChanged() {
        if (should_signal && should_signal_battery_voltage) {
            state_change_event->Signal();
        }
    }

private:
    void BindStateChangeEvent(HLERequestContext& ctx) {
        LOG_DEBUG(Service_PTM, "called");

        should_signal = true;

        IPC::ResponseBuilder rb{ctx, 2, 1};
        rb.Push(ResultSuccess);
        rb.PushCopyObjects(state_change_event->GetReadableEvent());
    }

    void UnbindStateChangeEvent(HLERequestContext& ctx) {
        LOG_DEBUG(Service_PTM, "called");

        should_signal = false;

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
    }

    void SetChargerTypeChangeEventEnabled(HLERequestContext& ctx) {
        IPC::RequestParser rp{ctx};
        const auto state = rp.Pop<bool>();
        LOG_DEBUG(Service_PTM, "called, state={}", state);

        should_signal_charger_type = state;

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
    }

    void SetPowerSupplyChangeEventEnabled(HLERequestContext& ctx) {
        IPC::RequestParser rp{ctx};
        const auto state = rp.Pop<bool>();
        LOG_DEBUG(Service_PTM, "called, state={}", state);

        should_signal_power_supply = state;

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
    }

    void SetBatteryVoltageStateChangeEventEnabled(HLERequestContext& ctx) {
        IPC::RequestParser rp{ctx};
        const auto state = rp.Pop<bool>();
        LOG_DEBUG(Service_PTM, "called, state={}", state);

        should_signal_battery_voltage = state;

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
    }

    KernelHelpers::ServiceContext service_context;

    bool should_signal_charger_type{};
    bool should_signal_power_supply{};
    bool should_signal_battery_voltage{};
    bool should_signal{};
    Kernel::KEvent* state_change_event;
};

PSM::PSM(Core::System& system_) : ServiceFramework{system_, "psm"} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
        {1, &PSM::GetChargerType, "GetChargerType"},
        {2, nullptr, "EnableBatteryCharging"},
        {3, nullptr, "DisableBatteryCharging"},
        {4, nullptr, "IsBatteryChargingEnabled"},
        {5, nullptr, "AcquireControllerPowerSupply"},
        {6, nullptr, "ReleaseControllerPowerSupply"},
        {7, &PSM::OpenSession, "OpenSession"},
        {8, nullptr, "EnableEnoughPowerChargeEmulation"},
        {9, nullptr, "DisableEnoughPowerChargeEmulation"},
        {10, nullptr, "EnableFastBatteryCharging"},
        {11, nullptr, "DisableFastBatteryCharging"},
        {12, nullptr, "GetBatteryVoltageState"},
        {13, nullptr, "GetRawBatteryChargePercentage"},
        {14, nullptr, "IsEnoughPowerSupplied"},
        {15, nullptr, "GetBatteryAgePercentage"},
        {16, nullptr, "GetBatteryChargeInfoEvent"},
        {17, nullptr, "GetBatteryChargeInfoFields"},
        {18, nullptr, "GetBatteryChargeCalibratedEvent"},
    };
    // clang-format on

    RegisterHandlers(functions);
}

PSM::~PSM() = default;

void PSM::GetBatteryChargePercentage(HLERequestContext& ctx) {
    LOG_DEBUG(Service_PTM, "called");

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.Push<u32>(battery_charge_percentage);
}

void PSM::GetChargerType(HLERequestContext& ctx) {
    LOG_DEBUG(Service_PTM, "called");

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(charger_type);
}

void PSM::OpenSession(HLERequestContext& ctx) {
    LOG_DEBUG(Service_PTM, "called");

    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
    rb.Push(ResultSuccess);
    rb.PushIpcInterface<IPsmSession>(system);
}

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/service.h"

namespace Service::PTM {

class PSM final : public ServiceFramework<PSM> {
public:
    explicit PSM(Core::System& system_);
    ~PSM() override;

private:
    enum class ChargerType : u32 {
        Unplugged = 0,
        RegularCharger = 1,
        LowPowerCharger = 2,
        Unknown = 3,
    };

    void GetBatteryChargePercentage(HLERequestContext& ctx);
    void GetChargerType(HLERequestContext& ctx);
    void OpenSession(HLERequestContext& ctx);

    u32 battery_charge_percentage{100};
    ChargerType charger_type{ChargerType::RegularCharger};
};

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <memory>

#include "core/core.h"
#include "core/hle/service/ptm/psm.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/ptm/ts.h"
#include "core/hle/service/server_manager.h"

namespace Service::PTM {

void LoopProcess(Core::System& system) {
    auto server_manager = std::make_unique<ServerManager>(system);

    server_manager->RegisterNamedService("psm", std::make_shared<PSM>(system));
    server_manager->RegisterNamedService("ts", std::make_shared<TS>(system));
    ServerManager::RunServer(std::move(server_manager));
}

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

namespace Core {
class System;
}

namespace Service::PTM {

void LoopProcess(Core::System& system);

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <memory>

#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ptm/ts.h"

namespace Service::PTM {

enum class Location : u8 {
    Internal,
    External,
};

class ISession : public ServiceFramework<ISession> {
public:
    explicit ISession(Core::System& system_) : ServiceFramework{system_, "ISession"} {
        // clang-format off
        static const FunctionInfo functions[] = {
            {0, nullptr, "GetTemperatureRange"},
            {2, nullptr, "SetMeasurementMode"},
            {4, &ISession::GetTemperature, "GetTemperature"},
        };
        // clang-format on

        RegisterHandlers(functions);
    }

private:
    void GetTemperature(HLERequestContext& ctx) {
        constexpr f32 temperature = 35;

        IPC::ResponseBuilder rb{ctx, 3};
        rb.Push(ResultSuccess);
        rb.Push(temperature);
    }
};

TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0, nullptr, "GetTemperatureRange"},
        {1, &TS::GetTemperature, "GetTemperature"},
        {2, nullptr, "SetMeasurementMode"},
        {3, &TS::GetTemperatureMilliC, "GetTemperatureMilliC"},
        {4, &TS::OpenSession, "OpenSession"},
    };
    // clang-format on

    RegisterHandlers(functions);
}

TS::~TS() = default;

void TS::GetTemperature(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto location{rp.PopEnum<Location>()};

    const s32 temperature = location == Location::Internal ? 35 : 20;

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.Push(temperature);
}

void TS::GetTemperatureMilliC(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto location{rp.PopEnum<Location>()};

    const s32 temperature = location == Location::Internal ? 35000 : 20000;

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.Push(temperature);
}

void TS::OpenSession(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    [[maybe_unused]] const u32 device_code = rp.Pop<u32>();

    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
    rb.Push(ResultSuccess);
    rb.PushIpcInterface<ISession>(system);
}

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/common_types.h"
#include "core/hle/service/service.h"

namespace Service::PTM {

class TS final : public ServiceFramework<TS> {
public:
    explicit TS(Core::System& system_);
    ~TS() override;

private:
    void GetTemperature(HLERequestContext& ctx);
    void GetTemperatureMilliC(HLERequestContext& ctx);
    void OpenSession(HLERequestContext& ctx);
};

} // namespace Service::PTM


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <mbedtls/sha256.h>

#include "common/scope_exit.h"
#include "core/hle/kernel/k_process.h"

#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ro/ro.h"
#include "core/hle/service/ro/ro_nro_utils.h"
#include "core/hle/service/ro/ro_results.h"
#include "core/hle/service/ro/ro_types.h"
#include "core/hle/service/server_manager.h"

namespace Service::RO {

namespace {

// Convenience definitions.
constexpr size_t MaxSessions = 0x3;
constexpr size_t MaxNrrInfos = 0x40;
constexpr size_t MaxNroInfos = 0x40;

constexpr u64 InvalidProcessId = 0xffffffffffffffffULL;
constexpr u64 InvalidContextId = 0xffffffffffffffffULL;

// Types.
using Sha256Hash = std::array<u8, 32>;

struct NroInfo {
    u64 base_address;
    u64 nro_heap_address;
    u64 nro_heap_size;
    u64 bss_heap_address;
    u64 bss_heap_size;
    u64 code_size;
    u64 rw_size;
    ModuleId module_id;
};

struct NrrInfo {
    u64 nrr_heap_address;
    u64 nrr_heap_size;

    // Verification.
    std::vector<Sha256Hash> hashes;
};

struct ProcessContext {
    constexpr ProcessContext() = default;

    void Initialize(Kernel::KProcess* process, u64 process_id) {
        ASSERT(!m_in_use);

        m_nro_in_use = {};
        m_nrr_in_use = {};
        m_nro_infos = {};
        m_nrr_infos = {};

        m_process = process;
        m_process_id = process_id;
        m_in_use = true;

        if (m_process) {
            m_process->Open();
        }
    }

    void Finalize() {
        ASSERT(m_in_use);

        if (m_process) {
            m_process->Close();
        }

        m_nro_in_use = {};
        m_nrr_in_use = {};
        m_nro_infos = {};
        m_nrr_infos = {};

        m_process = nullptr;
        m_process_id = InvalidProcessId;
        m_in_use = false;
    }

    Kernel::KProcess* GetProcess() const {
        return m_process;
    }

    u64 GetProcessId() const {
        return m_process_id;
    }

    bool IsFree() const {
        return !m_in_use;
    }

    u64 GetProgramId(Kernel::KProcess* other_process) const {
        // Automatically select a handle, allowing for override.
        if (other_process) {
            return other_process->GetProgramId();
        } else if (m_process) {
            return m_process->GetProgramId();
        } else {
            return 0;
        }
    }

    Result GetNrrInfoByAddress(NrrInfo** out, u64 nrr_heap_address) {
        for (size_t i = 0; i < MaxNrrInfos; i++) {
            if (m_nrr_in_use[i] && m_nrr_infos[i].nrr_heap_address == nrr_heap_address) {
                if (out != nullptr) {
                    *out = std::addressof(m_nrr_infos[i]);
                }
                R_SUCCEED();
            }
        }
        R_THROW(RO::ResultNotRegistered);
    }

    Result GetFreeNrrInfo(NrrInfo** out) {
        for (size_t i = 0; i < MaxNrrInfos; i++) {
            if (!m_nrr_in_use[i]) {
                if (out != nullptr) {
                    *out = std::addressof(m_nrr_infos[i]);
                }
                R_SUCCEED();
            }
        }
        R_THROW(RO::ResultTooManyNrr);
    }

    Result GetNroInfoByAddress(NroInfo** out, u64 nro_address) {
        for (size_t i = 0; i < MaxNroInfos; i++) {
            if (m_nro_in_use[i] && m_nro_infos[i].base_address == nro_address) {
                if (out != nullptr) {
                    *out = std::addressof(m_nro_infos[i]);
                }
                R_SUCCEED();
            }
        }
        R_THROW(RO::ResultNotLoaded);
    }

    Result GetNroInfoByModuleId(NroInfo** out, const ModuleId* module_id) {
        for (size_t i = 0; i < MaxNroInfos; i++) {
            if (m_nro_in_use[i] && std::memcmp(std::addressof(m_nro_infos[i].module_id), module_id,
                                               sizeof(*module_id)) == 0) {
                if (out != nullptr) {
                    *out = std::addressof(m_nro_infos[i]);
                }
                R_SUCCEED();
            }
        }
        R_THROW(RO::ResultNotLoaded);
    }

    Result GetFreeNroInfo(NroInfo** out) {
        for (size_t i = 0; i < MaxNroInfos; i++) {
            if (!m_nro_in_use[i]) {
                if (out != nullptr) {
                    *out = std::addressof(m_nro_infos[i]);
                }
                R_SUCCEED();
            }
        }
        R_THROW(RO::ResultTooManyNro);
    }

    Result ValidateHasNroHash(u64 base_address, const NroHeader* nro_header) const {
        // Calculate hash.
        Sha256Hash hash;
        {
            const u64 size = nro_header->GetSize();

            std::vector<u8> nro_data(size);
            m_process->GetMemory().ReadBlock(base_address, nro_data.data(), size);

            mbedtls_sha256_ret(nro_data.data(), size, hash.data(), 0);
        }

        for (size_t i = 0; i < MaxNrrInfos; i++) {
            // Ensure we only check NRRs that are used.
            if (!m_nrr_in_use[i]) {
                continue;
            }

            // Locate the hash within the hash list.
            const auto hash_it = std::ranges::find(m_nrr_infos[i].hashes, hash);
            if (hash_it == m_nrr_infos[i].hashes.end()) {
                continue;
            }

            // The hash is valid!
            R_SUCCEED();
        }

        R_THROW(RO::ResultNotAuthorized);
    }

    Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size,
                       u64* out_rw_size, u64 base_address, u64 expected_nro_size,
                       u64 expected_bss_size) {
        // Ensure we have a process to work on.
        R_UNLESS(m_process != nullptr, RO::ResultInvalidProcess);

        // Read the NRO header.
        NroHeader header{};
        m_process->GetMemory().ReadBlock(base_address, std::addressof(header), sizeof(header));

        // Validate header.
        R_UNLESS(header.IsMagicValid(), RO::ResultInvalidNro);

        // Read sizes from header.
        const u64 nro_size = header.GetSize();
        const u64 text_ofs = header.GetTextOffset();
        const u64 text_size = header.GetTextSize();
        const u64 ro_ofs = header.GetRoOffset();
        const u64 ro_size = header.GetRoSize();
        const u64 rw_ofs = header.GetRwOffset();
        const u64 rw_size = header.GetRwSize();
        const u64 bss_size = header.GetBssSize();

        // Validate sizes meet expected.
        R_UNLESS(nro_size == expected_nro_size, RO::ResultInvalidNro);
        R_UNLESS(bss_size == expected_bss_size, RO::ResultInvalidNro);

        // Validate all sizes are aligned.
        R_UNLESS(Common::IsAligned(text_size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidNro);
        R_UNLESS(Common::IsAligned(ro_size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidNro);
        R_UNLESS(Common::IsAligned(rw_size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidNro);
        R_UNLESS(Common::IsAligned(bss_size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidNro);

        // Validate sections are in order.
        R_UNLESS(text_ofs <= ro_ofs, RO::ResultInvalidNro);
        R_UNLESS(ro_ofs <= rw_ofs, RO::ResultInvalidNro);

        // Validate sections are sequential and contiguous.
        R_UNLESS(text_ofs == 0, RO::ResultInvalidNro);
        R_UNLESS(text_ofs + text_size == ro_ofs, RO::ResultInvalidNro);
        R_UNLESS(ro_ofs + ro_size == rw_ofs, RO::ResultInvalidNro);
        R_UNLESS(rw_ofs + rw_size == nro_size, RO::ResultInvalidNro);

        // Verify NRO hash.
        R_TRY(this->ValidateHasNroHash(base_address, std::addressof(header)));

        // Check if NRO has already been loaded.
        const ModuleId* module_id = header.GetModuleId();
        R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), RO::ResultAlreadyLoaded);

        // Apply patches to NRO.
        // LocateAndApplyIpsPatchesToModule(module_id, static_cast<u8*>(mapped_memory), nro_size);

        // Copy to output.
        *out_module_id = *module_id;
        *out_rx_size = text_size;
        *out_ro_size = ro_size;
        *out_rw_size = rw_size;
        R_SUCCEED();
    }

    void SetNrrInfoInUse(const NrrInfo* info, bool in_use) {
        ASSERT(std::addressof(m_nrr_infos[0]) <= info &&
               info <= std::addressof(m_nrr_infos[MaxNrrInfos - 1]));
        const size_t index = info - std::addressof(m_nrr_infos[0]);
        m_nrr_in_use[index] = in_use;
    }

    void SetNroInfoInUse(const NroInfo* info, bool in_use) {
        ASSERT(std::addressof(m_nro_infos[0]) <= info &&
               info <= std::addressof(m_nro_infos[MaxNroInfos - 1]));
        const size_t index = info - std::addressof(m_nro_infos[0]);
        m_nro_in_use[index] = in_use;
    }

private:
    std::array<bool, MaxNroInfos> m_nro_in_use{};
    std::array<bool, MaxNrrInfos> m_nrr_in_use{};
    std::array<NroInfo, MaxNroInfos> m_nro_infos{};
    std::array<NrrInfo, MaxNrrInfos> m_nrr_infos{};
    Kernel::KProcess* m_process{};
    u64 m_process_id{InvalidProcessId};
    bool m_in_use{};
};

Result ValidateAddressAndNonZeroSize(u64 address, u64 size) {
    R_UNLESS(Common::IsAligned(address, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidAddress);
    R_UNLESS(size != 0, RO::ResultInvalidSize);
    R_UNLESS(Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidSize);
    R_UNLESS(address < address + size, RO::ResultInvalidSize);
    R_SUCCEED();
}

Result ValidateAddressAndSize(u64 address, u64 size) {
    R_UNLESS(Common::IsAligned(address, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidAddress);
    R_UNLESS(Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE), RO::ResultInvalidSize);
    R_UNLESS(size == 0 || address < address + size, RO::ResultInvalidSize);
    R_SUCCEED();
}

class RoContext {
public:
    explicit RoContext() = default;

    Result RegisterProcess(size_t* out_context_id, Kernel::KProcess* process, u64 process_id) {
        // Validate process id.
        R_UNLESS(process->GetProcessId() == process_id, RO::ResultInvalidProcess);

        // Check if a process context already exists.
        R_UNLESS(this->GetContextByProcessId(process_id) == nullptr, RO::ResultInvalidSession);

        // Allocate a context to manage the process handle.
        *out_context_id = this->AllocateContext(process, process_id);

        R_SUCCEED();
    }

    Result ValidateProcess(size_t context_id, u64 process_id) {
        const ProcessContext* ctx = this->GetContextById(context_id);
        R_UNLESS(ctx != nullptr, RO::ResultInvalidProcess);
        R_UNLESS(ctx->GetProcessId() == process_id, RO::ResultInvalidProcess);
        R_SUCCEED();
    }

    void UnregisterProcess(size_t context_id) {
        this->FreeContext(context_id);
    }

    Result RegisterModuleInfo(size_t context_id, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind,
                              bool enforce_nrr_kind) {
        // Get context.
        ProcessContext* context = this->GetContextById(context_id);
        ASSERT(context != nullptr);

        // Validate address/size.
        R_TRY(ValidateAddressAndNonZeroSize(nrr_address, nrr_size));

        // Check we have space for a new NRR.
        NrrInfo* nrr_info = nullptr;
        R_TRY(context->GetFreeNrrInfo(std::addressof(nrr_info)));

        // Ensure we have a valid process to read from.
        Kernel::KProcess* process = context->GetProcess();
        R_UNLESS(process != nullptr, RO::ResultInvalidProcess);

        // Read NRR.
        NrrHeader header{};
        process->GetMemory().ReadBlock(nrr_address, std::addressof(header), sizeof(header));

        // Set NRR info.
        context->SetNrrInfoInUse(nrr_info, true);
        nrr_info->nrr_heap_address = nrr_address;
        nrr_info->nrr_heap_size = nrr_size;

        // Read NRR hash list.
        nrr_info->hashes.resize(header.GetNumHashes());
        process->GetMemory().ReadBlock(nrr_address + header.GetHashesOffset(),
                                       nrr_info->hashes.data(),
                                       sizeof(Sha256Hash) * header.GetNumHashes());

        R_SUCCEED();
    }

    Result UnregisterModuleInfo(size_t context_id, u64 nrr_address) {
        // Get context.
        ProcessContext* context = this->GetContextById(context_id);
        ASSERT(context != nullptr);

        // Validate address.
        R_UNLESS(Common::IsAligned(nrr_address, Core::Memory::YUZU_PAGESIZE),
                 RO::ResultInvalidAddress);

        // Check the NRR is loaded.
        NrrInfo* nrr_info = nullptr;
        R_TRY(context->GetNrrInfoByAddress(std::addressof(nrr_info), nrr_address));

        // Nintendo does this unconditionally, whether or not the actual unmap succeeds.
        context->SetNrrInfoInUse(nrr_info, false);
        *nrr_info = {};

        R_SUCCEED();
    }

    Result MapManualLoadModuleMemory(u64* out_address, size_t context_id, u64 nro_address,
                                     u64 nro_size, u64 bss_address, u64 bss_size) {
        // Get context.
        ProcessContext* context = this->GetContextById(context_id);
        ASSERT(context != nullptr);

        // Validate address/size.
        R_TRY(ValidateAddressAndNonZeroSize(nro_address, nro_size));
        R_TRY(ValidateAddressAndSize(bss_address, bss_size));

        const u64 total_size = nro_size + bss_size;
        R_UNLESS(total_size >= nro_size, RO::ResultInvalidSize);
        R_UNLESS(total_size >= bss_size, RO::ResultInvalidSize);

        // Check we have space for a new NRO.
        NroInfo* nro_info = nullptr;
        R_TRY(context->GetFreeNroInfo(std::addressof(nro_info)));
        nro_info->nro_heap_address = nro_address;
        nro_info->nro_heap_size = nro_size;
        nro_info->bss_heap_address = bss_address;
        nro_info->bss_heap_size = bss_size;

        // Map the NRO.
        R_TRY(MapNro(std::addressof(nro_info->base_address), context->GetProcess(), nro_address,
                     nro_size, bss_address, bss_size, generate_random));
        ON_RESULT_FAILURE {
            UnmapNro(context->GetProcess(), nro_info->base_address, nro_address, nro_size,
                     bss_address, bss_size);
        };

        // Validate the NRO (parsing region extents).
        u64 rx_size = 0, ro_size = 0, rw_size = 0;
        R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size),
                                   std::addressof(ro_size), std::addressof(rw_size),
                                   nro_info->base_address, nro_size, bss_size));

        // Set NRO perms.
        R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size,
                          rw_size + bss_size));

        context->SetNroInfoInUse(nro_info, true);
        nro_info->code_size = rx_size + ro_size;
        nro_info->rw_size = rw_size;
        *out_address = nro_info->base_address;
        R_SUCCEED();
    }

    Result UnmapManualLoadModuleMemory(size_t context_id, u64 nro_address) {
        // Get context.
        ProcessContext* context = this->GetContextById(context_id);
        ASSERT(context != nullptr);

        // Validate address.
        R_UNLESS(Common::IsAligned(nro_address, Core::Memory::YUZU_PAGESIZE),
                 RO::ResultInvalidAddress);

        // Check the NRO is loaded.
        NroInfo* nro_info = nullptr;
        R_TRY(context->GetNroInfoByAddress(std::addressof(nro_info), nro_address));

        // Unmap.
        const NroInfo nro_backup = *nro_info;
        {
            // Nintendo does this unconditionally, whether or not the actual unmap succeeds.
            context->SetNroInfoInUse(nro_info, false);
            std::memset(nro_info, 0, sizeof(*nro_info));
        }
        R_RETURN(UnmapNro(context->GetProcess(), nro_backup.base_address,
                          nro_backup.nro_heap_address, nro_backup.code_size + nro_backup.rw_size,
                          nro_backup.bss_heap_address, nro_backup.bss_heap_size));
    }

private:
    std::array<ProcessContext, MaxSessions> process_contexts;
    std::mt19937_64 generate_random;

    // Context Helpers.
    ProcessContext* GetContextById(size_t context_id) {
        if (context_id == InvalidContextId) {
            return nullptr;
        }

        ASSERT(context_id < process_contexts.size());
        return std::addressof(process_contexts[context_id]);
    }

    ProcessContext* GetContextByProcessId(u64 process_id) {
        for (size_t i = 0; i < MaxSessions; i++) {
            if (process_contexts[i].GetProcessId() == process_id) {
                return std::addressof(process_contexts[i]);
            }
        }
        return nullptr;
    }

    size_t AllocateContext(Kernel::KProcess* process, u64 process_id) {
        // Find a free process context.
        for (size_t i = 0; i < MaxSessions; i++) {
            ProcessContext* context = std::addressof(process_contexts[i]);

            if (context->IsFree()) {
                context->Initialize(process, process_id);
                return i;
            }
        }

        // Failure to find a free context is actually an abort condition.
        UNREACHABLE();
    }

    void FreeContext(size_t context_id) {
        if (ProcessContext* context = GetContextById(context_id); context != nullptr) {
            context->Finalize();
        }
    }
};

class RoInterface : public ServiceFramework<RoInterface> {
public:
    explicit RoInterface(Core::System& system_, const char* name_, std::shared_ptr<RoContext> ro,
                         NrrKind nrr_kind)
        : ServiceFramework{system_, name_}, m_ro(ro), m_context_id(InvalidContextId),
          m_nrr_kind(nrr_kind) {

        // clang-format off
        static const FunctionInfo functions[] = {
            {0,  C<&RoInterface::MapManualLoadModuleMemory>, "MapManualLoadModuleMemory"},
            {1,  C<&RoInterface::UnmapManualLoadModuleMemory>, "UnmapManualLoadModuleMemory"},
            {2,  C<&RoInterface::RegisterModuleInfo>, "RegisterModuleInfo"},
            {3,  C<&RoInterface::UnregisterModuleInfo>, "UnregisterModuleInfo"},
            {4,  C<&RoInterface::RegisterProcessHandle>, "RegisterProcessHandle"},
            {10, C<&RoInterface::RegisterProcessModuleInfo>, "RegisterProcessModuleInfo"},
        };
        // clang-format on

        RegisterHandlers(functions);
    }

    ~RoInterface() {
        m_ro->UnregisterProcess(m_context_id);
    }

    Result MapManualLoadModuleMemory(Out<u64> out_load_address, ClientProcessId client_pid,
                                     u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
        R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address,
                                                 nro_size, bss_address, bss_size));
    }

    Result UnmapManualLoadModuleMemory(ClientProcessId client_pid, u64 nro_address) {
        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
        R_RETURN(m_ro->UnmapManualLoadModuleMemory(m_context_id, nro_address));
    }

    Result RegisterModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size) {
        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
        R_RETURN(
            m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, NrrKind::User, true));
    }

    Result UnregisterModuleInfo(ClientProcessId client_pid, u64 nrr_address) {
        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
        R_RETURN(m_ro->UnregisterModuleInfo(m_context_id, nrr_address));
    }

    Result RegisterProcessHandle(ClientProcessId client_pid,
                                 InCopyHandle<Kernel::KProcess> process) {
        // Register the process.
        R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process.Get(), *client_pid));
    }

    Result RegisterProcessModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size,
                                     InCopyHandle<Kernel::KProcess> process) {
        // Validate the process.
        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));

        // Register the module.
        R_RETURN(m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, m_nrr_kind,
                                          m_nrr_kind == NrrKind::JitPlugin));
    }

private:
    std::shared_ptr<RoContext> m_ro{};
    size_t m_context_id{};
    NrrKind m_nrr_kind{};
};

} // namespace

void LoopProcess(Core::System& system) {
    auto server_manager = std::make_unique<ServerManager>(system);

    auto ro = std::make_shared<RoContext>();

    const auto RoInterfaceFactoryForUser = [&, ro] {
        return std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User);
    };

    const auto RoInterfaceFactoryForJitPlugin = [&, ro] {
        return std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin);
    };

    server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser));
    server_manager->RegisterNamedService("ro:1", std::move(RoInterfaceFactoryForJitPlugin));

    ServerManager::RunServer(std::move(server_manager));
}

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

namespace Core {
class System;
}

namespace Service::RO {

void LoopProcess(Core::System& system);

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/kernel/k_process.h"
#include "core/hle/service/ro/ro_nro_utils.h"
#include "core/hle/service/ro/ro_results.h"

namespace Service::RO {

namespace {

struct ProcessMemoryRegion {
    u64 address;
    u64 size;
};

size_t GetTotalProcessMemoryRegionSize(const ProcessMemoryRegion* regions, size_t num_regions) {
    size_t total = 0;

    for (size_t i = 0; i < num_regions; ++i) {
        total += regions[i].size;
    }

    return total;
}

size_t SetupNroProcessMemoryRegions(ProcessMemoryRegion* regions, u64 nro_heap_address,
                                    u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) {
    // Reset region count.
    size_t num_regions = 0;

    // We always want a region for the nro.
    regions[num_regions++] = {nro_heap_address, nro_heap_size};

    // If we have bss, create a region for bss.
    if (bss_heap_size > 0) {
        regions[num_regions++] = {bss_heap_address, bss_heap_size};
    }

    return num_regions;
}

Result SetProcessMemoryPermission(Kernel::KProcess* process, u64 address, u64 size,
                                  Kernel::Svc::MemoryPermission permission) {
    auto& page_table = process->GetPageTable();

    // Set permission.
    R_RETURN(page_table.SetProcessMemoryPermission(address, size, permission));
}

Result UnmapProcessCodeMemory(Kernel::KProcess* process, u64 process_code_address,
                              const ProcessMemoryRegion* regions, size_t num_regions) {
    // Get the total process memory region size.
    const size_t total_size = GetTotalProcessMemoryRegionSize(regions, num_regions);

    auto& page_table = process->GetPageTable();

    // Unmap each region in order.
    size_t cur_offset = total_size;
    for (size_t i = 0; i < num_regions; ++i) {
        // We want to unmap in reverse order.
        const auto& cur_region = regions[num_regions - 1 - i];

        // Subtract to update the current offset.
        cur_offset -= cur_region.size;

        // Unmap.
        R_TRY(page_table.UnmapCodeMemory(process_code_address + cur_offset, cur_region.address,
                                         cur_region.size));
    }

    R_SUCCEED();
}

Result EnsureGuardPages(Kernel::KProcessPageTable& page_table, u64 map_address, u64 map_size) {
    Kernel::KMemoryInfo memory_info;
    Kernel::Svc::PageInfo page_info;

    // Ensure page before mapping is unmapped.
    R_TRY(page_table.QueryInfo(std::addressof(memory_info), std::addressof(page_info),
                               map_address - 1));
    R_UNLESS(memory_info.GetSvcState() == Kernel::Svc::MemoryState::Free,
             Kernel::ResultInvalidState);

    // Ensure page after mapping is unmapped.
    R_TRY(page_table.QueryInfo(std::addressof(memory_info), std::addressof(page_info),
                               map_address + map_size));
    R_UNLESS(memory_info.GetSvcState() == Kernel::Svc::MemoryState::Free,
             Kernel::ResultInvalidState);

    // Successfully verified guard pages.
    R_SUCCEED();
}

Result MapProcessCodeMemory(u64* out, Kernel::KProcess* process, const ProcessMemoryRegion* regions,
                            size_t num_regions, std::mt19937_64& generate_random) {
    auto& page_table = process->GetPageTable();
    const u64 alias_code_start =
        GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize;
    const u64 alias_code_size = page_table.GetAliasCodeRegionSize() / Kernel::PageSize;

    for (size_t trial = 0; trial < 64; trial++) {
        // Generate a new trial address.
        const u64 mapped_address =
            (alias_code_start + (generate_random() % alias_code_size)) * Kernel::PageSize;

        const auto MapRegions = [&] {
            // Map the regions in order.
            u64 mapped_size = 0;
            for (size_t i = 0; i < num_regions; ++i) {
                // If we fail, unmap up to where we've mapped.
                ON_RESULT_FAILURE {
                    R_ASSERT(UnmapProcessCodeMemory(process, mapped_address, regions, i));
                };

                // Map the current region.
                R_TRY(page_table.MapCodeMemory(mapped_address + mapped_size, regions[i].address,
                                               regions[i].size));

                mapped_size += regions[i].size;
            }

            // If we fail, unmap all mapped regions.
            ON_RESULT_FAILURE {
                R_ASSERT(UnmapProcessCodeMemory(process, mapped_address, regions, num_regions));
            };

            // Ensure guard pages.
            R_RETURN(EnsureGuardPages(page_table, mapped_address, mapped_size));
        };

        if (R_SUCCEEDED(MapRegions())) {
            // Set the output address.
            *out = mapped_address;
            R_SUCCEED();
        }
    }

    // We failed to map anything.
    R_THROW(RO::ResultOutOfAddressSpace);
}

} // namespace

Result MapNro(u64* out_base_address, Kernel::KProcess* process, u64 nro_heap_address,
              u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size,
              std::mt19937_64& generate_random) {
    // Set up the process memory regions.
    std::array<ProcessMemoryRegion, 2> regions{};
    const size_t num_regions = SetupNroProcessMemoryRegions(
        regions.data(), nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size);

    // Re-map the nro/bss as code memory in the destination process.
    R_RETURN(MapProcessCodeMemory(out_base_address, process, regions.data(), num_regions,
                                  generate_random));
}

Result SetNroPerms(Kernel::KProcess* process, u64 base_address, u64 rx_size, u64 ro_size,
                   u64 rw_size) {
    const u64 rx_offset = 0;
    const u64 ro_offset = rx_offset + rx_size;
    const u64 rw_offset = ro_offset + ro_size;

    R_TRY(SetProcessMemoryPermission(process, base_address + rx_offset, rx_size,
                                     Kernel::Svc::MemoryPermission::ReadExecute));
    R_TRY(SetProcessMemoryPermission(process, base_address + ro_offset, ro_size,
                                     Kernel::Svc::MemoryPermission::Read));
    R_TRY(SetProcessMemoryPermission(process, base_address + rw_offset, rw_size,
                                     Kernel::Svc::MemoryPermission::ReadWrite));

    R_SUCCEED();
}

Result UnmapNro(Kernel::KProcess* process, u64 base_address, u64 nro_heap_address,
                u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) {
    // Set up the process memory regions.
    std::array<ProcessMemoryRegion, 2> regions{};
    const size_t num_regions = SetupNroProcessMemoryRegions(
        regions.data(), nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size);

    // Unmap the nro/bss.
    R_RETURN(UnmapProcessCodeMemory(process, base_address, regions.data(), num_regions));
}

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <random>

#include "common/common_types.h"

namespace Kernel {
class KProcess;
}

union Result;

namespace Service::RO {

Result MapNro(u64* out_base_address, Kernel::KProcess* process, u64 nro_heap_address,
              u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size,
              std::mt19937_64& generate_random);
Result SetNroPerms(Kernel::KProcess* process, u64 base_address, u64 rx_size, u64 ro_size,
                   u64 rw_size);
Result UnmapNro(Kernel::KProcess* process, u64 base_address, u64 nro_heap_address,
                u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size);

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/result.h"

namespace Service::RO {

constexpr Result ResultOutOfAddressSpace{ErrorModule::RO, 2};
constexpr Result ResultAlreadyLoaded{ErrorModule::RO, 3};
constexpr Result ResultInvalidNro{ErrorModule::RO, 4};
constexpr Result ResultInvalidNrr{ErrorModule::RO, 6};
constexpr Result ResultTooManyNro{ErrorModule::RO, 7};
constexpr Result ResultTooManyNrr{ErrorModule::RO, 8};
constexpr Result ResultNotAuthorized{ErrorModule::RO, 9};
constexpr Result ResultInvalidNrrKind{ErrorModule::RO, 10};
constexpr Result ResultInternalError{ErrorModule::RO, 1023};
constexpr Result ResultInvalidAddress{ErrorModule::RO, 1025};
constexpr Result ResultInvalidSize{ErrorModule::RO, 1026};
constexpr Result ResultNotLoaded{ErrorModule::RO, 1028};
constexpr Result ResultNotRegistered{ErrorModule::RO, 1029};
constexpr Result ResultInvalidSession{ErrorModule::RO, 1030};
constexpr Result ResultInvalidProcess{ErrorModule::RO, 1031};

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"

namespace Service::RO {

enum class NrrKind : u8 {
    User = 0,
    JitPlugin = 1,
    Count,
};

static constexpr size_t ModuleIdSize = 0x20;
struct ModuleId {
    std::array<u8, ModuleIdSize> data;
};
static_assert(sizeof(ModuleId) == ModuleIdSize);

struct NrrCertification {
    static constexpr size_t RsaKeySize = 0x100;
    static constexpr size_t SignedSize = 0x120;

    u64 program_id_mask;
    u64 program_id_pattern;
    std::array<u8, 0x10> reserved_10;
    std::array<u8, RsaKeySize> modulus;
    std::array<u8, RsaKeySize> signature;
};
static_assert(sizeof(NrrCertification) ==
              NrrCertification::RsaKeySize + NrrCertification::SignedSize);

class NrrHeader {
public:
    static constexpr u32 Magic = Common::MakeMagic('N', 'R', 'R', '0');

public:
    bool IsMagicValid() const {
        return m_magic == Magic;
    }

    bool IsProgramIdValid() const {
        return (m_program_id & m_certification.program_id_mask) ==
               m_certification.program_id_pattern;
    }

    NrrKind GetNrrKind() const {
        const NrrKind kind = static_cast<NrrKind>(m_nrr_kind);
        ASSERT(kind < NrrKind::Count);
        return kind;
    }

    u64 GetProgramId() const {
        return m_program_id;
    }

    u32 GetSize() const {
        return m_size;
    }

    u32 GetNumHashes() const {
        return m_num_hashes;
    }

    size_t GetHashesOffset() const {
        return m_hashes_offset;
    }

    u32 GetKeyGeneration() const {
        return m_key_generation;
    }

    const u8* GetCertificationSignature() const {
        return m_certification.signature.data();
    }

    const u8* GetCertificationSignedArea() const {
        return reinterpret_cast<const u8*>(std::addressof(m_certification));
    }

    const u8* GetCertificationModulus() const {
        return m_certification.modulus.data();
    }

    const u8* GetSignature() const {
        return m_signature.data();
    }

    size_t GetSignedAreaSize() const {
        return m_size - GetSignedAreaOffset();
    }

    static constexpr size_t GetSignedAreaOffset() {
        return offsetof(NrrHeader, m_program_id);
    }

private:
    u32 m_magic;
    u32 m_key_generation;
    INSERT_PADDING_BYTES_NOINIT(8);
    NrrCertification m_certification;
    std::array<u8, 0x100> m_signature;
    u64 m_program_id;
    u32 m_size;
    u8 m_nrr_kind; // 7.0.0+
    INSERT_PADDING_BYTES_NOINIT(3);
    u32 m_hashes_offset;
    u32 m_num_hashes;
    INSERT_PADDING_BYTES_NOINIT(8);
};
static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader has wrong size");

class NroHeader {
public:
    static constexpr u32 Magic = Common::MakeMagic('N', 'R', 'O', '0');

public:
    bool IsMagicValid() const {
        return m_magic == Magic;
    }

    u32 GetSize() const {
        return m_size;
    }

    u32 GetTextOffset() const {
        return m_text_offset;
    }

    u32 GetTextSize() const {
        return m_text_size;
    }

    u32 GetRoOffset() const {
        return m_ro_offset;
    }

    u32 GetRoSize() const {
        return m_ro_size;
    }

    u32 GetRwOffset() const {
        return m_rw_offset;
    }

    u32 GetRwSize() const {
        return m_rw_size;
    }

    u32 GetBssSize() const {
        return m_bss_size;
    }

    const ModuleId* GetModuleId() const {
        return std::addressof(m_module_id);
    }

private:
    u32 m_entrypoint_insn;
    u32 m_mod_offset;
    INSERT_PADDING_BYTES_NOINIT(0x8);
    u32 m_magic;
    INSERT_PADDING_BYTES_NOINIT(0x4);
    u32 m_size;
    INSERT_PADDING_BYTES_NOINIT(0x4);
    u32 m_text_offset;
    u32 m_text_size;
    u32 m_ro_offset;
    u32 m_ro_size;
    u32 m_rw_offset;
    u32 m_rw_size;
    u32 m_bss_size;
    INSERT_PADDING_BYTES_NOINIT(0x4);
    ModuleId m_module_id;
    INSERT_PADDING_BYTES_NOINIT(0x20);
};
static_assert(sizeof(NroHeader) == 0x80, "NroHeader has wrong size");

} // namespace Service::RO


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/scope_exit.h"

#include "core/core.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_object_name.h"
#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/sm/sm.h"

namespace Service {

enum class UserDataTag {
    Port,
    Session,
    DeferEvent,
};

class Port : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Port> {
public:
    explicit Port(Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory)
        : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) {
        this->SetUserData(static_cast<uintptr_t>(UserDataTag::Port));
    }

    ~Port() {
        this->GetNativeHandle()->Close();
    }

    SessionRequestHandlerPtr CreateHandler() {
        return m_handler_factory();
    }

private:
    const SessionRequestHandlerFactory m_handler_factory;
};

class Session : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Session> {
public:
    explicit Session(Kernel::KServerSession* server_session,
                     std::shared_ptr<SessionRequestManager>&& manager)
        : MultiWaitHolder(server_session), m_manager(std::move(manager)) {
        this->SetUserData(static_cast<uintptr_t>(UserDataTag::Session));
    }

    ~Session() {
        this->GetNativeHandle()->Close();
    }

    std::shared_ptr<SessionRequestManager>& GetManager() {
        return m_manager;
    }

    std::shared_ptr<HLERequestContext>& GetContext() {
        return m_context;
    }

private:
    std::shared_ptr<SessionRequestManager> m_manager;
    std::shared_ptr<HLERequestContext> m_context;
};

ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selection_mutex{system} {
    // Initialize event.
    m_wakeup_event = Kernel::KEvent::Create(system.Kernel());
    m_wakeup_event->Initialize(nullptr);

    // Register event.
    Kernel::KEvent::Register(system.Kernel(), m_wakeup_event);

    // Link to holder.
    m_wakeup_holder.emplace(std::addressof(m_wakeup_event->GetReadableEvent()));
    m_wakeup_holder->LinkToMultiWait(std::addressof(m_deferred_list));
}

ServerManager::~ServerManager() {
    // Signal stop.
    m_stop_source.request_stop();
    m_wakeup_event->Signal();

    // Wait for processing to stop.
    m_stopped.Wait();
    m_threads.clear();

    // Clean up ports.
    auto port_it = m_servers.begin();
    while (port_it != m_servers.end()) {
        auto* const port = std::addressof(*port_it);
        port_it = m_servers.erase(port_it);
        delete port;
    }

    // Clean up sessions.
    auto session_it = m_sessions.begin();
    while (session_it != m_sessions.end()) {
        auto* const session = std::addressof(*session_it);
        session_it = m_sessions.erase(session_it);
        delete session;
    }

    // Close wakeup event.
    m_wakeup_event->GetReadableEvent().Close();
    m_wakeup_event->Close();

    if (m_deferral_event) {
        m_deferral_event->GetReadableEvent().Close();
        // Write event is owned by ServiceManager
    }
}

void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) {
    server_manager->m_system.RunServer(std::move(server_manager));
}

Result ServerManager::RegisterSession(Kernel::KServerSession* server_session,
                                      std::shared_ptr<SessionRequestManager> manager) {
    // We are taking ownership of the server session, so don't open it.
    auto* session = new Session(server_session, std::move(manager));

    // Begin tracking the server session.
    {
        std::scoped_lock ll{m_deferred_list_mutex};
        m_sessions.push_back(*session);
    }

    // Register to wait on the session.
    this->LinkToDeferredList(session);

    R_SUCCEED();
}

Result ServerManager::RegisterNamedService(const std::string& service_name,
                                           SessionRequestHandlerFactory&& handler_factory,
                                           u32 max_sessions) {
    // Add the new server to sm: and get the moved server port.
    Kernel::KServerPort* server_port{};
    R_ASSERT(m_system.ServiceManager().RegisterService(std::addressof(server_port), service_name,
                                                       max_sessions, handler_factory));

    // We are taking ownership of the server port, so don't open it.
    auto* server = new Port(server_port, std::move(handler_factory));

    // Begin tracking the server port.
    {
        std::scoped_lock ll{m_deferred_list_mutex};
        m_servers.push_back(*server);
    }

    // Register to wait on the server port.
    this->LinkToDeferredList(server);

    R_SUCCEED();
}

Result ServerManager::RegisterNamedService(const std::string& service_name,
                                           std::shared_ptr<SessionRequestHandler>&& handler,
                                           u32 max_sessions) {
    // Make the factory.
    const auto HandlerFactory = [handler]() { return handler; };

    // Register the service with the new factory.
    R_RETURN(this->RegisterNamedService(service_name, std::move(HandlerFactory), max_sessions));
}

Result ServerManager::ManageNamedPort(const std::string& service_name,
                                      SessionRequestHandlerFactory&& handler_factory,
                                      u32 max_sessions) {
    // Create a new port.
    auto* port = Kernel::KPort::Create(m_system.Kernel());
    port->Initialize(max_sessions, false, 0);

    // Register the port.
    Kernel::KPort::Register(m_system.Kernel(), port);

    // Ensure that our reference to the port is closed if we fail to register it.
    SCOPE_EXIT {
        port->GetClientPort().Close();
        port->GetServerPort().Close();
    };

    // Register the object name with the kernel.
    R_TRY(Kernel::KObjectName::NewFromName(m_system.Kernel(), std::addressof(port->GetClientPort()),
                                           service_name.c_str()));

    // Open a new reference to the server port.
    port->GetServerPort().Open();

    // Transfer ownership into a new port object.
    auto* server = new Port(std::addressof(port->GetServerPort()), std::move(handler_factory));

    // Begin tracking the port.
    {
        std::scoped_lock ll{m_deferred_list_mutex};
        m_servers.push_back(*server);
    }

    // Register to wait on the port.
    this->LinkToDeferredList(server);

    // We succeeded.
    R_SUCCEED();
}

Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
    // Create a new event.
    m_deferral_event = Kernel::KEvent::Create(m_system.Kernel());
    ASSERT(m_deferral_event != nullptr);

    // Initialize the event.
    m_deferral_event->Initialize(nullptr);

    // Register the event.
    Kernel::KEvent::Register(m_system.Kernel(), m_deferral_event);

    // Set the output.
    *out_event = m_deferral_event;

    // Register to wait on the event.
    m_deferral_holder.emplace(std::addressof(m_deferral_event->GetReadableEvent()));
    m_deferral_holder->SetUserData(static_cast<uintptr_t>(UserDataTag::DeferEvent));
    this->LinkToDeferredList(std::addressof(*m_deferral_holder));

    // We succeeded.
    R_SUCCEED();
}

void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_threads) {
    for (size_t i = 0; i < num_threads; i++) {
        auto thread_name = fmt::format("{}:{}", name, i + 1);
        m_threads.emplace_back(m_system.Kernel().RunOnHostCoreThread(
            std::move(thread_name), [&] { this->LoopProcessImpl(); }));
    }
}

Result ServerManager::LoopProcess() {
    SCOPE_EXIT {
        m_stopped.Set();
    };

    R_RETURN(this->LoopProcessImpl());
}

void ServerManager::LinkToDeferredList(MultiWaitHolder* holder) {
    // Link.
    {
        std::scoped_lock lk{m_deferred_list_mutex};
        holder->LinkToMultiWait(std::addressof(m_deferred_list));
    }

    // Signal the wakeup event.
    m_wakeup_event->Signal();
}

void ServerManager::LinkDeferred() {
    std::scoped_lock lk{m_deferred_list_mutex};
    m_multi_wait.MoveAll(std::addressof(m_deferred_list));
}

MultiWaitHolder* ServerManager::WaitSignaled() {
    // Ensure we are the only thread waiting for this server.
    std::scoped_lock lk{m_selection_mutex};

    while (true) {
        this->LinkDeferred();

        // If we're done, return before we start waiting.
        if (m_stop_source.stop_requested()) {
            return nullptr;
        }

        auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
        if (selected == std::addressof(*m_wakeup_holder)) {
            // Clear and restart if we were woken up.
            m_wakeup_event->Clear();
        } else {
            // Unlink and handle the event.
            selected->UnlinkFromMultiWait();
            return selected;
        }
    }
}

Result ServerManager::Process(MultiWaitHolder* holder) {
    switch (static_cast<UserDataTag>(holder->GetUserData())) {
    case UserDataTag::Session:
        R_RETURN(this->OnSessionEvent(static_cast<Session*>(holder)));
    case UserDataTag::Port:
        R_RETURN(this->OnPortEvent(static_cast<Port*>(holder)));
    case UserDataTag::DeferEvent:
        R_RETURN(this->OnDeferralEvent());
    default:
        UNREACHABLE();
    }
}

bool ServerManager::WaitAndProcessImpl() {
    if (auto* signaled_holder = this->WaitSignaled(); signaled_holder != nullptr) {
        R_ASSERT(this->Process(signaled_holder));
        return true;
    } else {
        return false;
    }
}

Result ServerManager::LoopProcessImpl() {
    while (!m_stop_source.stop_requested()) {
        this->WaitAndProcessImpl();
    }

    R_SUCCEED();
}

Result ServerManager::OnPortEvent(Port* server) {
    // Accept a new server session.
    auto* server_port = static_cast<Kernel::KServerPort*>(server->GetNativeHandle());
    Kernel::KServerSession* server_session = server_port->AcceptSession();
    ASSERT(server_session != nullptr);

    // Create the session manager and install the handler.
    auto manager = std::make_shared<SessionRequestManager>(m_system.Kernel(), *this);
    manager->SetSessionHandler(server->CreateHandler());

    // Create and register the new session.
    this->RegisterSession(server_session, std::move(manager));

    // Resume tracking the port.
    this->LinkToDeferredList(server);

    // We succeeded.
    R_SUCCEED();
}

Result ServerManager::OnSessionEvent(Session* session) {
    Result res = ResultSuccess;

    // Try to receive a message.
    auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
    res = server_session->ReceiveRequestHLE(&session->GetContext(), session->GetManager());

    // If the session has been closed, we're done.
    if (res == Kernel::ResultSessionClosed) {
        this->DestroySession(session);
        R_SUCCEED();
    }

    R_ASSERT(res);

    // Complete the sync request with deferral handling.
    R_RETURN(this->CompleteSyncRequest(session));
}

Result ServerManager::CompleteSyncRequest(Session* session) {
    Result res = ResultSuccess;
    Result service_res = ResultSuccess;

    // Mark the request as not deferred.
    session->GetContext()->SetIsDeferred(false);

    // Complete the request. We have exclusive access to this session.
    auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
    service_res =
        session->GetManager()->CompleteSyncRequest(server_session, *session->GetContext());

    // If we've been deferred, we're done.
    if (session->GetContext()->GetIsDeferred()) {
        // Insert into deferred session list.
        std::scoped_lock ll{m_deferred_list_mutex};
        m_deferred_sessions.push_back(session);

        // Finish.
        R_SUCCEED();
    }

    // Send the reply.
    res = server_session->SendReplyHLE();

    // If the session has been closed, we're done.
    if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
        this->DestroySession(session);
        R_SUCCEED();
    }

    R_ASSERT(res);
    R_ASSERT(service_res);

    // We succeeded, so we can process future messages on this session.
    this->LinkToDeferredList(session);

    R_SUCCEED();
}

Result ServerManager::OnDeferralEvent() {
    // Clear event before grabbing the list.
    m_deferral_event->Clear();

    // Get and clear list.
    const auto deferrals = [&] {
        std::scoped_lock lk{m_deferred_list_mutex};
        return std::move(m_deferred_sessions);
    }();

    // Relink deferral event.
    this->LinkToDeferredList(std::addressof(*m_deferral_holder));

    // For each session, try again to complete the request.
    for (auto* session : deferrals) {
        R_ASSERT(this->CompleteSyncRequest(session));
    }

    R_SUCCEED();
}

void ServerManager::DestroySession(Session* session) {
    // Unlink.
    {
        std::scoped_lock lk{m_deferred_list_mutex};
        m_sessions.erase(m_sessions.iterator_to(*session));
    }

    // Free the session.
    delete session;
}

} // namespace Service


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <list>
#include <mutex>
#include <optional>
#include <vector>

#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/result.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/os/multi_wait.h"
#include "core/hle/service/os/mutex.h"

namespace Core {
class System;
}

namespace Kernel {
class KEvent;
class KServerPort;
class KServerSession;
} // namespace Kernel

namespace Service {

class Port;
class Session;

class ServerManager {
public:
    explicit ServerManager(Core::System& system);
    ~ServerManager();

    Result RegisterSession(Kernel::KServerSession* session,
                           std::shared_ptr<SessionRequestManager> manager);
    Result RegisterNamedService(const std::string& service_name,
                                SessionRequestHandlerFactory&& handler_factory,
                                u32 max_sessions = 64);
    Result RegisterNamedService(const std::string& service_name,
                                std::shared_ptr<SessionRequestHandler>&& handler,
                                u32 max_sessions = 64);
    Result ManageNamedPort(const std::string& service_name,
                           SessionRequestHandlerFactory&& handler_factory, u32 max_sessions = 64);
    Result ManageDeferral(Kernel::KEvent** out_event);

    Result LoopProcess();
    void StartAdditionalHostThreads(const char* name, size_t num_threads);

    static void RunServer(std::unique_ptr<ServerManager>&& server);

private:
    void LinkToDeferredList(MultiWaitHolder* holder);
    void LinkDeferred();
    MultiWaitHolder* WaitSignaled();
    Result Process(MultiWaitHolder* holder);
    bool WaitAndProcessImpl();
    Result LoopProcessImpl();

    Result OnPortEvent(Port* port);
    Result OnSessionEvent(Session* session);
    Result OnDeferralEvent();
    Result CompleteSyncRequest(Session* session);

private:
    void DestroySession(Session* session);

private:
    Core::System& m_system;
    Mutex m_selection_mutex;

    // Events
    Kernel::KEvent* m_wakeup_event{};
    Kernel::KEvent* m_deferral_event{};

    // Deferred wait list
    std::mutex m_deferred_list_mutex{};
    MultiWait m_deferred_list{};

    // Guest state tracking
    MultiWait m_multi_wait{};
    Common::IntrusiveListBaseTraits<Port>::ListType m_servers{};
    Common::IntrusiveListBaseTraits<Session>::ListType m_sessions{};
    std::list<Session*> m_deferred_sessions{};
    std::optional<MultiWaitHolder> m_wakeup_holder{};
    std::optional<MultiWaitHolder> m_deferral_holder{};

    // Host state tracking
    Common::Event m_stopped{};
    std::vector<std::jthread> m_threads{};
    std::stop_source m_stop_source{};
};

} // namespace Service


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <fmt/format.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/reporter.h"

namespace Service {

/**
 * Creates a function string for logging, complete with the name (or header code, depending
 * on what's passed in) the port name, and all the cmd_buff arguments.
 */
[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
                                                       std::string_view port_name,
                                                       const u32* cmd_buff) {
    // Number of params == bits 0-5 + bits 6-11
    int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);

    std::string function_string = fmt::format("function '{}': port={}", name, port_name);
    for (int i = 1; i <= num_params; ++i) {
        function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]);
    }
    return function_string;
}

ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_,
                                           u32 max_sessions_, InvokerFn* handler_invoker_)
    : SessionRequestHandler(system_.Kernel(), service_name_), system{system_},
      service_name{service_name_}, max_sessions{max_sessions_}, handler_invoker{handler_invoker_} {}

ServiceFrameworkBase::~ServiceFrameworkBase() {
    // Wait for other threads to release access before destroying
    const auto guard = LockService();
}

void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) {
    handlers.reserve(handlers.size() + n);
    for (std::size_t i = 0; i < n; ++i) {
        // Usually this array is sorted by id already, so hint to insert at the end
        handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]);
    }
}

void ServiceFrameworkBase::RegisterHandlersBaseTipc(const FunctionInfoBase* functions,
                                                    std::size_t n) {
    handlers_tipc.reserve(handlers_tipc.size() + n);
    for (std::size_t i = 0; i < n; ++i) {
        // Usually this array is sorted by id already, so hint to insert at the end
        handlers_tipc.emplace_hint(handlers_tipc.cend(), functions[i].expected_header,
                                   functions[i]);
    }
}

void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,
                                                       const FunctionInfoBase* info) {
    auto cmd_buf = ctx.CommandBuffer();
    std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name;

    fmt::memory_buffer buf;
    fmt::format_to(std::back_inserter(buf), "function '{}': port='{}' cmd_buf={{[0]=0x{:X}",
                   function_name, service_name, cmd_buf[0]);
    for (int i = 1; i <= 8; ++i) {
        fmt::format_to(std::back_inserter(buf), ", [{}]=0x{:X}", i, cmd_buf[i]);
    }
    buf.push_back('}');

    system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name,
                                                         service_name);
    UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
    if (Settings::values.use_auto_stub) {
        LOG_WARNING(Service, "Using auto stub fallback!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
    }
}

void ServiceFrameworkBase::InvokeRequest(HLERequestContext& ctx) {
    auto itr = handlers.find(ctx.GetCommand());
    const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
    if (info == nullptr || info->handler_callback == nullptr) {
        return ReportUnimplementedFunction(ctx, info);
    }

    LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
    handler_invoker(this, info->handler_callback, ctx);
}

void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) {
    boost::container::flat_map<u32, FunctionInfoBase>::iterator itr;

    itr = handlers_tipc.find(ctx.GetCommand());

    const FunctionInfoBase* info = itr == handlers_tipc.end() ? nullptr : &itr->second;
    if (info == nullptr || info->handler_callback == nullptr) {
        return ReportUnimplementedFunction(ctx, info);
    }

    LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
    handler_invoker(this, info->handler_callback, ctx);
}

Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
                                               HLERequestContext& ctx) {
    const auto guard = LockService();

    Result result = ResultSuccess;

    switch (ctx.GetCommandType()) {
    case IPC::CommandType::Close:
    case IPC::CommandType::TIPC_Close: {
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ResultSuccess);
        result = IPC::ResultSessionClosed;
        break;
    }
    case IPC::CommandType::ControlWithContext:
    case IPC::CommandType::Control: {
        system.ServiceManager().InvokeControlRequest(ctx);
        break;
    }
    case IPC::CommandType::RequestWithContext:
    case IPC::CommandType::Request: {
        InvokeRequest(ctx);
        break;
    }
    default:
        if (ctx.IsTipc()) {
            InvokeRequestTipc(ctx);
            break;
        }

        UNIMPLEMENTED_MSG("command_type={}", ctx.GetCommandType());
        break;
    }

    // If emulation was shutdown, we are closing service threads, do not write the response back to
    // memory that may be shutting down as well.
    if (system.IsPoweredOn()) {
        ctx.WriteToOutgoingCommandBuffer();
    }

    return result;
}

} // namespace Service