pctl: Rework how pctl works to be more accurate

Introduces the usage of compatibilities to allow it the module to be closer to how it works on hardware.
This commit is contained in:
Chloe Marcec 2021-03-27 02:03:18 +11:00
parent 65774084fd
commit e9a1f29e93
6 changed files with 254 additions and 32 deletions

View file

@ -100,6 +100,14 @@ u64 NACP::GetDeviceSaveDataSize() const {
return raw.device_save_data_size; return raw.device_save_data_size;
} }
u32 NACP::GetParentalControlFlag() const {
return raw.parental_control;
}
const std::array<u8, 0x20>& NACP::GetRatingAge() const {
return raw.rating_age;
}
std::vector<u8> NACP::GetRawBytes() const { std::vector<u8> NACP::GetRawBytes() const {
std::vector<u8> out(sizeof(RawNACP)); std::vector<u8> out(sizeof(RawNACP));
std::memcpy(out.data(), &raw, sizeof(RawNACP)); std::memcpy(out.data(), &raw, sizeof(RawNACP));

View file

@ -114,6 +114,8 @@ public:
std::vector<u8> GetRawBytes() const; std::vector<u8> GetRawBytes() const;
bool GetUserAccountSwitchLock() const; bool GetUserAccountSwitchLock() const;
u64 GetDeviceSaveDataSize() const; u64 GetDeviceSaveDataSize() const;
u32 GetParentalControlFlag() const;
const std::array<u8, 0x20>& GetRatingAge() const;
private: private:
RawNACP raw{}; RawNACP raw{};

View file

@ -3,16 +3,30 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/pctl/module.h" #include "core/hle/service/pctl/module.h"
#include "core/hle/service/pctl/pctl.h" #include "core/hle/service/pctl/pctl.h"
namespace Service::PCTL { namespace Service::PCTL {
namespace Error {
constexpr ResultCode ResultNoFreeCommunication{ErrorModule::PCTL, 101};
constexpr ResultCode ResultStereoVisionRestricted{ErrorModule::PCTL, 104};
constexpr ResultCode ResultNoCapatability{ErrorModule::PCTL, 131};
constexpr ResultCode ResultNoRestrictionEnabled{ErrorModule::PCTL, 181};
} // namespace Error
class IParentalControlService final : public ServiceFramework<IParentalControlService> { class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public: public:
explicit IParentalControlService(Core::System& system_) explicit IParentalControlService(Core::System& system_, Capability capability)
: ServiceFramework{system_, "IParentalControlService"} { : ServiceFramework{system_, "IParentalControlService"}, system(system_),
capability(capability) {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"}, {1, &IParentalControlService::Initialize, "Initialize"},
@ -28,13 +42,13 @@ public:
{1010, nullptr, "IsRestrictedSystemSettingsEntered"}, {1010, nullptr, "IsRestrictedSystemSettingsEntered"},
{1011, nullptr, "RevertRestrictedSystemSettingsEntered"}, {1011, nullptr, "RevertRestrictedSystemSettingsEntered"},
{1012, nullptr, "GetRestrictedFeatures"}, {1012, nullptr, "GetRestrictedFeatures"},
{1013, nullptr, "ConfirmStereoVisionPermission"}, {1013, &IParentalControlService::ConfirmStereoVisionPermission, "ConfirmStereoVisionPermission"},
{1014, nullptr, "ConfirmPlayableApplicationVideoOld"}, {1014, nullptr, "ConfirmPlayableApplicationVideoOld"},
{1015, nullptr, "ConfirmPlayableApplicationVideo"}, {1015, nullptr, "ConfirmPlayableApplicationVideo"},
{1016, nullptr, "ConfirmShowNewsPermission"}, {1016, nullptr, "ConfirmShowNewsPermission"},
{1017, nullptr, "EndFreeCommunication"}, {1017, nullptr, "EndFreeCommunication"},
{1018, nullptr, "IsFreeCommunicationAvailable"}, {1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"},
{1031, nullptr, "IsRestrictionEnabled"}, {1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"},
{1032, nullptr, "GetSafetyLevel"}, {1032, nullptr, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"},
{1034, nullptr, "GetSafetyLevelSettings"}, {1034, nullptr, "GetSafetyLevelSettings"},
@ -119,62 +133,234 @@ public:
} }
private: private:
void Initialize(Kernel::HLERequestContext& ctx) { bool CheckFreeCommunicationPermissionImpl() {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); if (states.temporary_unlocked) {
return true;
}
if ((states.application_info.parental_control_flag & 1) == 0) {
return true;
}
if (pin_code[0] == '\0') {
return true;
}
if (!settings.is_free_communication_default_on) {
return true;
}
// TODO(ogniK): Check for blacklisted/exempted applications
return true;
}
bool ConfirmStereoVisionPermissionImpl() {
if (states.temporary_unlocked) {
return true;
}
if (pin_code[0] == '\0') {
return true;
}
if (!settings.is_stero_vision_restricted) {
return false;
}
return true;
}
void SetStereoVisionRestrictionImpl(bool is_restricted) {
if (settings.disabled) {
return;
}
if (pin_code[0] == '\0') {
return;
}
settings.is_stero_vision_restricted = is_restricted;
}
void Initialize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 2};
if (False(capability & (Capability::Application | Capability::System))) {
LOG_ERROR(Service_PCTL, "Invalid capability! capability={:X}",
static_cast<s32>(capability));
return;
}
// TODO(ogniK): Recovery
const auto tid = system.CurrentProcess()->GetTitleID();
if (tid != 0) {
const FileSys::PatchManager pm{tid, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
if (control.first) {
states.tid_from_event = 0;
states.launch_time_valid = false;
states.is_suspended = false;
states.free_communication = false;
states.stereo_vision = false;
states.application_info = ApplicationInfo{
.tid = tid,
.age_rating = control.first->GetRatingAge(),
.parental_control_flag = control.first->GetParentalControlFlag(),
.capability = capability,
};
if (False(capability & (Capability::System | Capability::Recovery))) {
// TODO(ogniK): Signal application launch event
}
}
}
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 2};
if (!CheckFreeCommunicationPermissionImpl()) {
rb.Push(Error::ResultNoFreeCommunication);
} else {
rb.Push(RESULT_SUCCESS);
}
states.free_communication = true;
}
void ConfirmStereoVisionPermission(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
states.stereo_vision = true;
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) { void IsFreeCommunicationAvailable(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); LOG_WARNING(Service_PCTL, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
if (!CheckFreeCommunicationPermissionImpl()) {
rb.Push(Error::ResultNoFreeCommunication);
} else {
rb.Push(RESULT_SUCCESS);
}
}
void IsRestrictionEnabled(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 3};
if (False(capability & (Capability::Status | Capability::Recovery))) {
LOG_ERROR(Service_PCTL, "Application does not have Status or Recovery capabilities!");
rb.Push(Error::ResultNoCapatability);
rb.Push(false);
return;
}
rb.Push(pin_code[0] != '\0');
}
void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 2};
if (False(capability & Capability::SteroVision)) {
LOG_ERROR(Service_PCTL, "Application does not have SteroVision capability!");
rb.Push(Error::ResultNoCapatability);
return;
}
if (pin_code[0] == '\0') {
rb.Push(Error::ResultNoRestrictionEnabled);
return;
}
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) { void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
if (!ConfirmStereoVisionPermissionImpl()) {
rb.Push(Error::ResultStereoVisionRestricted);
rb.Push(false);
} else {
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push(true); rb.Push(true);
} }
}
void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto can_use = rp.Pop<bool>(); const auto can_use = rp.Pop<bool>();
LOG_WARNING(Service_PCTL, "(STUBBED) called, can_use={}", can_use); LOG_DEBUG(Service_PCTL, "called, can_use={}", can_use);
can_use_stereo_vision = can_use;
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
if (False(capability & Capability::SteroVision)) {
LOG_ERROR(Service_PCTL, "Application does not have SteroVision capability!");
rb.Push(Error::ResultNoCapatability);
return;
}
SetStereoVisionRestrictionImpl(can_use);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); LOG_DEBUG(Service_PCTL, "called");
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
if (False(capability & Capability::SteroVision)) {
LOG_ERROR(Service_PCTL, "Application does not have SteroVision capability!");
rb.Push(Error::ResultNoCapatability);
rb.Push(false);
return;
}
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push(can_use_stereo_vision); rb.Push(settings.is_stero_vision_restricted);
} }
void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) { void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called"); LOG_DEBUG(Service_PCTL, "called");
states.stereo_vision = false;
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
struct ApplicationInfo {
u64 tid{};
std::array<u8, 32> age_rating{};
u32 parental_control_flag{};
Capability capability{};
};
struct States {
u64 current_tid{};
ApplicationInfo application_info{};
u64 tid_from_event{};
bool launch_time_valid{};
bool is_suspended{};
bool temporary_unlocked{};
bool free_communication{};
bool stereo_vision{};
};
struct ParentalControlSettings {
bool is_stero_vision_restricted{};
bool is_free_communication_default_on{};
bool disabled{};
};
States states{};
ParentalControlSettings settings{};
std::array<char, 8> pin_code{};
bool can_use_stereo_vision = true; bool can_use_stereo_vision = true;
Core::System& system;
Capability capability{};
}; };
void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {
@ -182,7 +368,9 @@ void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IParentalControlService>(system); // TODO(ogniK): Get TID from process
rb.PushIpcInterface<IParentalControlService>(system, capability);
} }
void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) {
@ -190,21 +378,28 @@ void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IParentalControlService>(system); rb.PushIpcInterface<IParentalControlService>(system, capability);
} }
Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
const char* name) const char* name, Capability capability)
: ServiceFramework{system_, name}, module{std::move(module_)} {} : ServiceFramework{system_, name}, module{std::move(module_)}, capability(capability) {}
Module::Interface::~Interface() = default; Module::Interface::~Interface() = default;
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>(); auto module = std::make_shared<Module>();
std::make_shared<PCTL>(system, module, "pctl")->InstallAsService(service_manager); std::make_shared<PCTL>(system, module, "pctl",
std::make_shared<PCTL>(system, module, "pctl:a")->InstallAsService(service_manager); Capability::Application | Capability::SnsPost | Capability::Status |
std::make_shared<PCTL>(system, module, "pctl:r")->InstallAsService(service_manager); Capability::SteroVision)
std::make_shared<PCTL>(system, module, "pctl:s")->InstallAsService(service_manager); ->InstallAsService(service_manager);
// TODO(ogniK): Implement remaining capabilities
std::make_shared<PCTL>(system, module, "pctl:a", Capability::None)
->InstallAsService(service_manager);
std::make_shared<PCTL>(system, module, "pctl:r", Capability::None)
->InstallAsService(service_manager);
std::make_shared<PCTL>(system, module, "pctl:s", Capability::None)
->InstallAsService(service_manager);
} }
} // namespace Service::PCTL } // namespace Service::PCTL

View file

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "common/common_funcs.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
namespace Core { namespace Core {
@ -12,12 +13,23 @@ class System;
namespace Service::PCTL { namespace Service::PCTL {
enum class Capability : s32 {
None = 0x0,
Application = 1 << 0,
SnsPost = 1 << 1,
Recovery = 1 << 6,
Status = 1 << 8,
SteroVision = 1 << 9,
System = 1 << 15,
};
DECLARE_ENUM_FLAG_OPERATORS(Capability);
class Module final { class Module final {
public: public:
class Interface : public ServiceFramework<Interface> { class Interface : public ServiceFramework<Interface> {
public: public:
explicit Interface(Core::System& system_, std::shared_ptr<Module> module_, explicit Interface(Core::System& system_, std::shared_ptr<Module> module_, const char* name,
const char* name); Capability capability);
~Interface() override; ~Interface() override;
void CreateService(Kernel::HLERequestContext& ctx); void CreateService(Kernel::HLERequestContext& ctx);
@ -25,6 +37,9 @@ public:
protected: protected:
std::shared_ptr<Module> module; std::shared_ptr<Module> module;
private:
Capability capability{};
}; };
}; };

View file

@ -6,8 +6,9 @@
namespace Service::PCTL { namespace Service::PCTL {
PCTL::PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name) PCTL::PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name,
: Interface{system_, std::move(module_), name} { Capability capability)
: Interface{system_, std::move(module_), name, capability} {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &PCTL::CreateService, "CreateService"}, {0, &PCTL::CreateService, "CreateService"},
{1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"}, {1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"},

View file

@ -14,7 +14,8 @@ namespace Service::PCTL {
class PCTL final : public Module::Interface { class PCTL final : public Module::Interface {
public: public:
explicit PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name); explicit PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name,
Capability capability);
~PCTL() override; ~PCTL() override;
}; };