early-access version 1255

This commit is contained in:
pineappleEA
2020-12-28 15:15:37 +00:00
parent 84b39492d1
commit 78b48028e1
6254 changed files with 1868140 additions and 0 deletions

860
src/core/hle/service/acc/acc.cpp Executable file
View File

@@ -0,0 +1,860 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include "common/common_paths.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/constants.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/acc/acc_aa.h"
#include "core/hle/service/acc/acc_su.h"
#include "core/hle/service/acc/acc_u0.h"
#include "core/hle/service/acc/acc_u1.h"
#include "core/hle/service/acc/errors.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/glue/arp.h"
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
namespace Service::Account {
constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
static std::string GetImagePath(Common::UUID uuid) {
return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
}
static constexpr u32 SanitizeJPEGSize(std::size_t size) {
constexpr std::size_t max_jpeg_image_size = 0x20000;
return static_cast<u32>(std::min(size, max_jpeg_image_size));
}
class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> {
public:
explicit IManagerForSystemService(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IManagerForSystemService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
{1, nullptr, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{100, nullptr, "SetSystemProgramIdentification"},
{101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
{110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
{111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
{112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
{113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
{120, nullptr, "GetNintendoAccountId"},
{121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
{130, nullptr, "GetNintendoAccountUserResourceCache"},
{131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
{132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
{133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
{134, nullptr, "RefreshNintendoAccountVerificationUrlCache"}, // 9.0.0+
{135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
{140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
{141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
{142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
{150, nullptr, "CreateAuthorizationRequest"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 3.0.0+
class IFloatingRegistrationRequest final : public ServiceFramework<IFloatingRegistrationRequest> {
public:
explicit IFloatingRegistrationRequest(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IFloatingRegistrationRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{12, nullptr, "GetAccountId"},
{13, nullptr, "GetLinkedNintendoAccountId"},
{14, nullptr, "GetNickname"},
{15, nullptr, "GetProfileImage"},
{21, nullptr, "LoadIdTokenCache"},
{100, nullptr, "RegisterUser"}, // [1.0.0-3.0.2] RegisterAsync
{101, nullptr, "RegisterUserWithUid"}, // [1.0.0-3.0.2] RegisterWithUidAsync
{102, nullptr, "RegisterNetworkServiceAccountAsync"}, // 4.0.0+
{103, nullptr, "RegisterNetworkServiceAccountWithUidAsync"}, // 4.0.0+
{110, nullptr, "SetSystemProgramIdentification"},
{111, nullptr, "EnsureIdTokenCacheAsync"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IAdministrator final : public ServiceFramework<IAdministrator> {
public:
explicit IAdministrator(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IAdministrator"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
{1, nullptr, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{100, nullptr, "SetSystemProgramIdentification"},
{101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
{110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
{111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
{112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
{113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
{120, nullptr, "GetNintendoAccountId"},
{121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
{130, nullptr, "GetNintendoAccountUserResourceCache"},
{131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
{132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
{133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
{134, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsync"}, // 9.0.0+
{135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
{140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
{141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
{142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
{150, nullptr, "CreateAuthorizationRequest"},
{200, nullptr, "IsRegistered"},
{201, nullptr, "RegisterAsync"},
{202, nullptr, "UnregisterAsync"},
{203, nullptr, "DeleteRegistrationInfoLocally"},
{220, nullptr, "SynchronizeProfileAsync"},
{221, nullptr, "UploadProfileAsync"},
{222, nullptr, "SynchronizaProfileAsyncIfSecondsElapsed"},
{250, nullptr, "IsLinkedWithNintendoAccount"},
{251, nullptr, "CreateProcedureToLinkWithNintendoAccount"},
{252, nullptr, "ResumeProcedureToLinkWithNintendoAccount"},
{255, nullptr, "CreateProcedureToUpdateLinkageStateOfNintendoAccount"},
{256, nullptr, "ResumeProcedureToUpdateLinkageStateOfNintendoAccount"},
{260, nullptr, "CreateProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
{261, nullptr, "ResumeProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
{280, nullptr, "ProxyProcedureToAcquireApplicationAuthorizationForNintendoAccount"},
{290, nullptr, "GetRequestForNintendoAccountUserResourceView"}, // 8.0.0+
{300, nullptr, "TryRecoverNintendoAccountUserStateAsync"}, // 6.0.0+
{400, nullptr, "IsServiceEntryRequirementCacheRefreshRequiredForOnlinePlay"}, // 6.1.0+
{401, nullptr, "RefreshServiceEntryRequirementCacheForOnlinePlayAsync"}, // 6.1.0+
{900, nullptr, "GetAuthenticationInfoForWin"}, // 9.0.0+
{901, nullptr, "ImportAsyncForWin"}, // 9.0.0+
{997, nullptr, "DebugUnlinkNintendoAccountAsync"},
{998, nullptr, "DebugSetAvailabilityErrorDetail"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IAuthorizationRequest final : public ServiceFramework<IAuthorizationRequest> {
public:
explicit IAuthorizationRequest(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IAuthorizationRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{10, nullptr, "InvokeWithoutInteractionAsync"},
{19, nullptr, "IsAuthorized"},
{20, nullptr, "GetAuthorizationCode"},
{21, nullptr, "GetIdToken"},
{22, nullptr, "GetState"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IOAuthProcedure final : public ServiceFramework<IOAuthProcedure> {
public:
explicit IOAuthProcedure(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IOAuthProcedure"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 3.0.0+
class IOAuthProcedureForExternalNsa final : public ServiceFramework<IOAuthProcedureForExternalNsa> {
public:
explicit IOAuthProcedureForExternalNsa(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IOAuthProcedureForExternalNsa"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetAccountId"},
{101, nullptr, "GetLinkedNintendoAccountId"},
{102, nullptr, "GetNickname"},
{103, nullptr, "GetProfileImage"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IOAuthProcedureForNintendoAccountLinkage final
: public ServiceFramework<IOAuthProcedureForNintendoAccountLinkage> {
public:
explicit IOAuthProcedureForNintendoAccountLinkage(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IOAuthProcedureForNintendoAccountLinkage"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetRequestWithTheme"},
{101, nullptr, "IsNetworkServiceAccountReplaced"},
{199, nullptr, "GetUrlForIntroductionOfExtraMembership"}, // 2.0.0 - 5.1.0
};
// clang-format on
RegisterHandlers(functions);
}
};
class INotifier final : public ServiceFramework<INotifier> {
public:
explicit INotifier(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "INotifier"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IProfileCommon : public ServiceFramework<IProfileCommon> {
public:
explicit IProfileCommon(Core::System& system_, const char* name, bool editor_commands,
Common::UUID user_id_, ProfileManager& profile_manager_)
: ServiceFramework{system_, name}, profile_manager{profile_manager_}, user_id{user_id_} {
static const FunctionInfo functions[] = {
{0, &IProfileCommon::Get, "Get"},
{1, &IProfileCommon::GetBase, "GetBase"},
{10, &IProfileCommon::GetImageSize, "GetImageSize"},
{11, &IProfileCommon::LoadImage, "LoadImage"},
};
RegisterHandlers(functions);
if (editor_commands) {
static const FunctionInfo editor_functions[] = {
{100, &IProfileCommon::Store, "Store"},
{101, &IProfileCommon::StoreWithImage, "StoreWithImage"},
};
RegisterHandlers(editor_functions);
}
}
protected:
void Get(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
ProfileData data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
} else {
LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}",
user_id.Format());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Get actual error code
}
}
void GetBase(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
if (profile_manager.GetProfileBase(user_id, profile_base)) {
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
} else {
LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Get actual error code
}
}
void LoadImage(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG);
rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
return;
}
const u32 size = SanitizeJPEGSize(image.GetSize());
std::vector<u8> buffer(size);
image.ReadBytes(buffer.data(), buffer.size());
ctx.WriteBuffer(buffer);
rb.Push<u32>(size);
}
void GetImageSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
} else {
rb.Push(SanitizeJPEGSize(image.GetSize()));
}
}
void Store(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto base = rp.PopRaw<ProfileBase>();
const auto user_data = ctx.ReadBuffer();
LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(base.username.data()), base.username.size()),
base.timestamp, base.user_uuid.Format());
if (user_data.size() < sizeof(ProfileData)) {
LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_BUFFER_SIZE);
return;
}
ProfileData data;
std::memcpy(&data, user_data.data(), sizeof(ProfileData));
if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) {
LOG_ERROR(Service_ACC, "Failed to update profile data and base!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_FAILED_SAVE_DATA);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StoreWithImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto base = rp.PopRaw<ProfileBase>();
const auto user_data = ctx.ReadBuffer();
const auto image_data = ctx.ReadBuffer(1);
LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(base.username.data()), base.username.size()),
base.timestamp, base.user_uuid.Format());
if (user_data.size() < sizeof(ProfileData)) {
LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_BUFFER_SIZE);
return;
}
ProfileData data;
std::memcpy(&data, user_data.data(), sizeof(ProfileData));
Common::FS::IOFile image(GetImagePath(user_id), "wb");
if (!image.IsOpen() || !image.Resize(image_data.size()) ||
image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
!profile_manager.SetProfileBaseAndData(user_id, base, data)) {
LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_FAILED_SAVE_DATA);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
ProfileManager& profile_manager;
Common::UUID user_id{Common::INVALID_UUID}; ///< The user id this profile refers to.
};
class IProfile final : public IProfileCommon {
public:
explicit IProfile(Core::System& system_, Common::UUID user_id_,
ProfileManager& profile_manager_)
: IProfileCommon{system_, "IProfile", false, user_id_, profile_manager_} {}
};
class IProfileEditor final : public IProfileCommon {
public:
explicit IProfileEditor(Core::System& system_, Common::UUID user_id_,
ProfileManager& profile_manager_)
: IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {}
};
class IAsyncContext final : public ServiceFramework<IAsyncContext> {
public:
explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
{1, nullptr, "Cancel"},
{2, nullptr, "HasDone"},
{3, nullptr, "GetResult"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class ISessionObject final : public ServiceFramework<ISessionObject> {
public:
explicit ISessionObject(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "ISessionObject"} {
// clang-format off
static const FunctionInfo functions[] = {
{999, nullptr, "Dummy"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IGuestLoginRequest final : public ServiceFramework<IGuestLoginRequest> {
public:
explicit IGuestLoginRequest(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IGuestLoginRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{11, nullptr, "Unknown"}, // 1.0.0 - 2.3.0 (the name is blank on Switchbrew)
{12, nullptr, "GetAccountId"},
{13, nullptr, "GetLinkedNintendoAccountId"},
{14, nullptr, "GetNickname"},
{15, nullptr, "GetProfileImage"},
{21, nullptr, "LoadIdTokenCache"}, // 3.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
};
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_)
: ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
{1, &IManagerForApplication::GetAccountId, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"},
{150, nullptr, "CreateAuthorizationRequest"},
{160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CheckAvailability(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(false); // TODO: Check when this is supposed to return true and when not
}
void GetAccountId(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u64>(user_id.GetNintendoID());
}
void StoreOpenContext(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
Common::UUID user_id;
};
// 6.0.0+
class IAsyncNetworkServiceLicenseKindContext final
: public ServiceFramework<IAsyncNetworkServiceLicenseKindContext> {
public:
explicit IAsyncNetworkServiceLicenseKindContext(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IAsyncNetworkServiceLicenseKindContext"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
{1, nullptr, "Cancel"},
{2, nullptr, "HasDone"},
{3, nullptr, "GetResult"},
{4, nullptr, "GetNetworkServiceLicenseKind"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 8.0.0+
class IOAuthProcedureForUserRegistration final
: public ServiceFramework<IOAuthProcedureForUserRegistration> {
public:
explicit IOAuthProcedureForUserRegistration(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IOAuthProcedureForUserRegistration"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetAccountId"},
{101, nullptr, "GetLinkedNintendoAccountId"},
{102, nullptr, "GetNickname"},
{103, nullptr, "GetProfileImage"},
{110, nullptr, "RegisterUserAsync"},
{111, nullptr, "GetUid"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class DAUTH_O final : public ServiceFramework<DAUTH_O> {
public:
explicit DAUTH_O(Core::System& system_, Common::UUID) : ServiceFramework{system_, "dauth:o"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData
{1, nullptr, "LoadAuthenticationTokenCache"}, // 6.0.0+
{2, nullptr, "InvalidateAuthenticationTokenCache"}, // 6.0.0+
{10, nullptr, "EnsureEdgeTokenCacheAsync"}, // 6.0.0+
{11, nullptr, "LoadEdgeTokenCache"}, // 6.0.0+
{12, nullptr, "InvalidateEdgeTokenCache"}, // 6.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
};
// 6.0.0+
class IAsyncResult final : public ServiceFramework<IAsyncResult> {
public:
explicit IAsyncResult(Core::System& system_, Common::UUID)
: ServiceFramework{system_, "IAsyncResult"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetResult"},
{1, nullptr, "Cancel"},
{2, nullptr, "IsAvailable"},
{3, nullptr, "GetSystemEvent"},
};
// clang-format on
RegisterHandlers(functions);
}
};
void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount()));
}
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(profile_manager->UserExists(user_id));
}
void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetAllUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
ctx.WriteBuffer(profile_manager->GetOpenUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser());
}
void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IProfile>(system, user_id, *profile_manager);
}
void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(profile_manager->CanSystemRegisterUser());
}
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(InitializeApplicationInfoBase());
}
void Module::Interface::InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
LOG_WARNING(Service_ACC, "(Partial implementation) called");
// TODO(ogniK): We require checking if the user actually owns the title and what not. As of
// currently, we assume the user owns the title. InitializeApplicationInfoBase SHOULD be called
// first then we do extra checks if the game is a digital copy.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(InitializeApplicationInfoBase());
}
ResultCode Module::Interface::InitializeApplicationInfoBase() {
if (application_info) {
LOG_ERROR(Service_ACC, "Application already initialized");
return ERR_ACCOUNTINFO_ALREADY_INITIALIZED;
}
// TODO(ogniK): This should be changed to reflect the target process for when we have multiple
// processes emulated. As we don't actually have pid support we should assume we're just using
// our own process
const auto& current_process = system.Kernel().CurrentProcess();
const auto launch_property =
system.GetARPManager().GetLaunchProperty(current_process->GetTitleID());
if (launch_property.Failed()) {
LOG_ERROR(Service_ACC, "Failed to get launch property");
return ERR_ACCOUNTINFO_BAD_APPLICATION;
}
switch (launch_property->base_game_storage_id) {
case FileSys::StorageId::GameCard:
application_info.application_type = ApplicationType::GameCard;
break;
case FileSys::StorageId::Host:
case FileSys::StorageId::NandUser:
case FileSys::StorageId::SdCard:
case FileSys::StorageId::None: // Yuzu specific, differs from hardware
application_info.application_type = ApplicationType::Digital;
break;
default:
LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}",
launch_property->base_game_storage_id);
return ERR_ACCOUNTINFO_BAD_APPLICATION;
}
LOG_WARNING(Service_ACC, "ApplicationInfo init required");
// TODO(ogniK): Actual initalization here
return RESULT_SUCCESS;
}
void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
}
void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
FileSys::NACP nacp;
const auto res = system.GetAppLoader().ReadControlData(nacp);
bool is_locked = false;
if (res != Loader::ResultStatus::Success) {
const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(),
system.GetFileSystemController(),
system.GetContentProvider()};
const auto nacp_unique = pm.GetControlMetadata().first;
if (nacp_unique != nullptr) {
is_locked = nacp_unique->GetUserAccountSwitchLock();
} else {
LOG_ERROR(Service_ACC, "nacp_unique is null!");
}
} else {
is_locked = nacp.GetUserAccountSwitchLock();
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(is_locked);
}
void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();
LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IProfileEditor>(system, user_id, *profile_manager);
}
void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// All users should be qualified. We don't actually have parental control or anything to do with
// nintendo online currently. We're just going to assume the user running the game has access to
// the game regardless of parental control settings.
ctx.WriteBuffer(profile_manager->GetAllUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// This is similar to GetBaasAccountManagerForApplication
// This command is used concurrently with ListOpenContextStoredUsers
// TODO: Find the differences between this and GetBaasAccountManagerForApplication
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
}
void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// TODO(ogniK): Handle open contexts
ctx.WriteBuffer(profile_manager->GetOpenUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
// access to use the network or not by the looks of it
IPC::ResponseBuilder rb{ctx, 6};
if (profile_manager->GetUserCount() != 1) {
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u128>(Common::INVALID_UUID);
return;
}
const auto user_list = profile_manager->GetAllUsers();
if (std::all_of(user_list.begin(), user_list.end(),
[](const auto& user) { return user.uuid == Common::INVALID_UUID; })) {
rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Find the correct error code
rb.PushRaw<u128>(Common::INVALID_UUID);
return;
}
// Select the first user we have
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u128>(profile_manager->GetUser(0)->uuid);
}
Module::Interface::Interface(std::shared_ptr<Module> module_,
std::shared_ptr<ProfileManager> profile_manager_,
Core::System& system_, const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)}, profile_manager{std::move(
profile_manager_)} {}
Module::Interface::~Interface() = default;
void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
auto profile_manager = std::make_shared<ProfileManager>();
std::make_shared<ACC_AA>(module, profile_manager, system)
->InstallAsService(system.ServiceManager());
std::make_shared<ACC_SU>(module, profile_manager, system)
->InstallAsService(system.ServiceManager());
std::make_shared<ACC_U0>(module, profile_manager, system)
->InstallAsService(system.ServiceManager());
std::make_shared<ACC_U1>(module, profile_manager, system)
->InstallAsService(system.ServiceManager());
}
} // namespace Service::Account

69
src/core/hle/service/acc/acc.h Executable file
View File

@@ -0,0 +1,69 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
namespace Service::Account {
class ProfileManager;
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> module_,
std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_,
const char* name);
~Interface() override;
void GetUserCount(Kernel::HLERequestContext& ctx);
void GetUserExistence(Kernel::HLERequestContext& ctx);
void ListAllUsers(Kernel::HLERequestContext& ctx);
void ListOpenUsers(Kernel::HLERequestContext& ctx);
void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
void GetProfile(Kernel::HLERequestContext& ctx);
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
void InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx);
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
void LoadOpenContext(Kernel::HLERequestContext& ctx);
void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
private:
ResultCode InitializeApplicationInfoBase();
enum class ApplicationType : u32_le {
GameCard = 0,
Digital = 1,
Unknown = 3,
};
struct ApplicationInfo {
Service::Glue::ApplicationLaunchProperty launch_property;
ApplicationType application_type;
constexpr explicit operator bool() const {
return launch_property.title_id != 0x0;
}
};
ApplicationInfo application_info{};
protected:
std::shared_ptr<Module> module;
std::shared_ptr<ProfileManager> profile_manager;
};
};
/// Registers all ACC services with the specified service manager.
void InstallInterfaces(Core::System& system);
} // namespace Service::Account

View File

@@ -0,0 +1,24 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/acc/acc_aa.h"
namespace Service::Account {
ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system)
: Module::Interface(std::move(module), std::move(profile_manager), system, "acc:aa") {
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"},
{2, nullptr, "GetDeviceAccountId"},
{50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
{51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
};
RegisterHandlers(functions);
}
ACC_AA::~ACC_AA() = default;
} // namespace Service::Account

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/acc/acc.h"
namespace Service::Account {
class ACC_AA final : public Module::Interface {
public:
explicit ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system);
~ACC_AA() override;
};
} // namespace Service::Account

View File

@@ -0,0 +1,68 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/acc/acc_su.h"
namespace Service::Account {
ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system)
: Module::Interface(std::move(module), std::move(profile_manager), system, "acc:su") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_SU::GetUserCount, "GetUserCount"},
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
{2, &ACC_SU::ListAllUsers, "ListAllUsers"},
{3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_SU::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
{120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
{130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
{140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
{190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
{191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
{200, nullptr, "BeginUserRegistration"},
{201, nullptr, "CompleteUserRegistration"},
{202, nullptr, "CancelUserRegistration"},
{203, nullptr, "DeleteUser"},
{204, nullptr, "SetUserPosition"},
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
{206, nullptr, "CompleteUserRegistrationForcibly"},
{210, nullptr, "CreateFloatingRegistrationRequest"}, // 3.0.0+
{211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
{212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
{230, nullptr, "AuthenticateServiceAsync"},
{250, nullptr, "GetBaasAccountAdministrator"},
{290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
{291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, // 3.0.0+
{299, nullptr, "SuspendBackgroundDaemon"},
{997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
// clang-format on
RegisterHandlers(functions);
}
ACC_SU::~ACC_SU() = default;
} // namespace Service::Account

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/acc/acc.h"
namespace Service::Account {
class ACC_SU final : public Module::Interface {
public:
explicit ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system);
~ACC_SU() override;
};
} // namespace Service::Account

View File

@@ -0,0 +1,45 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/acc/acc_u0.h"
namespace Service::Account {
ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system)
: Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u0") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U0::GetUserCount, "GetUserCount"},
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
{2, &ACC_U0::ListAllUsers, "ListAllUsers"},
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U0::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
{102, nullptr, "AuthenticateApplicationAsync"},
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
{130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
ACC_U0::~ACC_U0() = default;
} // namespace Service::Account

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/acc/acc.h"
namespace Service::Account {
class ACC_U0 final : public Module::Interface {
public:
explicit ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system);
~ACC_U0() override;
};
} // namespace Service::Account

View File

@@ -0,0 +1,53 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/acc/acc_u1.h"
namespace Service::Account {
ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system)
: Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u1") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U1::GetUserCount, "GetUserCount"},
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
{2, &ACC_U1::ListAllUsers, "ListAllUsers"},
{3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U1::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
{120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
{130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
{140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
{190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
{191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
{997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
// clang-format on
RegisterHandlers(functions);
}
ACC_U1::~ACC_U1() = default;
} // namespace Service::Account

View File

@@ -0,0 +1,18 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/acc/acc.h"
namespace Service::Account {
class ACC_U1 final : public Module::Interface {
public:
explicit ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
Core::System& system);
~ACC_U1() override;
};
} // namespace Service::Account

View File

@@ -0,0 +1,14 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
namespace Service::Account {
constexpr ResultCode ERR_ACCOUNTINFO_BAD_APPLICATION{ErrorModule::Account, 22};
constexpr ResultCode ERR_ACCOUNTINFO_ALREADY_INITIALIZED{ErrorModule::Account, 41};
} // namespace Service::Account

View File

@@ -0,0 +1,396 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <random>
#include <fmt/format.h>
#include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
namespace Service::Account {
namespace FS = Common::FS;
using Common::UUID;
struct UserRaw {
UUID uuid{Common::INVALID_UUID};
UUID uuid2{Common::INVALID_UUID};
u64 timestamp{};
ProfileUsername username{};
ProfileData extra_data{};
};
static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
struct ProfileDataRaw {
INSERT_PADDING_BYTES(0x10);
std::array<UserRaw, MAX_USERS> users{};
};
static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
// TODO(ogniK): Get actual error codes
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1));
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2));
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
ProfileManager::ProfileManager() {
ParseUserSaveFile();
if (user_count == 0)
CreateNewUser(UUID::Generate(), "yuzu");
auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
if (UserExistsIndex(current))
current = 0;
OpenUser(*GetUser(current));
}
ProfileManager::~ProfileManager() {
WriteUserSaveFile();
}
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
if (user_count >= MAX_USERS) {
return std::nullopt;
}
profiles[user_count] = profile;
return user_count++;
}
/// Deletes a specific profile based on it's profile index
bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
if (index >= MAX_USERS || index >= user_count) {
return false;
}
if (index < user_count - 1) {
std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end());
}
profiles.back() = {};
user_count--;
return true;
}
/// Helper function to register a user to the system
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
if (!AddToProfiles(user)) {
return ERROR_TOO_MANY_USERS;
}
return RESULT_SUCCESS;
}
/// Create a new user on the system. If the uuid of the user already exists, the user is not
/// created.
ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
if (user_count == MAX_USERS) {
return ERROR_TOO_MANY_USERS;
}
if (!uuid) {
return ERROR_ARGUMENT_IS_NULL;
}
if (username[0] == 0x0) {
return ERROR_ARGUMENT_IS_NULL;
}
if (std::any_of(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
return ERROR_USER_ALREADY_EXISTS;
}
return AddUser({
.user_uuid = uuid,
.username = username,
.creation_time = 0,
.data = {},
.is_open = false,
});
}
/// Creates a new user on the system. This function allows a much simpler method of registration
/// specifically by allowing an std::string for the username. This is required specifically since
/// we're loading a string straight from the config
ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
ProfileUsername username_output{};
if (username.size() > username_output.size()) {
std::copy_n(username.begin(), username_output.size(), username_output.begin());
} else {
std::copy(username.begin(), username.end(), username_output.begin());
}
return CreateNewUser(uuid, username_output);
}
std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
if (index >= MAX_USERS) {
return std::nullopt;
}
return profiles[index].user_uuid;
}
/// Returns a users profile index based on their user id.
std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
return std::nullopt;
}
const auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
return std::nullopt;
}
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
}
/// Returns a users profile index based on their profile
std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
return GetUserIndex(user.user_uuid);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
if (!index || index >= MAX_USERS) {
return false;
}
const auto& prof_info = profiles[*index];
profile.user_uuid = prof_info.user_uuid;
profile.username = prof_info.username;
profile.timestamp = prof_info.creation_time;
return true;
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
const auto idx = GetUserIndex(uuid);
return GetProfileBase(idx, profile);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const {
return GetProfileBase(user.user_uuid, profile);
}
/// Returns the current user count on the system. We keep a variable which tracks the count so we
/// don't have to loop the internal profile array every call.
std::size_t ProfileManager::GetUserCount() const {
return user_count;
}
/// Lists the current "opened" users on the system. Users are typically not open until they sign
/// into something or pick a profile. As of right now users should all be open until qlaunch is
/// booting
std::size_t ProfileManager::GetOpenUserCount() const {
return std::count_if(profiles.begin(), profiles.end(),
[](const ProfileInfo& p) { return p.is_open; });
}
/// Checks if a user id exists in our profile manager
bool ProfileManager::UserExists(UUID uuid) const {
return GetUserIndex(uuid).has_value();
}
bool ProfileManager::UserExistsIndex(std::size_t index) const {
if (index >= MAX_USERS)
return false;
return profiles[index].user_uuid.uuid != Common::INVALID_UUID;
}
/// Opens a specific user
void ProfileManager::OpenUser(UUID uuid) {
const auto idx = GetUserIndex(uuid);
if (!idx) {
return;
}
profiles[*idx].is_open = true;
last_opened_user = uuid;
}
/// Closes a specific user
void ProfileManager::CloseUser(UUID uuid) {
const auto idx = GetUserIndex(uuid);
if (!idx) {
return;
}
profiles[*idx].is_open = false;
}
/// Gets all valid user ids on the system
UserIDArray ProfileManager::GetAllUsers() const {
UserIDArray output;
std::transform(profiles.begin(), profiles.end(), output.begin(),
[](const ProfileInfo& p) { return p.user_uuid; });
return output;
}
/// Get all the open users on the system and zero out the rest of the data. This is specifically
/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out
UserIDArray ProfileManager::GetOpenUsers() const {
UserIDArray output;
std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
if (p.is_open)
return p.user_uuid;
return UUID{Common::INVALID_UUID};
});
std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; });
return output;
}
/// Returns the last user which was opened
UUID ProfileManager::GetLastOpenedUser() const {
return last_opened_user;
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const {
if (GetProfileBase(index, profile)) {
data = profiles[*index].data;
return true;
}
return false;
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
ProfileData& data) const {
const auto idx = GetUserIndex(uuid);
return GetProfileBaseAndData(idx, profile, data);
}
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
ProfileData& data) const {
return GetProfileBaseAndData(user.user_uuid, profile, data);
}
/// Returns if the system is allowing user registrations or not
bool ProfileManager::CanSystemRegisterUser() const {
return false; // TODO(ogniK): Games shouldn't have
// access to user registration, when we
// emulate qlaunch. Update this to dynamically change.
}
bool ProfileManager::RemoveUser(UUID uuid) {
const auto index = GetUserIndex(uuid);
if (!index) {
return false;
}
profiles[*index] = ProfileInfo{};
std::stable_partition(profiles.begin(), profiles.end(),
[](const ProfileInfo& profile) { return profile.user_uuid; });
return true;
}
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
const auto index = GetUserIndex(uuid);
if (!index || profile_new.user_uuid == UUID(Common::INVALID_UUID)) {
return false;
}
auto& profile = profiles[*index];
profile.user_uuid = profile_new.user_uuid;
profile.username = profile_new.username;
profile.creation_time = profile_new.timestamp;
return true;
}
bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
const ProfileData& data_new) {
const auto index = GetUserIndex(uuid);
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new;
return true;
}
return false;
}
void ProfileManager::ParseUserSaveFile() {
const FS::IOFile save(
FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
"user 'yuzu' with random UUID.");
return;
}
ProfileDataRaw data;
if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) {
LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
"'yuzu' with random UUID.");
return;
}
for (const auto& user : data.users) {
if (user.uuid == UUID(Common::INVALID_UUID)) {
continue;
}
AddUser({
.user_uuid = user.uuid,
.username = user.username,
.creation_time = user.timestamp,
.data = user.extra_data,
.is_open = false,
});
}
std::stable_partition(profiles.begin(), profiles.end(),
[](const ProfileInfo& profile) { return profile.user_uuid; });
}
void ProfileManager::WriteUserSaveFile() {
ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) {
raw.users[i] = {
.uuid = profiles[i].user_uuid,
.uuid2 = profiles[i].user_uuid,
.timestamp = profiles[i].creation_time,
.username = profiles[i].username,
.extra_data = profiles[i].data,
};
}
const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010";
if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) {
FS::Delete(raw_path);
}
const auto path =
FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
if (!FS::CreateFullPath(path)) {
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
"nand/system/save/8000000000000010/su/avators to mitigate this "
"issue.");
return;
}
FS::IOFile save(path, "wb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
"made in current session will be saved.");
return;
}
save.Resize(sizeof(ProfileDataRaw));
save.WriteBytes(&raw, sizeof(ProfileDataRaw));
}
}; // namespace Service::Account

View File

@@ -0,0 +1,109 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <optional>
#include "common/common_types.h"
#include "common/swap.h"
#include "common/uuid.h"
#include "core/hle/result.h"
namespace Service::Account {
constexpr std::size_t MAX_USERS{8};
constexpr std::size_t profile_username_size{32};
using ProfileUsername = std::array<u8, profile_username_size>;
using UserIDArray = std::array<Common::UUID, MAX_USERS>;
/// Contains extra data related to a user.
/// TODO: RE this structure
struct ProfileData {
INSERT_PADDING_WORDS(1);
u32 icon_id{};
u8 bg_color_id{};
INSERT_PADDING_BYTES(0x7);
INSERT_PADDING_BYTES(0x10);
INSERT_PADDING_BYTES(0x60);
};
static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size");
/// This holds general information about a users profile. This is where we store all the information
/// based on a specific user
struct ProfileInfo {
Common::UUID user_uuid{Common::INVALID_UUID};
ProfileUsername username{};
u64 creation_time{};
ProfileData data{}; // TODO(ognik): Work out what this is
bool is_open{};
};
struct ProfileBase {
Common::UUID user_uuid{Common::INVALID_UUID};
u64_le timestamp{};
ProfileUsername username{};
// Zero out all the fields to make the profile slot considered "Empty"
void Invalidate() {
user_uuid.Invalidate();
timestamp = 0;
username.fill(0);
}
};
static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
/// The profile manager is used for handling multiple user profiles at once. It keeps track of open
/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo"
/// objects
class ProfileManager {
public:
ProfileManager();
~ProfileManager();
ResultCode AddUser(const ProfileInfo& user);
ResultCode CreateNewUser(Common::UUID uuid, const ProfileUsername& username);
ResultCode CreateNewUser(Common::UUID uuid, const std::string& username);
std::optional<Common::UUID> GetUser(std::size_t index) const;
std::optional<std::size_t> GetUserIndex(const Common::UUID& uuid) const;
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const;
bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, ProfileData& data) const;
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
ProfileData& data) const;
std::size_t GetUserCount() const;
std::size_t GetOpenUserCount() const;
bool UserExists(Common::UUID uuid) const;
bool UserExistsIndex(std::size_t index) const;
void OpenUser(Common::UUID uuid);
void CloseUser(Common::UUID uuid);
UserIDArray GetOpenUsers() const;
UserIDArray GetAllUsers() const;
Common::UUID GetLastOpenedUser() const;
bool CanSystemRegisterUser() const;
bool RemoveUser(Common::UUID uuid);
bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
const ProfileData& data_new);
private:
void ParseUserSaveFile();
void WriteUserSaveFile();
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
std::array<ProfileInfo, MAX_USERS> profiles{};
std::size_t user_count{};
Common::UUID last_opened_user{Common::INVALID_UUID};
};
}; // namespace Service::Account

1733
src/core/hle/service/am/am.cpp Executable file

File diff suppressed because it is too large Load Diff

332
src/core/hle/service/am/am.h Executable file
View File

@@ -0,0 +1,332 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <memory>
#include <queue>
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/service.h"
namespace Kernel {
class KernelCore;
class TransferMemory;
} // namespace Kernel
namespace Service::NVFlinger {
class NVFlinger;
}
namespace Service::AM {
enum SystemLanguage {
Japanese = 0,
English = 1, // en-US
French = 2,
German = 3,
Italian = 4,
Spanish = 5,
Chinese = 6,
Korean = 7,
Dutch = 8,
Portuguese = 9,
Russian = 10,
Taiwanese = 11,
BritishEnglish = 12, // en-GB
CanadianFrench = 13,
LatinAmericanSpanish = 14, // es-419
// 4.0.0+
SimplifiedChinese = 15,
TraditionalChinese = 16,
};
class AppletMessageQueue {
public:
enum class AppletMessage : u32 {
NoMessage = 0,
ExitRequested = 4,
FocusStateChanged = 15,
OperationModeChanged = 30,
PerformanceModeChanged = 31,
};
explicit AppletMessageQueue(Kernel::KernelCore& kernel);
~AppletMessageQueue();
const std::shared_ptr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const;
const std::shared_ptr<Kernel::ReadableEvent>& GetOperationModeChangedEvent() const;
void PushMessage(AppletMessage msg);
AppletMessage PopMessage();
std::size_t GetMessageCount() const;
void OperationModeChanged();
void RequestExit();
private:
std::queue<AppletMessage> messages;
Kernel::EventPair on_new_message;
Kernel::EventPair on_operation_mode_changed;
};
class IWindowController final : public ServiceFramework<IWindowController> {
public:
explicit IWindowController(Core::System& system_);
~IWindowController() override;
private:
void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
};
class IAudioController final : public ServiceFramework<IAudioController> {
public:
explicit IAudioController(Core::System& system_);
~IAudioController() override;
private:
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
static constexpr float min_allowed_volume = 0.0f;
static constexpr float max_allowed_volume = 1.0f;
float main_applet_volume{0.25f};
float library_applet_volume{max_allowed_volume};
float transparent_volume_rate{min_allowed_volume};
// Volume transition fade time in nanoseconds.
// e.g. If the main applet volume was 0% and was changed to 50%
// with a fade of 50ns, then over the course of 50ns,
// the volume will gradually fade up to 50%
std::chrono::nanoseconds fade_time_ns{0};
};
class IDisplayController final : public ServiceFramework<IDisplayController> {
public:
explicit IDisplayController(Core::System& system_);
~IDisplayController() override;
};
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
public:
explicit IDebugFunctions(Core::System& system_);
~IDebugFunctions() override;
};
class ISelfController final : public ServiceFramework<ISelfController> {
public:
explicit ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_);
~ISelfController() override;
private:
void Exit(Kernel::HLERequestContext& ctx);
void LockExit(Kernel::HLERequestContext& ctx);
void UnlockExit(Kernel::HLERequestContext& ctx);
void EnterFatalSection(Kernel::HLERequestContext& ctx);
void LeaveFatalSection(Kernel::HLERequestContext& ctx);
void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx);
void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx);
void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx);
void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx);
void SetAlbumImageOrientation(Kernel::HLERequestContext& ctx);
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
void CreateManagedDisplaySeparableLayer(Kernel::HLERequestContext& ctx);
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx);
void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
enum class ScreenshotPermission : u32 {
Inherit = 0,
Enable = 1,
Disable = 2,
};
NVFlinger::NVFlinger& nvflinger;
Kernel::EventPair launchable_event;
Kernel::EventPair accumulated_suspended_tick_changed_event;
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
bool is_auto_sleep_disabled = false;
ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
explicit ICommonStateGetter(Core::System& system_,
std::shared_ptr<AppletMessageQueue> msg_queue_);
~ICommonStateGetter() override;
private:
enum class FocusState : u8 {
InFocus = 1,
NotInFocus = 2,
};
enum class OperationMode : u8 {
Handheld = 0,
Docked = 1,
};
void GetEventHandle(Kernel::HLERequestContext& ctx);
void ReceiveMessage(Kernel::HLERequestContext& ctx);
void GetCurrentFocusState(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx);
void GetOperationMode(Kernel::HLERequestContext& ctx);
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
void GetBootMode(Kernel::HLERequestContext& ctx);
void IsVrModeEnabled(Kernel::HLERequestContext& ctx);
void SetVrModeEnabled(Kernel::HLERequestContext& ctx);
void SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx);
void EndVrModeEx(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx);
void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
std::shared_ptr<AppletMessageQueue> msg_queue;
bool vr_mode_state{};
};
class IStorageImpl {
public:
virtual ~IStorageImpl();
virtual std::vector<u8>& GetData() = 0;
virtual const std::vector<u8>& GetData() const = 0;
virtual std::size_t GetSize() const = 0;
};
class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(Core::System& system_, std::vector<u8>&& buffer);
~IStorage() override;
std::vector<u8>& GetData() {
return impl->GetData();
}
const std::vector<u8>& GetData() const {
return impl->GetData();
}
std::size_t GetSize() const {
return impl->GetSize();
}
private:
void Register();
void Open(Kernel::HLERequestContext& ctx);
std::shared_ptr<IStorageImpl> impl;
};
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
public:
explicit IStorageAccessor(Core::System& system_, IStorage& backing_);
~IStorageAccessor() override;
private:
void GetSize(Kernel::HLERequestContext& ctx);
void Write(Kernel::HLERequestContext& ctx);
void Read(Kernel::HLERequestContext& ctx);
IStorage& backing;
};
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
public:
explicit ILibraryAppletCreator(Core::System& system_);
~ILibraryAppletCreator() override;
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
public:
explicit IApplicationFunctions(Core::System& system_);
~IApplicationFunctions() override;
private:
void PopLaunchParameter(Kernel::HLERequestContext& ctx);
void CreateApplicationAndRequestToStartForQuest(Kernel::HLERequestContext& ctx);
void EnsureSaveData(Kernel::HLERequestContext& ctx);
void SetTerminateResult(Kernel::HLERequestContext& ctx);
void GetDisplayVersion(Kernel::HLERequestContext& ctx);
void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx);
void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
void NotifyRunning(Kernel::HLERequestContext& ctx);
void GetPseudoDeviceId(Kernel::HLERequestContext& ctx);
void ExtendSaveData(Kernel::HLERequestContext& ctx);
void GetSaveDataSize(Kernel::HLERequestContext& ctx);
void BeginBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx);
void EndBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx);
void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
void InitializeApplicationCopyrightFrameBuffer(Kernel::HLERequestContext& ctx);
void SetApplicationCopyrightImage(Kernel::HLERequestContext& ctx);
void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
void ExecuteProgram(Kernel::HLERequestContext& ctx);
void ClearUserChannel(Kernel::HLERequestContext& ctx);
void UnpopToUserChannel(Kernel::HLERequestContext& ctx);
void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false;
s32 previous_program_index{-1};
Kernel::EventPair gpu_error_detected_event;
Kernel::EventPair friend_invitation_storage_channel_event;
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
public:
explicit IHomeMenuFunctions(Core::System& system_);
~IHomeMenuFunctions() override;
private:
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
void GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx);
Kernel::EventPair pop_from_general_channel_event;
};
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
public:
explicit IGlobalStateController(Core::System& system_);
~IGlobalStateController() override;
};
class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
public:
explicit IApplicationCreator(Core::System& system_);
~IApplicationCreator() override;
};
class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
public:
explicit IProcessWindingController(Core::System& system_);
~IProcessWindingController() override;
};
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system);
} // namespace Service::AM

View File

@@ -0,0 +1,278 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
namespace Service::AM {
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
explicit ILibraryAppletProxy(NVFlinger::NVFlinger& nvflinger_,
std::shared_ptr<AppletMessageQueue> msg_queue_,
Core::System& system_)
: ServiceFramework{system_, "ILibraryAppletProxy"}, nvflinger{nvflinger_},
msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &ILibraryAppletProxy::GetSelfController, "GetSelfController"},
{2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"},
{3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"},
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{21, nullptr, "GetAppletCommonFunctions"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>(system);
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>(system);
}
void GetProcessWindingController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IProcessWindingController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>(system);
}
NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
explicit ISystemAppletProxy(NVFlinger::NVFlinger& nvflinger_,
std::shared_ptr<AppletMessageQueue> msg_queue_,
Core::System& system_)
: ServiceFramework{system_, "ISystemAppletProxy"}, nvflinger{nvflinger_},
msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &ISystemAppletProxy::GetSelfController, "GetSelfController"},
{2, &ISystemAppletProxy::GetWindowController, "GetWindowController"},
{3, &ISystemAppletProxy::GetAudioController, "GetAudioController"},
{4, &ISystemAppletProxy::GetDisplayController, "GetDisplayController"},
{10, nullptr, "GetProcessWindingController"},
{11, &ISystemAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
{23, nullptr, "GetAppletCommonFunctions"},
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>(system);
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IHomeMenuFunctions>(system);
}
void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IGlobalStateController>(system);
}
void GetApplicationCreator(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationCreator>(system);
}
NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
AppletAE::AppletAE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_,
Core::System& system_)
: ServiceFramework{system_, "appletAE"}, nvflinger{nvflinger_}, msg_queue{
std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
{200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
{201, &AppletAE::OpenLibraryAppletProxy, "OpenLibraryAppletProxy"},
{300, nullptr, "OpenOverlayAppletProxy"},
{350, nullptr, "OpenSystemApplicationProxy"},
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
{410, nullptr, "GetSystemAppletControllerForDebug"},
{1000, nullptr, "GetDebugFunctions"},
};
// clang-format on
RegisterHandlers(functions);
}
AppletAE::~AppletAE() = default;
const std::shared_ptr<AppletMessageQueue>& AppletAE::GetMessageQueue() const {
return msg_queue;
}
} // namespace Service::AM

View File

@@ -0,0 +1,42 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
namespace Service {
namespace FileSystem {
class FileSystemController;
}
namespace NVFlinger {
class NVFlinger;
}
namespace AM {
class AppletMessageQueue;
class AppletAE final : public ServiceFramework<AppletAE> {
public:
explicit AppletAE(NVFlinger::NVFlinger& nvflinger_,
std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_);
~AppletAE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
private:
void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
} // namespace AM
} // namespace Service

View File

@@ -0,0 +1,130 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/nvflinger/nvflinger.h"
namespace Service::AM {
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
explicit IApplicationProxy(NVFlinger::NVFlinger& nvflinger_,
std::shared_ptr<AppletMessageQueue> msg_queue_,
Core::System& system_)
: ServiceFramework{system_, "IApplicationProxy"}, nvflinger{nvflinger_},
msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &IApplicationProxy::GetSelfController, "GetSelfController"},
{2, &IApplicationProxy::GetWindowController, "GetWindowController"},
{3, &IApplicationProxy::GetAudioController, "GetAudioController"},
{4, &IApplicationProxy::GetDisplayController, "GetDisplayController"},
{10, nullptr, "GetProcessWindingController"},
{11, &IApplicationProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &IApplicationProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{1000, &IApplicationProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetAudioController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>(system);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>(system);
}
NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
}
AppletOE::AppletOE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_,
Core::System& system_)
: ServiceFramework{system_, "appletOE"}, nvflinger{nvflinger_}, msg_queue{
std::move(msg_queue_)} {
static const FunctionInfo functions[] = {
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};
RegisterHandlers(functions);
}
AppletOE::~AppletOE() = default;
const std::shared_ptr<AppletMessageQueue>& AppletOE::GetMessageQueue() const {
return msg_queue;
}
} // namespace Service::AM

View File

@@ -0,0 +1,40 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
namespace Service {
namespace FileSystem {
class FileSystemController;
}
namespace NVFlinger {
class NVFlinger;
}
namespace AM {
class AppletMessageQueue;
class AppletOE final : public ServiceFramework<AppletOE> {
public:
explicit AppletOE(NVFlinger::NVFlinger& nvflinger_,
std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_);
~AppletOE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
private:
void OpenApplicationProxy(Kernel::HLERequestContext& ctx);
NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
} // namespace AM
} // namespace Service

View File

@@ -0,0 +1,268 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "core/core.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/controller.h"
#include "core/hle/service/am/applets/error.h"
#include "core/hle/service/am/applets/general_backend.h"
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/am/applets/web_browser.h"
namespace Service::AM::Applets {
AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
state_changed_event =
Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:StateChangedEvent");
pop_out_data_event =
Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:PopDataOutEvent");
pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair(
kernel, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
}
AppletDataBroker::~AppletDataBroker() = default;
AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const {
std::vector<std::vector<u8>> out_normal;
for (const auto& storage : in_channel) {
out_normal.push_back(storage->GetData());
}
std::vector<std::vector<u8>> out_interactive;
for (const auto& storage : in_interactive_channel) {
out_interactive.push_back(storage->GetData());
}
return {std::move(out_normal), std::move(out_interactive)};
}
std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
if (out_channel.empty())
return nullptr;
auto out = std::move(out_channel.front());
out_channel.pop_front();
pop_out_data_event.writable->Clear();
return out;
}
std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
if (in_channel.empty())
return nullptr;
auto out = std::move(in_channel.front());
in_channel.pop_front();
return out;
}
std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
if (out_interactive_channel.empty())
return nullptr;
auto out = std::move(out_interactive_channel.front());
out_interactive_channel.pop_front();
pop_interactive_out_data_event.writable->Clear();
return out;
}
std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
if (in_interactive_channel.empty())
return nullptr;
auto out = std::move(in_interactive_channel.front());
in_interactive_channel.pop_front();
return out;
}
void AppletDataBroker::PushNormalDataFromGame(std::shared_ptr<IStorage>&& storage) {
in_channel.emplace_back(std::move(storage));
}
void AppletDataBroker::PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage) {
out_channel.emplace_back(std::move(storage));
pop_out_data_event.writable->Signal();
}
void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage) {
in_interactive_channel.emplace_back(std::move(storage));
}
void AppletDataBroker::PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage) {
out_interactive_channel.emplace_back(std::move(storage));
pop_interactive_out_data_event.writable->Signal();
}
void AppletDataBroker::SignalStateChanged() const {
state_changed_event.writable->Signal();
}
std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetNormalDataEvent() const {
return pop_out_data_event.readable;
}
std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const {
return pop_interactive_out_data_event.readable;
}
std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent() const {
return state_changed_event.readable;
}
Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
Applet::~Applet() = default;
void Applet::Initialize() {
const auto common = broker.PopNormalDataToApplet();
ASSERT(common != nullptr);
const auto common_data = common->GetData();
ASSERT(common_data.size() >= sizeof(CommonArguments));
std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments));
initialized = true;
}
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
parental_controls{std::move(parental_controls_applet)},
photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
AppletManager::AppletManager(Core::System& system_) : system{system_} {}
AppletManager::~AppletManager() = default;
const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
return frontend;
}
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.controller != nullptr) {
frontend.controller = std::move(set.controller);
}
if (set.error != nullptr) {
frontend.error = std::move(set.error);
}
if (set.parental_controls != nullptr) {
frontend.parental_controls = std::move(set.parental_controls);
}
if (set.photo_viewer != nullptr) {
frontend.photo_viewer = std::move(set.photo_viewer);
}
if (set.profile_select != nullptr) {
frontend.profile_select = std::move(set.profile_select);
}
if (set.software_keyboard != nullptr) {
frontend.software_keyboard = std::move(set.software_keyboard);
}
if (set.web_browser != nullptr) {
frontend.web_browser = std::move(set.web_browser);
}
}
void AppletManager::SetDefaultAppletFrontendSet() {
ClearAll();
SetDefaultAppletsIfMissing();
}
void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.controller == nullptr) {
frontend.controller =
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
if (frontend.parental_controls == nullptr) {
frontend.parental_controls =
std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
}
if (frontend.photo_viewer == nullptr) {
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
}
if (frontend.profile_select == nullptr) {
frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
}
if (frontend.software_keyboard == nullptr) {
frontend.software_keyboard =
std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
}
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
}
void AppletManager::ClearAll() {
frontend = {};
}
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(system, *frontend.parental_controls);
case AppletId::Controller:
return std::make_shared<Controller>(system, *frontend.controller);
case AppletId::Error:
return std::make_shared<Error>(system, *frontend.error);
case AppletId::ProfileSelect:
return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
case AppletId::Web:
case AppletId::Shop:
case AppletId::OfflineWeb:
case AppletId::LoginShare:
case AppletId::WebAuth:
return std::make_shared<WebBrowser>(system, *frontend.web_browser);
case AppletId::PhotoViewer:
return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
default:
UNIMPLEMENTED_MSG(
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
static_cast<u8>(id));
return std::make_shared<StubApplet>(system, id);
}
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,209 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <queue>
#include "common/swap.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/writable_event.h"
union ResultCode;
namespace Core {
class System;
}
namespace Core::Frontend {
class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
class ParentalControlsApplet;
class PhotoViewerApplet;
class ProfileSelectApplet;
class SoftwareKeyboardApplet;
class WebBrowserApplet;
} // namespace Core::Frontend
namespace Kernel {
class KernelCore;
}
namespace Service::AM {
class IStorage;
namespace Applets {
enum class AppletId : u32 {
OverlayDisplay = 0x02,
QLaunch = 0x03,
Starter = 0x04,
Auth = 0x0A,
Cabinet = 0x0B,
Controller = 0x0C,
DataErase = 0x0D,
Error = 0x0E,
NetConnect = 0x0F,
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
MiiEdit = 0x12,
Web = 0x13,
Shop = 0x14,
PhotoViewer = 0x15,
Settings = 0x16,
OfflineWeb = 0x17,
LoginShare = 0x18,
WebAuth = 0x19,
MyPage = 0x1A,
};
class AppletDataBroker final {
public:
explicit AppletDataBroker(Kernel::KernelCore& kernel_);
~AppletDataBroker();
struct RawChannelData {
std::vector<std::vector<u8>> normal;
std::vector<std::vector<u8>> interactive;
};
// Retrieves but does not pop the data sent to applet.
RawChannelData PeekDataToAppletForDebug() const;
std::shared_ptr<IStorage> PopNormalDataToGame();
std::shared_ptr<IStorage> PopNormalDataToApplet();
std::shared_ptr<IStorage> PopInteractiveDataToGame();
std::shared_ptr<IStorage> PopInteractiveDataToApplet();
void PushNormalDataFromGame(std::shared_ptr<IStorage>&& storage);
void PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage);
void PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage);
void PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage);
void SignalStateChanged() const;
std::shared_ptr<Kernel::ReadableEvent> GetNormalDataEvent() const;
std::shared_ptr<Kernel::ReadableEvent> GetInteractiveDataEvent() const;
std::shared_ptr<Kernel::ReadableEvent> GetStateChangedEvent() const;
private:
// Queues are named from applet's perspective
// PopNormalDataToApplet and PushNormalDataFromGame
std::deque<std::shared_ptr<IStorage>> in_channel;
// PopNormalDataToGame and PushNormalDataFromApplet
std::deque<std::shared_ptr<IStorage>> out_channel;
// PopInteractiveDataToApplet and PushInteractiveDataFromGame
std::deque<std::shared_ptr<IStorage>> in_interactive_channel;
// PopInteractiveDataToGame and PushInteractiveDataFromApplet
std::deque<std::shared_ptr<IStorage>> out_interactive_channel;
Kernel::EventPair state_changed_event;
// Signaled on PushNormalDataFromApplet
Kernel::EventPair pop_out_data_event;
// Signaled on PushInteractiveDataFromApplet
Kernel::EventPair pop_interactive_out_data_event;
};
class Applet {
public:
explicit Applet(Kernel::KernelCore& kernel_);
virtual ~Applet();
virtual void Initialize();
virtual bool TransactionComplete() const = 0;
virtual ResultCode GetStatus() const = 0;
virtual void ExecuteInteractive() = 0;
virtual void Execute() = 0;
bool IsInitialized() const {
return initialized;
}
AppletDataBroker& GetBroker() {
return broker;
}
const AppletDataBroker& GetBroker() const {
return broker;
}
protected:
struct CommonArguments {
u32_le arguments_version;
u32_le size;
u32_le library_version;
u32_le theme_color;
u8 play_startup_sound;
u64_le system_tick;
};
static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
CommonArguments common_args{};
AppletDataBroker broker;
bool initialized = false;
};
struct AppletFrontendSet {
using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
AppletFrontendSet();
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
WebBrowser web_browser_);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
AppletFrontendSet& operator=(const AppletFrontendSet&) = delete;
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
ControllerApplet controller;
ErrorApplet error;
ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
};
class AppletManager {
public:
explicit AppletManager(Core::System& system_);
~AppletManager();
const AppletFrontendSet& GetAppletFrontendSet() const;
void SetAppletFrontendSet(AppletFrontendSet set);
void SetDefaultAppletFrontendSet();
void SetDefaultAppletsIfMissing();
void ClearAll();
std::shared_ptr<Applet> GetApplet(AppletId id) const;
private:
AppletFrontendSet frontend;
Core::System& system;
};
} // namespace Applets
} // namespace Service::AM

View File

@@ -0,0 +1,251 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/controller.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/controller.h"
#include "core/hle/service/hid/controllers/npad.h"
namespace Service::AM::Applets {
// This error code (0x183ACA) is thrown when the applet fails to initialize.
[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
HID::Controller_NPad::NpadStyleSet npad_style_set;
npad_style_set.raw = private_arg.style_set;
return {
.min_players = std::max(s8{1}, header.player_count_min),
.max_players = header.player_count_max,
.keep_controllers_connected = header.enable_take_over_connection,
.enable_single_mode = header.enable_single_mode,
.enable_border_color = header.enable_identification_color,
.border_colors = std::move(identification_colors),
.enable_explain_text = enable_text,
.explain_text = std::move(text),
.allow_pro_controller = npad_style_set.pro_controller == 1,
.allow_handheld = npad_style_set.handheld == 1,
.allow_dual_joycons = npad_style_set.joycon_dual == 1,
.allow_left_joycon = npad_style_set.joycon_left == 1,
.allow_right_joycon = npad_style_set.joycon_right == 1,
};
}
Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
Controller::~Controller() = default;
void Controller::Initialize() {
Applet::Initialize();
LOG_INFO(Service_HID, "Initializing Controller Applet.");
LOG_DEBUG(Service_HID,
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
common_args.arguments_version, common_args.library_version,
common_args.play_startup_sound, common_args.size, common_args.system_tick,
common_args.theme_color);
controller_applet_version = ControllerAppletVersion{common_args.library_version};
const auto private_arg_storage = broker.PopNormalDataToApplet();
ASSERT(private_arg_storage != nullptr);
const auto& private_arg = private_arg_storage->GetData();
ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size());
ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
"Unknown ControllerSupportArgPrivate revision={} with size={}",
controller_applet_version, controller_private_arg.arg_private_size);
// Some games such as Cave Story+ set invalid values for the ControllerSupportMode.
// Defer to arg_size to set the ControllerSupportMode.
if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) {
switch (controller_private_arg.arg_size) {
case sizeof(ControllerSupportArgOld):
case sizeof(ControllerSupportArgNew):
controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
break;
case sizeof(ControllerUpdateFirmwareArg):
controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate;
break;
default:
UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}",
controller_private_arg.mode, controller_private_arg.arg_size);
controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
break;
}
}
// Some games such as Cave Story+ set invalid values for the ControllerSupportCaller.
// This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem.
if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) {
if (controller_private_arg.flag_1 &&
controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) {
controller_private_arg.caller = ControllerSupportCaller::System;
} else {
controller_private_arg.caller = ControllerSupportCaller::Application;
}
}
switch (controller_private_arg.mode) {
case ControllerSupportMode::ShowControllerSupport:
case ControllerSupportMode::ShowControllerStrapGuide: {
const auto user_arg_storage = broker.PopNormalDataToApplet();
ASSERT(user_arg_storage != nullptr);
const auto& user_arg = user_arg_storage->GetData();
switch (controller_applet_version) {
case ControllerAppletVersion::Version3:
case ControllerAppletVersion::Version4:
case ControllerAppletVersion::Version5:
ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size());
break;
case ControllerAppletVersion::Version7:
ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size());
break;
default:
UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
controller_applet_version, controller_private_arg.arg_size);
ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
break;
}
break;
}
case ControllerSupportMode::ShowControllerFirmwareUpdate: {
const auto update_arg_storage = broker.PopNormalDataToApplet();
ASSERT(update_arg_storage != nullptr);
const auto& update_arg = update_arg_storage->GetData();
ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg));
std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size());
break;
}
default: {
UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
break;
}
}
}
bool Controller::TransactionComplete() const {
return complete;
}
ResultCode Controller::GetStatus() const {
return status;
}
void Controller::ExecuteInteractive() {
UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
}
void Controller::Execute() {
switch (controller_private_arg.mode) {
case ControllerSupportMode::ShowControllerSupport: {
const auto parameters = [this] {
switch (controller_applet_version) {
case ControllerAppletVersion::Version3:
case ControllerAppletVersion::Version4:
case ControllerAppletVersion::Version5:
return ConvertToFrontendParameters(
controller_private_arg, controller_user_arg_old.header,
controller_user_arg_old.enable_explain_text,
std::vector<IdentificationColor>(
controller_user_arg_old.identification_colors.begin(),
controller_user_arg_old.identification_colors.end()),
std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
controller_user_arg_old.explain_text.end()));
case ControllerAppletVersion::Version7:
default:
return ConvertToFrontendParameters(
controller_private_arg, controller_user_arg_new.header,
controller_user_arg_new.enable_explain_text,
std::vector<IdentificationColor>(
controller_user_arg_new.identification_colors.begin(),
controller_user_arg_new.identification_colors.end()),
std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
controller_user_arg_new.explain_text.end()));
}
}();
is_single_mode = parameters.enable_single_mode;
LOG_DEBUG(Service_HID,
"Controller Parameters: min_players={}, max_players={}, "
"keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
"enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
"allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
parameters.min_players, parameters.max_players,
parameters.keep_controllers_connected, parameters.enable_single_mode,
parameters.enable_border_color, parameters.enable_explain_text,
parameters.allow_pro_controller, parameters.allow_handheld,
parameters.allow_dual_joycons, parameters.allow_left_joycon,
parameters.allow_right_joycon);
frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
break;
}
case ControllerSupportMode::ShowControllerStrapGuide:
case ControllerSupportMode::ShowControllerFirmwareUpdate:
UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented",
controller_private_arg.mode);
[[fallthrough]];
default: {
ConfigurationComplete();
break;
}
}
}
void Controller::ConfigurationComplete() {
ControllerSupportResultInfo result_info{};
const auto& players = Settings::values.players.GetValue();
// If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
// Otherwise, only count connected players from P1-P8.
result_info.player_count =
is_single_mode
? 1
: static_cast<s8>(std::count_if(players.begin(), players.end() - 2,
[](const auto& player) { return player.connected; }));
result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance(
players.begin(), std::find_if(players.begin(), players.end(),
[](const auto& player) { return player.connected; })));
result_info.result = 0;
LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
result_info.player_count, result_info.selected_id, result_info.result);
complete = true;
out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
std::memcpy(out_data.data(), &result_info, out_data.size());
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,136 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
namespace Core {
class System;
}
namespace Service::AM::Applets {
using IdentificationColor = std::array<u8, 4>;
using ExplainText = std::array<char, 0x81>;
enum class ControllerAppletVersion : u32_le {
Version3 = 0x3, // 1.0.0 - 2.3.0
Version4 = 0x4, // 3.0.0 - 5.1.0
Version5 = 0x5, // 6.0.0 - 7.0.1
Version7 = 0x7, // 8.0.0+
};
enum class ControllerSupportMode : u8 {
ShowControllerSupport,
ShowControllerStrapGuide,
ShowControllerFirmwareUpdate,
MaxControllerSupportMode,
};
enum class ControllerSupportCaller : u8 {
Application,
System,
MaxControllerSupportCaller,
};
struct ControllerSupportArgPrivate {
u32 arg_private_size{};
u32 arg_size{};
bool flag_0{};
bool flag_1{};
ControllerSupportMode mode{};
ControllerSupportCaller caller{};
u32 style_set{};
u32 joy_hold_type{};
};
static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
"ControllerSupportArgPrivate has incorrect size.");
struct ControllerSupportArgHeader {
s8 player_count_min{};
s8 player_count_max{};
bool enable_take_over_connection{};
bool enable_left_justify{};
bool enable_permit_joy_dual{};
bool enable_single_mode{};
bool enable_identification_color{};
};
static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
"ControllerSupportArgHeader has incorrect size.");
// LibraryAppletVersion 0x3, 0x4, 0x5
struct ControllerSupportArgOld {
ControllerSupportArgHeader header{};
std::array<IdentificationColor, 4> identification_colors{};
bool enable_explain_text{};
std::array<ExplainText, 4> explain_text{};
};
static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
"ControllerSupportArgOld has incorrect size.");
// LibraryAppletVersion 0x7
struct ControllerSupportArgNew {
ControllerSupportArgHeader header{};
std::array<IdentificationColor, 8> identification_colors{};
bool enable_explain_text{};
std::array<ExplainText, 8> explain_text{};
};
static_assert(sizeof(ControllerSupportArgNew) == 0x430,
"ControllerSupportArgNew has incorrect size.");
struct ControllerUpdateFirmwareArg {
bool enable_force_update{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4,
"ControllerUpdateFirmwareArg has incorrect size.");
struct ControllerSupportResultInfo {
s8 player_count{};
INSERT_PADDING_BYTES(3);
u32 selected_id{};
u32 result{};
};
static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
"ControllerSupportResultInfo has incorrect size.");
class Controller final : public Applet {
public:
explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
~Controller() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void ConfigurationComplete();
private:
const Core::Frontend::ControllerApplet& frontend;
Core::System& system;
ControllerAppletVersion controller_applet_version;
ControllerSupportArgPrivate controller_private_arg;
ControllerSupportArgOld controller_user_arg_old;
ControllerSupportArgNew controller_user_arg_new;
ControllerUpdateFirmwareArg controller_update_arg;
bool complete{false};
ResultCode status{RESULT_SUCCESS};
bool is_single_mode{false};
std::vector<u8> out_data;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,193 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstring>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/error.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/error.h"
#include "core/reporter.h"
namespace Service::AM::Applets {
#pragma pack(push, 4)
struct ShowError {
u8 mode;
bool jump;
INSERT_UNION_PADDING_BYTES(4);
bool use_64bit_error_code;
INSERT_UNION_PADDING_BYTES(1);
u64 error_code_64;
u32 error_code_32;
};
static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size.");
#pragma pack(pop)
struct ShowErrorRecord {
u8 mode;
bool jump;
INSERT_UNION_PADDING_BYTES(6);
u64 error_code_64;
u64 posix_time;
};
static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect size.");
struct SystemErrorArg {
u8 mode;
bool jump;
INSERT_UNION_PADDING_BYTES(6);
u64 error_code_64;
std::array<char, 8> language_code;
std::array<char, 0x800> main_text;
std::array<char, 0x800> detail_text;
};
static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect size.");
struct ApplicationErrorArg {
u8 mode;
bool jump;
INSERT_UNION_PADDING_BYTES(6);
u32 error_code;
std::array<char, 8> language_code;
std::array<char, 0x800> main_text;
std::array<char, 0x800> detail_text;
};
static_assert(sizeof(ApplicationErrorArg) == 0x1014, "ApplicationErrorArg has incorrect size.");
union Error::ErrorArguments {
ShowError error;
ShowErrorRecord error_record;
SystemErrorArg system_error;
ApplicationErrorArg application_error;
std::array<u8, 0x1018> raw{};
};
namespace {
template <typename T>
void CopyArgumentData(const std::vector<u8>& data, T& variable) {
ASSERT(data.size() >= sizeof(T));
std::memcpy(&variable, data.data(), sizeof(T));
}
ResultCode Decode64BitError(u64 error) {
const auto description = (error >> 32) & 0x1FFF;
auto module = error & 0x3FF;
if (module >= 2000)
module -= 2000;
module &= 0x1FF;
return {static_cast<ErrorModule>(module), static_cast<u32>(description)};
}
} // Anonymous namespace
Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
Error::~Error() = default;
void Error::Initialize() {
Applet::Initialize();
args = std::make_unique<ErrorArguments>();
complete = false;
const auto storage = broker.PopNormalDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
ASSERT(!data.empty());
std::memcpy(&mode, data.data(), sizeof(ErrorAppletMode));
switch (mode) {
case ErrorAppletMode::ShowError:
CopyArgumentData(data, args->error);
if (args->error.use_64bit_error_code) {
error_code = Decode64BitError(args->error.error_code_64);
} else {
error_code = ResultCode(args->error.error_code_32);
}
break;
case ErrorAppletMode::ShowSystemError:
CopyArgumentData(data, args->system_error);
error_code = ResultCode(Decode64BitError(args->system_error.error_code_64));
break;
case ErrorAppletMode::ShowApplicationError:
CopyArgumentData(data, args->application_error);
error_code = ResultCode(args->application_error.error_code);
break;
case ErrorAppletMode::ShowErrorRecord:
CopyArgumentData(data, args->error_record);
error_code = Decode64BitError(args->error_record.error_code_64);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode);
}
}
bool Error::TransactionComplete() const {
return complete;
}
ResultCode Error::GetStatus() const {
return RESULT_SUCCESS;
}
void Error::ExecuteInteractive() {
UNREACHABLE_MSG("Unexpected interactive applet data!");
}
void Error::Execute() {
if (complete) {
return;
}
const auto callback = [this] { DisplayCompleted(); };
const auto title_id = system.CurrentProcess()->GetTitleID();
const auto& reporter{system.GetReporter()};
switch (mode) {
case ErrorAppletMode::ShowError:
reporter.SaveErrorReport(title_id, error_code);
frontend.ShowError(error_code, callback);
break;
case ErrorAppletMode::ShowSystemError:
case ErrorAppletMode::ShowApplicationError: {
const auto system = mode == ErrorAppletMode::ShowSystemError;
const auto& main_text =
system ? args->system_error.main_text : args->application_error.main_text;
const auto& detail_text =
system ? args->system_error.detail_text : args->application_error.detail_text;
const auto main_text_string =
Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size());
const auto detail_text_string =
Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size());
reporter.SaveErrorReport(title_id, error_code, main_text_string, detail_text_string);
frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
break;
}
case ErrorAppletMode::ShowErrorRecord:
reporter.SaveErrorReport(title_id, error_code,
fmt::format("{:016X}", args->error_record.posix_time));
frontend.ShowErrorWithTimestamp(
error_code, std::chrono::seconds{args->error_record.posix_time}, callback);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode);
DisplayCompleted();
}
}
void Error::DisplayCompleted() {
complete = true;
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{}));
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,52 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
namespace Core {
class System;
}
namespace Service::AM::Applets {
enum class ErrorAppletMode : u8 {
ShowError = 0,
ShowSystemError = 1,
ShowApplicationError = 2,
ShowEula = 3,
ShowErrorPctl = 4,
ShowErrorRecord = 5,
ShowUpdateEula = 8,
};
class Error final : public Applet {
public:
explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_);
~Error() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void DisplayCompleted();
private:
union ErrorArguments;
const Core::Frontend::ErrorApplet& frontend;
ResultCode error_code = RESULT_SUCCESS;
ErrorAppletMode mode = ErrorAppletMode::ShowError;
std::unique_ptr<ErrorArguments> args;
bool complete = false;
Core::System& system;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,253 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <string_view>
#include "common/assert.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/general_backend.h"
#include "core/reporter.h"
namespace Service::AM::Applets {
constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
std::shared_ptr<IStorage> storage = broker.PopNormalDataToApplet();
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
"called (STUBBED), during {} received normal data with size={:08X}, data={}",
prefix, data.size(), Common::HexToString(data));
}
storage = broker.PopInteractiveDataToApplet();
for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
"called (STUBBED), during {} received interactive data with size={:08X}, data={}",
prefix, data.size(), Common::HexToString(data));
}
}
Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
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}",
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 is_successful) {
successful = is_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(std::make_shared<IStorage>(system, std::move(out)));
broker.SignalStateChanged();
}
PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
PhotoViewer::~PhotoViewer() = default;
void PhotoViewer::Initialize() {
Applet::Initialize();
complete = false;
const auto storage = broker.PopNormalDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
ASSERT(!data.empty());
mode = static_cast<PhotoViewerAppletMode>(data[0]);
}
bool PhotoViewer::TransactionComplete() const {
return complete;
}
ResultCode PhotoViewer::GetStatus() const {
return RESULT_SUCCESS;
}
void PhotoViewer::ExecuteInteractive() {
UNREACHABLE_MSG("Unexpected interactive applet data.");
}
void PhotoViewer::Execute() {
if (complete)
return;
const auto callback = [this] { ViewFinished(); };
switch (mode) {
case PhotoViewerAppletMode::CurrentApp:
frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback);
break;
case PhotoViewerAppletMode::AllApps:
frontend.ShowAllPhotos(callback);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode);
}
}
void PhotoViewer::ViewFinished() {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{}));
broker.SignalStateChanged();
}
StubApplet::StubApplet(Core::System& system_, AppletId id_)
: Applet{system_.Kernel()}, id{id_}, system{system_} {}
StubApplet::~StubApplet() = default;
void StubApplet::Initialize() {
LOG_WARNING(Service_AM, "called (STUBBED)");
Applet::Initialize();
const auto data = broker.PeekDataToAppletForDebug();
system.GetReporter().SaveUnimplementedAppletReport(
static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
data.normal, data.interactive);
LogCurrentStorage(broker, "Initialize");
}
bool StubApplet::TransactionComplete() const {
LOG_WARNING(Service_AM, "called (STUBBED)");
return true;
}
ResultCode StubApplet::GetStatus() const {
LOG_WARNING(Service_AM, "called (STUBBED)");
return RESULT_SUCCESS;
}
void StubApplet::ExecuteInteractive() {
LOG_WARNING(Service_AM, "called (STUBBED)");
LogCurrentStorage(broker, "ExecuteInteractive");
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.PushInteractiveDataFromApplet(
std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.SignalStateChanged();
}
void StubApplet::Execute() {
LOG_WARNING(Service_AM, "called (STUBBED)");
LogCurrentStorage(broker, "Execute");
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.PushInteractiveDataFromApplet(
std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,88 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/am/applets/applets.h"
namespace Core {
class System;
}
namespace Service::AM::Applets {
enum class AuthAppletType : u32 {
ShowParentalAuthentication,
RegisterParentalPasscode,
ChangeParentalPasscode,
};
class Auth final : public Applet {
public:
explicit Auth(Core::System& system_, 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 is_successful = true);
private:
Core::Frontend::ParentalControlsApplet& frontend;
Core::System& system;
bool complete = false;
bool successful = false;
AuthAppletType type = AuthAppletType::ShowParentalAuthentication;
u8 arg0 = 0;
u8 arg1 = 0;
u8 arg2 = 0;
};
enum class PhotoViewerAppletMode : u8 {
CurrentApp = 0,
AllApps = 1,
};
class PhotoViewer final : public Applet {
public:
explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_);
~PhotoViewer() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void ViewFinished();
private:
const Core::Frontend::PhotoViewerApplet& frontend;
bool complete = false;
PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp;
Core::System& system;
};
class StubApplet final : public Applet {
public:
explicit StubApplet(Core::System& system_, AppletId id_);
~StubApplet() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
private:
AppletId id;
Core::System& system;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,78 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/profile_select.h"
namespace Service::AM::Applets {
constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
ProfileSelect::ProfileSelect(Core::System& system_,
const Core::Frontend::ProfileSelectApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
ProfileSelect::~ProfileSelect() = default;
void ProfileSelect::Initialize() {
complete = false;
status = RESULT_SUCCESS;
final_data.clear();
Applet::Initialize();
const auto user_config_storage = broker.PopNormalDataToApplet();
ASSERT(user_config_storage != nullptr);
const auto& user_config = user_config_storage->GetData();
ASSERT(user_config.size() >= sizeof(UserSelectionConfig));
std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig));
}
bool ProfileSelect::TransactionComplete() const {
return complete;
}
ResultCode ProfileSelect::GetStatus() const {
return status;
}
void ProfileSelect::ExecuteInteractive() {
UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
}
void ProfileSelect::Execute() {
if (complete) {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
return;
}
frontend.SelectProfile([this](std::optional<Common::UUID> uuid) { SelectionComplete(uuid); });
}
void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) {
UserSelectionOutput output{};
if (uuid.has_value() && uuid->uuid != Common::INVALID_UUID) {
output.result = 0;
output.uuid_selected = uuid->uuid;
} else {
status = ERR_USER_CANCELLED_SELECTION;
output.result = ERR_USER_CANCELLED_SELECTION.raw;
output.uuid_selected = Common::INVALID_UUID;
}
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
std::memcpy(final_data.data(), &output, final_data.size());
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,59 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_funcs.h"
#include "common/uuid.h"
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
namespace Core {
class System;
}
namespace Service::AM::Applets {
struct UserSelectionConfig {
// TODO(DarkLordZach): RE this structure
// It seems to be flags and the like that determine the UI of the applet on the switch... from
// my research this is safe to ignore for now.
INSERT_PADDING_BYTES(0xA0);
};
static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size.");
struct UserSelectionOutput {
u64 result;
u128 uuid_selected;
};
static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size.");
class ProfileSelect final : public Applet {
public:
explicit ProfileSelect(Core::System& system_,
const Core::Frontend::ProfileSelectApplet& frontend_);
~ProfileSelect() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void SelectionComplete(std::optional<Common::UUID> uuid);
private:
const Core::Frontend::ProfileSelectApplet& frontend;
UserSelectionConfig config;
bool complete = false;
ResultCode status = RESULT_SUCCESS;
std::vector<u8> final_data;
Core::System& system;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,196 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/software_keyboard.h"
namespace Service::AM::Applets {
namespace {
enum class Request : u32 {
Finalize = 0x4,
SetUserWordInfo = 0x6,
SetCustomizeDic = 0x7,
Calc = 0xa,
SetCustomizedDictionaries = 0xb,
UnsetCustomizedDictionaries = 0xc,
UnknownD = 0xd,
UnknownE = 0xe,
};
constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8;
constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8;
constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4;
constexpr std::size_t DEFAULT_MAX_LENGTH = 500;
constexpr bool INTERACTIVE_STATUS_OK = false;
} // Anonymous namespace
static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
KeyboardConfig config, std::u16string initial_text) {
Core::Frontend::SoftwareKeyboardParameters params{};
params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
config.submit_text.data(), config.submit_text.size());
params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
config.header_text.data(), config.header_text.size());
params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(),
config.sub_text.size());
params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(),
config.guide_text.size());
params.initial_text = std::move(initial_text);
params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit;
params.password = static_cast<bool>(config.is_password);
params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position);
params.value = static_cast<u8>(config.keyset_disable_bitmask);
return params;
}
SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
const Core::Frontend::SoftwareKeyboardApplet& frontend_)
: Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
SoftwareKeyboard::~SoftwareKeyboard() = default;
void SoftwareKeyboard::Initialize() {
complete = false;
is_inline = false;
initial_text.clear();
final_data.clear();
Applet::Initialize();
const auto keyboard_config_storage = broker.PopNormalDataToApplet();
ASSERT(keyboard_config_storage != nullptr);
const auto& keyboard_config = keyboard_config_storage->GetData();
if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) {
is_inline = true;
return;
}
ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig));
std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig));
const auto work_buffer_storage = broker.PopNormalDataToApplet();
ASSERT_OR_EXECUTE(work_buffer_storage != nullptr, { return; });
const auto& work_buffer = work_buffer_storage->GetData();
if (config.initial_string_size == 0)
return;
std::vector<char16_t> string(config.initial_string_size);
std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset,
string.size() * 2);
initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size());
}
bool SoftwareKeyboard::TransactionComplete() const {
return complete;
}
ResultCode SoftwareKeyboard::GetStatus() const {
return RESULT_SUCCESS;
}
void SoftwareKeyboard::ExecuteInteractive() {
if (complete)
return;
const auto storage = broker.PopInteractiveDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
if (!is_inline) {
const auto status = static_cast<bool>(data[0]);
if (status == INTERACTIVE_STATUS_OK) {
complete = true;
} else {
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
std::memcpy(string.data(), data.data() + 4, string.size() * 2);
frontend.SendTextCheckDialog(
Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
[this] { broker.SignalStateChanged(); });
}
} else {
Request request{};
std::memcpy(&request, data.data(), sizeof(Request));
switch (request) {
case Request::Calc: {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{1}));
broker.SignalStateChanged();
break;
}
default:
UNIMPLEMENTED_MSG("Request {:X} is not implemented", request);
break;
}
}
}
void SoftwareKeyboard::Execute() {
if (complete) {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
broker.SignalStateChanged();
return;
}
const auto parameters = ConvertToFrontendParameters(config, initial_text);
if (!is_inline) {
frontend.RequestText(
[this](std::optional<std::u16string> text) { WriteText(std::move(text)); }, parameters);
}
}
void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE);
if (text.has_value()) {
std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE);
if (config.utf_8) {
const u64 size = text->size() + sizeof(u64);
const auto new_text = Common::UTF16ToUTF8(*text);
std::memcpy(output_sub.data(), &size, sizeof(u64));
std::memcpy(output_sub.data() + 8, new_text.data(),
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8));
output_main[0] = INTERACTIVE_STATUS_OK;
std::memcpy(output_main.data() + 4, new_text.data(),
std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4));
} else {
const u64 size = text->size() * 2 + sizeof(u64);
std::memcpy(output_sub.data(), &size, sizeof(u64));
std::memcpy(output_sub.data() + 8, text->data(),
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8));
output_main[0] = INTERACTIVE_STATUS_OK;
std::memcpy(output_main.data() + 4, text->data(),
std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4));
}
complete = !config.text_check;
final_data = output_main;
if (complete) {
broker.PushNormalDataFromApplet(
std::make_shared<IStorage>(system, std::move(output_main)));
broker.SignalStateChanged();
} else {
broker.PushInteractiveDataFromApplet(
std::make_shared<IStorage>(system, std::move(output_sub)));
}
} else {
output_main[0] = 1;
complete = true;
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(output_main)));
broker.SignalStateChanged();
}
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,86 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <string>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
union ResultCode;
namespace Core {
class System;
}
namespace Service::AM::Applets {
enum class KeysetDisable : u32 {
Space = 0x02,
Address = 0x04,
Percent = 0x08,
Slashes = 0x10,
Numbers = 0x40,
DownloadCode = 0x80,
};
struct KeyboardConfig {
INSERT_PADDING_BYTES(4);
std::array<char16_t, 9> submit_text;
u16_le left_symbol_key;
u16_le right_symbol_key;
INSERT_PADDING_BYTES(1);
KeysetDisable keyset_disable_bitmask;
u32_le initial_cursor_position;
std::array<char16_t, 65> header_text;
std::array<char16_t, 129> sub_text;
std::array<char16_t, 257> guide_text;
u32_le length_limit;
INSERT_PADDING_BYTES(4);
u32_le is_password;
INSERT_PADDING_BYTES(5);
bool utf_8;
bool draw_background;
u32_le initial_string_offset;
u32_le initial_string_size;
u32_le user_dictionary_offset;
u32_le user_dictionary_size;
bool text_check;
u64_le text_check_callback;
};
static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size.");
class SoftwareKeyboard final : public Applet {
public:
explicit SoftwareKeyboard(Core::System& system_,
const Core::Frontend::SoftwareKeyboardApplet& frontend_);
~SoftwareKeyboard() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void WriteText(std::optional<std::u16string> text);
private:
const Core::Frontend::SoftwareKeyboardApplet& frontend;
KeyboardConfig config;
std::u16string initial_text;
bool complete = false;
bool is_inline = false;
std::vector<u8> final_data;
Core::System& system;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,478 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs_vector.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/pl_u.h"
namespace Service::AM::Applets {
namespace {
template <typename T>
void ParseRawValue(T& value, const std::vector<u8>& data) {
static_assert(std::is_trivially_copyable_v<T>,
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, data.data(), data.size());
}
template <typename T>
T ParseRawValue(const std::vector<u8>& data) {
T value;
ParseRawValue(value, data);
return value;
}
std::string ParseStringValue(const std::vector<u8>& data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
data.size());
}
std::string GetMainURL(const std::string& url) {
const auto index = url.find('?');
if (index == std::string::npos) {
return url;
}
return url.substr(0, index);
}
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
if (web_arg.size() == sizeof(WebArgHeader)) {
return {};
}
WebArgInputTLVMap input_tlv_map;
u64 current_offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
return input_tlv_map;
}
WebArgInputTLV input_tlv;
std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
current_offset += sizeof(WebArgInputTLV);
if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
return input_tlv_map;
}
std::vector<u8> data(input_tlv.arg_data_size);
std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
current_offset += input_tlv.arg_data_size;
input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
}
return input_tlv_map;
}
FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
FileSys::ContentRecordType nca_type) {
if (nca_type == FileSys::ContentRecordType::Data) {
const auto nca =
system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type);
if (nca == nullptr) {
LOG_ERROR(Service_AM,
"NCA of type={} with title_id={:016X} is not found in the System NAND!",
nca_type, title_id);
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
}
return nca->GetRomFS();
} else {
const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type);
if (nca == nullptr) {
LOG_ERROR(Service_AM,
"NCA of type={} with title_id={:016X} is not found in the ContentProvider!",
nca_type, title_id);
return nullptr;
}
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
}
}
void ExtractSharedFonts(Core::System& system) {
static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
"FontStandard.ttf",
"FontChineseSimplified.ttf",
"FontExtendedChineseSimplified.ttf",
"FontChineseTraditional.ttf",
"FontKorean.ttf",
"FontNintendoExtended.ttf",
"FontNintendoExtended2.ttf",
};
for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
const auto fonts_dir = Common::FS::SanitizePath(
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
Common::FS::DirectorySeparator::PlatformDefault);
const auto font_file_path =
Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]),
Common::FS::DirectorySeparator::PlatformDefault);
if (Common::FS::Exists(font_file_path)) {
continue;
}
const auto font = NS::SHARED_FONTS[i];
const auto font_title_id = static_cast<u64>(font.first);
const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry(
font_title_id, FileSys::ContentRecordType::Data);
FileSys::VirtualFile romfs;
if (!nca) {
romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id);
} else {
romfs = nca->GetRomFS();
}
if (!romfs) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!",
font_title_id);
continue;
}
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
if (!extracted_romfs) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!",
font_title_id);
continue;
}
const auto font_file = extracted_romfs->GetFile(font.second);
if (!font_file) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!",
font_title_id, font.second);
continue;
}
std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32));
font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize());
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
Common::swap32);
std::vector<u8> decrypted_data(font_file->GetSize() - 8);
NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data);
FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
const auto temp_dir =
system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite);
const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
FileSys::VfsRawCopy(decrypted_font, out_file);
}
}
} // namespace
WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_)
: Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
WebBrowser::~WebBrowser() = default;
void WebBrowser::Initialize() {
Applet::Initialize();
LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
LOG_DEBUG(Service_AM,
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
common_args.arguments_version, common_args.library_version,
common_args.play_startup_sound, common_args.size, common_args.system_tick,
common_args.theme_color);
web_applet_version = WebAppletVersion{common_args.library_version};
const auto web_arg_storage = broker.PopNormalDataToApplet();
ASSERT(web_arg_storage != nullptr);
const auto& web_arg = web_arg_storage->GetData();
ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });
web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);
LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
web_arg_header.total_tlv_entries, web_arg_header.shim_kind);
ExtractSharedFonts(system);
switch (web_arg_header.shim_kind) {
case ShimKind::Shop:
InitializeShop();
break;
case ShimKind::Login:
InitializeLogin();
break;
case ShimKind::Offline:
InitializeOffline();
break;
case ShimKind::Share:
InitializeShare();
break;
case ShimKind::Web:
InitializeWeb();
break;
case ShimKind::Wifi:
InitializeWifi();
break;
case ShimKind::Lobby:
InitializeLobby();
break;
default:
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
break;
}
}
bool WebBrowser::TransactionComplete() const {
return complete;
}
ResultCode WebBrowser::GetStatus() const {
return status;
}
void WebBrowser::ExecuteInteractive() {
UNIMPLEMENTED_MSG("WebSession is not implemented");
}
void WebBrowser::Execute() {
switch (web_arg_header.shim_kind) {
case ShimKind::Shop:
ExecuteShop();
break;
case ShimKind::Login:
ExecuteLogin();
break;
case ShimKind::Offline:
ExecuteOffline();
break;
case ShimKind::Share:
ExecuteShare();
break;
case ShimKind::Web:
ExecuteWeb();
break;
case ShimKind::Wifi:
ExecuteWifi();
break;
case ShimKind::Lobby:
ExecuteLobby();
break;
default:
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
WebBrowserExit(WebExitReason::EndButtonPressed);
break;
}
}
void WebBrowser::ExtractOfflineRomFS() {
LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir);
const auto extracted_romfs_dir =
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
const auto temp_dir =
system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
}
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
if ((web_arg_header.shim_kind == ShimKind::Share &&
web_applet_version >= WebAppletVersion::Version196608) ||
(web_arg_header.shim_kind == ShimKind::Web &&
web_applet_version >= WebAppletVersion::Version524288)) {
// TODO: Push Output TLVs instead of a WebCommonReturnValue
}
WebCommonReturnValue web_common_return_value;
web_common_return_value.exit_reason = exit_reason;
std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
web_common_return_value.last_url_size = last_url.size();
LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
exit_reason, last_url, last_url.size());
complete = true;
std::vector<u8> out_data(sizeof(WebCommonReturnValue));
std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
broker.SignalStateChanged();
}
bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const {
return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end();
}
std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) {
const auto map_it = web_arg_input_tlv_map.find(input_tlv_type);
if (map_it == web_arg_input_tlv_map.end()) {
return std::nullopt;
}
return map_it->second;
}
void WebBrowser::InitializeShop() {}
void WebBrowser::InitializeLogin() {}
void WebBrowser::InitializeOffline() {
const auto document_path =
ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value());
const auto document_kind =
ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value());
std::string additional_paths;
switch (document_kind) {
case DocumentKind::OfflineHtmlPage:
default:
title_id = system.CurrentProcess()->GetTitleID();
nca_type = FileSys::ContentRecordType::HtmlDocument;
additional_paths = "html-document";
break;
case DocumentKind::ApplicationLegalInformation:
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value());
nca_type = FileSys::ContentRecordType::LegalInformation;
break;
case DocumentKind::SystemDataPage:
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value());
nca_type = FileSys::ContentRecordType::Data;
break;
}
static constexpr std::array<const char*, 3> RESOURCE_TYPES{
"manual",
"legal_information",
"system_data",
};
offline_cache_dir = Common::FS::SanitizePath(
fmt::format("{}/offline_web_applet_{}/{:016X}",
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir),
RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id),
Common::FS::DirectorySeparator::PlatformDefault);
offline_document = Common::FS::SanitizePath(
fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path),
Common::FS::DirectorySeparator::PlatformDefault);
}
void WebBrowser::InitializeShare() {}
void WebBrowser::InitializeWeb() {
external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());
}
void WebBrowser::InitializeWifi() {}
void WebBrowser::InitializeLobby() {}
void WebBrowser::ExecuteShop() {
LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteLogin() {
LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteOffline() {
const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document),
Common::FS::DirectorySeparator::PlatformDefault);
if (!Common::FS::Exists(main_url)) {
offline_romfs = GetOfflineRomFS(system, title_id, nca_type);
if (offline_romfs == nullptr) {
LOG_ERROR(Service_AM,
"RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id,
nca_type);
WebBrowserExit(WebExitReason::WindowClosed);
return;
}
}
LOG_INFO(Service_AM, "Opening offline document at {}", offline_document);
frontend.OpenLocalWebPage(
offline_document, [this] { ExtractOfflineRomFS(); },
[this](WebExitReason exit_reason, std::string last_url) {
WebBrowserExit(exit_reason, last_url);
});
}
void WebBrowser::ExecuteShare() {
LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteWeb() {
LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
frontend.OpenExternalWebPage(external_url,
[this](WebExitReason exit_reason, std::string last_url) {
WebBrowserExit(exit_reason, last_url);
});
}
void WebBrowser::ExecuteWifi() {
LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteLobby() {
LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,87 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <optional>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/web_types.h"
namespace Core {
class System;
}
namespace FileSys {
enum class ContentRecordType : u8;
}
namespace Service::AM::Applets {
class WebBrowser final : public Applet {
public:
WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_);
~WebBrowser() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void ExtractOfflineRomFS();
void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");
private:
bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const;
std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type);
// Initializers for the various types of browser applets
void InitializeShop();
void InitializeLogin();
void InitializeOffline();
void InitializeShare();
void InitializeWeb();
void InitializeWifi();
void InitializeLobby();
// Executors for the various types of browser applets
void ExecuteShop();
void ExecuteLogin();
void ExecuteOffline();
void ExecuteShare();
void ExecuteWeb();
void ExecuteWifi();
void ExecuteLobby();
const Core::Frontend::WebBrowserApplet& frontend;
bool complete{false};
ResultCode status{RESULT_SUCCESS};
WebAppletVersion web_applet_version;
WebExitReason web_exit_reason;
WebArgHeader web_arg_header;
WebArgInputTLVMap web_arg_input_tlv_map;
u64 title_id;
FileSys::ContentRecordType nca_type;
std::string offline_cache_dir;
std::string offline_document;
FileSys::VirtualFile offline_romfs;
std::string external_url;
Core::System& system;
};
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,178 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <unordered_map>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace Service::AM::Applets {
enum class WebAppletVersion : u32_le {
Version0 = 0x0, // Only used by WifiWebAuthApplet
Version131072 = 0x20000, // 1.0.0 - 2.3.0
Version196608 = 0x30000, // 3.0.0 - 4.1.0
Version327680 = 0x50000, // 5.0.0 - 5.1.0
Version393216 = 0x60000, // 6.0.0 - 7.0.1
Version524288 = 0x80000, // 8.0.0+
};
enum class ShimKind : u32 {
Shop = 1,
Login = 2,
Offline = 3,
Share = 4,
Web = 5,
Wifi = 6,
Lobby = 7,
};
enum class WebExitReason : u32 {
EndButtonPressed = 0,
BackButtonPressed = 1,
ExitRequested = 2,
CallbackURL = 3,
WindowClosed = 4,
ErrorDialog = 7,
};
enum class WebArgInputTLVType : u16 {
InitialURL = 0x1,
CallbackURL = 0x3,
CallbackableURL = 0x4,
ApplicationID = 0x5,
DocumentPath = 0x6,
DocumentKind = 0x7,
SystemDataID = 0x8,
ShareStartPage = 0x9,
Whitelist = 0xA,
News = 0xB,
UserID = 0xE,
AlbumEntry0 = 0xF,
ScreenShotEnabled = 0x10,
EcClientCertEnabled = 0x11,
PlayReportEnabled = 0x13,
BootDisplayKind = 0x17,
BackgroundKind = 0x18,
FooterEnabled = 0x19,
PointerEnabled = 0x1A,
LeftStickMode = 0x1B,
KeyRepeatFrame1 = 0x1C,
KeyRepeatFrame2 = 0x1D,
BootAsMediaPlayerInverted = 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,
YouTubeVideoWhitelist = 0x31,
FooterFixedKind = 0x32,
PageFadeEnabled = 0x33,
MediaCreatorApplicationRatingAge = 0x34,
BootLoadingIconEnabled = 0x35,
PageScrollIndicatorEnabled = 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,
MediaPlayerOfflineEnabled = 0x43,
};
enum class WebArgOutputTLVType : u16 {
ShareExitReason = 0x1,
LastURL = 0x2,
LastURLSize = 0x3,
SharePostResult = 0x4,
PostServiceName = 0x5,
PostServiceNameSize = 0x6,
PostID = 0x7,
PostIDSize = 0x8,
MediaPlayerAutoClosedByCompletion = 0x9,
};
enum class DocumentKind : u32 {
OfflineHtmlPage = 1,
ApplicationLegalInformation = 2,
SystemDataPage = 3,
};
enum class ShareStartPage : u32 {
Default,
Settings,
};
enum class BootDisplayKind : u32 {
Default,
White,
Black,
};
enum class BackgroundKind : u32 {
Default,
};
enum class LeftStickMode : u32 {
Pointer,
Cursor,
};
enum class WebSessionBootMode : u32 {
AllForeground,
AllForegroundInitiallyHidden,
};
struct WebArgHeader {
u16 total_tlv_entries{};
INSERT_PADDING_BYTES(2);
ShimKind shim_kind{};
};
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
struct WebArgInputTLV {
WebArgInputTLVType input_tlv_type{};
u16 arg_data_size{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");
struct WebArgOutputTLV {
WebArgOutputTLVType output_tlv_type{};
u16 arg_data_size{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");
struct WebCommonReturnValue {
WebExitReason exit_reason{};
INSERT_PADDING_WORDS(1);
std::array<char, 0x1000> last_url{};
u64 last_url_size{};
};
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,26 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/am/idle.h"
namespace Service::AM {
IdleSys::IdleSys(Core::System& system_) : ServiceFramework{system_, "idle:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAutoPowerDownEvent"},
{1, nullptr, "IsAutoPowerDownRequested"},
{2, nullptr, "Unknown2"},
{3, nullptr, "SetHandlingContext"},
{4, nullptr, "LoadAndApplySettings"},
{5, nullptr, "ReportUserIsActive"},
};
// clang-format on
RegisterHandlers(functions);
}
IdleSys::~IdleSys() = default;
} // namespace Service::AM

21
src/core/hle/service/am/idle.h Executable file
View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::AM {
class IdleSys final : public ServiceFramework<IdleSys> {
public:
explicit IdleSys(Core::System& system_);
~IdleSys() override;
};
} // namespace Service::AM

48
src/core/hle/service/am/omm.cpp Executable file
View File

@@ -0,0 +1,48 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/am/omm.h"
namespace Service::AM {
OMM::OMM(Core::System& system_) : ServiceFramework{system_, "omm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetOperationMode"},
{1, nullptr, "GetOperationModeChangeEvent"},
{2, nullptr, "EnableAudioVisual"},
{3, nullptr, "DisableAudioVisual"},
{4, nullptr, "EnterSleepAndWait"},
{5, nullptr, "GetCradleStatus"},
{6, nullptr, "FadeInDisplay"},
{7, nullptr, "FadeOutDisplay"},
{8, nullptr, "GetCradleFwVersion"},
{9, nullptr, "NotifyCecSettingsChanged"},
{10, nullptr, "SetOperationModePolicy"},
{11, nullptr, "GetDefaultDisplayResolution"},
{12, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
{13, nullptr, "UpdateDefaultDisplayResolution"},
{14, nullptr, "ShouldSleepOnBoot"},
{15, nullptr, "NotifyHdcpApplicationExecutionStarted"},
{16, nullptr, "NotifyHdcpApplicationExecutionFinished"},
{17, nullptr, "NotifyHdcpApplicationDrawingStarted"},
{18, nullptr, "NotifyHdcpApplicationDrawingFinished"},
{19, nullptr, "GetHdcpAuthenticationFailedEvent"},
{20, nullptr, "GetHdcpAuthenticationFailedEmulationEnabled"},
{21, nullptr, "SetHdcpAuthenticationFailedEmulation"},
{22, nullptr, "GetHdcpStateChangeEvent"},
{23, nullptr, "GetHdcpState"},
{24, nullptr, "ShowCardUpdateProcessing"},
{25, nullptr, "SetApplicationCecSettingsAndNotifyChanged"},
{26, nullptr, "GetOperationModeSystemInfo"},
{27, nullptr, "GetAppletFullAwakingSystemEvent"},
};
// clang-format on
RegisterHandlers(functions);
}
OMM::~OMM() = default;
} // namespace Service::AM

21
src/core/hle/service/am/omm.h Executable file
View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::AM {
class OMM final : public ServiceFramework<OMM> {
public:
explicit OMM(Core::System& system_);
~OMM() override;
};
} // namespace Service::AM

View File

@@ -0,0 +1,32 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/am/spsm.h"
namespace Service::AM {
SPSM::SPSM(Core::System& system_) : ServiceFramework{system_, "spsm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
{1, nullptr, "EnterSleep"},
{2, nullptr, "GetLastWakeReason"},
{3, nullptr, "Shutdown"},
{4, nullptr, "GetNotificationMessageEventHandle"},
{5, nullptr, "ReceiveNotificationMessage"},
{6, nullptr, "AnalyzeLogForLastSleepWakeSequence"},
{7, nullptr, "ResetEventLog"},
{8, nullptr, "AnalyzePerformanceLogForLastSleepWakeSequence"},
{9, nullptr, "ChangeHomeButtonLongPressingTime"},
{10, nullptr, "PutErrorState"},
{11, nullptr, "InvalidateCurrentHomeButtonPressing"},
};
// clang-format on
RegisterHandlers(functions);
}
SPSM::~SPSM() = default;
} // namespace Service::AM

21
src/core/hle/service/am/spsm.h Executable file
View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::AM {
class SPSM final : public ServiceFramework<SPSM> {
public:
explicit SPSM(Core::System& system_);
~SPSM() override;
};
} // namespace Service::AM

View File

@@ -0,0 +1,23 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/am/tcap.h"
namespace Service::AM {
TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetContinuousHighSkinTemperatureEvent"},
{1, nullptr, "SetOperationMode"},
{2, nullptr, "LoadAndApplySettings"},
};
// clang-format on
RegisterHandlers(functions);
}
TCAP::~TCAP() = default;
} // namespace Service::AM

21
src/core/hle/service/am/tcap.h Executable file
View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::AM {
class TCAP final : public ServiceFramework<TCAP> {
public:
explicit TCAP(Core::System& system_);
~TCAP() override;
};
} // namespace Service::AM

View File

@@ -0,0 +1,278 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <numeric>
#include <vector>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/aoc/aoc_u.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Service::AOC {
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
std::vector<u64> add_on_content;
const auto& rcu = system.GetContentProvider();
const auto list =
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
[](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
add_on_content.erase(
std::remove_if(
add_on_content.begin(), add_on_content.end(),
[&rcu](u64 tid) {
return rcu.GetEntry(tid, FileSys::ContentRecordType::Data)->GetStatus() !=
Loader::ResultStatus::Success;
}),
add_on_content.end());
return add_on_content;
}
class IPurchaseEventManager final : public ServiceFramework<IPurchaseEventManager> {
public:
explicit IPurchaseEventManager(Core::System& system_)
: ServiceFramework{system_, "IPurchaseEventManager"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
{3, nullptr, "PopPurchasedProductInfo"},
{4, nullptr, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
RegisterHandlers(functions);
purchased_event = Kernel::WritableEvent::CreateEventPair(
system.Kernel(), "IPurchaseEventManager:PurchasedEvent");
}
private:
void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown_1 = rp.Pop<u64>();
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetDeliveryTarget(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown_1 = rp.Pop<u64>();
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(purchased_event.readable);
}
Kernel::EventPair purchased_event;
};
AOC_U::AOC_U(Core::System& system_)
: ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CountAddOnContentByApplicationId"},
{1, nullptr, "ListAddOnContentByApplicationId"},
{2, &AOC_U::CountAddOnContent, "CountAddOnContent"},
{3, &AOC_U::ListAddOnContent, "ListAddOnContent"},
{4, nullptr, "GetAddOnContentBaseIdByApplicationId"},
{5, &AOC_U::GetAddOnContentBaseId, "GetAddOnContentBaseId"},
{6, nullptr, "PrepareAddOnContentByApplicationId"},
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
{9, nullptr, "GetAddOnContentLostErrorCode"},
{100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},
{101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = system.Kernel();
aoc_change_event =
Kernel::WritableEvent::CreateEventPair(kernel, "GetAddOnContentListChanged:Event");
}
AOC_U::~AOC_U() = default;
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
struct Parameters {
u64 process_id;
};
static_assert(sizeof(Parameters) == 8);
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<Parameters>();
LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
const auto current = system.CurrentProcess()->GetTitleID();
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
rb.Push<u32>(0);
return;
}
rb.Push<u32>(static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
struct Parameters {
u32 offset;
u32 count;
u64 process_id;
};
static_assert(sizeof(Parameters) == 16);
IPC::RequestParser rp{ctx};
const auto [offset, count, process_id] = rp.PopRaw<Parameters>();
LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id);
const auto current = system.CurrentProcess()->GetTitleID();
std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
for (u64 content_id : add_on_content) {
if (FileSys::GetBaseTitleID(content_id) != current) {
continue;
}
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
}
}
if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find the correct error code.
rb.Push(RESULT_UNKNOWN);
return;
}
const auto out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
out.resize(out_count);
ctx.WriteBuffer(out);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(out_count);
}
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
struct Parameters {
u64 process_id;
};
static_assert(sizeof(Parameters) == 8);
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<Parameters>();
LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
const auto title_id = system.CurrentProcess()->GetTitleID();
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto res = pm.GetControlMetadata();
if (res.first == nullptr) {
rb.Push(FileSys::GetAOCBaseTitleID(title_id));
return;
}
rb.Push(res.first->GetDLCBaseTitleId());
}
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
struct Parameters {
s32 addon_index;
u64 process_id;
};
static_assert(sizeof(Parameters) == 16);
IPC::RequestParser rp{ctx};
const auto [addon_index, process_id] = rp.PopRaw<Parameters>();
LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index,
process_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(aoc_change_event.readable);
}
void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IPurchaseEventManager>(system);
}
void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IPurchaseEventManager>(system);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<AOC_U>(system)->InstallAsService(service_manager);
}
} // namespace Service::AOC

View File

@@ -0,0 +1,40 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class WritableEvent;
}
namespace Service::AOC {
class AOC_U final : public ServiceFramework<AOC_U> {
public:
explicit AOC_U(Core::System& system);
~AOC_U() override;
private:
void CountAddOnContent(Kernel::HLERequestContext& ctx);
void ListAddOnContent(Kernel::HLERequestContext& ctx);
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
std::vector<u64> add_on_content;
Kernel::EventPair aoc_change_event;
};
/// Registers all AOC services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::AOC

View File

@@ -0,0 +1,27 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
namespace Service::APM {
Module::Module() = default;
Module::~Module() = default;
void InstallInterfaces(Core::System& system) {
auto module_ = std::make_shared<Module>();
std::make_shared<APM>(system, module_, system.GetAPMController(), "apm")
->InstallAsService(system.ServiceManager());
std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:p")
->InstallAsService(system.ServiceManager());
std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:am")
->InstallAsService(system.ServiceManager());
std::make_shared<APM_Sys>(system, system.GetAPMController())
->InstallAsService(system.ServiceManager());
}
} // namespace Service::APM

22
src/core/hle/service/apm/apm.h Executable file
View File

@@ -0,0 +1,22 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
}
namespace Service::APM {
class Module final {
public:
Module();
~Module();
};
/// Registers all AM services with the specified service manager.
void InstallInterfaces(Core::System& system);
} // namespace Service::APM

View File

@@ -0,0 +1,89 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <utility>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/hle/service/apm/controller.h"
#include "core/settings.h"
namespace Service::APM {
constexpr auto DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Config7;
Controller::Controller(Core::Timing::CoreTiming& core_timing)
: core_timing{core_timing}, configs{
{PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
{PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
} {}
Controller::~Controller() = default;
void Controller::SetPerformanceConfiguration(PerformanceMode mode,
PerformanceConfiguration config) {
static constexpr std::array<std::pair<PerformanceConfiguration, u32>, 16> config_to_speed{{
{PerformanceConfiguration::Config1, 1020},
{PerformanceConfiguration::Config2, 1020},
{PerformanceConfiguration::Config3, 1224},
{PerformanceConfiguration::Config4, 1020},
{PerformanceConfiguration::Config5, 1020},
{PerformanceConfiguration::Config6, 1224},
{PerformanceConfiguration::Config7, 1020},
{PerformanceConfiguration::Config8, 1020},
{PerformanceConfiguration::Config9, 1020},
{PerformanceConfiguration::Config10, 1020},
{PerformanceConfiguration::Config11, 1020},
{PerformanceConfiguration::Config12, 1020},
{PerformanceConfiguration::Config13, 1785},
{PerformanceConfiguration::Config14, 1785},
{PerformanceConfiguration::Config15, 1020},
{PerformanceConfiguration::Config16, 1020},
}};
const auto iter = std::find_if(config_to_speed.cbegin(), config_to_speed.cend(),
[config](const auto& entry) { return entry.first == config; });
if (iter == config_to_speed.cend()) {
LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config);
return;
}
SetClockSpeed(iter->second);
configs.insert_or_assign(mode, config);
}
void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
constexpr std::array<PerformanceConfiguration, 3> BOOST_MODE_TO_CONFIG_MAP{{
PerformanceConfiguration::Config7,
PerformanceConfiguration::Config13,
PerformanceConfiguration::Config15,
}};
SetPerformanceConfiguration(PerformanceMode::Docked,
BOOST_MODE_TO_CONFIG_MAP.at(static_cast<u32>(mode)));
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
: PerformanceMode::Handheld;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
if (configs.find(mode) == configs.end()) {
configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION);
}
return configs[mode];
}
void Controller::SetClockSpeed(u32 mhz) {
LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
// TODO(DarkLordZach): Actually signal core_timing to change clock speed.
// TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
}
} // namespace Service::APM

View File

@@ -0,0 +1,70 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include "common/common_types.h"
namespace Core::Timing {
class CoreTiming;
}
namespace Service::APM {
enum class PerformanceConfiguration : u32 {
Config1 = 0x00010000,
Config2 = 0x00010001,
Config3 = 0x00010002,
Config4 = 0x00020000,
Config5 = 0x00020001,
Config6 = 0x00020002,
Config7 = 0x00020003,
Config8 = 0x00020004,
Config9 = 0x00020005,
Config10 = 0x00020006,
Config11 = 0x92220007,
Config12 = 0x92220008,
Config13 = 0x92220009,
Config14 = 0x9222000A,
Config15 = 0x9222000B,
Config16 = 0x9222000C,
};
enum class CpuBoostMode : u32 {
Disabled = 0,
Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16
Partial = 2, // GPU Only -> Config 15 or 16
};
enum class PerformanceMode : u8 {
Handheld = 0,
Docked = 1,
};
// Class to manage the state and change of the emulated system performance.
// Specifically, this deals with PerformanceMode, which corresponds to the system being docked or
// undocked, and PerformanceConfig which specifies the exact CPU, GPU, and Memory clocks to operate
// at. Additionally, this manages 'Boost Mode', which allows games to temporarily overclock the
// system during times of high load -- this simply maps to different PerformanceConfigs to use.
class Controller {
public:
explicit Controller(Core::Timing::CoreTiming& core_timing);
~Controller();
void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config);
void SetFromCpuBoostMode(CpuBoostMode mode);
PerformanceMode GetCurrentPerformanceMode() const;
PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode mode);
private:
void SetClockSpeed(u32 mhz);
[[maybe_unused]] Core::Timing::CoreTiming& core_timing;
std::map<PerformanceMode, PerformanceConfiguration> configs;
};
} // namespace Service::APM

View File

@@ -0,0 +1,130 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/controller.h"
#include "core/hle/service/apm/interface.h"
namespace Service::APM {
class ISession final : public ServiceFramework<ISession> {
public:
explicit ISession(Core::System& system_, Controller& controller_)
: ServiceFramework{system_, "ISession"}, controller{controller_} {
static const FunctionInfo functions[] = {
{0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"},
{1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"},
{2, nullptr, "SetCpuOverclockEnabled"},
};
RegisterHandlers(functions);
}
private:
void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<PerformanceMode>();
const auto config = rp.PopEnum<PerformanceConfiguration>();
LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config);
controller.SetPerformanceConfiguration(mode, config);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<PerformanceMode>();
LOG_DEBUG(Service_APM, "called mode={}", mode);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(controller.GetCurrentPerformanceConfiguration(mode));
}
Controller& controller;
};
APM::APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_,
const char* name)
: ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} {
static const FunctionInfo functions[] = {
{0, &APM::OpenSession, "OpenSession"},
{1, &APM::GetPerformanceMode, "GetPerformanceMode"},
{6, nullptr, "IsCpuOverclockEnabled"},
};
RegisterHandlers(functions);
}
APM::~APM() = default;
void APM::OpenSession(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_APM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISession>(system, controller);
}
void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_APM, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.PushEnum(controller.GetCurrentPerformanceMode());
}
APM_Sys::APM_Sys(Core::System& system_, Controller& controller_)
: ServiceFramework{system_, "apm:sys"}, controller{controller_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestPerformanceMode"},
{1, &APM_Sys::GetPerformanceEvent, "GetPerformanceEvent"},
{2, nullptr, "GetThrottlingState"},
{3, nullptr, "GetLastThrottlingState"},
{4, nullptr, "ClearLastThrottlingState"},
{5, nullptr, "LoadAndApplySettings"},
{6, &APM_Sys::SetCpuBoostMode, "SetCpuBoostMode"},
{7, &APM_Sys::GetCurrentPerformanceConfiguration, "GetCurrentPerformanceConfiguration"},
};
// clang-format on
RegisterHandlers(functions);
}
APM_Sys::~APM_Sys() = default;
void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_APM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISession>(system, controller);
}
void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<CpuBoostMode>();
LOG_DEBUG(Service_APM, "called, mode={:08X}", mode);
controller.SetFromCpuBoostMode(mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void APM_Sys::GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_APM, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(
controller.GetCurrentPerformanceConfiguration(controller.GetCurrentPerformanceMode()));
}
} // namespace Service::APM

View File

@@ -0,0 +1,42 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::APM {
class Controller;
class Module;
class APM final : public ServiceFramework<APM> {
public:
explicit APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_,
const char* name);
~APM() override;
private:
void OpenSession(Kernel::HLERequestContext& ctx);
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
std::shared_ptr<Module> apm;
Controller& controller;
};
class APM_Sys final : public ServiceFramework<APM_Sys> {
public:
explicit APM_Sys(Core::System& system_, Controller& controller);
~APM_Sys() override;
void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
private:
void GetPerformanceEvent(Kernel::HLERequestContext& ctx);
void GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx);
Controller& controller;
};
} // namespace Service::APM

View File

@@ -0,0 +1,76 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/audio/audctl.h"
namespace Service::Audio {
AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetTargetVolume"},
{1, nullptr, "SetTargetVolume"},
{2, &AudCtl::GetTargetVolumeMin, "GetTargetVolumeMin"},
{3, &AudCtl::GetTargetVolumeMax, "GetTargetVolumeMax"},
{4, nullptr, "IsTargetMute"},
{5, nullptr, "SetTargetMute"},
{6, nullptr, "IsTargetConnected"},
{7, nullptr, "SetDefaultTarget"},
{8, nullptr, "GetDefaultTarget"},
{9, nullptr, "GetAudioOutputMode"},
{10, nullptr, "SetAudioOutputMode"},
{11, nullptr, "SetForceMutePolicy"},
{12, nullptr, "GetForceMutePolicy"},
{13, nullptr, "GetOutputModeSetting"},
{14, nullptr, "SetOutputModeSetting"},
{15, nullptr, "SetOutputTarget"},
{16, nullptr, "SetInputTargetForceEnabled"},
{17, nullptr, "SetHeadphoneOutputLevelMode"},
{18, nullptr, "GetHeadphoneOutputLevelMode"},
{19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
{20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
{21, nullptr, "GetAudioOutputTargetForPlayReport"},
{22, nullptr, "NotifyHeadphoneVolumeWarningDisplayedEvent"},
{23, nullptr, "SetSystemOutputMasterVolume"},
{24, nullptr, "GetSystemOutputMasterVolume"},
{25, nullptr, "GetAudioVolumeDataForPlayReport"},
{26, nullptr, "UpdateHeadphoneSettings"},
{27, nullptr, "SetVolumeMappingTableForDev"},
{28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
{29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
};
// clang-format on
RegisterHandlers(functions);
}
AudCtl::~AudCtl() = default;
void AudCtl::GetTargetVolumeMin(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called.");
// This service function is currently hardcoded on the
// actual console to this value (as of 8.0.0).
constexpr s32 target_min_volume = 0;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(target_min_volume);
}
void AudCtl::GetTargetVolumeMax(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called.");
// This service function is currently hardcoded on the
// actual console to this value (as of 8.0.0).
constexpr s32 target_max_volume = 15;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(target_max_volume);
}
} // namespace Service::Audio

View File

@@ -0,0 +1,25 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudCtl final : public ServiceFramework<AudCtl> {
public:
explicit AudCtl(Core::System& system_);
~AudCtl() override;
private:
void GetTargetVolumeMin(Kernel::HLERequestContext& ctx);
void GetTargetVolumeMax(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio

View File

@@ -0,0 +1,22 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/auddbg.h"
namespace Service::Audio {
AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendForDebug"},
{1, nullptr, "RequestResumeForDebug"},
};
// clang-format on
RegisterHandlers(functions);
}
AudDbg::~AudDbg() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudDbg final : public ServiceFramework<AudDbg> {
public:
explicit AudDbg(Core::System& system_, const char* name);
~AudDbg() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,24 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audin_a.h"
namespace Service::Audio {
AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioIns"},
{1, nullptr, "RequestResumeAudioIns"},
{2, nullptr, "GetAudioInsProcessMasterVolume"},
{3, nullptr, "SetAudioInsProcessMasterVolume"},
};
// clang-format on
RegisterHandlers(functions);
}
AudInA::~AudInA() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudInA final : public ServiceFramework<AudInA> {
public:
explicit AudInA(Core::System& system_);
~AudInA() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,112 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audin_u.h"
namespace Service::Audio {
class IAudioIn final : public ServiceFramework<IAudioIn> {
public:
explicit IAudioIn(Core::System& system_) : ServiceFramework{system_, "IAudioIn"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAudioInState"},
{1, nullptr, "StartAudioIn"},
{2, nullptr, "StopAudioIn"},
{3, nullptr, "AppendAudioInBuffer"},
{4, nullptr, "RegisterBufferEvent"},
{5, nullptr, "GetReleasedAudioInBuffer"},
{6, nullptr, "ContainsAudioInBuffer"},
{7, nullptr, "AppendAudioInBufferWithUserEvent"},
{8, nullptr, "AppendAudioInBufferAuto"},
{9, nullptr, "GetReleasedAudioInBufferAuto"},
{10, nullptr, "AppendAudioInBufferWithUserEventAuto"},
{11, nullptr, "GetAudioInBufferCount"},
{12, nullptr, "SetAudioInDeviceGain"},
{13, nullptr, "GetAudioInDeviceGain"},
{14, nullptr, "FlushAudioInBuffers"},
};
// clang-format on
RegisterHandlers(functions);
}
};
AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudInU::ListAudioIns, "ListAudioIns"},
{1, &AudInU::OpenAudioIn, "OpenAudioIn"},
{2, &AudInU::ListAudioIns, "ListAudioInsAuto"},
{3, &AudInU::OpenAudioIn, "OpenAudioInAuto"},
{4, &AudInU::ListAudioInsAutoFiltered, "ListAudioInsAutoFiltered"},
{5, &AudInU::OpenAudioInProtocolSpecified, "OpenAudioInProtocolSpecified"},
};
// clang-format on
RegisterHandlers(functions);
}
AudInU::~AudInU() = default;
void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName);
const std::size_t device_count = std::min(count, audio_device_names.size());
std::vector<AudioInDeviceName> device_names;
device_names.reserve(device_count);
for (std::size_t i = 0; i < device_count; i++) {
const auto& device_name = audio_device_names[i];
auto& entry = device_names.emplace_back();
device_name.copy(entry.data(), device_name.size());
}
ctx.WriteBuffer(device_names);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(device_names.size()));
}
void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
constexpr u32 device_count = 0;
// Since we don't actually use any other audio input devices, we return 0 devices. Filtered
// device listing just omits the default input device
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(device_count));
}
void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) {
AudInOutParams params{};
params.channel_count = 2;
params.sample_format = SampleFormat::PCM16;
params.sample_rate = 48000;
params.state = State::Started;
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<AudInOutParams>(params);
rb.PushIpcInterface<IAudioIn>(system);
}
void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
OpenInOutImpl(ctx);
}
void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
OpenInOutImpl(ctx);
}
} // namespace Service::Audio

View File

@@ -0,0 +1,54 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class HLERequestContext;
}
namespace Service::Audio {
class AudInU final : public ServiceFramework<AudInU> {
public:
explicit AudInU(Core::System& system_);
~AudInU() override;
private:
enum class SampleFormat : u32_le {
PCM16 = 2,
};
enum class State : u32_le {
Started = 0,
Stopped = 1,
};
struct AudInOutParams {
u32_le sample_rate{};
u32_le channel_count{};
SampleFormat sample_format{};
State state{};
};
static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size");
using AudioInDeviceName = std::array<char, 256>;
static constexpr std::array<std::string_view, 1> audio_device_names{{
"BuiltInHeadset",
}};
void ListAudioIns(Kernel::HLERequestContext& ctx);
void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx);
void OpenInOutImpl(Kernel::HLERequestContext& ctx);
void OpenAudioIn(Kernel::HLERequestContext& ctx);
void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio

View File

@@ -0,0 +1,41 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audctl.h"
#include "core/hle/service/audio/auddbg.h"
#include "core/hle/service/audio/audin_a.h"
#include "core/hle/service/audio/audin_u.h"
#include "core/hle/service/audio/audio.h"
#include "core/hle/service/audio/audout_a.h"
#include "core/hle/service/audio/audout_u.h"
#include "core/hle/service/audio/audrec_a.h"
#include "core/hle/service/audio/audrec_u.h"
#include "core/hle/service/audio/audren_a.h"
#include "core/hle/service/audio/audren_u.h"
#include "core/hle/service/audio/codecctl.h"
#include "core/hle/service/audio/hwopus.h"
#include "core/hle/service/service.h"
namespace Service::Audio {
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<AudCtl>(system)->InstallAsService(service_manager);
std::make_shared<AudOutA>(system)->InstallAsService(service_manager);
std::make_shared<AudOutU>(system)->InstallAsService(service_manager);
std::make_shared<AudInA>(system)->InstallAsService(service_manager);
std::make_shared<AudInU>(system)->InstallAsService(service_manager);
std::make_shared<AudRecA>(system)->InstallAsService(service_manager);
std::make_shared<AudRecU>(system)->InstallAsService(service_manager);
std::make_shared<AudRenA>(system)->InstallAsService(service_manager);
std::make_shared<AudRenU>(system)->InstallAsService(service_manager);
std::make_shared<CodecCtl>(system)->InstallAsService(service_manager);
std::make_shared<HwOpus>(system)->InstallAsService(service_manager);
std::make_shared<AudDbg>(system, "audin:d")->InstallAsService(service_manager);
std::make_shared<AudDbg>(system, "audout:d")->InstallAsService(service_manager);
std::make_shared<AudDbg>(system, "audrec:d")->InstallAsService(service_manager);
std::make_shared<AudDbg>(system, "audren:d")->InstallAsService(service_manager);
}
} // namespace Service::Audio

View File

@@ -0,0 +1,20 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
}
namespace Service::SM {
class ServiceManager;
}
namespace Service::Audio {
/// Registers all Audio services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Audio

View File

@@ -0,0 +1,26 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audout_a.h"
namespace Service::Audio {
AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioOuts"},
{1, nullptr, "RequestResumeAudioOuts"},
{2, nullptr, "GetAudioOutsProcessMasterVolume"},
{3, nullptr, "SetAudioOutsProcessMasterVolume"},
{4, nullptr, "GetAudioOutsProcessRecordVolume"},
{5, nullptr, "SetAudioOutsProcessRecordVolume"},
};
// clang-format on
RegisterHandlers(functions);
}
AudOutA::~AudOutA() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudOutA final : public ServiceFramework<AudOutA> {
public:
explicit AudOutA(Core::System& system_);
~AudOutA() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,281 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstring>
#include <vector>
#include "audio_core/audio_out.h"
#include "audio_core/codec.h"
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/audio/audout_u.h"
#include "core/hle/service/audio/errors.h"
#include "core/memory.h"
namespace Service::Audio {
constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
constexpr int DefaultSampleRate{48000};
struct AudoutParams {
s32_le sample_rate;
u16_le channel_count;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
enum class AudioState : u32 {
Started,
Stopped,
};
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
IAudioOut(Core::System& system_, AudoutParams audio_params_, AudioCore::AudioOut& audio_core_,
std::string&& device_name_, std::string&& unique_name)
: ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_},
device_name{std::move(device_name_)}, audio_params{audio_params_}, main_memory{
system.Memory()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
{2, &IAudioOut::StopAudioOut, "StopAudioOut"},
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
{6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"},
{12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},
{13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"},
};
// clang-format on
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
buffer_event =
Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased");
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
audio_params.channel_count, std::move(unique_name),
[this] { buffer_event.writable->Signal(); });
}
private:
struct AudioBuffer {
u64_le next;
u64_le buffer;
u64_le buffer_capacity;
u64_le buffer_size;
u64_le offset;
};
static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size");
void GetAudioOutState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped));
}
void StartAudioOut(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
if (stream->IsPlaying()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_OPERATION_FAILED);
return;
}
audio_core.StartStream(stream);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StopAudioOut(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
if (stream->IsPlaying()) {
audio_core.StopStream(stream);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(buffer_event.readable);
}
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
IPC::RequestParser rp{ctx};
const auto& input_buffer{ctx.ReadBuffer()};
ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer),
"AudioBuffer input is an invalid size!");
AudioBuffer audio_buffer{};
std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer));
const u64 tag{rp.Pop<u64>()};
std::vector<s16> samples(audio_buffer.buffer_size / sizeof(s16));
main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size);
if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_BUFFER_COUNT_EXCEEDED);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called {}", ctx.Description());
const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)};
const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
std::vector<u64> tags{released_buffers};
tags.resize(max_count);
ctx.WriteBuffer(tags);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(released_buffers.size()));
}
void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::RequestParser rp{ctx};
const u64 tag{rp.Pop<u64>()};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(stream->ContainsBuffer(tag));
}
void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const float volume = rp.Pop<float>();
LOG_DEBUG(Service_Audio, "called, volume={}", volume);
stream->SetVolume(volume);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetAudioOutVolume(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(stream->GetVolume());
}
AudioCore::AudioOut& audio_core;
AudioCore::StreamPtr stream;
std::string device_name;
[[maybe_unused]] AudoutParams audio_params{};
/// This is the event handle used to check if the audio buffer was released
Kernel::EventPair buffer_event;
Core::Memory::Memory& main_memory;
};
AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
{1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
{2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
{3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"},
};
// clang-format on
RegisterHandlers(functions);
audio_core = std::make_unique<AudioCore::AudioOut>();
}
AudOutU::~AudOutU() = default;
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
ctx.WriteBuffer(DefaultDevice);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1); // Amount of audio devices
}
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
const auto device_name_data{ctx.ReadBuffer()};
std::string device_name;
if (device_name_data[0] != '\0') {
device_name.assign(device_name_data.begin(), device_name_data.end());
} else {
device_name.assign(DefaultDevice.begin(), DefaultDevice.end());
}
ctx.WriteBuffer(device_name);
IPC::RequestParser rp{ctx};
auto params{rp.PopRaw<AudoutParams>()};
if (params.channel_count <= 2) {
// Mono does not exist for audout
params.channel_count = 2;
} else {
params.channel_count = 6;
}
if (!params.sample_rate) {
params.sample_rate = DefaultSampleRate;
}
std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())};
auto audio_out_interface = std::make_shared<IAudioOut>(
system, params, *audio_core, std::move(device_name), std::move(unique_name));
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(DefaultSampleRate);
rb.Push<u32>(params.channel_count);
rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16));
rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
rb.PushIpcInterface<IAudioOut>(audio_out_interface);
audio_out_interfaces.push_back(std::move(audio_out_interface));
}
} // namespace Service::Audio

View File

@@ -0,0 +1,39 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "core/hle/service/service.h"
namespace AudioCore {
class AudioOut;
}
namespace Core {
class System;
}
namespace Kernel {
class HLERequestContext;
}
namespace Service::Audio {
class IAudioOut;
class AudOutU final : public ServiceFramework<AudOutU> {
public:
explicit AudOutU(Core::System& system_);
~AudOutU() override;
private:
void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces;
std::unique_ptr<AudioCore::AudioOut> audio_core;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,22 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audrec_a.h"
namespace Service::Audio {
AudRecA::AudRecA(Core::System& system_) : ServiceFramework{system_, "audrec:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendFinalOutputRecorders"},
{1, nullptr, "RequestResumeFinalOutputRecorders"},
};
// clang-format on
RegisterHandlers(functions);
}
AudRecA::~AudRecA() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudRecA final : public ServiceFramework<AudRecA> {
public:
explicit AudRecA(Core::System& system_);
~AudRecA() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,42 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audrec_u.h"
namespace Service::Audio {
class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> {
public:
explicit IFinalOutputRecorder(Core::System& system_)
: ServiceFramework{system_, "IFinalOutputRecorder"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetFinalOutputRecorderState"},
{1, nullptr, "StartFinalOutputRecorder"},
{2, nullptr, "StopFinalOutputRecorder"},
{3, nullptr, "AppendFinalOutputRecorderBuffer"},
{4, nullptr, "RegisterBufferEvent"},
{5, nullptr, "GetReleasedFinalOutputRecorderBuffer"},
{6, nullptr, "ContainsFinalOutputRecorderBuffer"},
{7, nullptr, "GetFinalOutputRecorderBufferEndTime"},
{8, nullptr, "AppendFinalOutputRecorderBufferAuto"},
{9, nullptr, "GetReleasedFinalOutputRecorderBufferAuto"},
{10, nullptr, "FlushFinalOutputRecorderBuffers"},
};
// clang-format on
RegisterHandlers(functions);
}
};
AudRecU::AudRecU(Core::System& system_) : ServiceFramework{system_, "audrec:u"} {
static const FunctionInfo functions[] = {
{0, nullptr, "OpenFinalOutputRecorder"},
};
RegisterHandlers(functions);
}
AudRecU::~AudRecU() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudRecU final : public ServiceFramework<AudRecU> {
public:
explicit AudRecU(Core::System& system_);
~AudRecU() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,28 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/audren_a.h"
namespace Service::Audio {
AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioRenderers"},
{1, nullptr, "RequestResumeAudioRenderers"},
{2, nullptr, "GetAudioRenderersProcessMasterVolume"},
{3, nullptr, "SetAudioRenderersProcessMasterVolume"},
{4, nullptr, "RegisterAppletResourceUserId"},
{5, nullptr, "UnregisterAppletResourceUserId"},
{6, nullptr, "GetAudioRenderersProcessRecordVolume"},
{7, nullptr, "SetAudioRenderersProcessRecordVolume"},
};
// clang-format on
RegisterHandlers(functions);
}
AudRenA::~AudRenA() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class AudRenA final : public ServiceFramework<AudRenA> {
public:
explicit AudRenA(Core::System& system_);
~AudRenA() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,713 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <memory>
#include <string_view>
#include "audio_core/audio_renderer.h"
#include "common/alignment.h"
#include "common/bit_util.h"
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/audio/audren_u.h"
#include "core/hle/service/audio/errors.h"
namespace Service::Audio {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
const std::size_t instance_number)
: ServiceFramework{system, "IAudioRenderer"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
{1, &IAudioRenderer::GetSampleCount, "GetSampleCount"},
{2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"},
{3, &IAudioRenderer::GetState, "GetState"},
{4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"},
{5, &IAudioRenderer::Start, "Start"},
{6, &IAudioRenderer::Stop, "Stop"},
{7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
{8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"},
{9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"},
{10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"},
{11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"},
};
// clang-format on
RegisterHandlers(functions);
system_event =
Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent");
renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), system.Memory(),
audren_params, system_event.writable,
instance_number);
}
private:
void UpdateAudioCallback() {
system_event.writable->Signal();
}
void GetSampleRate(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleRate());
}
void GetSampleCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleCount());
}
void GetState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(renderer->GetStreamState()));
}
void GetMixBufferCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetMixBufferCount());
}
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called");
std::vector<u8> output_params(ctx.GetWriteBufferSize());
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
if (result.IsSuccess()) {
ctx.WriteBuffer(output_params);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Start(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Stop(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(system_event.readable);
}
void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
rendering_time_limit_percent = rp.Pop<u32>();
LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}",
rendering_time_limit_percent);
ASSERT(rendering_time_limit_percent <= 100);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(rendering_time_limit_percent);
}
void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
// This service command currently only reports an unsupported operation
// error code, or aborts. Given that, we just always return an error
// code in this case.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_SUPPORTED);
}
Kernel::EventPair system_event;
std::unique_ptr<AudioCore::AudioRenderer> renderer;
u32 rendering_time_limit_percent = 100;
};
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
public:
explicit IAudioDevice(Core::System& system_, u32_le revision_num)
: ServiceFramework{system_, "IAudioDevice"}, revision{revision_num} {
static const FunctionInfo functions[] = {
{0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
{1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
{2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"},
{3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
{4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
{5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
{6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"},
{7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
{8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"},
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
{11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
{12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
{13, nullptr, "GetAudioSystemMasterVolumeSetting"},
};
RegisterHandlers(functions);
auto& kernel = system.Kernel();
buffer_event =
Kernel::WritableEvent::CreateEventPair(kernel, "IAudioOutBufferReleasedEvent");
// Should be similar to audio_output_device_switch_event
audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
kernel, "IAudioDevice:AudioInputDeviceSwitchedEvent");
// Should only be signalled when an audio output device has been changed, example: speaker
// to headset
audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
kernel, "IAudioDevice:AudioOutputDeviceSwitchedEvent");
}
private:
using AudioDeviceName = std::array<char, 256>;
static constexpr std::array<std::string_view, 4> audio_device_names{{
"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput",
"AudioTvOutput",
"AudioUsbDeviceOutput",
}};
enum class DeviceType {
AHUBHeadphones,
AHUBSpeakers,
HDA,
USBOutput,
};
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
const bool usb_output_supported =
IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision);
const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName);
std::vector<AudioDeviceName> name_buffer;
name_buffer.reserve(audio_device_names.size());
for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) {
const auto type = static_cast<DeviceType>(i);
if (!usb_output_supported && type == DeviceType::USBOutput) {
continue;
}
const auto& device_name = audio_device_names[i];
auto& entry = name_buffer.emplace_back();
device_name.copy(entry.data(), device_name.size());
}
ctx.WriteBuffer(name_buffer);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(name_buffer.size()));
}
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const f32 volume = rp.Pop<f32>();
const auto device_name_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(device_name_buffer);
LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
const auto device_name_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(device_name_buffer);
LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(1.0f);
}
void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
// Currently set to always be TV audio output.
const auto& device_name = audio_device_names[2];
AudioDeviceName out_device_name{};
device_name.copy(out_device_name.data(), device_name.size());
ctx.WriteBuffer(out_device_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
buffer_event.writable->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(buffer_event.readable);
}
void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1);
}
// Should be similar to QueryAudioDeviceOutputEvent
void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(audio_input_device_switch_event.readable);
}
void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(audio_output_device_switch_event.readable);
}
u32_le revision = 0;
Kernel::EventPair buffer_event;
Kernel::EventPair audio_input_device_switch_event;
Kernel::EventPair audio_output_device_switch_event;
}; // namespace Audio
AudRenU::AudRenU(Core::System& system_) : ServiceFramework{system_, "audren:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
{2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"},
{3, &AudRenU::OpenAudioRendererAuto, "OpenAudioRendererAuto"},
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"},
};
// clang-format on
RegisterHandlers(functions);
}
AudRenU::~AudRenU() = default;
void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
OpenAudioRendererImpl(ctx);
}
static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
// +1 represents the final mix.
return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
1;
}
void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
// Several calculations below align the sizes being calculated
// onto a 64 byte boundary.
static constexpr u64 buffer_alignment_size = 64;
// Some calculations that calculate portions of the buffer
// that will contain information, on the other hand, align
// the result of some of their calcularions on a 16 byte boundary.
static constexpr u64 info_field_alignment_size = 16;
// Maximum detail entries that may exist at one time for performance
// frame statistics.
static constexpr u64 max_perf_detail_entries = 100;
// Size of the data structure representing the bulk of the voice-related state.
static constexpr u64 voice_state_size = 0x100;
// Size of the upsampler manager data structure
constexpr u64 upsampler_manager_size = 0x48;
// Calculates the part of the size that relates to mix buffers.
const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
// As of 8.0.0 this is the maximum on voice channels.
constexpr u64 max_voice_channels = 6;
// The service expects the sample_count member of the parameters to either be
// a value of 160 or 240, so the maximum sample count is assumed in order
// to adequately handle all values at runtime.
constexpr u64 default_max_sample_count = 240;
const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels;
u64 size = 0;
size += total_mix_buffers * (sizeof(s32) * params.sample_count);
size += total_mix_buffers * (sizeof(s32) * default_max_sample_count);
size += u64{params.submix_count} + params.sink_count;
size = Common::AlignUp(size, buffer_alignment_size);
size += Common::AlignUp(params.unknown_30, buffer_alignment_size);
size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size);
return size;
};
// Calculates the portion of the size related to the mix data (and the sorting thereof).
const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
// The size of the mixing info data structure.
constexpr u64 mix_info_size = 0x940;
// Consists of total submixes with the final mix included.
const u64 total_mix_count = u64{params.submix_count} + 1;
// The total number of effects that may be available to the audio renderer at any time.
constexpr u64 max_effects = 256;
// Calculates the part of the size related to the audio node state.
// This will only be used if the audio revision supports the splitter.
const auto calculate_node_state_size = [](std::size_t num_nodes) {
// Internally within a nodestate, it appears to use a data structure
// similar to a std::bitset<64> twice.
constexpr u64 bit_size = Common::BitSize<u64>();
constexpr u64 num_bitsets = 2;
// Node state instances have three states internally for performing
// depth-first searches of nodes. Initialized, Found, and Done Sorting.
constexpr u64 num_states = 3;
u64 size = 0;
size += (num_nodes * num_nodes) * sizeof(s32);
size += num_states * (num_nodes * sizeof(s32));
size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>());
return size;
};
// Calculates the part of the size related to the adjacency (aka edge) matrix.
const auto calculate_edge_matrix_size = [](std::size_t num_nodes) {
return (num_nodes * num_nodes) * sizeof(s32);
};
u64 size = 0;
size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size);
size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size);
size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count,
info_field_alignment_size);
if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
size += Common::AlignUp(calculate_node_state_size(total_mix_count) +
calculate_edge_matrix_size(total_mix_count),
info_field_alignment_size);
}
return size;
};
// Calculates the part of the size related to voice channel info.
const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 voice_info_size = 0x220;
constexpr u64 voice_resource_size = 0xD0;
u64 size = 0;
size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size);
size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size);
size +=
Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size);
size += Common::AlignUp(voice_state_size * params.voice_count, info_field_alignment_size);
return size;
};
// Calculates the part of the size related to memory pools.
const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
const u64 memory_pool_info_size = 0x20;
return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
};
// Calculates the part of the size related to the splitter context.
const auto calculate_splitter_context_size =
[](const AudioCommon::AudioRendererParameter& params) -> u64 {
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
return 0;
}
constexpr u64 splitter_info_size = 0x20;
constexpr u64 splitter_destination_data_size = 0xE0;
u64 size = 0;
size += params.num_splitter_send_channels;
size +=
Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size);
size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels,
info_field_alignment_size);
return size;
};
// Calculates the part of the size related to the upsampler info.
const auto calculate_upsampler_info_size =
[](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 upsampler_info_size = 0x280;
// Yes, using the buffer size over info alignment size is intentional here.
return Common::AlignUp(upsampler_info_size *
(u64{params.submix_count} + params.sink_count),
buffer_alignment_size);
};
// Calculates the part of the size related to effect info.
const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 effect_info_size = 0x2B0;
return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
};
// Calculates the part of the size related to audio sink info.
const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 sink_info_size = 0x170;
return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
};
// Calculates the part of the size related to voice state info.
const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 voice_state_size = 0x100;
const u64 additional_size = buffer_alignment_size - 1;
return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
info_field_alignment_size);
};
// Calculates the part of the size related to performance statistics.
const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
// Extra size value appended to the end of the calculation.
constexpr u64 appended = 128;
// Whether or not we assume the newer version of performance metrics data structures.
const bool is_v2 =
IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision);
// Data structure sizes
constexpr u64 perf_statistics_size = 0x0C;
const u64 header_size = is_v2 ? 0x30 : 0x18;
const u64 entry_size = is_v2 ? 0x18 : 0x10;
const u64 detail_size = is_v2 ? 0x18 : 0x10;
const u64 entry_count = CalculateNumPerformanceEntries(params);
const u64 size_per_frame =
header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries);
u64 size = 0;
size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1,
buffer_alignment_size);
size += Common::AlignUp(perf_statistics_size, buffer_alignment_size);
size += appended;
return size;
};
// Calculates the part of the size that relates to the audio command buffer.
const auto calculate_command_buffer_size =
[](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
constexpr u64 command_buffer_size = 0x18000;
return command_buffer_size + alignment;
}
// When the variadic command buffer is supported, this means
// the command generator for the audio renderer can issue commands
// that are (as one would expect), variable in size. So what we need to do
// is determine the maximum possible size for a few command data structures
// then multiply them by the amount of present commands indicated by the given
// respective audio parameters.
constexpr u64 max_biquad_filters = 2;
constexpr u64 max_mix_buffers = 24;
constexpr u64 biquad_filter_command_size = 0x2C;
constexpr u64 depop_mix_command_size = 0x24;
constexpr u64 depop_setup_command_size = 0x50;
constexpr u64 effect_command_max_size = 0x540;
constexpr u64 mix_command_size = 0x1C;
constexpr u64 mix_ramp_command_size = 0x24;
constexpr u64 mix_ramp_grouped_command_size = 0x13C;
constexpr u64 perf_command_size = 0x28;
constexpr u64 sink_command_size = 0x130;
constexpr u64 submix_command_max_size =
depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
constexpr u64 volume_command_size = 0x1C;
constexpr u64 volume_ramp_command_size = 0x20;
constexpr u64 voice_biquad_filter_command_size =
biquad_filter_command_size * max_biquad_filters;
constexpr u64 voice_data_command_size = 0x9C;
const u64 voice_command_max_size =
(params.splitter_count * depop_setup_command_size) +
(voice_data_command_size + voice_biquad_filter_command_size +
volume_ramp_command_size + mix_ramp_grouped_command_size);
// Now calculate the individual elements that comprise the size and add them together.
const u64 effect_commands_size = params.effect_count * effect_command_max_size;
const u64 final_mix_commands_size =
depop_mix_command_size + volume_command_size * max_mix_buffers;
const u64 perf_commands_size =
perf_command_size *
(CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
const u64 sink_commands_size = params.sink_count * sink_command_size;
const u64 splitter_commands_size =
params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
const u64 submix_commands_size = params.submix_count * submix_command_max_size;
const u64 voice_commands_size = params.voice_count * voice_command_max_size;
return effect_commands_size + final_mix_commands_size + perf_commands_size +
sink_commands_size + splitter_commands_size + submix_commands_size +
voice_commands_size + alignment;
};
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
u64 size = 0;
size += calculate_mix_buffer_sizes(params);
size += calculate_mix_info_size(params);
size += calculate_voice_info_size(params);
size += upsampler_manager_size;
size += calculate_memory_pools_size(params);
size += calculate_splitter_context_size(params);
size = Common::AlignUp(size, buffer_alignment_size);
size += calculate_upsampler_info_size(params);
size += calculate_effect_info_size(params);
size += calculate_sink_info_size(params);
size += calculate_voice_state_size(params);
size += calculate_perf_size(params);
size += calculate_command_buffer_size(params);
// finally, 4KB page align the size, and we're done.
size = Common::AlignUp(size, 4096);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(size);
LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size);
}
void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 aruid = rp.Pop<u64>();
LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid);
// Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that
// always assumes the initial release revision (REV1).
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioDevice>(system, Common::MakeMagic('R', 'E', 'V', '1'));
}
void AudRenU::OpenAudioRendererAuto(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
OpenAudioRendererImpl(ctx);
}
void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
struct Parameters {
u32 revision;
u64 aruid;
};
IPC::RequestParser rp{ctx};
const auto [revision, aruid] = rp.PopRaw<Parameters>();
LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioDevice>(system, revision);
}
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++);
}
bool IsFeatureSupported(AudioFeatures feature, u32_le revision) {
// Byte swap
const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
switch (feature) {
case AudioFeatures::AudioUSBDeviceOutput:
return version_num >= 4U;
case AudioFeatures::Splitter:
return version_num >= 2U;
case AudioFeatures::PerformanceMetricsVersion2:
case AudioFeatures::VariadicCommandBuffer:
return version_num >= 5U;
default:
return false;
}
}
} // namespace Service::Audio

View File

@@ -0,0 +1,47 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class HLERequestContext;
}
namespace Service::Audio {
class AudRenU final : public ServiceFramework<AudRenU> {
public:
explicit AudRenU(Core::System& system_);
~AudRenU() override;
private:
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetAudioDeviceService(Kernel::HLERequestContext& ctx);
void OpenAudioRendererAuto(Kernel::HLERequestContext& ctx);
void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx);
std::size_t audren_instance_count = 0;
};
// Describes a particular audio feature that may be supported in a particular revision.
enum class AudioFeatures : u32 {
AudioUSBDeviceOutput,
Splitter,
PerformanceMetricsVersion2,
VariadicCommandBuffer,
};
// Tests if a particular audio feature is supported with a given audio revision.
bool IsFeatureSupported(AudioFeatures feature, u32_le revision);
} // namespace Service::Audio

View File

@@ -0,0 +1,30 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/audio/codecctl.h"
namespace Service::Audio {
CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} {
static const FunctionInfo functions[] = {
{0, nullptr, "InitializeCodecController"},
{1, nullptr, "FinalizeCodecController"},
{2, nullptr, "SleepCodecController"},
{3, nullptr, "WakeCodecController"},
{4, nullptr, "SetCodecVolume"},
{5, nullptr, "GetCodecVolumeMax"},
{6, nullptr, "GetCodecVolumeMin"},
{7, nullptr, "SetCodecActiveTarget"},
{8, nullptr, "GetCodecActiveTarget"},
{9, nullptr, "BindCodecHeadphoneMicJackInterrupt"},
{10, nullptr, "IsCodecHeadphoneMicJackInserted"},
{11, nullptr, "ClearCodecHeadphoneMicJackInterrupt"},
{12, nullptr, "IsCodecDeviceRequested"},
};
RegisterHandlers(functions);
}
CodecCtl::~CodecCtl() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,21 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class CodecCtl final : public ServiceFramework<CodecCtl> {
public:
explicit CodecCtl(Core::System& system_);
~CodecCtl() override;
};
} // namespace Service::Audio

View File

@@ -0,0 +1,15 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
namespace Service::Audio {
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::Audio, 2};
constexpr ResultCode ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8};
constexpr ResultCode ERR_NOT_SUPPORTED{ErrorModule::Audio, 513};
} // namespace Service::Audio

View File

@@ -0,0 +1,306 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <cstring>
#include <memory>
#include <vector>
#include <opus.h>
#include <opus_multistream.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/hwopus.h"
namespace Service::Audio {
namespace {
struct OpusDeleter {
void operator()(OpusMSDecoder* ptr) const {
opus_multistream_decoder_destroy(ptr);
}
};
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
struct OpusPacketHeader {
// Packet size in bytes.
u32_be size;
// Indicates the final range of the codec's entropy coder.
u32_be final_range;
};
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
class OpusDecoderState {
public:
/// Describes extra behavior that may be asked of the decoding context.
enum class ExtraBehavior {
/// No extra behavior.
None,
/// Resets the decoder context back to a freshly initialized state.
ResetContext,
};
enum class PerfTime {
Disabled,
Enabled,
};
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
// perform the decoding, as well as any relevant extra behavior.
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
ExtraBehavior extra_behavior) {
if (perf_time == PerfTime::Disabled) {
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
} else {
u64 performance = 0;
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
}
}
private:
void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
ExtraBehavior extra_behavior) {
u32 consumed = 0;
u32 sample_count = 0;
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
if (extra_behavior == ExtraBehavior::ResetContext) {
ResetDecoderContext();
}
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
LOG_ERROR(Audio, "Failed to decode opus data");
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(RESULT_UNKNOWN);
return;
}
const u32 param_size = performance != nullptr ? 6 : 4;
IPC::ResponseBuilder rb{ctx, param_size};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(consumed);
rb.Push<u32>(sample_count);
if (performance) {
rb.Push<u64>(*performance);
}
ctx.WriteBuffer(samples);
}
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
std::vector<opus_int16>& output, u64* out_performance_time) const {
const auto start_time = std::chrono::high_resolution_clock::now();
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
if (sizeof(OpusPacketHeader) > input.size()) {
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
sizeof(OpusPacketHeader), input.size());
return false;
}
OpusPacketHeader hdr{};
std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
return false;
}
const auto frame = input.data() + sizeof(OpusPacketHeader);
const auto decoded_sample_count = opus_packet_get_nb_samples(
frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
static_cast<opus_int32>(sample_rate));
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
LOG_ERROR(
Audio,
"Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
return false;
}
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
const auto out_sample_count =
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
if (out_sample_count < 0) {
LOG_ERROR(Audio,
"Incorrect sample count received from opus_decode, "
"output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
out_sample_count, frame_size, static_cast<u32>(hdr.size));
return false;
}
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
sample_count = out_sample_count;
consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
if (out_performance_time != nullptr) {
*out_performance_time =
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
}
return true;
}
void ResetDecoderContext() {
ASSERT(decoder != nullptr);
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
}
OpusDecoderPtr decoder;
u32 sample_rate;
u32 channel_count;
};
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
public:
explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state)
: ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
std::move(decoder_state)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
{1, nullptr, "SetContext"},
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
{3, nullptr, "SetContextForMultiStream"},
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
{7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
{8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
{9, nullptr, "DecodeInterleavedForMultiStream"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
OpusDecoderState::ExtraBehavior::None);
}
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
OpusDecoderState::ExtraBehavior::None);
}
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
IPC::RequestParser rp{ctx};
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
: OpusDecoderState::ExtraBehavior::None;
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
}
OpusDecoderState decoder_state;
};
std::size_t WorkerBufferSize(u32 channel_count) {
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
constexpr int num_streams = 1;
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
}
// Creates the mapping table that maps the input channels to the particular
// output channels. In the stereo case, we map the left and right input channels
// to the left and right output channels respectively.
//
// However, in the monophonic case, we only map the one available channel
// to the sole output channel. We specify 255 for the would-be right channel
// as this is a special value defined by Opus to indicate to the decoder to
// ignore that channel.
std::array<u8, 2> CreateMappingTable(u32 channel_count) {
if (channel_count == 2) {
return {{0, 1}};
}
return {{0, 255}};
}
} // Anonymous namespace
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
const auto channel_count = rp.Pop<u32>();
LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(worker_buffer_sz);
}
void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
const auto channel_count = rp.Pop<u32>();
const auto buffer_sz = rp.Pop<u32>();
LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
channel_count, buffer_sz);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
const std::size_t worker_sz = WorkerBufferSize(channel_count);
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
const auto mapping_table = CreateMappingTable(channel_count);
int error = 0;
OpusDecoderPtr decoder{
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
num_stereo_streams, mapping_table.data(), &error)};
if (error != OPUS_OK || decoder == nullptr) {
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(RESULT_UNKNOWN);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
}
HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
static const FunctionInfo functions[] = {
{0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
{2, nullptr, "OpenOpusDecoderForMultiStream"},
{3, nullptr, "GetWorkBufferSizeForMultiStream"},
};
RegisterHandlers(functions);
}
HwOpus::~HwOpus() = default;
} // namespace Service::Audio

View File

@@ -0,0 +1,25 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Audio {
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
~HwOpus() override;
private:
void OpenOpusDecoder(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio

View File

@@ -0,0 +1,136 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/lock.h"
#include "core/hle/service/bcat/backend/backend.h"
namespace Service::BCAT {
ProgressServiceBackend::ProgressServiceBackend(Kernel::KernelCore& kernel,
std::string_view event_name) {
event = Kernel::WritableEvent::CreateEventPair(
kernel, std::string("ProgressServiceBackend:UpdateEvent:").append(event_name));
}
std::shared_ptr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const {
return event.readable;
}
DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
return impl;
}
void ProgressServiceBackend::SetNeedHLELock(bool need) {
need_hle_lock = need;
}
void ProgressServiceBackend::SetTotalSize(u64 size) {
impl.total_bytes = size;
SignalUpdate();
}
void ProgressServiceBackend::StartConnecting() {
impl.status = DeliveryCacheProgressImpl::Status::Connecting;
SignalUpdate();
}
void ProgressServiceBackend::StartProcessingDataList() {
impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
SignalUpdate();
}
void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
std::string_view file_name, u64 file_size) {
impl.status = DeliveryCacheProgressImpl::Status::Downloading;
impl.current_downloaded_bytes = 0;
impl.current_total_bytes = file_size;
std::memcpy(impl.current_directory.data(), dir_name.data(),
std::min<u64>(dir_name.size(), 0x31ull));
std::memcpy(impl.current_file.data(), file_name.data(),
std::min<u64>(file_name.size(), 0x31ull));
SignalUpdate();
}
void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
impl.current_downloaded_bytes = downloaded;
SignalUpdate();
}
void ProgressServiceBackend::FinishDownloadingFile() {
impl.total_downloaded_bytes += impl.current_total_bytes;
SignalUpdate();
}
void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
impl.status = DeliveryCacheProgressImpl::Status::Committing;
impl.current_file.fill(0);
impl.current_downloaded_bytes = 0;
impl.current_total_bytes = 0;
std::memcpy(impl.current_directory.data(), dir_name.data(),
std::min<u64>(dir_name.size(), 0x31ull));
SignalUpdate();
}
void ProgressServiceBackend::FinishDownload(ResultCode result) {
impl.total_downloaded_bytes = impl.total_bytes;
impl.status = DeliveryCacheProgressImpl::Status::Done;
impl.result = result;
SignalUpdate();
}
void ProgressServiceBackend::SignalUpdate() const {
if (need_hle_lock) {
std::lock_guard lock(HLE::g_hle_lock);
event.writable->Signal();
} else {
event.writable->Signal();
}
}
Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
Backend::~Backend() = default;
NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {}
NullBackend::~NullBackend() = default;
bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
title.build_id);
progress.FinishDownload(RESULT_SUCCESS);
return true;
}
bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
ProgressServiceBackend& progress) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
title.build_id, name);
progress.FinishDownload(RESULT_SUCCESS);
return true;
}
bool NullBackend::Clear(u64 title_id) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
return true;
}
void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
Common::HexToString(passphrase));
}
std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
title.build_id);
return std::nullopt;
}
} // namespace Service::BCAT

View File

@@ -0,0 +1,158 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/result.h"
namespace Core {
class System;
}
namespace Kernel {
class KernelCore;
}
namespace Service::BCAT {
struct DeliveryCacheProgressImpl;
using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
using Passphrase = std::array<u8, 0x20>;
struct TitleIDVersion {
u64 title_id;
u64 build_id;
};
using DirectoryName = std::array<char, 0x20>;
using FileName = std::array<char, 0x20>;
struct DeliveryCacheProgressImpl {
enum class Status : s32 {
None = 0x0,
Queued = 0x1,
Connecting = 0x2,
ProcessingDataList = 0x3,
Downloading = 0x4,
Committing = 0x5,
Done = 0x9,
};
Status status;
ResultCode result = RESULT_SUCCESS;
DirectoryName current_directory;
FileName current_file;
s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
s64 current_total_bytes; ///< Bytes total on current file.
s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
s64 total_bytes; ///< Bytes total on overall download.
INSERT_PADDING_BYTES(
0x198); ///< Appears to be unused in official code, possibly reserved for future use.
};
static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
"DeliveryCacheProgressImpl has incorrect size.");
// A class to manage the signalling to the game about BCAT download progress.
// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
class ProgressServiceBackend {
friend class IBcatService;
public:
// Clients should call this with true if any of the functions are going to be called from a
// non-HLE thread and this class need to lock the hle mutex. (default is false)
void SetNeedHLELock(bool need);
// Sets the number of bytes total in the entire download.
void SetTotalSize(u64 size);
// Notifies the application that the backend has started connecting to the server.
void StartConnecting();
// Notifies the application that the backend has begun accumulating and processing metadata.
void StartProcessingDataList();
// Notifies the application that a file is starting to be downloaded.
void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
// Updates the progress of the current file to the size passed.
void UpdateFileProgress(u64 downloaded);
// Notifies the application that the current file has completed download.
void FinishDownloadingFile();
// Notifies the application that all files in this directory have completed and are being
// finalized.
void CommitDirectory(std::string_view dir_name);
// Notifies the application that the operation completed with result code result.
void FinishDownload(ResultCode result);
private:
explicit ProgressServiceBackend(Kernel::KernelCore& kernel, std::string_view event_name);
std::shared_ptr<Kernel::ReadableEvent> GetEvent() const;
DeliveryCacheProgressImpl& GetImpl();
void SignalUpdate() const;
DeliveryCacheProgressImpl impl{};
Kernel::EventPair event;
bool need_hle_lock = false;
};
// A class representing an abstract backend for BCAT functionality.
class Backend {
public:
explicit Backend(DirectoryGetter getter);
virtual ~Backend();
// Called when the backend is needed to synchronize the data for the game with title ID and
// version in title. A ProgressServiceBackend object is provided to alert the application of
// status.
virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
// Very similar to Synchronize, but only for the directory provided. Backends should not alter
// the data for any other directories.
virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
ProgressServiceBackend& progress) = 0;
// Removes all cached data associated with title id provided.
virtual bool Clear(u64 title_id) = 0;
// Sets the BCAT Passphrase to be used with the associated title ID.
virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
// Gets the launch parameter used by AM associated with the title ID and version provided.
virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
protected:
DirectoryGetter dir_getter;
};
// A backend of BCAT that provides no operation.
class NullBackend : public Backend {
public:
explicit NullBackend(DirectoryGetter getter);
~NullBackend() override;
bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
bool SynchronizeDirectory(TitleIDVersion title, std::string name,
ProgressServiceBackend& progress) override;
bool Clear(u64 title_id) override;
void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
};
std::unique_ptr<Backend> CreateBackendFromSettings(Core::System& system, DirectoryGetter getter);
} // namespace Service::BCAT

View File

@@ -0,0 +1,521 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/ostream.h>
#include <httplib.h>
#include <mbedtls/sha256.h>
#include <nlohmann/json.hpp>
#include "common/hex_util.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_libzip.h"
#include "core/file_sys/vfs_vector.h"
#include "core/frontend/applets/error.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/bcat/backend/boxcat.h"
#include "core/settings.h"
namespace Service::BCAT {
namespace {
// Prevents conflicts with windows macro called CreateFile
FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
return dir->CreateFile(name);
}
// Prevents conflicts with windows macro called DeleteFile
bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
return dir->DeleteFile(name);
}
constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
// Formatted using fmt with arg[0] = hex title id
constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
constexpr char BOXCAT_API_VERSION[] = "1";
constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
// HTTP status codes for Boxcat
enum class ResponseStatus {
Ok = 200, ///< Operation completed successfully.
BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
NoUpdate = 304, ///< The digest provided would match the new data, no need to update.
NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation.
NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format
///< issues or whatnot) and has no data.
};
enum class DownloadResult {
Success = 0,
NoResponse,
GeneralWebError,
NoMatchTitleId,
NoMatchBuildId,
InvalidContentType,
GeneralFSError,
BadClientVersion,
};
constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
"Success",
"There was no response from the server.",
"There was a general web error code returned from the server.",
"The title ID of the current game doesn't have a boxcat implementation. If you believe an "
"implementation should be added, contact yuzu support.",
"The build ID of the current version of the game is marked as incompatible with the current "
"BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
"The content type of the web response was invalid.",
"There was a general filesystem error while saving the zip file.",
"The server is either too new or too old to serve the request. Try using the latest version of "
"an official release of yuzu.",
};
std::ostream& operator<<(std::ostream& os, DownloadResult result) {
return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
}
constexpr u32 PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30;
[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
std::string GetBINFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/launchparam.bin",
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
std::string GetZIPFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/data.zip",
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
// If the error is something the user should know about (build ID mismatch, bad client version),
// display an error.
void HandleDownloadDisplayResult(const AM::Applets::AppletManager& applet_manager,
DownloadResult res) {
if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
return;
}
const auto& frontend{applet_manager.GetAppletFrontendSet()};
frontend.error->ShowCustomErrorText(
RESULT_UNKNOWN, "There was an error while attempting to use Boxcat.",
DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
}
bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
std::string_view dir_name, ProgressServiceBackend& progress,
std::size_t block_size = 0x1000) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
if (!dest->Resize(src->GetSize()))
return false;
progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
std::vector<u8> temp(std::min(block_size, src->GetSize()));
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
const auto read = std::min(block_size, src->GetSize() - i);
if (src->Read(temp.data(), read, i) != read) {
return false;
}
if (dest->Write(temp.data(), read, i) != read) {
return false;
}
progress.UpdateFileProgress(i);
}
progress.FinishDownloadingFile();
return true;
}
bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
for (const auto& file : src->GetFiles()) {
const auto out_file = VfsCreateFileWrap(dest, file->GetName());
if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
return false;
}
}
progress.CommitDirectory(src->GetName());
return true;
}
bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
for (const auto& dir : src->GetSubdirectories()) {
const auto out = dest->CreateSubdirectory(dir->GetName());
if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
return false;
}
}
return true;
}
} // Anonymous namespace
class Boxcat::Client {
public:
Client(std::string path, u64 title_id, u64 build_id)
: path(std::move(path)), title_id(title_id), build_id(build_id) {}
DownloadResult DownloadDataZip() {
return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
"application/zip");
}
DownloadResult DownloadLaunchParam() {
return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
TIMEOUT_SECONDS / 3, "application/octet-stream");
}
private:
DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
const std::string& content_type_name) {
if (client == nullptr) {
client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT);
client->set_connection_timeout(timeout_seconds);
client->set_read_timeout(timeout_seconds);
client->set_write_timeout(timeout_seconds);
}
httplib::Headers headers{
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
{std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
};
if (Common::FS::Exists(path)) {
Common::FS::IOFile file{path, "rb"};
if (file.IsOpen()) {
std::vector<u8> bytes(file.GetSize());
file.ReadBytes(bytes.data(), bytes.size());
const auto digest = DigestFile(bytes);
headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
}
}
const auto response = client->Get(resolved_path.c_str(), headers);
if (response == nullptr)
return DownloadResult::NoResponse;
if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
return DownloadResult::Success;
if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
return DownloadResult::BadClientVersion;
if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
return DownloadResult::NoMatchTitleId;
if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
return DownloadResult::NoMatchBuildId;
if (response->status != static_cast<int>(ResponseStatus::Ok))
return DownloadResult::GeneralWebError;
const auto content_type = response->headers.find("content-type");
if (content_type == response->headers.end() ||
content_type->second.find(content_type_name) == std::string::npos) {
return DownloadResult::InvalidContentType;
}
Common::FS::CreateFullPath(path);
Common::FS::IOFile file{path, "wb"};
if (!file.IsOpen())
return DownloadResult::GeneralFSError;
if (!file.Resize(response->body.size()))
return DownloadResult::GeneralFSError;
if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
return DownloadResult::GeneralFSError;
return DownloadResult::Success;
}
using Digest = std::array<u8, 0x20>;
static Digest DigestFile(std::vector<u8> bytes) {
Digest out{};
mbedtls_sha256_ret(bytes.data(), bytes.size(), out.data(), 0);
return out;
}
std::unique_ptr<httplib::SSLClient> client;
std::string path;
u64 title_id;
u64 build_id;
};
Boxcat::Boxcat(AM::Applets::AppletManager& applet_manager_, DirectoryGetter getter)
: Backend(std::move(getter)), applet_manager{applet_manager_} {}
Boxcat::~Boxcat() = default;
void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGetter dir_getter,
TitleIDVersion title, ProgressServiceBackend& progress,
std::optional<std::string> dir_name = {}) {
progress.SetNeedHLELock(true);
if (Settings::values.bcat_boxcat_local) {
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
const auto dir = dir_getter(title.title_id);
if (dir)
progress.SetTotalSize(dir->GetSize());
progress.FinishDownload(RESULT_SUCCESS);
return;
}
const auto zip_path{GetZIPFilePath(title.title_id)};
Boxcat::Client client{zip_path, title.title_id, title.build_id};
progress.StartConnecting();
const auto res = client.DownloadDataZip();
if (res != DownloadResult::Success) {
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
Common::FS::Delete(zip_path);
}
HandleDownloadDisplayResult(applet_manager, res);
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
progress.StartProcessingDataList();
Common::FS::IOFile zip{zip_path, "rb"};
const auto size = zip.GetSize();
std::vector<u8> bytes(size);
if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
if (extracted == nullptr) {
LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
if (dir_name == std::nullopt) {
progress.SetTotalSize(extracted->GetSize());
const auto target_dir = dir_getter(title.title_id);
if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
} else {
const auto target_dir = dir_getter(title.title_id);
if (target_dir == nullptr) {
LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
const auto target_sub = target_dir->GetSubdirectory(*dir_name);
const auto source_sub = extracted->GetSubdirectory(*dir_name);
progress.SetTotalSize(source_sub->GetSize());
std::vector<std::string> filenames;
{
const auto files = target_sub->GetFiles();
std::transform(files.begin(), files.end(), std::back_inserter(filenames),
[](const auto& vfile) { return vfile->GetName(); });
}
for (const auto& filename : filenames) {
VfsDeleteFileWrap(target_sub, filename);
}
if (target_sub == nullptr || source_sub == nullptr ||
!VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
return;
}
}
progress.FinishDownload(RESULT_SUCCESS);
}
bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
is_syncing.exchange(true);
std::thread([this, title, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress);
}).detach();
return true;
}
bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
ProgressServiceBackend& progress) {
is_syncing.exchange(true);
std::thread([this, title, name, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress, name);
}).detach();
return true;
}
bool Boxcat::Clear(u64 title_id) {
if (Settings::values.bcat_boxcat_local) {
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
return true;
}
const auto dir = dir_getter(title_id);
std::vector<std::string> dirnames;
for (const auto& subdir : dir->GetSubdirectories())
dirnames.push_back(subdir->GetName());
for (const auto& subdir : dirnames) {
if (!dir->DeleteSubdirectoryRecursive(subdir))
return false;
}
return true;
}
void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
Common::HexToString(passphrase));
}
std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
const auto path{GetBINFilePath(title.title_id)};
if (Settings::values.bcat_boxcat_local) {
LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
} else {
Boxcat::Client client{path, title.title_id, title.build_id};
const auto res = client.DownloadLaunchParam();
if (res != DownloadResult::Success) {
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
Common::FS::Delete(path);
}
HandleDownloadDisplayResult(applet_manager, res);
return std::nullopt;
}
}
Common::FS::IOFile bin{path, "rb"};
const auto size = bin.GetSize();
std::vector<u8> bytes(size);
if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
path);
return std::nullopt;
}
return bytes;
}
Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
std::map<std::string, EventStatus>& games) {
httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)};
client.set_connection_timeout(static_cast<int>(TIMEOUT_SECONDS));
client.set_read_timeout(static_cast<int>(TIMEOUT_SECONDS));
client.set_write_timeout(static_cast<int>(TIMEOUT_SECONDS));
httplib::Headers headers{
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
};
if (!client.is_valid()) {
LOG_ERROR(Service_BCAT, "Client is invalid, going offline!");
return StatusResult::Offline;
}
if (!client.is_socket_open()) {
LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!");
return StatusResult::Offline;
}
const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
if (response == nullptr)
return StatusResult::Offline;
if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
return StatusResult::BadClientVersion;
try {
nlohmann::json json = nlohmann::json::parse(response->body);
if (!json["online"].get<bool>())
return StatusResult::Offline;
if (json["global"].is_null())
global = std::nullopt;
else
global = json["global"].get<std::string>();
if (json["games"].is_array()) {
for (const auto& object : json["games"]) {
if (object.is_object() && object.find("name") != object.end()) {
EventStatus detail{};
if (object["header"].is_string()) {
detail.header = object["header"].get<std::string>();
} else {
detail.header = std::nullopt;
}
if (object["footer"].is_string()) {
detail.footer = object["footer"].get<std::string>();
} else {
detail.footer = std::nullopt;
}
if (object["events"].is_array()) {
for (const auto& event : object["events"]) {
if (!event.is_string())
continue;
detail.events.push_back(event.get<std::string>());
}
}
games.insert_or_assign(object["name"], std::move(detail));
}
}
}
return StatusResult::Success;
} catch (const nlohmann::json::parse_error& error) {
LOG_ERROR(Service_BCAT, "{}", error.what());
return StatusResult::ParseError;
}
}
} // namespace Service::BCAT

View File

@@ -0,0 +1,64 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <map>
#include <optional>
#include "core/hle/service/bcat/backend/backend.h"
namespace Service::AM::Applets {
class AppletManager;
}
namespace Service::BCAT {
struct EventStatus {
std::optional<std::string> header;
std::optional<std::string> footer;
std::vector<std::string> events;
};
/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and
/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
class Boxcat final : public Backend {
friend void SynchronizeInternal(AM::Applets::AppletManager& applet_manager,
DirectoryGetter dir_getter, TitleIDVersion title,
ProgressServiceBackend& progress,
std::optional<std::string> dir_name);
public:
explicit Boxcat(AM::Applets::AppletManager& applet_manager_, DirectoryGetter getter);
~Boxcat() override;
bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
bool SynchronizeDirectory(TitleIDVersion title, std::string name,
ProgressServiceBackend& progress) override;
bool Clear(u64 title_id) override;
void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
enum class StatusResult {
Success,
Offline,
ParseError,
BadClientVersion,
};
static StatusResult GetStatus(std::optional<std::string>& global,
std::map<std::string, EventStatus>& games);
private:
std::atomic_bool is_syncing{false};
class Client;
std::unique_ptr<Client> client;
AM::Applets::AppletManager& applet_manager;
};
} // namespace Service::BCAT

View File

@@ -0,0 +1,25 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/bcat/bcat.h"
namespace Service::BCAT {
BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module,
FileSystem::FileSystemController& fsc, const char* name)
: Interface(system, std::move(module), fsc, name) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BCAT::CreateBcatService, "CreateBcatService"},
{1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
{2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
{3, nullptr, "CreateDeliveryCacheProgressService"},
{4, nullptr, "CreateDeliveryCacheProgressServiceWithApplicationId"},
};
// clang-format on
RegisterHandlers(functions);
}
BCAT::~BCAT() = default;
} // namespace Service::BCAT

View File

@@ -0,0 +1,22 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/bcat/module.h"
namespace Core {
class System;
}
namespace Service::BCAT {
class BCAT final : public Module::Interface {
public:
explicit BCAT(Core::System& system, std::shared_ptr<Module> module,
FileSystem::FileSystemController& fsc, const char* name);
~BCAT() override;
};
} // namespace Service::BCAT

View File

@@ -0,0 +1,604 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cctype>
#include <mbedtls/md5.h>
#include "backend/boxcat.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/bcat/module.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/settings.h"
namespace Service::BCAT {
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
// and if any of them have a non-zero result it just forwards that result. This is the FS error code
// for permission denied, which is the closest approximation of this scenario.
constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
using BCATDigest = std::array<u8, 0x10>;
namespace {
u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
u64 out{};
std::memcpy(&out, id.data(), sizeof(u64));
return out;
}
// The digest is only used to determine if a file is unique compared to others of the same name.
// Since the algorithm isn't ever checked in game, MD5 is safe.
BCATDigest DigestFile(const FileSys::VirtualFile& file) {
BCATDigest out{};
const auto bytes = file->ReadAllBytes();
mbedtls_md5_ret(bytes.data(), bytes.size(), out.data());
return out;
}
// For a name to be valid it must be non-empty, must have a null terminating character as the final
// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
// file.
bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
char match_char) {
const auto null_chars = std::count(name.begin(), name.end(), 0);
const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
});
if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
LOG_ERROR(Service_BCAT, "Name passed was invalid!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return false;
}
return true;
}
bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
return VerifyNameValidInternal(ctx, name, '-');
}
bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
return VerifyNameValidInternal(ctx, name, '.');
}
} // Anonymous namespace
struct DeliveryCacheDirectoryEntry {
FileName name;
u64 size;
BCATDigest digest;
};
class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
public:
explicit IDeliveryCacheProgressService(Core::System& system_,
std::shared_ptr<Kernel::ReadableEvent> event_,
const DeliveryCacheProgressImpl& impl_)
: ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{std::move(event_)},
impl{impl_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
{1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event);
}
void GetImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
ctx.WriteBuffer(impl);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
std::shared_ptr<Kernel::ReadableEvent> event;
const DeliveryCacheProgressImpl& impl;
};
class IBcatService final : public ServiceFramework<IBcatService> {
public:
explicit IBcatService(Core::System& system_, Backend& backend_)
: ServiceFramework{system_, "IBcatService"}, backend{backend_},
progress{{
ProgressServiceBackend{system_.Kernel(), "Normal"},
ProgressServiceBackend{system_.Kernel(), "Directory"},
}} {
// clang-format off
static const FunctionInfo functions[] = {
{10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
{10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
{10200, nullptr, "CancelSyncDeliveryCacheRequest"},
{20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
{20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
{20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"},
{20301, nullptr, "RequestSuspendDeliveryTask"},
{20400, nullptr, "RegisterSystemApplicationDeliveryTask"},
{20401, nullptr, "UnregisterSystemApplicationDeliveryTask"},
{20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"},
{30100, &IBcatService::SetPassphrase, "SetPassphrase"},
{30101, nullptr, "Unknown"},
{30102, nullptr, "Unknown2"},
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
{30202, nullptr, "BlockDeliveryTask"},
{30203, nullptr, "UnblockDeliveryTask"},
{30210, nullptr, "SetDeliveryTaskTimer"},
{30300, nullptr, "RegisterSystemApplicationDeliveryTasks"},
{90100, nullptr, "EnumerateBackgroundDeliveryTask"},
{90200, nullptr, "GetDeliveryList"},
{90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
{90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"},
{90300, nullptr, "GetPushNotificationLog"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
enum class SyncType {
Normal,
Directory,
Count,
};
std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
auto& backend{progress.at(static_cast<std::size_t>(type))};
return std::make_shared<IDeliveryCacheProgressService>(system, backend.GetEvent(),
backend.GetImpl());
}
void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
backend.Synchronize({system.CurrentProcess()->GetTitleID(),
GetCurrentBuildID(system.GetCurrentProcessBuildID())},
progress.at(static_cast<std::size_t>(SyncType::Normal)));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
}
void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto name_raw = rp.PopRaw<DirectoryName>();
const auto name =
Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
LOG_DEBUG(Service_BCAT, "called, name={}", name);
backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(),
GetCurrentBuildID(system.GetCurrentProcessBuildID())},
name,
progress.at(static_cast<std::size_t>(SyncType::Directory)));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
}
void SetPassphrase(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
const auto passphrase_raw = ctx.ReadBuffer();
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
Common::HexToString(passphrase_raw));
if (title_id == 0) {
LOG_ERROR(Service_BCAT, "Invalid title ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
}
if (passphrase_raw.size() > 0x40) {
LOG_ERROR(Service_BCAT, "Passphrase too large!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
Passphrase passphrase{};
std::memcpy(passphrase.data(), passphrase_raw.data(),
std::min(passphrase.size(), passphrase_raw.size()));
backend.SetPassphrase(title_id, passphrase);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
if (title_id == 0) {
LOG_ERROR(Service_BCAT, "Invalid title ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
if (!backend.Clear(title_id)) {
LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_FAILED_CLEAR_CACHE);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
Backend& backend;
std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress;
};
void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IBcatService>(system, *backend);
}
class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
public:
explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_)
: ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheFileService::Open, "Open"},
{1, &IDeliveryCacheFileService::Read, "Read"},
{2, &IDeliveryCacheFileService::GetSize, "GetSize"},
{3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Open(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto dir_name_raw = rp.PopRaw<DirectoryName>();
const auto file_name_raw = rp.PopRaw<FileName>();
const auto dir_name =
Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
const auto file_name =
Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
return;
if (current_file != nullptr) {
LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_ENTITY_ALREADY_OPEN);
return;
}
const auto dir = root->GetSubdirectory(dir_name);
if (dir == nullptr) {
LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_FAILED_OPEN_ENTITY);
return;
}
current_file = dir->GetFile(file_name);
if (current_file == nullptr) {
LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_FAILED_OPEN_ENTITY);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto offset{rp.PopRaw<u64>()};
auto size = ctx.GetWriteBufferSize();
LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
if (current_file == nullptr) {
LOG_ERROR(Service_BCAT, "There is no file currently open!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_NO_OPEN_ENTITY);
}
size = std::min<u64>(current_file->GetSize() - offset, size);
const auto buffer = current_file->ReadBytes(size, offset);
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(buffer.size());
}
void GetSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
if (current_file == nullptr) {
LOG_ERROR(Service_BCAT, "There is no file currently open!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_NO_OPEN_ENTITY);
}
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(current_file->GetSize());
}
void GetDigest(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
if (current_file == nullptr) {
LOG_ERROR(Service_BCAT, "There is no file currently open!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_NO_OPEN_ENTITY);
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(DigestFile(current_file));
}
FileSys::VirtualDir root;
FileSys::VirtualFile current_file;
};
class IDeliveryCacheDirectoryService final
: public ServiceFramework<IDeliveryCacheDirectoryService> {
public:
explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_)
: ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheDirectoryService::Open, "Open"},
{1, &IDeliveryCacheDirectoryService::Read, "Read"},
{2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Open(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto name_raw = rp.PopRaw<DirectoryName>();
const auto name =
Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
LOG_DEBUG(Service_BCAT, "called, name={}", name);
if (!VerifyNameValidDir(ctx, name_raw))
return;
if (current_dir != nullptr) {
LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_ENTITY_ALREADY_OPEN);
return;
}
current_dir = root->GetSubdirectory(name);
if (current_dir == nullptr) {
LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_FAILED_OPEN_ENTITY);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Read(Kernel::HLERequestContext& ctx) {
auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
if (current_dir == nullptr) {
LOG_ERROR(Service_BCAT, "There is no open directory!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_NO_OPEN_ENTITY);
return;
}
const auto files = current_dir->GetFiles();
write_size = std::min<u64>(write_size, files.size());
std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
std::transform(
files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
FileName name{};
std::memcpy(name.data(), file->GetName().data(),
std::min(file->GetName().size(), name.size()));
return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
});
ctx.WriteBuffer(entries);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)));
}
void GetCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
if (current_dir == nullptr) {
LOG_ERROR(Service_BCAT, "There is no open directory!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_NO_OPEN_ENTITY);
return;
}
const auto files = current_dir->GetFiles();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(files.size()));
}
FileSys::VirtualDir root;
FileSys::VirtualDir current_dir;
};
class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
public:
explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_)
: ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
{1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
{10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
};
// clang-format on
RegisterHandlers(functions);
for (const auto& subdir : root->GetSubdirectories()) {
DirectoryName name{};
std::memcpy(name.data(), subdir->GetName().data(),
std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
entries.push_back(name);
}
}
private:
void CreateFileService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDeliveryCacheFileService>(system, root);
}
void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDeliveryCacheDirectoryService>(system, root);
}
void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
size = std::min<u64>(size, entries.size() - next_read_index);
ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
next_read_index += size;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(size));
}
FileSys::VirtualDir root;
std::vector<DirectoryName> entries;
u64 next_read_index = 0;
};
void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
const auto title_id = system.CurrentProcess()->GetTitleID();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
}
void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
}
std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
DirectoryGetter getter) {
#ifdef YUZU_ENABLE_BOXCAT
if (Settings::values.bcat_backend == "boxcat") {
return std::make_unique<Boxcat>(system.GetAppletManager(), std::move(getter));
}
#endif
return std::make_unique<NullBackend>(std::move(getter));
}
Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
FileSystem::FileSystemController& fsc_, const char* name)
: ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)},
backend{CreateBackendFromSettings(system_,
[&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {}
Module::Interface::~Interface() = default;
void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a")
->InstallAsService(system.ServiceManager());
std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m")
->InstallAsService(system.ServiceManager());
std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u")
->InstallAsService(system.ServiceManager());
std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s")
->InstallAsService(system.ServiceManager());
}
} // namespace Service::BCAT

View File

@@ -0,0 +1,48 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service {
namespace FileSystem {
class FileSystemController;
} // namespace FileSystem
namespace BCAT {
class Backend;
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
FileSystem::FileSystemController& fsc_, const char* name);
~Interface() override;
void CreateBcatService(Kernel::HLERequestContext& ctx);
void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
protected:
FileSystem::FileSystemController& fsc;
std::shared_ptr<Module> module;
std::unique_ptr<Backend> backend;
};
};
/// Registers all BCAT services with the specified service manager.
void InstallInterfaces(Core::System& system);
} // namespace BCAT
} // namespace Service

View File

@@ -0,0 +1,63 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "core/hle/service/bpc/bpc.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::BPC {
class BPC final : public ServiceFramework<BPC> {
public:
explicit BPC(Core::System& system_) : ServiceFramework{system_, "bpc"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ShutdownSystem"},
{1, nullptr, "RebootSystem"},
{2, nullptr, "GetWakeupReason"},
{3, nullptr, "GetShutdownReason"},
{4, nullptr, "GetAcOk"},
{5, nullptr, "GetBoardPowerControlEvent"},
{6, nullptr, "GetSleepButtonState"},
{7, nullptr, "GetPowerEvent"},
{8, nullptr, "CreateWakeupTimer"},
{9, nullptr, "CancelWakeupTimer"},
{10, nullptr, "EnableWakeupTimerOnDevice"},
{11, nullptr, "CreateWakeupTimerEx"},
{12, nullptr, "GetLastEnabledWakeupTimerType"},
{13, nullptr, "CleanAllWakeupTimers"},
{14, nullptr, "Unknown"},
{15, nullptr, "Unknown2"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class BPC_R final : public ServiceFramework<BPC_R> {
public:
explicit BPC_R(Core::System& system_) : ServiceFramework{system_, "bpc:r"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetRtcTime"},
{1, nullptr, "SetRtcTime"},
{2, nullptr, "GetRtcResetDetected"},
{3, nullptr, "ClearRtcResetDetected"},
{4, nullptr, "SetUpRtcResetOnShutdown"},
};
// clang-format on
RegisterHandlers(functions);
}
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<BPC>(system)->InstallAsService(sm);
std::make_shared<BPC_R>(system)->InstallAsService(sm);
}
} // namespace Service::BPC

19
src/core/hle/service/bpc/bpc.h Executable file
View File

@@ -0,0 +1,19 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
}
namespace Service::SM {
class ServiceManager;
}
namespace Service::BPC {
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BPC

View File

@@ -0,0 +1,173 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::BtDrv {
class Bt final : public ServiceFramework<Bt> {
public:
explicit Bt(Core::System& system_) : ServiceFramework{system_, "bt"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LeClientReadCharacteristic"},
{1, nullptr, "LeClientReadDescriptor"},
{2, nullptr, "LeClientWriteCharacteristic"},
{3, nullptr, "LeClientWriteDescriptor"},
{4, nullptr, "LeClientRegisterNotification"},
{5, nullptr, "LeClientDeregisterNotification"},
{6, nullptr, "SetLeResponse"},
{7, nullptr, "LeSendIndication"},
{8, nullptr, "GetLeEventInfo"},
{9, &Bt::RegisterBleEvent, "RegisterBleEvent"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = system.Kernel();
register_event = Kernel::WritableEvent::CreateEventPair(kernel, "BT:RegisterEvent");
}
private:
void RegisterBleEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_BTM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(register_event.readable);
}
Kernel::EventPair register_event;
};
class BtDrv final : public ServiceFramework<BtDrv> {
public:
explicit BtDrv(Core::System& system_) : ServiceFramework{system_, "btdrv"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "InitializeBluetoothDriver"},
{1, nullptr, "InitializeBluetooth"},
{2, nullptr, "EnableBluetooth"},
{3, nullptr, "DisableBluetooth"},
{4, nullptr, "FinalizeBluetooth"},
{5, nullptr, "GetAdapterProperties"},
{6, nullptr, "GetAdapterProperty"},
{7, nullptr, "SetAdapterProperty"},
{8, nullptr, "StartInquiry"},
{9, nullptr, "StopInquiry"},
{10, nullptr, "CreateBond"},
{11, nullptr, "RemoveBond"},
{12, nullptr, "CancelBond"},
{13, nullptr, "RespondToPinRequest"},
{14, nullptr, "RespondToSspRequest"},
{15, nullptr, "GetEventInfo"},
{16, nullptr, "InitializeHid"},
{17, nullptr, "OpenHidConnection"},
{18, nullptr, "CloseHidConnection"},
{19, nullptr, "WriteHidData"},
{20, nullptr, "WriteHidData2"},
{21, nullptr, "SetHidReport"},
{22, nullptr, "GetHidReport"},
{23, nullptr, "TriggerConnection"},
{24, nullptr, "AddPairedDeviceInfo"},
{25, nullptr, "GetPairedDeviceInfo"},
{26, nullptr, "FinalizeHid"},
{27, nullptr, "GetHidEventInfo"},
{28, nullptr, "SetTsi"},
{29, nullptr, "EnableBurstMode"},
{30, nullptr, "SetZeroRetransmission"},
{31, nullptr, "EnableMcMode"},
{32, nullptr, "EnableLlrScan"},
{33, nullptr, "DisableLlrScan"},
{34, nullptr, "EnableRadio"},
{35, nullptr, "SetVisibility"},
{36, nullptr, "EnableTbfcScan"},
{37, nullptr, "RegisterHidReportEvent"},
{38, nullptr, "GetHidReportEventInfo"},
{39, nullptr, "GetLatestPlr"},
{40, nullptr, "GetPendingConnections"},
{41, nullptr, "GetChannelMap"},
{42, nullptr, "EnableTxPowerBoostSetting"},
{43, nullptr, "IsTxPowerBoostSettingEnabled"},
{44, nullptr, "EnableAfhSetting"},
{45, nullptr, "IsAfhSettingEnabled"},
{46, nullptr, "InitializeBle"},
{47, nullptr, "EnableBle"},
{48, nullptr, "DisableBle"},
{49, nullptr, "FinalizeBle"},
{50, nullptr, "SetBleVisibility"},
{51, nullptr, "SetBleConnectionParameter"},
{52, nullptr, "SetBleDefaultConnectionParameter"},
{53, nullptr, "SetBleAdvertiseData"},
{54, nullptr, "SetBleAdvertiseParameter"},
{55, nullptr, "StartBleScan"},
{56, nullptr, "StopBleScan"},
{57, nullptr, "AddBleScanFilterCondition"},
{58, nullptr, "DeleteBleScanFilterCondition"},
{59, nullptr, "DeleteBleScanFilter"},
{60, nullptr, "ClearBleScanFilters"},
{61, nullptr, "EnableBleScanFilter"},
{62, nullptr, "RegisterGattClient"},
{63, nullptr, "UnregisterGattClient"},
{64, nullptr, "UnregisterAllGattClients"},
{65, nullptr, "ConnectGattServer"},
{66, nullptr, "CancelConnectGattServer"},
{67, nullptr, "DisconnectGattServer"},
{68, nullptr, "GetGattAttribute"},
{69, nullptr, "GetGattService"},
{70, nullptr, "ConfigureAttMtu"},
{71, nullptr, "RegisterGattServer"},
{72, nullptr, "UnregisterGattServer"},
{73, nullptr, "ConnectGattClient"},
{74, nullptr, "DisconnectGattClient"},
{75, nullptr, "AddGattService"},
{76, nullptr, "EnableGattService"},
{77, nullptr, "AddGattCharacteristic"},
{78, nullptr, "AddGattDescriptor"},
{79, nullptr, "GetBleManagedEventInfo"},
{80, nullptr, "GetGattFirstCharacteristic"},
{81, nullptr, "GetGattNextCharacteristic"},
{82, nullptr, "GetGattFirstDescriptor"},
{83, nullptr, "GetGattNextDescriptor"},
{84, nullptr, "RegisterGattManagedDataPath"},
{85, nullptr, "UnregisterGattManagedDataPath"},
{86, nullptr, "RegisterGattHidDataPath"},
{87, nullptr, "UnregisterGattHidDataPath"},
{88, nullptr, "RegisterGattDataPath"},
{89, nullptr, "UnregisterGattDataPath"},
{90, nullptr, "ReadGattCharacteristic"},
{91, nullptr, "ReadGattDescriptor"},
{92, nullptr, "WriteGattCharacteristic"},
{93, nullptr, "WriteGattDescriptor"},
{94, nullptr, "RegisterGattNotification"},
{95, nullptr, "UnregisterGattNotification"},
{96, nullptr, "GetLeHidEventInfo"},
{97, nullptr, "RegisterBleHidEvent"},
{98, nullptr, "SetBleScanParameter"},
{99, nullptr, "MoveToSecondaryPiconet"},
{256, nullptr, "IsManufacturingMode"},
{257, nullptr, "EmulateBluetoothCrash"},
{258, nullptr, "GetBleChannelMap"},
};
// clang-format on
RegisterHandlers(functions);
}
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<BtDrv>(system)->InstallAsService(sm);
std::make_shared<Bt>(system)->InstallAsService(sm);
}
} // namespace Service::BtDrv

View File

@@ -0,0 +1,20 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Core {
class System;
}
namespace Service::BtDrv {
/// Registers all BtDrv services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BtDrv

282
src/core/hle/service/btm/btm.cpp Executable file
View File

@@ -0,0 +1,282 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/btm/btm.h"
#include "core/hle/service/service.h"
namespace Service::BTM {
class IBtmUserCore final : public ServiceFramework<IBtmUserCore> {
public:
explicit IBtmUserCore(Core::System& system_) : ServiceFramework{system_, "IBtmUserCore"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"},
{1, nullptr, "GetBleScanFilterParameter"},
{2, nullptr, "GetBleScanFilterParameter2"},
{3, nullptr, "StartBleScanForGeneral"},
{4, nullptr, "StopBleScanForGeneral"},
{5, nullptr, "GetBleScanResultsForGeneral"},
{6, nullptr, "StartBleScanForPaired"},
{7, nullptr, "StopBleScanForPaired"},
{8, nullptr, "StartBleScanForSmartDevice"},
{9, nullptr, "StopBleScanForSmartDevice"},
{10, nullptr, "GetBleScanResultsForSmartDevice"},
{17, &IBtmUserCore::AcquireBleConnectionEvent, "AcquireBleConnectionEvent"},
{18, nullptr, "BleConnect"},
{19, nullptr, "BleDisconnect"},
{20, nullptr, "BleGetConnectionState"},
{21, nullptr, "AcquireBlePairingEvent"},
{22, nullptr, "BlePairDevice"},
{23, nullptr, "BleUnPairDevice"},
{24, nullptr, "BleUnPairDevice2"},
{25, nullptr, "BleGetPairedDevices"},
{26, &IBtmUserCore::AcquireBleServiceDiscoveryEvent, "AcquireBleServiceDiscoveryEvent"},
{27, nullptr, "GetGattServices"},
{28, nullptr, "GetGattService"},
{29, nullptr, "GetGattIncludedServices"},
{30, nullptr, "GetBelongingGattService"},
{31, nullptr, "GetGattCharacteristics"},
{32, nullptr, "GetGattDescriptors"},
{33, &IBtmUserCore::AcquireBleMtuConfigEvent, "AcquireBleMtuConfigEvent"},
{34, nullptr, "ConfigureBleMtu"},
{35, nullptr, "GetBleMtu"},
{36, nullptr, "RegisterBleGattDataPath"},
{37, nullptr, "UnregisterBleGattDataPath"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = system.Kernel();
scan_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ScanEvent");
connection_event =
Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConnectionEvent");
service_discovery =
Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:Discovery");
config_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConfigEvent");
}
private:
void AcquireBleScanEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_BTM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(scan_event.readable);
}
void AcquireBleConnectionEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_BTM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(connection_event.readable);
}
void AcquireBleServiceDiscoveryEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_BTM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(service_discovery.readable);
}
void AcquireBleMtuConfigEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_BTM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(config_event.readable);
}
Kernel::EventPair scan_event;
Kernel::EventPair connection_event;
Kernel::EventPair service_discovery;
Kernel::EventPair config_event;
};
class BTM_USR final : public ServiceFramework<BTM_USR> {
public:
explicit BTM_USR(Core::System& system_) : ServiceFramework{system_, "btm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BTM_USR::GetCore, "GetCore"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetCore(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BTM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IBtmUserCore>(system);
}
};
class BTM final : public ServiceFramework<BTM> {
public:
explicit BTM(Core::System& system_) : ServiceFramework{system_, "btm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
{1, nullptr, "GetHostDeviceProperty"},
{2, nullptr, "AcquireDeviceConditionEvent"},
{3, nullptr, "GetDeviceCondition"},
{4, nullptr, "SetBurstMode"},
{5, nullptr, "SetSlotMode"},
{6, nullptr, "SetBluetoothMode"},
{7, nullptr, "SetWlanMode"},
{8, nullptr, "AcquireDeviceInfoEvent"},
{9, nullptr, "GetDeviceInfo"},
{10, nullptr, "AddDeviceInfo"},
{11, nullptr, "RemoveDeviceInfo"},
{12, nullptr, "IncreaseDeviceInfoOrder"},
{13, nullptr, "LlrNotify"},
{14, nullptr, "EnableRadio"},
{15, nullptr, "DisableRadio"},
{16, nullptr, "HidDisconnect"},
{17, nullptr, "HidSetRetransmissionMode"},
{18, nullptr, "AcquireAwakeReqEvent"},
{19, nullptr, "AcquireLlrStateEvent"},
{20, nullptr, "IsLlrStarted"},
{21, nullptr, "EnableSlotSaving"},
{22, nullptr, "ProtectDeviceInfo"},
{23, nullptr, "AcquireBleScanEvent"},
{24, nullptr, "GetBleScanParameterGeneral"},
{25, nullptr, "GetBleScanParameterSmartDevice"},
{26, nullptr, "StartBleScanForGeneral"},
{27, nullptr, "StopBleScanForGeneral"},
{28, nullptr, "GetBleScanResultsForGeneral"},
{29, nullptr, "StartBleScanForPairedDevice"},
{30, nullptr, "StopBleScanForPairedDevice"},
{31, nullptr, "StartBleScanForSmartDevice"},
{32, nullptr, "StopBleScanForSmartDevice"},
{33, nullptr, "GetBleScanResultsForSmartDevice"},
{34, nullptr, "AcquireBleConnectionEvent"},
{35, nullptr, "BleConnect"},
{36, nullptr, "BleOverrideConnection"},
{37, nullptr, "BleDisconnect"},
{38, nullptr, "BleGetConnectionState"},
{39, nullptr, "BleGetGattClientConditionList"},
{40, nullptr, "AcquireBlePairingEvent"},
{41, nullptr, "BlePairDevice"},
{42, nullptr, "BleUnpairDeviceOnBoth"},
{43, nullptr, "BleUnpairDevice"},
{44, nullptr, "BleGetPairedAddresses"},
{45, nullptr, "AcquireBleServiceDiscoveryEvent"},
{46, nullptr, "GetGattServices"},
{47, nullptr, "GetGattService"},
{48, nullptr, "GetGattIncludedServices"},
{49, nullptr, "GetBelongingService"},
{50, nullptr, "GetGattCharacteristics"},
{51, nullptr, "GetGattDescriptors"},
{52, nullptr, "AcquireBleMtuConfigEvent"},
{53, nullptr, "ConfigureBleMtu"},
{54, nullptr, "GetBleMtu"},
{55, nullptr, "RegisterBleGattDataPath"},
{56, nullptr, "UnregisterBleGattDataPath"},
{57, nullptr, "RegisterAppletResourceUserId"},
{58, nullptr, "UnregisterAppletResourceUserId"},
{59, nullptr, "SetAppletResourceUserId"},
{60, nullptr, "Unknown60"},
{61, nullptr, "Unknown61"},
{62, nullptr, "Unknown62"},
{63, nullptr, "Unknown63"},
{64, nullptr, "Unknown64"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class BTM_DBG final : public ServiceFramework<BTM_DBG> {
public:
explicit BTM_DBG(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "AcquireDiscoveryEvent"},
{1, nullptr, "StartDiscovery"},
{2, nullptr, "CancelDiscovery"},
{3, nullptr, "GetDeviceProperty"},
{4, nullptr, "CreateBond"},
{5, nullptr, "CancelBond"},
{6, nullptr, "SetTsiMode"},
{7, nullptr, "GeneralTest"},
{8, nullptr, "HidConnect"},
{9, nullptr, "GeneralGet"},
{10, nullptr, "GetGattClientDisconnectionReason"},
{11, nullptr, "GetBleConnectionParameter"},
{12, nullptr, "GetBleConnectionParameterRequest"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IBtmSystemCore final : public ServiceFramework<IBtmSystemCore> {
public:
explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "StartGamepadPairing"},
{1, nullptr, "CancelGamepadPairing"},
{2, nullptr, "ClearGamepadPairingDatabase"},
{3, nullptr, "GetPairedGamepadCount"},
{4, nullptr, "EnableRadio"},
{5, nullptr, "DisableRadio"},
{6, nullptr, "GetRadioOnOff"},
{7, nullptr, "AcquireRadioEvent"},
{8, nullptr, "AcquireGamepadPairingEvent"},
{9, nullptr, "IsGamepadPairingStarted"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class BTM_SYS final : public ServiceFramework<BTM_SYS> {
public:
explicit BTM_SYS(Core::System& system_) : ServiceFramework{system_, "btm:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BTM_SYS::GetCore, "GetCore"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void GetCore(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BTM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IBtmSystemCore>(system);
}
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<BTM>(system)->InstallAsService(sm);
std::make_shared<BTM_DBG>(system)->InstallAsService(sm);
std::make_shared<BTM_SYS>(system)->InstallAsService(sm);
std::make_shared<BTM_USR>(system)->InstallAsService(sm);
}
} // namespace Service::BTM

19
src/core/hle/service/btm/btm.h Executable file
View File

@@ -0,0 +1,19 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Core {
class System;
};
namespace Service::BTM {
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BTM

View File

@@ -0,0 +1,25 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/caps/caps_a.h"
#include "core/hle/service/caps/caps_c.h"
#include "core/hle/service/caps/caps_sc.h"
#include "core/hle/service/caps/caps_ss.h"
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_u.h"
#include "core/hle/service/service.h"
namespace Service::Capture {
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
std::make_shared<CAPS_A>(system)->InstallAsService(sm);
std::make_shared<CAPS_C>(system)->InstallAsService(sm);
std::make_shared<CAPS_U>(system)->InstallAsService(sm);
std::make_shared<CAPS_SC>(system)->InstallAsService(sm);
std::make_shared<CAPS_SS>(system)->InstallAsService(sm);
std::make_shared<CAPS_SU>(system)->InstallAsService(sm);
}
} // namespace Service::Capture

View File

@@ -0,0 +1,96 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::SM {
class ServiceManager;
}
namespace Service::Capture {
enum class AlbumImageOrientation {
Orientation0 = 0,
Orientation1 = 1,
Orientation2 = 2,
Orientation3 = 3,
};
enum class AlbumReportOption {
Disable = 0,
Enable = 1,
};
enum class ContentType : u8 {
Screenshot = 0,
Movie = 1,
ExtraMovie = 3,
};
enum class AlbumStorage : u8 {
NAND = 0,
SD = 1,
};
struct AlbumFileDateTime {
s16 year{};
s8 month{};
s8 day{};
s8 hour{};
s8 minute{};
s8 second{};
s8 uid{};
};
static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
struct AlbumEntry {
u64 size{};
u64 application_id{};
AlbumFileDateTime datetime{};
AlbumStorage storage{};
ContentType content{};
INSERT_PADDING_BYTES(6);
};
static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
struct AlbumFileEntry {
u64 size{}; // Size of the entry
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
AlbumFileDateTime datetime{};
AlbumStorage storage{};
ContentType content{};
INSERT_PADDING_BYTES(5);
u8 unknown{1}; // Set to 1 on official SW
};
static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
struct ApplicationAlbumEntry {
u64 size{}; // Size of the entry
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
AlbumFileDateTime datetime{};
AlbumStorage storage{};
ContentType content{};
INSERT_PADDING_BYTES(5);
u8 unknown{1}; // Set to 1 on official SW
};
static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
struct ApplicationAlbumFileEntry {
ApplicationAlbumEntry entry{};
AlbumFileDateTime datetime{};
u64 unknown{};
};
static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
"ApplicationAlbumFileEntry has incorrect size.");
/// Registers all Capture services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::Capture

View File

@@ -0,0 +1,79 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/caps/caps_a.h"
namespace Service::Capture {
class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
public:
explicit IAlbumAccessorSession(Core::System& system_)
: ServiceFramework{system_, "IAlbumAccessorSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
{2002, nullptr, "CloseAlbumMovieReadStream"},
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
{2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
{2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
{2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
};
// clang-format on
RegisterHandlers(functions);
}
};
CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
{1, nullptr, "GetAlbumFileList"},
{2, nullptr, "LoadAlbumFile"},
{3, nullptr, "DeleteAlbumFile"},
{4, nullptr, "StorageCopyAlbumFile"},
{5, nullptr, "IsAlbumMounted"},
{6, nullptr, "GetAlbumUsage"},
{7, nullptr, "GetAlbumFileSize"},
{8, nullptr, "LoadAlbumFileThumbnail"},
{9, nullptr, "LoadAlbumScreenShotImage"},
{10, nullptr, "LoadAlbumScreenShotThumbnailImage"},
{11, nullptr, "GetAlbumEntryFromApplicationAlbumEntry"},
{12, nullptr, "LoadAlbumScreenShotImageEx"},
{13, nullptr, "LoadAlbumScreenShotThumbnailImageEx"},
{14, nullptr, "LoadAlbumScreenShotImageEx0"},
{15, nullptr, "GetAlbumUsage3"},
{16, nullptr, "GetAlbumMountResult"},
{17, nullptr, "GetAlbumUsage16"},
{18, nullptr, "Unknown18"},
{100, nullptr, "GetAlbumFileCountEx0"},
{101, nullptr, "GetAlbumFileListEx0"},
{202, nullptr, "SaveEditedScreenShot"},
{301, nullptr, "GetLastThumbnail"},
{302, nullptr, "GetLastOverlayMovieThumbnail"},
{401, nullptr, "GetAutoSavingStorage"},
{501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
{1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
{1002, nullptr, "LoadAlbumScreenShotImageEx1"},
{1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"},
{8001, nullptr, "ForceAlbumUnmounted"},
{8002, nullptr, "ResetAlbumMountStatus"},
{8011, nullptr, "RefreshAlbumCache"},
{8012, nullptr, "GetAlbumCache"},
{8013, nullptr, "GetAlbumCacheEx"},
{8021, nullptr, "GetAlbumEntryFromApplicationAlbumEntryAruid"},
{10011, nullptr, "SetInternalErrorConversionEnabled"},
{50000, nullptr, "LoadMakerNoteInfoForDebug"},
{60002, nullptr, "OpenAccessorSession"},
};
// clang-format on
RegisterHandlers(functions);
}
CAPS_A::~CAPS_A() = default;
} // namespace Service::Capture

View File

@@ -0,0 +1,25 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class HLERequestContext;
}
namespace Service::Capture {
class CAPS_A final : public ServiceFramework<CAPS_A> {
public:
explicit CAPS_A(Core::System& system_);
~CAPS_A() override;
};
} // namespace Service::Capture

View File

@@ -0,0 +1,90 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/caps/caps_c.h"
namespace Service::Capture {
class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
public:
explicit IAlbumControlSession(Core::System& system_)
: ServiceFramework{system_, "IAlbumControlSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
{2002, nullptr, "CloseAlbumMovieReadStream"},
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
{2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
{2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
{2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
{2401, nullptr, "OpenAlbumMovieWriteStream"},
{2402, nullptr, "FinishAlbumMovieWriteStream"},
{2403, nullptr, "CommitAlbumMovieWriteStream"},
{2404, nullptr, "DiscardAlbumMovieWriteStream"},
{2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
{2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
{2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
{2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
{2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
{2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
{2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
{2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
{2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
{2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
{2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
{2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
};
// clang-format on
RegisterHandlers(functions);
}
};
CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
{33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
{2001, nullptr, "NotifyAlbumStorageIsAvailable"},
{2002, nullptr, "NotifyAlbumStorageIsUnavailable"},
{2011, nullptr, "RegisterAppletResourceUserId"},
{2012, nullptr, "UnregisterAppletResourceUserId"},
{2013, nullptr, "GetApplicationIdFromAruid"},
{2014, nullptr, "CheckApplicationIdRegistered"},
{2101, nullptr, "GenerateCurrentAlbumFileId"},
{2102, nullptr, "GenerateApplicationAlbumEntry"},
{2201, nullptr, "SaveAlbumScreenShotFile"},
{2202, nullptr, "SaveAlbumScreenShotFileEx"},
{2301, nullptr, "SetOverlayScreenShotThumbnailData"},
{2302, nullptr, "SetOverlayMovieThumbnailData"},
{60001, nullptr, "OpenControlSession"},
};
// clang-format on
RegisterHandlers(functions);
}
CAPS_C::~CAPS_C() = default;
void CAPS_C::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
library_version, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
} // namespace Service::Capture

View File

@@ -0,0 +1,28 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class HLERequestContext;
}
namespace Service::Capture {
class CAPS_C final : public ServiceFramework<CAPS_C> {
public:
explicit CAPS_C(Core::System& system_);
~CAPS_C() override;
private:
void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture

View File

@@ -0,0 +1,40 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/caps/caps_sc.h"
namespace Service::Capture {
CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
{3, nullptr, "AttachSharedBuffer"},
{5, nullptr, "CaptureRawImageToAttachedSharedBuffer"},
{210, nullptr, "Unknown210"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1003, nullptr, "RequestTakingScreenShotEx"},
{1004, nullptr, "RequestTakingScreenShotEx1"},
{1009, nullptr, "CancelTakingScreenShot"},
{1010, nullptr, "SetTakingScreenShotCancelState"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
{1012, nullptr, "NotifyTakingScreenShotFailed"},
{1101, nullptr, "SetupOverlayMovieThumbnail"},
{1106, nullptr, "Unknown1106"},
{1107, nullptr, "Unknown1107"},
{1201, nullptr, "OpenRawScreenShotReadStream"},
{1202, nullptr, "CloseRawScreenShotReadStream"},
{1203, nullptr, "ReadRawScreenShotReadStream"},
{1204, nullptr, "Unknown1204"},
};
// clang-format on
RegisterHandlers(functions);
}
CAPS_SC::~CAPS_SC() = default;
} // namespace Service::Capture

View File

@@ -0,0 +1,21 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Capture {
class CAPS_SC final : public ServiceFramework<CAPS_SC> {
public:
explicit CAPS_SC(Core::System& system_);
~CAPS_SC() override;
};
} // namespace Service::Capture

View File

@@ -0,0 +1,26 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/caps/caps_ss.h"
namespace Service::Capture {
CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
// clang-format off
static const FunctionInfo functions[] = {
{201, nullptr, "SaveScreenShot"},
{202, nullptr, "SaveEditedScreenShot"},
{203, nullptr, "SaveScreenShotEx0"},
{204, nullptr, "SaveEditedScreenShotEx0"},
{206, nullptr, "Unknown206"},
{208, nullptr, "SaveScreenShotOfMovieEx1"},
};
// clang-format on
RegisterHandlers(functions);
}
CAPS_SS::~CAPS_SS() = default;
} // namespace Service::Capture

Some files were not shown because too many files have changed in this diff Show More