mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-30 10:42:47 +01:00
Merge pull request #2548 from DarkLordZach/applet-shopn
applets: Implement backend and default frontend for Parental Controls and EShop (ShopN) applets
This commit is contained in:
commit
5829ba1ccc
20 changed files with 889 additions and 128 deletions
|
@ -7,9 +7,38 @@
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
|
||||||
|
ParentalControlsApplet::~ParentalControlsApplet() = default;
|
||||||
|
|
||||||
|
DefaultParentalControlsApplet::~DefaultParentalControlsApplet() = default;
|
||||||
|
|
||||||
|
void DefaultParentalControlsApplet::VerifyPIN(std::function<void(bool)> finished,
|
||||||
|
bool suspend_future_verification_temporarily) {
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend to verify PIN (normal), "
|
||||||
|
"suspend_future_verification_temporarily={}, verifying as correct.",
|
||||||
|
suspend_future_verification_temporarily);
|
||||||
|
finished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultParentalControlsApplet::VerifyPINForSettings(std::function<void(bool)> finished) {
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend to verify PIN (settings), verifying as correct.");
|
||||||
|
finished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultParentalControlsApplet::RegisterPIN(std::function<void()> finished) {
|
||||||
|
LOG_INFO(Service_AM, "Application requested frontend to register new PIN");
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultParentalControlsApplet::ChangePIN(std::function<void()> finished) {
|
||||||
|
LOG_INFO(Service_AM, "Application requested frontend to change PIN to new value");
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
PhotoViewerApplet::~PhotoViewerApplet() = default;
|
PhotoViewerApplet::~PhotoViewerApplet() = default;
|
||||||
|
|
||||||
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {}
|
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() = default;
|
||||||
|
|
||||||
void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
|
void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
|
||||||
std::function<void()> finished) const {
|
std::function<void()> finished) const {
|
||||||
|
@ -24,4 +53,72 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
|
||||||
finished();
|
finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ECommerceApplet::~ECommerceApplet() = default;
|
||||||
|
|
||||||
|
DefaultECommerceApplet::~DefaultECommerceApplet() = default;
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowApplicationInformation(
|
||||||
|
std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
|
||||||
|
std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
|
||||||
|
const auto value = user_id.value_or(u128{});
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend show application information for EShop, "
|
||||||
|
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
|
||||||
|
title_id, value[1], value[0],
|
||||||
|
full_display.has_value() ? fmt::format("{}", *full_display) : "null",
|
||||||
|
extra_parameter.value_or("null"));
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id,
|
||||||
|
std::optional<bool> full_display) {
|
||||||
|
const auto value = user_id.value_or(u128{});
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend show add on content list for EShop, "
|
||||||
|
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
|
||||||
|
title_id, value[1], value[0],
|
||||||
|
full_display.has_value() ? fmt::format("{}", *full_display) : "null");
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id) {
|
||||||
|
const auto value = user_id.value_or(u128{});
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend show subscription list for EShop, title_id={:016X}, "
|
||||||
|
"user_id={:016X}{:016X}",
|
||||||
|
title_id, value[1], value[0]);
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id) {
|
||||||
|
const auto value = user_id.value_or(u128{});
|
||||||
|
LOG_INFO(
|
||||||
|
Service_AM,
|
||||||
|
"Application requested frontend show consumable item list for EShop, title_id={:016X}, "
|
||||||
|
"user_id={:016X}{:016X}",
|
||||||
|
title_id, value[1], value[0]);
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
|
||||||
|
bool full_display) {
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
|
||||||
|
"full_display={}",
|
||||||
|
user_id[1], user_id[0], full_display);
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
|
||||||
|
bool full_display) {
|
||||||
|
LOG_INFO(Service_AM,
|
||||||
|
"Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
|
||||||
|
"full_display={}",
|
||||||
|
user_id[1], user_id[0], full_display);
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core::Frontend
|
} // namespace Core::Frontend
|
||||||
|
|
|
@ -5,10 +5,43 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
|
||||||
|
class ParentalControlsApplet {
|
||||||
|
public:
|
||||||
|
virtual ~ParentalControlsApplet();
|
||||||
|
|
||||||
|
// Prompts the user to enter a PIN and calls the callback with whether or not it matches the
|
||||||
|
// correct PIN. If the bool is passed, and the PIN was recently entered correctly, the frontend
|
||||||
|
// should not prompt and simply return true.
|
||||||
|
virtual void VerifyPIN(std::function<void(bool)> finished,
|
||||||
|
bool suspend_future_verification_temporarily) = 0;
|
||||||
|
|
||||||
|
// Prompts the user to enter a PIN and calls the callback for correctness. Frontends can
|
||||||
|
// optionally alert the user that this is to change parental controls settings.
|
||||||
|
virtual void VerifyPINForSettings(std::function<void(bool)> finished) = 0;
|
||||||
|
|
||||||
|
// Prompts the user to create a new PIN for pctl and stores it with the service.
|
||||||
|
virtual void RegisterPIN(std::function<void()> finished) = 0;
|
||||||
|
|
||||||
|
// Prompts the user to verify the current PIN and then store a new one into pctl.
|
||||||
|
virtual void ChangePIN(std::function<void()> finished) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultParentalControlsApplet final : public ParentalControlsApplet {
|
||||||
|
public:
|
||||||
|
~DefaultParentalControlsApplet() override;
|
||||||
|
|
||||||
|
void VerifyPIN(std::function<void(bool)> finished,
|
||||||
|
bool suspend_future_verification_temporarily) override;
|
||||||
|
void VerifyPINForSettings(std::function<void(bool)> finished) override;
|
||||||
|
void RegisterPIN(std::function<void()> finished) override;
|
||||||
|
void ChangePIN(std::function<void()> finished) override;
|
||||||
|
};
|
||||||
|
|
||||||
class PhotoViewerApplet {
|
class PhotoViewerApplet {
|
||||||
public:
|
public:
|
||||||
virtual ~PhotoViewerApplet();
|
virtual ~PhotoViewerApplet();
|
||||||
|
@ -25,4 +58,55 @@ public:
|
||||||
void ShowAllPhotos(std::function<void()> finished) const override;
|
void ShowAllPhotos(std::function<void()> finished) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ECommerceApplet {
|
||||||
|
public:
|
||||||
|
virtual ~ECommerceApplet();
|
||||||
|
|
||||||
|
// Shows a page with application icons, description, name, and price.
|
||||||
|
virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id = {},
|
||||||
|
std::optional<bool> full_display = {},
|
||||||
|
std::optional<std::string> extra_parameter = {}) = 0;
|
||||||
|
|
||||||
|
// Shows a page with all of the add on content available for a game, with name, description, and
|
||||||
|
// price.
|
||||||
|
virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id = {},
|
||||||
|
std::optional<bool> full_display = {}) = 0;
|
||||||
|
|
||||||
|
// Shows a page with all of the subscriptions (recurring payments) for a game, with name,
|
||||||
|
// description, price, and renewal period.
|
||||||
|
virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id = {}) = 0;
|
||||||
|
|
||||||
|
// Shows a page with a list of any additional game related purchasable items (DLC,
|
||||||
|
// subscriptions, etc) for a particular game, with name, description, type, and price.
|
||||||
|
virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id = {}) = 0;
|
||||||
|
|
||||||
|
// Shows the home page of the shop.
|
||||||
|
virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||||
|
|
||||||
|
// Shows the user settings page of the shop.
|
||||||
|
virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultECommerceApplet : public ECommerceApplet {
|
||||||
|
public:
|
||||||
|
~DefaultECommerceApplet() override;
|
||||||
|
|
||||||
|
void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id, std::optional<bool> full_display,
|
||||||
|
std::optional<std::string> extra_parameter) override;
|
||||||
|
void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id,
|
||||||
|
std::optional<bool> full_display) override;
|
||||||
|
void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id) override;
|
||||||
|
void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||||
|
std::optional<u128> user_id) override;
|
||||||
|
void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||||
|
void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Core::Frontend
|
} // namespace Core::Frontend
|
||||||
|
|
|
@ -11,9 +11,9 @@ WebBrowserApplet::~WebBrowserApplet() = default;
|
||||||
|
|
||||||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
||||||
|
|
||||||
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
|
void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
|
||||||
std::function<void()> unpack_romfs_callback,
|
std::function<void()> unpack_romfs_callback,
|
||||||
std::function<void()> finished_callback) {
|
std::function<void()> finished_callback) {
|
||||||
LOG_INFO(Service_AM,
|
LOG_INFO(Service_AM,
|
||||||
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
||||||
"at '{}'!",
|
"at '{}'!",
|
||||||
|
|
|
@ -13,16 +13,16 @@ class WebBrowserApplet {
|
||||||
public:
|
public:
|
||||||
virtual ~WebBrowserApplet();
|
virtual ~WebBrowserApplet();
|
||||||
|
|
||||||
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||||
std::function<void()> finished_callback) = 0;
|
std::function<void()> finished_callback) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
||||||
public:
|
public:
|
||||||
~DefaultWebBrowserApplet() override;
|
~DefaultWebBrowserApplet() override;
|
||||||
|
|
||||||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||||
std::function<void()> finished_callback) override;
|
std::function<void()> finished_callback) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core::Frontend
|
} // namespace Core::Frontend
|
||||||
|
|
|
@ -887,7 +887,9 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
|
ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id)
|
||||||
|
: ServiceFramework("ILibraryAppletCreator"),
|
||||||
|
current_process_title_id(current_process_title_id) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
||||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||||
|
@ -910,7 +912,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
|
||||||
static_cast<u32>(applet_id), applet_mode);
|
static_cast<u32>(applet_id), applet_mode);
|
||||||
|
|
||||||
const auto& applet_manager{Core::System::GetInstance().GetAppletManager()};
|
const auto& applet_manager{Core::System::GetInstance().GetAppletManager()};
|
||||||
const auto applet = applet_manager.GetApplet(applet_id);
|
const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id);
|
||||||
|
|
||||||
if (applet == nullptr) {
|
if (applet == nullptr) {
|
||||||
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
|
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
|
||||||
|
@ -1234,13 +1236,13 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
|
||||||
auto message_queue = std::make_shared<AppletMessageQueue>();
|
auto message_queue = std::make_shared<AppletMessageQueue>();
|
||||||
message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on
|
message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on
|
||||||
// game boot
|
// game boot
|
||||||
|
|
||||||
std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager);
|
std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
|
||||||
std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager);
|
std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
|
||||||
std::make_shared<IdleSys>()->InstallAsService(service_manager);
|
std::make_shared<IdleSys>()->InstallAsService(service_manager);
|
||||||
std::make_shared<OMM>()->InstallAsService(service_manager);
|
std::make_shared<OMM>()->InstallAsService(service_manager);
|
||||||
std::make_shared<SPSM>()->InstallAsService(service_manager);
|
std::make_shared<SPSM>()->InstallAsService(service_manager);
|
||||||
|
|
|
@ -201,13 +201,15 @@ private:
|
||||||
|
|
||||||
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
|
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
|
||||||
public:
|
public:
|
||||||
ILibraryAppletCreator();
|
ILibraryAppletCreator(u64 current_process_title_id);
|
||||||
~ILibraryAppletCreator() override;
|
~ILibraryAppletCreator() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
||||||
void CreateStorage(Kernel::HLERequestContext& ctx);
|
void CreateStorage(Kernel::HLERequestContext& ctx);
|
||||||
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
|
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
u64 current_process_title_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
||||||
|
@ -264,7 +266,7 @@ public:
|
||||||
|
|
||||||
/// Registers all AM services with the specified service manager.
|
/// Registers all AM services with the specified service manager.
|
||||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
|
||||||
|
|
||||||
} // namespace AM
|
} // namespace AM
|
||||||
} // namespace Service
|
} // namespace Service
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/hle/service/am/applet_ae.h"
|
#include "core/hle/service/am/applet_ae.h"
|
||||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||||
|
@ -13,9 +14,10 @@ namespace Service::AM {
|
||||||
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
|
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
|
||||||
public:
|
public:
|
||||||
explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
std::shared_ptr<AppletMessageQueue> msg_queue,
|
||||||
|
Core::System& system)
|
||||||
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
|
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
|
||||||
msg_queue(std::move(msg_queue)) {
|
msg_queue(std::move(msg_queue)), system(system) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||||
|
@ -96,7 +98,7 @@ private:
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -109,14 +111,15 @@ private:
|
||||||
|
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
|
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
|
||||||
public:
|
public:
|
||||||
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||||
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
|
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
|
||||||
msg_queue(std::move(msg_queue)) {
|
msg_queue(std::move(msg_queue)), system(system) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||||
|
@ -191,7 +194,7 @@ private:
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
|
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -219,6 +222,7 @@ private:
|
||||||
}
|
}
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
|
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -226,7 +230,7 @@ void AppletAE::OpenSystemAppletProxy(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<ISystemAppletProxy>(nvflinger, msg_queue);
|
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
|
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -234,7 +238,7 @@ void AppletAE::OpenLibraryAppletProxy(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<ILibraryAppletProxy>(nvflinger, msg_queue);
|
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -242,13 +246,13 @@ void AppletAE::OpenLibraryAppletProxyOld(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<ILibraryAppletProxy>(nvflinger, msg_queue);
|
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||||
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
|
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
|
||||||
msg_queue(std::move(msg_queue)) {
|
msg_queue(std::move(msg_queue)), system(system) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
|
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace AM {
|
||||||
class AppletAE final : public ServiceFramework<AppletAE> {
|
class AppletAE final : public ServiceFramework<AppletAE> {
|
||||||
public:
|
public:
|
||||||
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue);
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
|
||||||
~AppletAE() override;
|
~AppletAE() override;
|
||||||
|
|
||||||
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
||||||
|
@ -30,6 +30,7 @@ private:
|
||||||
|
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AM
|
} // namespace AM
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/hle/service/am/applet_oe.h"
|
#include "core/hle/service/am/applet_oe.h"
|
||||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||||
|
@ -13,9 +14,9 @@ namespace Service::AM {
|
||||||
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
|
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
|
||||||
public:
|
public:
|
||||||
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||||
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
|
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
|
||||||
msg_queue(std::move(msg_queue)) {
|
msg_queue(std::move(msg_queue)), system(system) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||||
|
@ -87,7 +88,7 @@ private:
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -100,6 +101,7 @@ private:
|
||||||
|
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
|
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -107,13 +109,13 @@ void AppletOE::OpenApplicationProxy(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<IApplicationProxy>(nvflinger, msg_queue);
|
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||||
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
|
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
|
||||||
msg_queue(std::move(msg_queue)) {
|
msg_queue(std::move(msg_queue)), system(system) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
|
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace AM {
|
||||||
class AppletOE final : public ServiceFramework<AppletOE> {
|
class AppletOE final : public ServiceFramework<AppletOE> {
|
||||||
public:
|
public:
|
||||||
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue);
|
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
|
||||||
~AppletOE() override;
|
~AppletOE() override;
|
||||||
|
|
||||||
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
||||||
|
@ -28,6 +28,7 @@ private:
|
||||||
|
|
||||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AM
|
} // namespace AM
|
||||||
|
|
|
@ -139,12 +139,14 @@ void Applet::Initialize() {
|
||||||
|
|
||||||
AppletFrontendSet::AppletFrontendSet() = default;
|
AppletFrontendSet::AppletFrontendSet() = default;
|
||||||
|
|
||||||
AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
|
AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
|
||||||
ProfileSelect profile_select,
|
PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
|
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
|
||||||
: error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move(
|
ECommerceApplet e_commerce)
|
||||||
profile_select)},
|
: parental_controls{std::move(parental_controls)}, error{std::move(error)},
|
||||||
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {}
|
photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
|
||||||
|
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
|
||||||
|
e_commerce{std::move(e_commerce)} {}
|
||||||
|
|
||||||
AppletFrontendSet::~AppletFrontendSet() = default;
|
AppletFrontendSet::~AppletFrontendSet() = default;
|
||||||
|
|
||||||
|
@ -157,6 +159,8 @@ AppletManager::AppletManager() = default;
|
||||||
AppletManager::~AppletManager() = default;
|
AppletManager::~AppletManager() = default;
|
||||||
|
|
||||||
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||||
|
if (set.parental_controls != nullptr)
|
||||||
|
frontend.parental_controls = std::move(set.parental_controls);
|
||||||
if (set.error != nullptr)
|
if (set.error != nullptr)
|
||||||
frontend.error = std::move(set.error);
|
frontend.error = std::move(set.error);
|
||||||
if (set.photo_viewer != nullptr)
|
if (set.photo_viewer != nullptr)
|
||||||
|
@ -167,17 +171,21 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||||
frontend.software_keyboard = std::move(set.software_keyboard);
|
frontend.software_keyboard = std::move(set.software_keyboard);
|
||||||
if (set.web_browser != nullptr)
|
if (set.web_browser != nullptr)
|
||||||
frontend.web_browser = std::move(set.web_browser);
|
frontend.web_browser = std::move(set.web_browser);
|
||||||
|
if (set.e_commerce != nullptr)
|
||||||
|
frontend.e_commerce = std::move(set.e_commerce);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppletManager::SetDefaultAppletFrontendSet() {
|
void AppletManager::SetDefaultAppletFrontendSet() {
|
||||||
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
ClearAll();
|
||||||
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
|
SetDefaultAppletsIfMissing();
|
||||||
frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
|
|
||||||
frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
|
|
||||||
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppletManager::SetDefaultAppletsIfMissing() {
|
void AppletManager::SetDefaultAppletsIfMissing() {
|
||||||
|
if (frontend.parental_controls == nullptr) {
|
||||||
|
frontend.parental_controls =
|
||||||
|
std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
|
||||||
|
}
|
||||||
|
|
||||||
if (frontend.error == nullptr) {
|
if (frontend.error == nullptr) {
|
||||||
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
||||||
}
|
}
|
||||||
|
@ -198,14 +206,20 @@ void AppletManager::SetDefaultAppletsIfMissing() {
|
||||||
if (frontend.web_browser == nullptr) {
|
if (frontend.web_browser == nullptr) {
|
||||||
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frontend.e_commerce == nullptr) {
|
||||||
|
frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppletManager::ClearAll() {
|
void AppletManager::ClearAll() {
|
||||||
frontend = {};
|
frontend = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
|
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
case AppletId::Auth:
|
||||||
|
return std::make_shared<Auth>(*frontend.parental_controls);
|
||||||
case AppletId::Error:
|
case AppletId::Error:
|
||||||
return std::make_shared<Error>(*frontend.error);
|
return std::make_shared<Error>(*frontend.error);
|
||||||
case AppletId::ProfileSelect:
|
case AppletId::ProfileSelect:
|
||||||
|
@ -214,8 +228,11 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
|
||||||
return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
|
return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
|
||||||
case AppletId::PhotoViewer:
|
case AppletId::PhotoViewer:
|
||||||
return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
|
return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
|
||||||
|
case AppletId::LibAppletShop:
|
||||||
|
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id,
|
||||||
|
frontend.e_commerce.get());
|
||||||
case AppletId::LibAppletOff:
|
case AppletId::LibAppletOff:
|
||||||
return std::make_shared<WebBrowser>(*frontend.web_browser);
|
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id);
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG(
|
UNIMPLEMENTED_MSG(
|
||||||
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
|
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
union ResultCode;
|
union ResultCode;
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
class ECommerceApplet;
|
||||||
class ErrorApplet;
|
class ErrorApplet;
|
||||||
|
class ParentalControlsApplet;
|
||||||
class PhotoViewerApplet;
|
class PhotoViewerApplet;
|
||||||
class ProfileSelectApplet;
|
class ProfileSelectApplet;
|
||||||
class SoftwareKeyboardApplet;
|
class SoftwareKeyboardApplet;
|
||||||
|
@ -145,15 +147,19 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppletFrontendSet {
|
struct AppletFrontendSet {
|
||||||
|
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
|
||||||
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
|
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
|
||||||
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
|
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
|
||||||
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
|
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
|
||||||
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
|
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
|
||||||
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
||||||
|
using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
|
||||||
|
|
||||||
AppletFrontendSet();
|
AppletFrontendSet();
|
||||||
AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
|
AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
|
||||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser);
|
PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||||
|
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
|
||||||
|
ECommerceApplet e_commerce);
|
||||||
~AppletFrontendSet();
|
~AppletFrontendSet();
|
||||||
|
|
||||||
AppletFrontendSet(const AppletFrontendSet&) = delete;
|
AppletFrontendSet(const AppletFrontendSet&) = delete;
|
||||||
|
@ -162,11 +168,13 @@ struct AppletFrontendSet {
|
||||||
AppletFrontendSet(AppletFrontendSet&&) noexcept;
|
AppletFrontendSet(AppletFrontendSet&&) noexcept;
|
||||||
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
|
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
|
||||||
|
|
||||||
|
ParentalControlsApplet parental_controls;
|
||||||
ErrorApplet error;
|
ErrorApplet error;
|
||||||
PhotoViewer photo_viewer;
|
PhotoViewer photo_viewer;
|
||||||
ProfileSelect profile_select;
|
ProfileSelect profile_select;
|
||||||
SoftwareKeyboard software_keyboard;
|
SoftwareKeyboard software_keyboard;
|
||||||
WebBrowser web_browser;
|
WebBrowser web_browser;
|
||||||
|
ECommerceApplet e_commerce;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AppletManager {
|
class AppletManager {
|
||||||
|
@ -179,7 +187,7 @@ public:
|
||||||
void SetDefaultAppletsIfMissing();
|
void SetDefaultAppletsIfMissing();
|
||||||
void ClearAll();
|
void ClearAll();
|
||||||
|
|
||||||
std::shared_ptr<Applet> GetApplet(AppletId id) const;
|
std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AppletFrontendSet frontend;
|
AppletFrontendSet frontend;
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
namespace Service::AM::Applets {
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
|
constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
|
||||||
|
|
||||||
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
|
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
|
||||||
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
|
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
|
||||||
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
|
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
|
||||||
|
@ -35,6 +37,120 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {}
|
||||||
|
|
||||||
|
Auth::~Auth() = default;
|
||||||
|
|
||||||
|
void Auth::Initialize() {
|
||||||
|
Applet::Initialize();
|
||||||
|
complete = false;
|
||||||
|
|
||||||
|
const auto storage = broker.PopNormalDataToApplet();
|
||||||
|
ASSERT(storage != nullptr);
|
||||||
|
const auto data = storage->GetData();
|
||||||
|
ASSERT(data.size() >= 0xC);
|
||||||
|
|
||||||
|
struct Arg {
|
||||||
|
INSERT_PADDING_BYTES(4);
|
||||||
|
AuthAppletType type;
|
||||||
|
u8 arg0;
|
||||||
|
u8 arg1;
|
||||||
|
u8 arg2;
|
||||||
|
INSERT_PADDING_BYTES(1);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Arg) == 0xC, "Arg (AuthApplet) has incorrect size.");
|
||||||
|
|
||||||
|
Arg arg{};
|
||||||
|
std::memcpy(&arg, data.data(), sizeof(Arg));
|
||||||
|
|
||||||
|
type = arg.type;
|
||||||
|
arg0 = arg.arg0;
|
||||||
|
arg1 = arg.arg1;
|
||||||
|
arg2 = arg.arg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Auth::TransactionComplete() const {
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode Auth::GetStatus() const {
|
||||||
|
return successful ? RESULT_SUCCESS : ERROR_INVALID_PIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Auth::ExecuteInteractive() {
|
||||||
|
UNREACHABLE_MSG("Unexpected interactive applet data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Auth::Execute() {
|
||||||
|
if (complete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto unimplemented_log = [this] {
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, "
|
||||||
|
"arg1={:02X}, arg2={:02X}",
|
||||||
|
static_cast<u32>(type), arg0, arg1, arg2);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case AuthAppletType::ShowParentalAuthentication: {
|
||||||
|
const auto callback = [this](bool successful) { AuthFinished(successful); };
|
||||||
|
|
||||||
|
if (arg0 == 1 && arg1 == 0 && arg2 == 1) {
|
||||||
|
// ShowAuthenticatorForConfiguration
|
||||||
|
frontend.VerifyPINForSettings(callback);
|
||||||
|
} else if (arg1 == 0 && arg2 == 0) {
|
||||||
|
// ShowParentalAuthentication(bool)
|
||||||
|
frontend.VerifyPIN(callback, static_cast<bool>(arg0));
|
||||||
|
} else {
|
||||||
|
unimplemented_log();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AuthAppletType::RegisterParentalPasscode: {
|
||||||
|
const auto callback = [this] { AuthFinished(true); };
|
||||||
|
|
||||||
|
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
|
||||||
|
// RegisterParentalPasscode
|
||||||
|
frontend.RegisterPIN(callback);
|
||||||
|
} else {
|
||||||
|
unimplemented_log();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AuthAppletType::ChangeParentalPasscode: {
|
||||||
|
const auto callback = [this] { AuthFinished(true); };
|
||||||
|
|
||||||
|
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
|
||||||
|
// ChangeParentalPasscode
|
||||||
|
frontend.ChangePIN(callback);
|
||||||
|
} else {
|
||||||
|
unimplemented_log();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
unimplemented_log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Auth::AuthFinished(bool successful) {
|
||||||
|
this->successful = successful;
|
||||||
|
|
||||||
|
struct Return {
|
||||||
|
ResultCode result_code;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size.");
|
||||||
|
|
||||||
|
Return return_{GetStatus()};
|
||||||
|
|
||||||
|
std::vector<u8> out(sizeof(Return));
|
||||||
|
std::memcpy(out.data(), &return_, sizeof(Return));
|
||||||
|
|
||||||
|
broker.PushNormalDataFromApplet(IStorage{out});
|
||||||
|
broker.SignalStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
|
PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
|
||||||
|
|
||||||
PhotoViewer::~PhotoViewer() = default;
|
PhotoViewer::~PhotoViewer() = default;
|
||||||
|
|
|
@ -8,6 +8,36 @@
|
||||||
|
|
||||||
namespace Service::AM::Applets {
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
|
enum class AuthAppletType : u32 {
|
||||||
|
ShowParentalAuthentication,
|
||||||
|
RegisterParentalPasscode,
|
||||||
|
ChangeParentalPasscode,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Auth final : public Applet {
|
||||||
|
public:
|
||||||
|
explicit Auth(Core::Frontend::ParentalControlsApplet& frontend);
|
||||||
|
~Auth() override;
|
||||||
|
|
||||||
|
void Initialize() override;
|
||||||
|
bool TransactionComplete() const override;
|
||||||
|
ResultCode GetStatus() const override;
|
||||||
|
void ExecuteInteractive() override;
|
||||||
|
void Execute() override;
|
||||||
|
|
||||||
|
void AuthFinished(bool successful = true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::Frontend::ParentalControlsApplet& frontend;
|
||||||
|
bool complete = false;
|
||||||
|
bool successful = false;
|
||||||
|
|
||||||
|
AuthAppletType type = AuthAppletType::ShowParentalAuthentication;
|
||||||
|
u8 arg0 = 0;
|
||||||
|
u8 arg1 = 0;
|
||||||
|
u8 arg2 = 0;
|
||||||
|
};
|
||||||
|
|
||||||
enum class PhotoViewerAppletMode : u8 {
|
enum class PhotoViewerAppletMode : u8 {
|
||||||
CurrentApp = 0,
|
CurrentApp = 0,
|
||||||
AllApps = 1,
|
AllApps = 1,
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
#include "core/file_sys/nca_metadata.h"
|
#include "core/file_sys/nca_metadata.h"
|
||||||
#include "core/file_sys/registered_cache.h"
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "core/file_sys/romfs.h"
|
#include "core/file_sys/romfs.h"
|
||||||
|
#include "core/file_sys/system_archive/system_archive.h"
|
||||||
#include "core/file_sys/vfs_types.h"
|
#include "core/file_sys/vfs_types.h"
|
||||||
|
#include "core/frontend/applets/general_frontend.h"
|
||||||
#include "core/frontend/applets/web_browser.h"
|
#include "core/frontend/applets/web_browser.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/am/applets/web_browser.h"
|
#include "core/hle/service/am/applets/web_browser.h"
|
||||||
|
@ -28,74 +30,187 @@
|
||||||
|
|
||||||
namespace Service::AM::Applets {
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
|
enum class WebArgTLVType : u16 {
|
||||||
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
|
InitialURL = 0x1,
|
||||||
// but some may be worth an implementation.
|
ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
|
||||||
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
|
CallbackURL = 0x3,
|
||||||
|
CallbackableURL = 0x4,
|
||||||
struct WebBufferHeader {
|
ApplicationID = 0x5,
|
||||||
u16 count;
|
DocumentPath = 0x6,
|
||||||
INSERT_PADDING_BYTES(6);
|
DocumentKind = 0x7,
|
||||||
|
SystemDataID = 0x8,
|
||||||
|
ShareStartPage = 0x9,
|
||||||
|
Whitelist = 0xA,
|
||||||
|
News = 0xB,
|
||||||
|
UserID = 0xE,
|
||||||
|
AlbumEntry0 = 0xF,
|
||||||
|
ScreenShotEnabled = 0x10,
|
||||||
|
EcClientCertEnabled = 0x11,
|
||||||
|
Unk12 = 0x12,
|
||||||
|
PlayReportEnabled = 0x13,
|
||||||
|
Unk14 = 0x14,
|
||||||
|
Unk15 = 0x15,
|
||||||
|
BootDisplayKind = 0x17,
|
||||||
|
BackgroundKind = 0x18,
|
||||||
|
FooterEnabled = 0x19,
|
||||||
|
PointerEnabled = 0x1A,
|
||||||
|
LeftStickMode = 0x1B,
|
||||||
|
KeyRepeatFrame1 = 0x1C,
|
||||||
|
KeyRepeatFrame2 = 0x1D,
|
||||||
|
BootAsMediaPlayerInv = 0x1E,
|
||||||
|
DisplayUrlKind = 0x1F,
|
||||||
|
BootAsMediaPlayer = 0x21,
|
||||||
|
ShopJumpEnabled = 0x22,
|
||||||
|
MediaAutoPlayEnabled = 0x23,
|
||||||
|
LobbyParameter = 0x24,
|
||||||
|
ApplicationAlbumEntry = 0x26,
|
||||||
|
JsExtensionEnabled = 0x27,
|
||||||
|
AdditionalCommentText = 0x28,
|
||||||
|
TouchEnabledOnContents = 0x29,
|
||||||
|
UserAgentAdditionalString = 0x2A,
|
||||||
|
AdditionalMediaData0 = 0x2B,
|
||||||
|
MediaPlayerAutoCloseEnabled = 0x2C,
|
||||||
|
PageCacheEnabled = 0x2D,
|
||||||
|
WebAudioEnabled = 0x2E,
|
||||||
|
Unk2F = 0x2F,
|
||||||
|
YouTubeVideoWhitelist = 0x31,
|
||||||
|
FooterFixedKind = 0x32,
|
||||||
|
PageFadeEnabled = 0x33,
|
||||||
|
MediaCreatorApplicationRatingAge = 0x34,
|
||||||
|
BootLoadingIconEnabled = 0x35,
|
||||||
|
PageScrollIndicationEnabled = 0x36,
|
||||||
|
MediaPlayerSpeedControlEnabled = 0x37,
|
||||||
|
AlbumEntry1 = 0x38,
|
||||||
|
AlbumEntry2 = 0x39,
|
||||||
|
AlbumEntry3 = 0x3A,
|
||||||
|
AdditionalMediaData1 = 0x3B,
|
||||||
|
AdditionalMediaData2 = 0x3C,
|
||||||
|
AdditionalMediaData3 = 0x3D,
|
||||||
|
BootFooterButton = 0x3E,
|
||||||
|
OverrideWebAudioVolume = 0x3F,
|
||||||
|
OverrideMediaAudioVolume = 0x40,
|
||||||
|
BootMode = 0x41,
|
||||||
|
WebSessionEnabled = 0x42,
|
||||||
};
|
};
|
||||||
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
|
|
||||||
|
|
||||||
struct WebArgumentHeader {
|
enum class ShimKind : u32 {
|
||||||
u16 type;
|
Shop = 1,
|
||||||
|
Login = 2,
|
||||||
|
Offline = 3,
|
||||||
|
Share = 4,
|
||||||
|
Web = 5,
|
||||||
|
Wifi = 6,
|
||||||
|
Lobby = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ShopWebTarget {
|
||||||
|
ApplicationInfo,
|
||||||
|
AddOnContentList,
|
||||||
|
SubscriptionList,
|
||||||
|
ConsumableItemList,
|
||||||
|
Home,
|
||||||
|
Settings,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr std::size_t SHIM_KIND_COUNT = 0x8;
|
||||||
|
|
||||||
|
struct WebArgHeader {
|
||||||
|
u16 count;
|
||||||
|
INSERT_PADDING_BYTES(2);
|
||||||
|
ShimKind kind;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
|
||||||
|
|
||||||
|
struct WebArgTLV {
|
||||||
|
WebArgTLVType type;
|
||||||
u16 size;
|
u16 size;
|
||||||
u32 offset;
|
u32 offset;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
|
static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
|
||||||
|
|
||||||
struct WebArgumentResult {
|
struct WebCommonReturnValue {
|
||||||
u32 result_code;
|
u32 result_code;
|
||||||
|
INSERT_PADDING_BYTES(0x4);
|
||||||
std::array<char, 0x1000> last_url;
|
std::array<char, 0x1000> last_url;
|
||||||
u64 last_url_size;
|
u64 last_url_size;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
|
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
|
||||||
|
|
||||||
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
|
struct WebWifiPageArg {
|
||||||
WebBufferHeader header;
|
INSERT_PADDING_BYTES(4);
|
||||||
ASSERT(sizeof(WebBufferHeader) <= data.size());
|
std::array<char, 0x100> connection_test_url;
|
||||||
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
|
std::array<char, 0x400> initial_url;
|
||||||
|
std::array<u8, 0x10> nifm_network_uuid;
|
||||||
|
u32 nifm_requirement;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
|
||||||
|
|
||||||
u64 offset = sizeof(WebBufferHeader);
|
struct WebWifiReturnValue {
|
||||||
for (u16 i = 0; i < header.count; ++i) {
|
INSERT_PADDING_BYTES(4);
|
||||||
WebArgumentHeader arg;
|
u32 result;
|
||||||
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
|
};
|
||||||
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
|
static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
|
||||||
offset += sizeof(WebArgumentHeader);
|
|
||||||
|
|
||||||
if (arg.type == type) {
|
enum class OfflineWebSource : u32 {
|
||||||
std::vector<u8> out(arg.size);
|
OfflineHtmlPage = 0x1,
|
||||||
offset += arg.offset;
|
ApplicationLegalInformation = 0x2,
|
||||||
ASSERT(offset + arg.size <= data.size());
|
SystemDataPage = 0x3,
|
||||||
std::memcpy(out.data(), data.data() + offset, out.size());
|
};
|
||||||
|
|
||||||
|
std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
|
||||||
|
if (arg.size() < sizeof(WebArgHeader))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
WebArgHeader header{};
|
||||||
|
std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
|
||||||
|
|
||||||
|
std::map<WebArgTLVType, std::vector<u8>> out;
|
||||||
|
u64 offset = sizeof(WebArgHeader);
|
||||||
|
for (std::size_t i = 0; i < header.count; ++i) {
|
||||||
|
if (arg.size() < (offset + sizeof(WebArgTLV)))
|
||||||
return out;
|
return out;
|
||||||
}
|
|
||||||
|
|
||||||
offset += arg.offset + arg.size;
|
WebArgTLV tlv{};
|
||||||
|
std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
|
||||||
|
offset += sizeof(WebArgTLV);
|
||||||
|
|
||||||
|
offset += tlv.offset;
|
||||||
|
if (arg.size() < (offset + tlv.size))
|
||||||
|
return out;
|
||||||
|
|
||||||
|
std::vector<u8> data(tlv.size);
|
||||||
|
std::memcpy(data.data(), arg.data() + offset, tlv.size);
|
||||||
|
offset += tlv.size;
|
||||||
|
|
||||||
|
out.insert_or_assign(tlv.type, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FileSys::VirtualFile GetManualRomFS() {
|
FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) {
|
||||||
auto& loader{Core::System::GetInstance().GetAppLoader()};
|
|
||||||
|
|
||||||
FileSys::VirtualFile out;
|
|
||||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
|
||||||
return out;
|
|
||||||
|
|
||||||
const auto& installed{Core::System::GetInstance().GetContentProvider()};
|
const auto& installed{Core::System::GetInstance().GetContentProvider()};
|
||||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
const auto res = installed.GetEntry(title_id, type);
|
||||||
FileSys::ContentRecordType::Manual);
|
|
||||||
|
|
||||||
if (res != nullptr)
|
if (res != nullptr) {
|
||||||
return res->GetRomFS();
|
return res->GetRomFS();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == FileSys::ContentRecordType::Data) {
|
||||||
|
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||||
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {}
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
|
||||||
|
Core::Frontend::ECommerceApplet* frontend_e_commerce)
|
||||||
|
: frontend(frontend), frontend_e_commerce(frontend_e_commerce),
|
||||||
|
current_process_title_id(current_process_title_id) {}
|
||||||
|
|
||||||
WebBrowser::~WebBrowser() = default;
|
WebBrowser::~WebBrowser() = default;
|
||||||
|
|
||||||
|
@ -111,24 +226,12 @@ void WebBrowser::Initialize() {
|
||||||
ASSERT(web_arg_storage != nullptr);
|
ASSERT(web_arg_storage != nullptr);
|
||||||
const auto& web_arg = web_arg_storage->GetData();
|
const auto& web_arg = web_arg_storage->GetData();
|
||||||
|
|
||||||
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
|
ASSERT(web_arg.size() >= 0x8);
|
||||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
|
||||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
|
||||||
|
|
||||||
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
args = GetWebArguments(web_arg);
|
||||||
"web_applet_manual",
|
|
||||||
FileUtil::DirectorySeparator::PlatformDefault);
|
|
||||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
|
||||||
|
|
||||||
manual_romfs = GetManualRomFS();
|
InitializeInternal();
|
||||||
if (manual_romfs == nullptr) {
|
|
||||||
status = ResultCode(-1);
|
|
||||||
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
|
|
||||||
}
|
|
||||||
|
|
||||||
filename =
|
|
||||||
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
|
|
||||||
FileUtil::DirectorySeparator::PlatformDefault);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebBrowser::TransactionComplete() const {
|
bool WebBrowser::TransactionComplete() const {
|
||||||
|
@ -144,24 +247,25 @@ void WebBrowser::ExecuteInteractive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowser::Execute() {
|
void WebBrowser::Execute() {
|
||||||
if (complete)
|
if (complete) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (status != RESULT_SUCCESS) {
|
if (status != RESULT_SUCCESS) {
|
||||||
complete = true;
|
complete = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
ExecuteInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowser::UnpackRomFS() {
|
void WebBrowser::UnpackRomFS() {
|
||||||
if (unpacked)
|
if (unpacked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ASSERT(manual_romfs != nullptr);
|
ASSERT(offline_romfs != nullptr);
|
||||||
const auto dir =
|
const auto dir =
|
||||||
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||||
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
|
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
|
||||||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
||||||
FileSys::VfsRawCopyD(dir, temp_dir);
|
FileSys::VfsRawCopyD(dir, temp_dir);
|
||||||
|
@ -172,17 +276,275 @@ void WebBrowser::UnpackRomFS() {
|
||||||
void WebBrowser::Finalize() {
|
void WebBrowser::Finalize() {
|
||||||
complete = true;
|
complete = true;
|
||||||
|
|
||||||
WebArgumentResult out{};
|
WebCommonReturnValue out{};
|
||||||
out.result_code = 0;
|
out.result_code = 0;
|
||||||
out.last_url_size = 0;
|
out.last_url_size = 0;
|
||||||
|
|
||||||
std::vector<u8> data(sizeof(WebArgumentResult));
|
std::vector<u8> data(sizeof(WebCommonReturnValue));
|
||||||
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
|
std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
|
||||||
|
|
||||||
broker.PushNormalDataFromApplet(IStorage{data});
|
broker.PushNormalDataFromApplet(IStorage{data});
|
||||||
broker.SignalStateChanged();
|
broker.SignalStateChanged();
|
||||||
|
|
||||||
|
if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
|
||||||
|
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::InitializeInternal() {
|
||||||
|
using WebAppletInitializer = void (WebBrowser::*)();
|
||||||
|
|
||||||
|
constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
|
||||||
|
nullptr, &WebBrowser::InitializeShop,
|
||||||
|
nullptr, &WebBrowser::InitializeOffline,
|
||||||
|
nullptr, nullptr,
|
||||||
|
nullptr, nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto index = static_cast<u32>(kind);
|
||||||
|
|
||||||
|
if (index > functions.size() || functions[index] == nullptr) {
|
||||||
|
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto function = functions[index];
|
||||||
|
(this->*function)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::ExecuteInternal() {
|
||||||
|
using WebAppletExecutor = void (WebBrowser::*)();
|
||||||
|
|
||||||
|
constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
|
||||||
|
nullptr, &WebBrowser::ExecuteShop,
|
||||||
|
nullptr, &WebBrowser::ExecuteOffline,
|
||||||
|
nullptr, nullptr,
|
||||||
|
nullptr, nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto index = static_cast<u32>(kind);
|
||||||
|
|
||||||
|
if (index > functions.size() || functions[index] == nullptr) {
|
||||||
|
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto function = functions[index];
|
||||||
|
(this->*function)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::InitializeShop() {
|
||||||
|
if (frontend_e_commerce == nullptr) {
|
||||||
|
LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto user_id_data = args.find(WebArgTLVType::UserID);
|
||||||
|
|
||||||
|
user_id = std::nullopt;
|
||||||
|
if (user_id_data != args.end()) {
|
||||||
|
user_id = u128{};
|
||||||
|
std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
|
||||||
|
|
||||||
|
if (url == args.end()) {
|
||||||
|
LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> split_query;
|
||||||
|
Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
|
||||||
|
reinterpret_cast<const char*>(url->second.data()), url->second.size()),
|
||||||
|
'?', split_query);
|
||||||
|
|
||||||
|
// 2 -> Main URL '?' Query Parameters
|
||||||
|
// Less is missing info, More is malformed
|
||||||
|
if (split_query.size() != 2) {
|
||||||
|
LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> queries;
|
||||||
|
Common::SplitString(split_query[1], '&', queries);
|
||||||
|
|
||||||
|
const auto split_single_query =
|
||||||
|
[](const std::string& in) -> std::pair<std::string, std::string> {
|
||||||
|
const auto index = in.find('=');
|
||||||
|
if (index == std::string::npos || index == in.size() - 1) {
|
||||||
|
return {in, ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {in.substr(0, index), in.substr(index + 1)};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::transform(queries.begin(), queries.end(),
|
||||||
|
std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
|
||||||
|
|
||||||
|
const auto scene = shop_query.find("scene");
|
||||||
|
|
||||||
|
if (scene == shop_query.end()) {
|
||||||
|
LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::map<std::string, ShopWebTarget, std::less<>> target_map{
|
||||||
|
{"product_detail", ShopWebTarget::ApplicationInfo},
|
||||||
|
{"aocs", ShopWebTarget::AddOnContentList},
|
||||||
|
{"subscriptions", ShopWebTarget::SubscriptionList},
|
||||||
|
{"consumption", ShopWebTarget::ConsumableItemList},
|
||||||
|
{"settings", ShopWebTarget::Settings},
|
||||||
|
{"top", ShopWebTarget::Home},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto target = target_map.find(scene->second);
|
||||||
|
if (target == target_map.end()) {
|
||||||
|
LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shop_web_target = target->second;
|
||||||
|
|
||||||
|
const auto title_id_data = shop_query.find("dst_app_id");
|
||||||
|
if (title_id_data != shop_query.end()) {
|
||||||
|
title_id = std::stoull(title_id_data->second, nullptr, 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto mode_data = shop_query.find("mode");
|
||||||
|
if (mode_data != shop_query.end()) {
|
||||||
|
shop_full_display = mode_data->second == "full";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::InitializeOffline() {
|
||||||
|
if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
|
||||||
|
args.find(WebArgTLVType::DocumentKind) == args.end() ||
|
||||||
|
args.find(WebArgTLVType::ApplicationID) == args.end()) {
|
||||||
|
status = ResultCode(-1);
|
||||||
|
LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto url_data = args[WebArgTLVType::DocumentPath];
|
||||||
|
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||||
|
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||||
|
|
||||||
|
OfflineWebSource source;
|
||||||
|
ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
|
||||||
|
std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
|
||||||
|
"manual",
|
||||||
|
"legal",
|
||||||
|
"system",
|
||||||
|
};
|
||||||
|
|
||||||
|
temporary_dir =
|
||||||
|
FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
|
||||||
|
WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
|
||||||
|
FileUtil::DirectorySeparator::PlatformDefault);
|
||||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||||
|
|
||||||
|
u64 title_id = 0; // 0 corresponds to current process
|
||||||
|
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
|
||||||
|
std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
|
||||||
|
FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
|
||||||
|
|
||||||
|
switch (source) {
|
||||||
|
case OfflineWebSource::OfflineHtmlPage:
|
||||||
|
// While there is an AppID TLV field, in official SW this is always ignored.
|
||||||
|
title_id = 0;
|
||||||
|
type = FileSys::ContentRecordType::Manual;
|
||||||
|
break;
|
||||||
|
case OfflineWebSource::ApplicationLegalInformation:
|
||||||
|
type = FileSys::ContentRecordType::Legal;
|
||||||
|
break;
|
||||||
|
case OfflineWebSource::SystemDataPage:
|
||||||
|
type = FileSys::ContentRecordType::Data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title_id == 0) {
|
||||||
|
title_id = current_process_title_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
offline_romfs = GetApplicationRomFS(title_id, type);
|
||||||
|
if (offline_romfs == nullptr) {
|
||||||
|
status = ResultCode(-1);
|
||||||
|
LOG_ERROR(Service_AM, "Failed to find offline data for request!");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_additional_directory;
|
||||||
|
if (source == OfflineWebSource::OfflineHtmlPage) {
|
||||||
|
path_additional_directory = std::string(DIR_SEP).append("html-document");
|
||||||
|
}
|
||||||
|
|
||||||
|
filename =
|
||||||
|
FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
|
||||||
|
FileUtil::DirectorySeparator::PlatformDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::ExecuteShop() {
|
||||||
|
const auto callback = [this]() { Finalize(); };
|
||||||
|
|
||||||
|
const auto check_optional_parameter = [this](const auto& p) {
|
||||||
|
if (!p.has_value()) {
|
||||||
|
LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
|
||||||
|
status = ResultCode(-1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (shop_web_target) {
|
||||||
|
case ShopWebTarget::ApplicationInfo:
|
||||||
|
if (!check_optional_parameter(title_id))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
|
||||||
|
shop_full_display, shop_extra_parameter);
|
||||||
|
break;
|
||||||
|
case ShopWebTarget::AddOnContentList:
|
||||||
|
if (!check_optional_parameter(title_id))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
|
||||||
|
break;
|
||||||
|
case ShopWebTarget::ConsumableItemList:
|
||||||
|
if (!check_optional_parameter(title_id))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
|
||||||
|
break;
|
||||||
|
case ShopWebTarget::Home:
|
||||||
|
if (!check_optional_parameter(user_id))
|
||||||
|
return;
|
||||||
|
if (!check_optional_parameter(shop_full_display))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
|
||||||
|
break;
|
||||||
|
case ShopWebTarget::Settings:
|
||||||
|
if (!check_optional_parameter(user_id))
|
||||||
|
return;
|
||||||
|
if (!check_optional_parameter(shop_full_display))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
|
||||||
|
break;
|
||||||
|
case ShopWebTarget::SubscriptionList:
|
||||||
|
if (!check_optional_parameter(title_id))
|
||||||
|
return;
|
||||||
|
frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebBrowser::ExecuteOffline() {
|
||||||
|
frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::AM::Applets
|
} // namespace Service::AM::Applets
|
||||||
|
|
|
@ -4,15 +4,22 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include "core/file_sys/vfs_types.h"
|
#include "core/file_sys/vfs_types.h"
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/hle/service/am/applets/applets.h"
|
#include "core/hle/service/am/applets/applets.h"
|
||||||
|
|
||||||
namespace Service::AM::Applets {
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
|
enum class ShimKind : u32;
|
||||||
|
enum class ShopWebTarget;
|
||||||
|
enum class WebArgTLVType : u16;
|
||||||
|
|
||||||
class WebBrowser final : public Applet {
|
class WebBrowser final : public Applet {
|
||||||
public:
|
public:
|
||||||
WebBrowser(Core::Frontend::WebBrowserApplet& frontend);
|
WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
|
||||||
|
Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr);
|
||||||
|
|
||||||
~WebBrowser() override;
|
~WebBrowser() override;
|
||||||
|
|
||||||
void Initialize() override;
|
void Initialize() override;
|
||||||
|
@ -32,15 +39,41 @@ public:
|
||||||
void Finalize();
|
void Finalize();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void InitializeInternal();
|
||||||
|
void ExecuteInternal();
|
||||||
|
|
||||||
|
// Specific initializers for the types of web applets
|
||||||
|
void InitializeShop();
|
||||||
|
void InitializeOffline();
|
||||||
|
|
||||||
|
// Specific executors for the types of web applets
|
||||||
|
void ExecuteShop();
|
||||||
|
void ExecuteOffline();
|
||||||
|
|
||||||
Core::Frontend::WebBrowserApplet& frontend;
|
Core::Frontend::WebBrowserApplet& frontend;
|
||||||
|
|
||||||
|
// Extra frontends for specialized functions
|
||||||
|
Core::Frontend::ECommerceApplet* frontend_e_commerce;
|
||||||
|
|
||||||
bool complete = false;
|
bool complete = false;
|
||||||
bool unpacked = false;
|
bool unpacked = false;
|
||||||
ResultCode status = RESULT_SUCCESS;
|
ResultCode status = RESULT_SUCCESS;
|
||||||
|
|
||||||
FileSys::VirtualFile manual_romfs;
|
u64 current_process_title_id;
|
||||||
|
|
||||||
|
ShimKind kind;
|
||||||
|
std::map<WebArgTLVType, std::vector<u8>> args;
|
||||||
|
|
||||||
|
FileSys::VirtualFile offline_romfs;
|
||||||
std::string temporary_dir;
|
std::string temporary_dir;
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
|
||||||
|
ShopWebTarget shop_web_target;
|
||||||
|
std::map<std::string, std::string, std::less<>> shop_query;
|
||||||
|
std::optional<u64> title_id = 0;
|
||||||
|
std::optional<u128> user_id;
|
||||||
|
std::optional<bool> shop_full_display;
|
||||||
|
std::string shop_extra_parameter;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::AM::Applets
|
} // namespace Service::AM::Applets
|
||||||
|
|
|
@ -204,7 +204,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
|
||||||
SM::ServiceManager::InstallInterfaces(sm);
|
SM::ServiceManager::InstallInterfaces(sm);
|
||||||
|
|
||||||
Account::InstallInterfaces(system);
|
Account::InstallInterfaces(system);
|
||||||
AM::InstallInterfaces(*sm, nv_flinger);
|
AM::InstallInterfaces(*sm, nv_flinger, system);
|
||||||
AOC::InstallInterfaces(*sm);
|
AOC::InstallInterfaces(*sm);
|
||||||
APM::InstallInterfaces(*sm);
|
APM::InstallInterfaces(*sm);
|
||||||
Audio::InstallInterfaces(*sm);
|
Audio::InstallInterfaces(*sm);
|
||||||
|
|
|
@ -87,8 +87,8 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
|
||||||
|
|
||||||
QtWebBrowser::~QtWebBrowser() = default;
|
QtWebBrowser::~QtWebBrowser() = default;
|
||||||
|
|
||||||
void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||||
std::function<void()> finished_callback) {
|
std::function<void()> finished_callback) {
|
||||||
this->unpack_romfs_callback = std::move(unpack_romfs_callback);
|
this->unpack_romfs_callback = std::move(unpack_romfs_callback);
|
||||||
this->finished_callback = std::move(finished_callback);
|
this->finished_callback = std::move(finished_callback);
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ public:
|
||||||
explicit QtWebBrowser(GMainWindow& main_window);
|
explicit QtWebBrowser(GMainWindow& main_window);
|
||||||
~QtWebBrowser() override;
|
~QtWebBrowser() override;
|
||||||
|
|
||||||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||||
std::function<void()> finished_callback) override;
|
std::function<void()> finished_callback) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
|
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
|
||||||
|
|
|
@ -814,11 +814,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
system.SetGPUDebugContext(debug_context);
|
system.SetGPUDebugContext(debug_context);
|
||||||
|
|
||||||
system.SetAppletFrontendSet({
|
system.SetAppletFrontendSet({
|
||||||
std::make_unique<QtErrorDisplay>(*this),
|
nullptr, // Parental Controls
|
||||||
nullptr,
|
std::make_unique<QtErrorDisplay>(*this), //
|
||||||
std::make_unique<QtProfileSelector>(*this),
|
nullptr, // Photo Viewer
|
||||||
std::make_unique<QtSoftwareKeyboard>(*this),
|
std::make_unique<QtProfileSelector>(*this), //
|
||||||
std::make_unique<QtWebBrowser>(*this),
|
std::make_unique<QtSoftwareKeyboard>(*this), //
|
||||||
|
std::make_unique<QtWebBrowser>(*this), //
|
||||||
|
nullptr, // E-Commerce
|
||||||
});
|
});
|
||||||
|
|
||||||
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
|
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
|
||||||
|
|
Loading…
Reference in a new issue