First Commit

This commit is contained in:
2025-02-06 22:24:29 +08:00
parent ed7df4c81e
commit 7539e6a53c
18116 changed files with 6181499 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/applets/applet.h"
#include "core/hle/result.h"
namespace HLE::Applets {
bool Applet::IsRunning() const {
return is_running;
}
bool Applet::IsActive() const {
return is_active;
}
Result Applet::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
switch (parameter.signal) {
case Service::APT::SignalType::Wakeup: {
Result result = Start(parameter);
if (!result.IsError()) {
is_active = true;
}
return result;
}
case Service::APT::SignalType::WakeupByCancel:
return Finalize();
default:
return ReceiveParameterImpl(parameter);
}
}
void Applet::SendParameter(const Service::APT::MessageParameter& parameter) {
if (auto locked = manager.lock()) {
locked->CancelAndSendParameter(parameter);
} else {
LOG_ERROR(Service_APT, "called after destructing applet manager");
}
}
void Applet::CloseApplet(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer) {
if (auto locked = manager.lock()) {
locked->PrepareToCloseLibraryApplet(true, false, false);
locked->CloseLibraryApplet(std::move(object), buffer);
} else {
LOG_ERROR(Service_APT, "called after destructing applet manager");
}
is_active = false;
is_running = false;
}
} // namespace HLE::Applets

View File

@@ -0,0 +1,84 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hle/result.h"
#include "core/hle/service/apt/applet_manager.h"
namespace HLE::Applets {
class Applet {
public:
virtual ~Applet() = default;
/**
* Handles a parameter from the application.
* @param parameter Parameter data to handle.
* @returns Result Whether the operation was successful or not.
*/
Result ReceiveParameter(const Service::APT::MessageParameter& parameter);
/**
* Whether the applet is currently running.
*/
[[nodiscard]] bool IsRunning() const;
/**
* Whether the applet is currently active instead of the host application or not.
*/
[[nodiscard]] bool IsActive() const;
/**
* Handles an update tick for the Applet, lets it update the screen, send commands, etc.
*/
virtual void Update() = 0;
protected:
Applet(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: system(system), id(id), parent(parent), preload(preload), manager(std::move(manager)) {}
/**
* Handles a parameter from the application.
* @param parameter Parameter data to handle.
* @returns Result Whether the operation was successful or not.
*/
virtual Result ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) = 0;
/**
* Handles the Applet start event, triggered from the application.
* @param parameter Parameter data to handle.
* @returns Result Whether the operation was successful or not.
*/
virtual Result Start(const Service::APT::MessageParameter& parameter) = 0;
/**
* Sends the LibAppletClosing signal to the application,
* along with the relevant data buffers.
*/
virtual Result Finalize() = 0;
Core::System& system;
Service::APT::AppletId id; ///< Id of this Applet
Service::APT::AppletId parent; ///< Id of this Applet's parent
bool preload; ///< Whether the Applet is being preloaded.
std::shared_ptr<std::vector<u8>> heap_memory; ///< Heap memory for this Applet
/// Whether this applet is running.
bool is_running = true;
/// Whether this applet is currently active instead of the host application or not.
bool is_active = false;
void SendParameter(const Service::APT::MessageParameter& parameter);
void CloseApplet(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
private:
std::weak_ptr<Service::APT::AppletManager> manager;
};
} // namespace HLE::Applets

View File

@@ -0,0 +1,67 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/service/apt/apt.h"
namespace HLE::Applets {
Result ErrEula::ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) {
if (parameter.signal != Service::APT::SignalType::Request) {
LOG_ERROR(Service_APT, "unsupported signal {}", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
return ResultUnknown;
}
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared
// memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer.size());
std::memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
// TODO: allocated memory never released
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"ErrEula Memory");
// Send the response message with the newly created SharedMemory
SendParameter({
.sender_id = id,
.destination_id = parent,
.signal = Service::APT::SignalType::Response,
.object = framebuffer_memory,
});
return ResultSuccess;
}
Result ErrEula::Start(const Service::APT::MessageParameter& parameter) {
startup_param = parameter.buffer;
// TODO(Subv): Set the expected fields in the response buffer before resending it to the
// application.
// TODO(Subv): Reverse the parameter format for the ErrEula applet
// Let the application know that we're closing.
Finalize();
return ResultSuccess;
}
Result ErrEula::Finalize() {
std::vector<u8> buffer(startup_param.size());
std::fill(buffer.begin(), buffer.end(), 0);
CloseApplet(nullptr, buffer);
return ResultSuccess;
}
void ErrEula::Update() {}
} // namespace HLE::Applets

View File

@@ -0,0 +1,33 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
namespace HLE::Applets {
class ErrEula final : public Applet {
public:
explicit ErrEula(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: Applet(system, id, parent, preload, std::move(manager)) {}
Result ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override;
Result Start(const Service::APT::MessageParameter& parameter) override;
Result Finalize() override;
void Update() override;
private:
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// Parameter received by the applet on start.
std::vector<u8> startup_param;
};
} // namespace HLE::Applets

View File

@@ -0,0 +1,136 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <string>
#include <boost/crc.hpp>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/mii_selector.h"
#include "core/hle/applets/mii_selector.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
namespace HLE::Applets {
Result MiiSelector::ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) {
if (parameter.signal != Service::APT::SignalType::Request) {
LOG_ERROR(Service_APT, "unsupported signal {}", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
return ResultUnknown;
}
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared
// memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer.size());
std::memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"MiiSelector Memory");
// Send the response message with the newly created SharedMemory
SendParameter({
.sender_id = id,
.destination_id = parent,
.signal = Service::APT::SignalType::Response,
.object = framebuffer_memory,
});
return ResultSuccess;
}
Result MiiSelector::Start(const Service::APT::MessageParameter& parameter) {
ASSERT_MSG(parameter.buffer.size() == sizeof(config),
"The size of the parameter (MiiConfig) is wrong");
std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
using namespace Frontend;
frontend_applet = system.GetMiiSelector();
ASSERT(frontend_applet);
MiiSelectorConfig frontend_config = ToFrontendConfig(config);
frontend_applet->Setup(frontend_config);
return ResultSuccess;
}
void MiiSelector::Update() {
using namespace Frontend;
const MiiSelectorData& data = frontend_applet->ReceiveData();
result.return_code = data.return_code;
result.selected_mii_data = data.mii;
result.selected_guest_mii_index = 0xFFFFFFFF;
// TODO(Subv): We're finalizing the applet immediately after it's started,
// but we should defer this call until after all the input has been collected.
Finalize();
}
Result MiiSelector::Finalize() {
std::vector<u8> buffer(sizeof(MiiResult));
std::memcpy(buffer.data(), &result, buffer.size());
CloseApplet(nullptr, buffer);
return ResultSuccess;
}
MiiResult MiiSelector::GetStandardMiiResult() {
// This data was obtained by writing the returned buffer in AppletManager::GlanceParameter of
// the LLEd Mii picker of version system version 11.8.0 to a file and then matching the values
// to the members of the MiiResult struct
Mii::MiiData mii_data;
mii_data.version = 0x03;
mii_data.mii_options.raw = 0x00;
mii_data.mii_pos.raw = 0x10;
mii_data.console_identity.raw = 0x30;
mii_data.system_id = 0xD285B6B300C8850A;
mii_data.mii_id = 0x98391EE4;
mii_data.mac = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10};
mii_data.pad = 0x0000;
mii_data.mii_details.raw = 0xA600;
mii_data.mii_name = {'C', 'i', 't', 'r', 'a', 0x0, 0x0, 0x0, 0x0, 0x0};
mii_data.height = 0x40;
mii_data.width = 0x40;
mii_data.face_style.raw = 0x00;
mii_data.face_details.raw = 0x00;
mii_data.hair_style = 0x21;
mii_data.hair_details.raw = 0x01;
mii_data.eye_details.raw = 0x02684418;
mii_data.eyebrow_details.raw = 0x26344614;
mii_data.nose_details.raw = 0x8112;
mii_data.mouth_details.raw = 0x1768;
mii_data.mustache_details.raw = 0x0D00;
mii_data.beard_details.raw = 0x0029;
mii_data.glasses_details.raw = 0x0052;
mii_data.mole_details.raw = 0x4850;
mii_data.author_name = {u'f', u'l', u'T', u'o', u'b', u'i'};
MiiResult result;
result.return_code = 0x0;
result.is_guest_mii_selected = 0x0;
result.selected_guest_mii_index = 0xFFFFFFFF;
result.selected_mii_data = mii_data;
result.guest_mii_name.fill(0x0);
return result;
}
Frontend::MiiSelectorConfig MiiSelector::ToFrontendConfig(const MiiConfig& config) const {
Frontend::MiiSelectorConfig frontend_config;
frontend_config.enable_cancel_button = config.enable_cancel_button == 1;
frontend_config.title = Common::UTF16BufferToUTF8(config.title);
frontend_config.initially_selected_mii_index = config.initially_selected_mii_index;
return frontend_config;
}
} // namespace HLE::Applets

View File

@@ -0,0 +1,90 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/mii.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
namespace Frontend {
class MiiSelector;
struct MiiSelectorConfig;
} // namespace Frontend
namespace HLE::Applets {
struct MiiConfig {
u8 enable_cancel_button;
u8 enable_guest_mii;
u8 show_on_top_screen;
INSERT_PADDING_BYTES(5);
std::array<u16_le, 0x40> title;
INSERT_PADDING_BYTES(4);
u8 show_guest_miis;
INSERT_PADDING_BYTES(3);
u32 initially_selected_mii_index;
std::array<u8, 0x6> guest_mii_whitelist;
std::array<u8, 0x64> user_mii_whitelist;
INSERT_PADDING_BYTES(2);
u32 magic_value;
};
static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size");
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MiiConfig, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(title, 0x08);
ASSERT_REG_POSITION(show_guest_miis, 0x8C);
ASSERT_REG_POSITION(initially_selected_mii_index, 0x90);
ASSERT_REG_POSITION(guest_mii_whitelist, 0x94);
#undef ASSERT_REG_POSITION
struct MiiResult {
u32_be return_code;
u32_be is_guest_mii_selected;
u32_be selected_guest_mii_index;
Mii::ChecksummedMiiData selected_mii_data;
std::array<u16_le, 0xC> guest_mii_name;
};
static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MiiResult, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(selected_mii_data, 0x0C);
ASSERT_REG_POSITION(guest_mii_name, 0x6C);
#undef ASSERT_REG_POSITION
class MiiSelector final : public Applet {
public:
MiiSelector(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: Applet(system, id, parent, preload, std::move(manager)) {}
Result ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override;
Result Start(const Service::APT::MessageParameter& parameter) override;
Result Finalize() override;
void Update() override;
static MiiResult GetStandardMiiResult();
private:
Frontend::MiiSelectorConfig ToFrontendConfig(const MiiConfig& config) const;
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
MiiConfig config;
MiiResult result{};
std::shared_ptr<Frontend::MiiSelector> frontend_applet;
};
} // namespace HLE::Applets

View File

@@ -0,0 +1,67 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/mint.h"
#include "core/hle/service/apt/apt.h"
namespace HLE::Applets {
Result Mint::ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) {
if (parameter.signal != Service::APT::SignalType::Request) {
LOG_ERROR(Service_APT, "unsupported signal {}", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
return ResultUnknown;
}
// The Request message contains a buffer with the size of the framebuffer shared
// memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer.size());
std::memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
// TODO: allocated memory never released
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"Mint Memory");
// Send the response message with the newly created SharedMemory
SendParameter({
.sender_id = id,
.destination_id = parent,
.signal = Service::APT::SignalType::Response,
.object = framebuffer_memory,
});
return ResultSuccess;
}
Result Mint::Start(const Service::APT::MessageParameter& parameter) {
startup_param = parameter.buffer;
// TODO(Subv): Set the expected fields in the response buffer before resending it to the
// application.
// TODO(Subv): Reverse the parameter format for the Mint applet
// Let the application know that we're closing
Finalize();
return ResultSuccess;
}
Result Mint::Finalize() {
std::vector<u8> buffer(startup_param.size());
std::fill(buffer.begin(), buffer.end(), 0);
CloseApplet(nullptr, buffer);
return ResultSuccess;
}
void Mint::Update() {}
} // namespace HLE::Applets

View File

@@ -0,0 +1,33 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
namespace HLE::Applets {
class Mint final : public Applet {
public:
explicit Mint(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: Applet(system, id, parent, preload, std::move(manager)) {}
Result ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override;
Result Start(const Service::APT::MessageParameter& parameter) override;
Result Finalize() override;
void Update() override;
private:
/// This SharedMemory will be created when we receive the Request message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// Parameter received by the applet on start.
std::vector<u8> startup_param;
};
} // namespace HLE::Applets

View File

@@ -0,0 +1,205 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <string>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/swkbd.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/gsp/gsp.h"
#include "core/hle/service/hid/hid.h"
#include "core/memory.h"
namespace HLE::Applets {
Result SoftwareKeyboard::ReceiveParameterImpl(Service::APT::MessageParameter const& parameter) {
switch (parameter.signal) {
case Service::APT::SignalType::Request: {
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared
// memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer.size());
std::memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"SoftwareKeyboard Memory");
// Send the response message with the newly created SharedMemory
SendParameter({
.sender_id = id,
.destination_id = parent,
.signal = Service::APT::SignalType::Response,
.object = framebuffer_memory,
});
return ResultSuccess;
}
case Service::APT::SignalType::Message: {
// Callback result
ASSERT_MSG(parameter.buffer.size() == sizeof(config),
"The size of the parameter (SoftwareKeyboardConfig) is wrong");
std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
switch (config.callback_result) {
case SoftwareKeyboardCallbackResult::OK:
// Finish execution
Finalize();
return ResultSuccess;
case SoftwareKeyboardCallbackResult::Close:
// Let the frontend display error and quit
frontend_applet->ShowError(Common::UTF16BufferToUTF8(config.callback_msg));
config.return_code = SoftwareKeyboardResult::BannedInput;
config.text_offset = config.text_length = 0;
Finalize();
return ResultSuccess;
case SoftwareKeyboardCallbackResult::Continue:
// Let the frontend display error and get input again
// The input will be sent for validation again on next Update().
frontend_applet->ShowError(Common::UTF16BufferToUTF8(config.callback_msg));
frontend_applet->Execute(ToFrontendConfig(config));
return ResultSuccess;
default:
UNREACHABLE();
}
}
default: {
LOG_ERROR(Service_APT, "unsupported signal {}", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
return ResultUnknown;
}
}
}
Result SoftwareKeyboard::Start(Service::APT::MessageParameter const& parameter) {
ASSERT_MSG(parameter.buffer.size() == sizeof(config),
"The size of the parameter (SoftwareKeyboardConfig) is wrong");
std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
text_memory = std::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object);
DrawScreenKeyboard();
using namespace Frontend;
frontend_applet = system.GetSoftwareKeyboard();
ASSERT(frontend_applet);
frontend_applet->Execute(ToFrontendConfig(config));
return ResultSuccess;
}
void SoftwareKeyboard::Update() {
if (!frontend_applet->DataReady())
return;
using namespace Frontend;
const KeyboardData& data = frontend_applet->ReceiveData();
std::u16string text = Common::UTF8ToUTF16(data.text);
// Include a null terminator
std::memcpy(text_memory->GetPointer(), text.c_str(), (text.length() + 1) * sizeof(char16_t));
switch (config.num_buttons_m1) {
case SoftwareKeyboardButtonConfig::SingleButton:
config.return_code = SoftwareKeyboardResult::D0Click;
break;
case SoftwareKeyboardButtonConfig::DualButton:
if (data.button == 0)
config.return_code = SoftwareKeyboardResult::D1Click0;
else
config.return_code = SoftwareKeyboardResult::D1Click1;
break;
case SoftwareKeyboardButtonConfig::TripleButton:
if (data.button == 0)
config.return_code = SoftwareKeyboardResult::D2Click0;
else if (data.button == 1)
config.return_code = SoftwareKeyboardResult::D2Click1;
else
config.return_code = SoftwareKeyboardResult::D2Click2;
break;
case SoftwareKeyboardButtonConfig::NoButton:
// TODO: find out what is actually returned
config.return_code = SoftwareKeyboardResult::None;
break;
default:
LOG_CRITICAL(Applet_SWKBD, "Unknown button config {}", config.num_buttons_m1);
UNREACHABLE();
}
config.text_length = static_cast<u16>(text.size());
config.text_offset = 0;
if (config.filter_flags & HLE::Applets::SoftwareKeyboardFilter::Callback) {
std::vector<u8> buffer(sizeof(SoftwareKeyboardConfig));
std::memcpy(buffer.data(), &config, buffer.size());
// Send the message to invoke callback
SendParameter({
.sender_id = id,
.destination_id = parent,
.signal = Service::APT::SignalType::Message,
.buffer = buffer,
});
} else {
Finalize();
}
}
void SoftwareKeyboard::DrawScreenKeyboard() {
// TODO(Subv): Draw the HLE keyboard, for now just do nothing
}
Result SoftwareKeyboard::Finalize() {
std::vector<u8> buffer(sizeof(SoftwareKeyboardConfig));
std::memcpy(buffer.data(), &config, buffer.size());
CloseApplet(nullptr, buffer);
text_memory = nullptr;
return ResultSuccess;
}
Frontend::KeyboardConfig SoftwareKeyboard::ToFrontendConfig(
const SoftwareKeyboardConfig& config) const {
using namespace Frontend;
KeyboardConfig frontend_config;
frontend_config.button_config =
static_cast<ButtonConfig>(static_cast<u32>(config.num_buttons_m1));
frontend_config.accept_mode = static_cast<AcceptedInput>(static_cast<u32>(config.valid_input));
frontend_config.multiline_mode = config.multiline;
frontend_config.max_text_length = config.max_text_length;
frontend_config.max_digits = config.max_digits;
frontend_config.hint_text = Common::UTF16BufferToUTF8(config.hint_text);
for (const auto& text : config.button_text) {
frontend_config.button_text.push_back(Common::UTF16BufferToUTF8(text));
}
frontend_config.filters.prevent_digit =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Digits);
frontend_config.filters.prevent_at =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::At);
frontend_config.filters.prevent_percent =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Percent);
frontend_config.filters.prevent_backslash =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Backslash);
frontend_config.filters.prevent_profanity =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Profanity);
frontend_config.filters.enable_callback =
static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Callback);
return frontend_config;
}
} // namespace HLE::Applets

View File

@@ -0,0 +1,208 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/frontend/applets/swkbd.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
namespace HLE::Applets {
/// Maximum number of buttons that can be in the keyboard.
constexpr int MAX_BUTTON = 3;
/// Maximum button text length, in UTF-16 code units.
constexpr int MAX_BUTTON_TEXT_LEN = 16;
/// Maximum hint text length, in UTF-16 code units.
constexpr int MAX_HINT_TEXT_LEN = 64;
/// Maximum filter callback error message length, in UTF-16 code units.
constexpr int MAX_CALLBACK_MSG_LEN = 256;
/// Keyboard types
enum class SoftwareKeyboardType : u32 {
Normal, ///< Normal keyboard with several pages (QWERTY/accents/symbol/mobile)
QWERTY, ///< QWERTY keyboard only.
NumPad, ///< Number pad.
Western, ///< On JPN systems, a text keyboard without Japanese input capabilities,
/// otherwise same as SWKBD_TYPE_NORMAL.
};
/// Keyboard dialog buttons.
enum class SoftwareKeyboardButtonConfig : u32 {
SingleButton, ///< Ok button
DualButton, ///< Cancel | Ok buttons
TripleButton, ///< Cancel | I Forgot | Ok buttons
NoButton, ///< No button (returned by swkbdInputText in special cases)
};
/// Accepted input types.
enum class SoftwareKeyboardValidInput : u32 {
Anything, ///< All inputs are accepted.
NotEmpty, ///< Empty inputs are not accepted.
NotEmptyNotBlank, ///< Empty or blank inputs (consisting solely of whitespace) are not
/// accepted.
NotBlank, ///< Blank inputs (consisting solely of whitespace) are not accepted, but empty
/// inputs are.
FixedLen, ///< The input must have a fixed length (specified by maxTextLength in
/// swkbdInit).
};
/// Keyboard password modes.
enum class SoftwareKeyboardPasswordMode : u32 {
None, ///< Characters are not concealed.
Hide, ///< Characters are concealed immediately.
HideDelay, ///< Characters are concealed a second after they've been typed.
};
/// Keyboard input filtering flags. Allows the caller to specify what input is explicitly not
/// allowed
namespace SoftwareKeyboardFilter {
enum Filter {
Digits = 1, ///< Disallow the use of more than a certain number of digits (0 or more)
At = 1 << 1, ///< Disallow the use of the @ sign.
Percent = 1 << 2, ///< Disallow the use of the % sign.
Backslash = 1 << 3, ///< Disallow the use of the \ sign.
Profanity = 1 << 4, ///< Disallow profanity using Nintendo's profanity filter.
Callback = 1 << 5, ///< Use a callback in order to check the input.
};
} // namespace SoftwareKeyboardFilter
/// Keyboard features.
namespace SoftwareKeyboardFeature {
enum Feature {
Parental = 1, ///< Parental PIN mode.
DarkenTopScreen = 1 << 1, ///< Darken the top screen when the keyboard is shown.
PredictiveInput =
1 << 2, ///< Enable predictive input (necessary for Kanji input in JPN systems).
Multiline = 1 << 3, ///< Enable multiline input.
FixedWidth = 1 << 4, ///< Enable fixed-width mode.
AllowHome = 1 << 5, ///< Allow the usage of the HOME button.
AllowReset = 1 << 6, ///< Allow the usage of a software-reset combination.
AllowPower = 1 << 7, ///< Allow the usage of the POWER button.
DefaultQWERTY = 1 << 9, ///< Default to the QWERTY page when the keyboard is shown.
};
} // namespace SoftwareKeyboardFeature
/// Keyboard filter callback return values.
enum class SoftwareKeyboardCallbackResult : u32 {
OK, ///< Specifies that the input is valid.
Close, ///< Displays an error message, then closes the keyboard.
Continue, ///< Displays an error message and continues displaying the keyboard.
};
/// Keyboard return values.
enum class SoftwareKeyboardResult : s32 {
None = -1, ///< Dummy/unused.
InvalidInput = -2, ///< Invalid parameters to swkbd.
OutOfMem = -3, ///< Out of memory.
D0Click = 0, ///< The button was clicked in 1-button dialogs.
D1Click0, ///< The left button was clicked in 2-button dialogs.
D1Click1, ///< The right button was clicked in 2-button dialogs.
D2Click0, ///< The left button was clicked in 3-button dialogs.
D2Click1, ///< The middle button was clicked in 3-button dialogs.
D2Click2, ///< The right button was clicked in 3-button dialogs.
HomePressed = 10, ///< The HOME button was pressed.
ResetPressed, ///< The soft-reset key combination was pressed.
PowerPressed, ///< The POWER button was pressed.
ParentalOK = 20, ///< The parental PIN was verified successfully.
ParentalFail, ///< The parental PIN was incorrect.
BannedInput = 30, ///< The filter callback returned SoftwareKeyboardCallback::CLOSE.
};
struct SoftwareKeyboardConfig {
enum_le<SoftwareKeyboardType> type;
enum_le<SoftwareKeyboardButtonConfig> num_buttons_m1;
enum_le<SoftwareKeyboardValidInput> valid_input;
enum_le<SoftwareKeyboardPasswordMode> password_mode;
s32_le is_parental_screen;
s32_le darken_top_screen;
u32_le filter_flags;
u32_le save_state_flags;
u16_le max_text_length;
u16_le dict_word_count;
u16_le max_digits;
std::array<std::array<u16_le, MAX_BUTTON_TEXT_LEN + 1>, MAX_BUTTON> button_text;
std::array<u16_le, 2> numpad_keys;
std::array<u16_le, MAX_HINT_TEXT_LEN + 1>
hint_text; ///< Text to display when asking the user for input
bool predictive_input;
bool multiline;
bool fixed_width;
bool allow_home;
bool allow_reset;
bool allow_power;
bool unknown;
bool default_qwerty;
std::array<bool, 4> button_submits_text;
u16_le language;
u32_le initial_text_offset; ///< Offset of the default text in the output SharedMemory
u32_le dict_offset;
u32_le initial_status_offset;
u32_le initial_learning_offset;
u32_le shared_memory_size; ///< Size of the SharedMemory
u32_le version;
enum_le<SoftwareKeyboardResult> return_code;
u32_le status_offset;
u32_le learning_offset;
u32_le text_offset; ///< Offset in the SharedMemory where the output text starts
u16_le text_length; ///< Length in characters of the output text
enum_le<SoftwareKeyboardCallbackResult> callback_result;
std::array<u16_le, MAX_CALLBACK_MSG_LEN + 1> callback_msg;
bool skip_at_check;
INSERT_PADDING_BYTES(0xAB);
};
/**
* The size of this structure (0x400) has been verified via reverse engineering of multiple games
* that use the software keyboard.
*/
static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software Keyboard Config size is wrong");
class SoftwareKeyboard final : public Applet {
public:
SoftwareKeyboard(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: Applet(system, id, parent, preload, std::move(manager)) {}
Result ReceiveParameterImpl(const Service::APT::MessageParameter& parameter) override;
Result Start(const Service::APT::MessageParameter& parameter) override;
Result Finalize() override;
void Update() override;
/**
* Draws a keyboard to the current bottom screen framebuffer.
*/
void DrawScreenKeyboard();
private:
Frontend::KeyboardConfig ToFrontendConfig(const SoftwareKeyboardConfig& config) const;
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// SharedMemory where the output text will be stored
std::shared_ptr<Kernel::SharedMemory> text_memory;
/// Configuration of this instance of the SoftwareKeyboard, as received from the application
SoftwareKeyboardConfig config;
std::shared_ptr<Frontend::SoftwareKeyboard> frontend_applet;
};
} // namespace HLE::Applets

155
src/core/hle/ipc.h Normal file
View File

@@ -0,0 +1,155 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
namespace IPC {
/// Size of the command buffer area, in 32-bit words.
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
// Maximum number of static buffers per thread.
constexpr std::size_t MAX_STATIC_BUFFERS = 16;
// These errors are commonly returned by invalid IPC translations, so alias them here for
// convenience.
// TODO(yuriks): These will probably go away once translation is implemented inside the kernel.
using Kernel::ResultInvalidBufferDescriptor;
constexpr auto ResultInvalidHandle = Kernel::ResultInvalidHandleOs;
enum DescriptorType : u32 {
// Buffer related descriptors types (mask : 0x0F)
StaticBuffer = 0x02,
PXIBuffer = 0x04,
MappedBuffer = 0x08,
// Handle related descriptors types (mask : 0x30, but need to check for buffer related
// descriptors first )
CopyHandle = 0x00,
MoveHandle = 0x10,
CallingPid = 0x20,
};
union Header {
u32 raw;
BitField<0, 6, u32> translate_params_size;
BitField<6, 6, u32> normal_params_size;
BitField<16, 16, u32> command_id;
};
/**
* @brief Creates a command header to be used for IPC
* @param command_id ID of the command to create a header for.
* @param normal_params_size Size of the normal parameters in words. Up to 63.
* @param translate_params_size Size of the translate parameters in words. Up to 63.
* @return The created IPC header.
*
* Normal parameters are sent directly to the process while the translate parameters might go
* through modifications and checks by the kernel.
* The translate parameters are described by headers generated with the IPC::*Desc functions.
*
* @note While @p normal_params_size is equivalent to the number of normal parameters,
* @p translate_params_size includes the size occupied by the translate parameters headers.
*/
inline constexpr u32 MakeHeader(u16 command_id, u32 normal_params_size, u32 translate_params_size) {
Header header{};
header.command_id.Assign(command_id);
header.normal_params_size.Assign(normal_params_size);
header.translate_params_size.Assign(translate_params_size);
return header.raw;
}
constexpr u32 MoveHandleDesc(u32 num_handles = 1) {
return MoveHandle | ((num_handles - 1) << 26);
}
constexpr u32 CopyHandleDesc(u32 num_handles = 1) {
return CopyHandle | ((num_handles - 1) << 26);
}
constexpr u32 CallingPidDesc() {
return CallingPid;
}
constexpr bool IsHandleDescriptor(u32 descriptor) {
return (descriptor & 0xF) == 0x0;
}
constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) {
return (handle_descriptor >> 26) + 1;
}
union StaticBufferDescInfo {
u32 raw;
BitField<0, 4, u32> descriptor_type;
BitField<10, 4, u32> buffer_id;
BitField<14, 18, u32> size;
};
inline u32 StaticBufferDesc(std::size_t size, u8 buffer_id) {
StaticBufferDescInfo info{};
info.descriptor_type.Assign(StaticBuffer);
info.buffer_id.Assign(buffer_id);
info.size.Assign(static_cast<u32>(size));
return info.raw;
}
/**
* @brief Creates a header describing a buffer to be sent over PXI.
* @param size Size of the buffer. Max 0x00FFFFFF.
* @param buffer_id The Id of the buffer. Max 0xF.
* @param is_read_only true if the buffer is read-only. If false, the buffer is considered to have
* read-write access.
* @return The created PXI buffer header.
*
* The next value is a phys-address of a table located in the BASE memregion.
*/
inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) {
u32 type = PXIBuffer;
if (is_read_only)
type |= 0x2;
return type | (size << 8) | ((buffer_id & 0xF) << 4);
}
enum MappedBufferPermissions : u32 {
R = 1,
W = 2,
RW = R | W,
};
union MappedBufferDescInfo {
u32 raw;
BitField<0, 4, u32> flags;
BitField<1, 2, MappedBufferPermissions> perms;
BitField<4, 28, u32> size;
};
inline u32 MappedBufferDesc(std::size_t size, MappedBufferPermissions perms) {
MappedBufferDescInfo info{};
info.flags.Assign(MappedBuffer);
info.perms.Assign(perms);
info.size.Assign(static_cast<u32>(size));
return info.raw;
}
inline DescriptorType GetDescriptorType(u32 descriptor) {
// Note: Those checks must be done in this order
if (IsHandleDescriptor(descriptor))
return (DescriptorType)(descriptor & 0x30);
// handle the fact that the following descriptors can have rights
if (descriptor & MappedBuffer)
return MappedBuffer;
if (descriptor & PXIBuffer)
return PXIBuffer;
return StaticBuffer;
}
} // namespace IPC

465
src/core/hle/ipc_helpers.h Normal file
View File

@@ -0,0 +1,465 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "core/hle/ipc.h"
#include "core/hle/kernel/hle_ipc.h"
namespace IPC {
class RequestHelperBase {
protected:
Kernel::HLERequestContext* context;
u32* cmdbuf;
std::size_t index = 1;
Header header;
public:
RequestHelperBase(Kernel::HLERequestContext& context, Header desired_header)
: context(&context), cmdbuf(context.CommandBuffer()), header(desired_header) {}
/// Returns the total size of the request in words
std::size_t TotalSize() const {
return 1 /* command header */ + header.normal_params_size + header.translate_params_size;
}
void ValidateHeader() {
DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd {:#x})",
header.raw);
}
void Skip(unsigned size_in_words, bool set_to_null) {
if (set_to_null)
std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
index += size_in_words;
}
};
class RequestBuilder : public RequestHelperBase {
public:
RequestBuilder(Kernel::HLERequestContext& context, Header command_header)
: RequestHelperBase(context, command_header) {
// From this point we will start overwriting the existing command buffer, so it's safe to
// release all previous incoming Object pointers since they won't be usable anymore.
context.ClearIncomingObjects();
cmdbuf[0] = header.raw;
}
RequestBuilder(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size,
unsigned translate_params_size)
: RequestBuilder(
context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {}
RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size,
unsigned translate_params_size)
: RequestBuilder(context, Header{MakeHeader(context.CommandID(), normal_params_size,
translate_params_size)}) {}
// Validate on destruction, as there shouldn't be any case where we don't want it
~RequestBuilder() {
ValidateHeader();
}
template <typename T>
void Push(T value);
template <typename First, typename... Other>
void Push(const First& first_value, const Other&... other_values);
template <typename T>
void PushEnum(T value) {
static_assert(std::is_enum<T>(), "T must be an enum type within a PushEnum call.");
static_assert(!std::is_convertible<T, int>(),
"enum type in PushEnum must be a strongly typed enum.");
static_assert(sizeof(value) < sizeof(u64), "64-bit enums may not be pushed.");
Push(static_cast<std::underlying_type_t<T>>(value));
}
/**
* @brief Copies the content of the given trivially copyable class to the buffer as a normal
* param
* @note: The input class must be correctly packed/padded to fit hardware layout.
*/
template <typename T>
void PushRaw(const T& value);
// TODO : ensure that translate params are added after all regular params
template <typename... O>
void PushCopyObjects(std::shared_ptr<O>... pointers);
template <typename... O>
void PushMoveObjects(std::shared_ptr<O>... pointers);
void PushStaticBuffer(std::vector<u8> buffer, u8 buffer_id);
/// Pushes an HLE MappedBuffer interface back to unmapped the buffer.
void PushMappedBuffer(const Kernel::MappedBuffer& mapped_buffer);
private:
template <typename... H>
void PushCopyHLEHandles(H... handles);
template <typename... H>
void PushMoveHLEHandles(H... handles);
};
/// Push ///
template <>
inline void RequestBuilder::Push(u32 value) {
cmdbuf[index++] = value;
}
template <>
inline void RequestBuilder::Push(s32 value) {
cmdbuf[index++] = static_cast<u32>(value);
}
template <typename T>
void RequestBuilder::PushRaw(const T& value) {
static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable");
std::memcpy(cmdbuf + index, &value, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
template <>
inline void RequestBuilder::Push(u8 value) {
PushRaw(value);
}
template <>
inline void RequestBuilder::Push(u16 value) {
PushRaw(value);
}
template <>
inline void RequestBuilder::Push(u64 value) {
Push(static_cast<u32>(value));
Push(static_cast<u32>(value >> 32));
}
template <>
inline void RequestBuilder::Push(f32 value) {
u32 integral;
std::memcpy(&integral, &value, sizeof(u32));
Push(integral);
}
template <>
inline void RequestBuilder::Push(f64 value) {
u64 integral;
std::memcpy(&integral, &value, sizeof(u64));
Push(integral);
}
template <>
inline void RequestBuilder::Push(bool value) {
Push(static_cast<u8>(value));
}
template <>
inline void RequestBuilder::Push(Result value) {
Push(value.raw);
}
template <typename First, typename... Other>
void RequestBuilder::Push(const First& first_value, const Other&... other_values) {
Push(first_value);
Push(other_values...);
}
template <typename... H>
inline void RequestBuilder::PushCopyHLEHandles(H... handles) {
Push(CopyHandleDesc(sizeof...(H)));
Push(static_cast<u32>(handles)...);
}
template <typename... H>
inline void RequestBuilder::PushMoveHLEHandles(H... handles) {
Push(MoveHandleDesc(sizeof...(H)));
Push(static_cast<u32>(handles)...);
}
template <typename... O>
inline void RequestBuilder::PushCopyObjects(std::shared_ptr<O>... pointers) {
PushCopyHLEHandles(context->AddOutgoingHandle(std::move(pointers))...);
}
template <typename... O>
inline void RequestBuilder::PushMoveObjects(std::shared_ptr<O>... pointers) {
PushMoveHLEHandles(context->AddOutgoingHandle(std::move(pointers))...);
}
inline void RequestBuilder::PushStaticBuffer(std::vector<u8> buffer, u8 buffer_id) {
ASSERT_MSG(buffer_id < MAX_STATIC_BUFFERS, "Invalid static buffer id");
Push(StaticBufferDesc(buffer.size(), buffer_id));
// This address will be replaced by the correct static buffer address during IPC translation.
Push<VAddr>(0xDEADC0DE);
context->AddStaticBuffer(buffer_id, std::move(buffer));
}
inline void RequestBuilder::PushMappedBuffer(const Kernel::MappedBuffer& mapped_buffer) {
Push(mapped_buffer.GenerateDescriptor());
Push(mapped_buffer.GetId());
}
class RequestParser : public RequestHelperBase {
public:
RequestParser(Kernel::HLERequestContext& context)
: RequestHelperBase(context, context.CommandHeader()) {}
RequestBuilder MakeBuilder(u32 normal_params_size, u32 translate_params_size,
bool validateHeader = true) {
if (validateHeader)
ValidateHeader();
Header builderHeader{MakeHeader(static_cast<u16>(header.command_id), normal_params_size,
translate_params_size)};
return {*context, builderHeader};
}
template <typename T>
T Pop();
template <typename T>
void Pop(T& value);
template <typename First, typename... Other>
void Pop(First& first_value, Other&... other_values);
template <typename T>
T PopEnum() {
static_assert(std::is_enum<T>(), "T must be an enum type within a PopEnum call.");
static_assert(!std::is_convertible<T, int>(),
"enum type in PopEnum must be a strongly typed enum.");
static_assert(sizeof(T) < sizeof(u64), "64-bit enums cannot be popped.");
return static_cast<T>(Pop<std::underlying_type_t<T>>());
}
/// Equivalent to calling `PopGenericObjects<1>()[0]`.
std::shared_ptr<Kernel::Object> PopGenericObject();
/// Equivalent to calling `std::get<0>(PopObjects<T>())`.
template <typename T>
std::shared_ptr<T> PopObject();
/**
* Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a
* handle is invalid, null is returned for that object instead. The descriptor must contain
* exactly `N` handles, it is not permitted to, for example, call PopGenericObjects<1>() twice
* to read a multi-handle descriptor with 2 handles, or to make a single PopGenericObjects<2>()
* call to read 2 single-handle descriptors.
*/
template <unsigned int N>
std::array<std::shared_ptr<Kernel::Object>, N> PopGenericObjects();
/**
* Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to
* the passed `T` types, while verifying that the cast is valid. If the type of an object does
* not match, null is returned instead.
*/
template <typename... T>
std::tuple<std::shared_ptr<T>...> PopObjects();
/// Convenience wrapper around PopObjects() which assigns the handles to the passed references.
template <typename... T>
void PopObjects(std::shared_ptr<T>&... pointers) {
std::tie(pointers...) = PopObjects<T...>();
}
u32 PopPID();
/**
* @brief Pops a static buffer from the IPC request buffer.
* @return The buffer that was copied from the IPC request originator.
*
* In real services, static buffers must be set up before any IPC request using those is sent.
* It is the duty of the process (usually services) to allocate and set up the receiving static
* buffer information. Our HLE services do not need to set up the buffers beforehand.
*/
const std::vector<u8>& PopStaticBuffer();
/// Pops a mapped buffer descriptor with its vaddr and resolves it to an HLE interface
Kernel::MappedBuffer& PopMappedBuffer();
/**
* @brief Reads the next normal parameters as a struct, by copying it
* @note: The output class must be correctly packed/padded to fit hardware layout.
*/
template <typename T>
void PopRaw(T& value);
/**
* @brief Reads the next normal parameters as a struct, by copying it into a new value
* @note: The output class must be correctly packed/padded to fit hardware layout.
*/
template <typename T>
T PopRaw();
private:
template <unsigned int N>
std::array<u32, N> PopHLEHandles();
};
/// Pop ///
template <>
inline u32 RequestParser::Pop() {
return cmdbuf[index++];
}
template <typename T>
void RequestParser::PopRaw(T& value) {
static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable");
std::memcpy(&value, cmdbuf + index, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
template <typename T>
T RequestParser::PopRaw() {
T value;
PopRaw(value);
return value;
}
template <>
inline u8 RequestParser::Pop() {
return PopRaw<u8>();
}
template <>
inline u16 RequestParser::Pop() {
return PopRaw<u16>();
}
template <>
inline u64 RequestParser::Pop() {
const u64 lsw = Pop<u32>();
const u64 msw = Pop<u32>();
return msw << 32 | lsw;
}
template <>
inline s32 RequestParser::Pop() {
s32_le data = PopRaw<s32_le>();
return data;
}
template <>
inline f32 RequestParser::Pop() {
const u32 value = Pop<u32>();
float real;
std::memcpy(&real, &value, sizeof(real));
return real;
}
template <>
inline f64 RequestParser::Pop() {
const u64 value = Pop<u64>();
f64 real;
std::memcpy(&real, &value, sizeof(real));
return real;
}
template <>
inline bool RequestParser::Pop() {
return Pop<u8>() != 0;
}
template <>
inline Result RequestParser::Pop() {
return Result{Pop<u32>()};
}
template <typename T>
void RequestParser::Pop(T& value) {
value = Pop<T>();
}
template <typename First, typename... Other>
void RequestParser::Pop(First& first_value, Other&... other_values) {
first_value = Pop<First>();
Pop(other_values...);
}
template <unsigned int N>
std::array<u32, N> RequestParser::PopHLEHandles() {
u32 handle_descriptor = Pop<u32>();
ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
"Tried to pop handle(s) but the descriptor is not a handle descriptor");
ASSERT_MSG(N == HandleNumberFromDesc(handle_descriptor),
"Number of handles doesn't match the descriptor");
std::array<u32, N> handles{};
for (u32& handle : handles) {
handle = Pop<u32>();
}
return handles;
}
inline std::shared_ptr<Kernel::Object> RequestParser::PopGenericObject() {
auto [handle] = PopHLEHandles<1>();
return context->GetIncomingHandle(handle);
}
template <typename T>
std::shared_ptr<T> RequestParser::PopObject() {
return Kernel::DynamicObjectCast<T>(PopGenericObject());
}
template <unsigned int N>
inline std::array<std::shared_ptr<Kernel::Object>, N> RequestParser::PopGenericObjects() {
std::array<u32, N> handles = PopHLEHandles<N>();
std::array<std::shared_ptr<Kernel::Object>, N> pointers;
for (int i = 0; i < N; ++i) {
pointers[i] = context->GetIncomingHandle(handles[i]);
}
return pointers;
}
namespace detail {
template <typename... T, std::size_t... I>
std::tuple<std::shared_ptr<T>...> PopObjectsHelper(
std::array<std::shared_ptr<Kernel::Object>, sizeof...(T)>&& pointers,
std::index_sequence<I...>) {
return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...);
}
} // namespace detail
template <typename... T>
inline std::tuple<std::shared_ptr<T>...> RequestParser::PopObjects() {
return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(),
std::index_sequence_for<T...>{});
}
inline u32 RequestParser::PopPID() {
ASSERT(Pop<u32>() == static_cast<u32>(DescriptorType::CallingPid));
return Pop<u32>();
}
inline const std::vector<u8>& RequestParser::PopStaticBuffer() {
const u32 sbuffer_descriptor = Pop<u32>();
// Pop the address from the incoming request buffer
Pop<VAddr>();
StaticBufferDescInfo buffer_info{sbuffer_descriptor};
return context->GetStaticBuffer(static_cast<u8>(buffer_info.buffer_id));
}
inline Kernel::MappedBuffer& RequestParser::PopMappedBuffer() {
u32 mapped_buffer_descriptor = Pop<u32>();
ASSERT_MSG(GetDescriptorType(mapped_buffer_descriptor) == MappedBuffer,
"Tried to pop mapped buffer but the descriptor is not a mapped buffer descriptor");
return context->GetMappedBuffer(Pop<u32>());
}
} // namespace IPC

View File

@@ -0,0 +1,220 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter)
SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter::Callback)
namespace Kernel {
void AddressArbiter::WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address) {
thread->wait_address = wait_address;
thread->status = ThreadStatus::WaitArb;
waiting_threads.emplace_back(std::move(thread));
}
u64 AddressArbiter::ResumeAllThreads(VAddr address) {
// Determine which threads are waiting on this address, those should be woken up.
auto itr = std::stable_partition(waiting_threads.begin(), waiting_threads.end(),
[address](const auto& thread) {
ASSERT_MSG(thread->status == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->wait_address != address;
});
// Wake up all the found threads
const u64 num_threads = std::distance(itr, waiting_threads.end());
std::for_each(itr, waiting_threads.end(), [](auto& thread) { thread->ResumeFromWait(); });
// Remove the woken up threads from the wait list.
waiting_threads.erase(itr, waiting_threads.end());
return num_threads;
}
bool AddressArbiter::ResumeHighestPriorityThread(VAddr address) {
// Determine which threads are waiting on this address, those should be considered for wakeup.
auto matches_start = std::stable_partition(
waiting_threads.begin(), waiting_threads.end(), [address](const auto& thread) {
ASSERT_MSG(thread->status == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->wait_address != address;
});
// Iterate through threads, find highest priority thread that is waiting to be arbitrated.
// Note: The real kernel will pick the first thread in the list if more than one have the
// same highest priority value. Lower priority values mean higher priority.
auto itr = std::min_element(matches_start, waiting_threads.end(),
[](const auto& lhs, const auto& rhs) {
return lhs->current_priority < rhs->current_priority;
});
if (itr == waiting_threads.end()) {
return false;
}
auto thread = *itr;
thread->ResumeFromWait();
waiting_threads.erase(itr);
return true;
}
AddressArbiter::AddressArbiter(KernelSystem& kernel)
: Object(kernel), kernel(kernel), timeout_callback(std::make_shared<Callback>(*this)) {}
AddressArbiter::~AddressArbiter() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::AddressArbiter, 1);
}
}
std::shared_ptr<AddressArbiter> KernelSystem::CreateAddressArbiter(std::string name) {
auto address_arbiter = std::make_shared<AddressArbiter>(*this);
address_arbiter->name = std::move(name);
return address_arbiter;
}
class AddressArbiter::Callback : public WakeupCallback {
public:
explicit Callback(AddressArbiter& _parent) : parent(_parent) {}
AddressArbiter& parent;
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) override {
parent.WakeUp(reason, std::move(thread), std::move(object));
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WakeupCallback>(*this);
}
friend class boost::serialization::access;
};
void AddressArbiter::WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) {
ASSERT(reason == ThreadWakeupReason::Timeout);
// Remove the newly-awakened thread from the Arbiter's waiting list.
waiting_threads.erase(std::remove(waiting_threads.begin(), waiting_threads.end(), thread),
waiting_threads.end());
};
Result AddressArbiter::ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type,
VAddr address, s32 value, u64 nanoseconds) {
switch (type) {
// Signal thread(s) waiting for arbitrate address...
case ArbitrationType::Signal: {
u64 num_threads{};
// Negative value means resume all threads
if (value < 0) {
num_threads = ResumeAllThreads(address);
} else {
// Resume first N threads
for (s32 i = 0; i < value; i++) {
num_threads += ResumeHighestPriorityThread(address);
}
}
// Prevents lag from low priority threads that spam svcArbitrateAddress and wake no threads
// The tick count is taken directly from official HOS kernel. The priority value is one less
// than official kernel as the affected FMV threads dont meet the priority threshold of 50.
// TODO: Revisit this when scheduler is rewritten and adjust if there isn't a problem there.
if (num_threads == 0 && thread->current_priority >= 49) {
kernel.current_cpu->GetTimer().AddTicks(1614u);
}
break;
}
// Wait current thread (acquire the arbiter)...
case ArbitrationType::WaitIfLessThan:
if ((s32)kernel.memory.Read32(address) < value) {
WaitThread(std::move(thread), address);
}
break;
case ArbitrationType::WaitIfLessThanWithTimeout:
if ((s32)kernel.memory.Read32(address) < value) {
thread->wakeup_callback = timeout_callback;
thread->WakeAfterDelay(nanoseconds);
WaitThread(std::move(thread), address);
}
break;
case ArbitrationType::DecrementAndWaitIfLessThan: {
s32 memory_value = kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
kernel.memory.Write32(address, (s32)memory_value - 1);
WaitThread(std::move(thread), address);
}
break;
}
case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: {
s32 memory_value = kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
kernel.memory.Write32(address, (s32)memory_value - 1);
thread->wakeup_callback = timeout_callback;
thread->WakeAfterDelay(nanoseconds);
WaitThread(std::move(thread), address);
}
break;
}
default:
LOG_ERROR(Kernel, "unknown type={}", type);
return ResultInvalidEnumValueFnd;
}
// The calls that use a timeout seem to always return a Timeout error even if they did not put
// the thread to sleep
if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) {
return ResultTimeout;
}
return ResultSuccess;
}
template <class Archive>
void AddressArbiter::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& name;
ar& waiting_threads;
ar& timeout_callback;
ar& resource_limit;
}
SERIALIZE_IMPL(AddressArbiter)
} // namespace Kernel
namespace boost::serialization {
template <class Archive>
void save_construct_data(Archive& ar, const Kernel::AddressArbiter::Callback* t,
const unsigned int) {
ar << Kernel::SharedFrom(&t->parent);
}
template <class Archive>
void load_construct_data(Archive& ar, Kernel::AddressArbiter::Callback* t, const unsigned int) {
std::shared_ptr<Kernel::AddressArbiter> parent;
ar >> parent;
::new (t) Kernel::AddressArbiter::Callback(*parent);
}
} // namespace boost::serialization

View File

@@ -0,0 +1,88 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
// Address arbiters are an underlying kernel synchronization object that can be created/used via
// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR
// applications use them as an underlying mechanism to implement thread-safe barriers, events, and
// semaphores.
namespace Kernel {
class Thread;
class ResourceLimit;
enum class ArbitrationType : u32 {
Signal,
WaitIfLessThan,
DecrementAndWaitIfLessThan,
WaitIfLessThanWithTimeout,
DecrementAndWaitIfLessThanWithTimeout,
};
class AddressArbiter final : public Object, public WakeupCallback {
public:
explicit AddressArbiter(KernelSystem& kernel);
~AddressArbiter() override;
std::string GetTypeName() const override {
return "Arbiter";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::AddressArbiter;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ResourceLimit> resource_limit;
std::string name; ///< Name of address arbiter object (optional)
Result ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type, VAddr address,
s32 value, u64 nanoseconds);
class Callback;
private:
KernelSystem& kernel;
/// Puts the thread to wait on the specified arbitration address under this address arbiter.
void WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address);
/// Resume all threads found to be waiting on the address under this address arbiter
u64 ResumeAllThreads(VAddr address);
/// Resume one thread found to be waiting on the address under this address arbiter and return
/// the resumed thread.
bool ResumeHighestPriorityThread(VAddr address);
/// Threads waiting for the address arbiter to be signaled.
std::vector<std::shared_ptr<Thread>> waiting_threads;
std::shared_ptr<Callback> timeout_callback;
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) override;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter)
BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter::Callback)
CONSTRUCT_KERNEL_OBJECT(Kernel::AddressArbiter)

View File

@@ -0,0 +1,63 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/global.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
SERIALIZE_EXPORT_IMPL(Kernel::ClientPort)
namespace Kernel {
ClientPort::ClientPort(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
ClientPort::~ClientPort() = default;
Result ClientPort::Connect(std::shared_ptr<ClientSession>* out_client_session) {
// Note: Threads do not wait for the server endpoint to call
// AcceptSession before returning from this call.
R_UNLESS(active_sessions < max_sessions, ResultMaxConnectionsReached);
active_sessions++;
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
auto [server, client] = kernel.CreateSessionPair(server_port->GetName(), SharedFrom(this));
if (server_port->hle_handler) {
server_port->hle_handler->ClientConnected(server);
} else {
server_port->pending_sessions.push_back(server);
}
// Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads();
*out_client_session = client;
return ResultSuccess;
}
void ClientPort::ConnectionClosed() {
ASSERT(active_sessions > 0);
--active_sessions;
}
template <class Archive>
void ClientPort::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& server_port;
ar& max_sessions;
ar& active_sessions;
ar& name;
}
SERIALIZE_IMPL(ClientPort)
} // namespace Kernel

View File

@@ -0,0 +1,73 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
namespace Kernel {
class ClientSession;
class ClientPort final : public Object {
public:
explicit ClientPort(KernelSystem& kernel);
~ClientPort() override;
friend class ServerPort;
std::string GetTypeName() const override {
return "ClientPort";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ServerPort> GetServerPort() const {
return server_port;
}
/**
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
* list of pending sessions, and signals the ServerPort, causing any threads
* waiting on it to awake.
* @returns ClientSession The client endpoint of the created Session pair, or error code.
*/
Result Connect(std::shared_ptr<ClientSession>* out_client_session);
/**
* Signifies that a previously active connection has been closed,
* decreasing the total number of active connections to this port.
*/
void ConnectionClosed();
private:
KernelSystem& kernel;
std::shared_ptr<ServerPort> server_port; ///< ServerPort associated with this client port.
u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have
u32 active_sessions = 0; ///< Number of currently open sessions to this port
std::string name; ///< Name of client port (optional)
friend class KernelSystem;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ClientPort)
CONSTRUCT_KERNEL_OBJECT(Kernel::ClientPort)

View File

@@ -0,0 +1,67 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::ClientSession)
namespace Kernel {
ClientSession::ClientSession(KernelSystem& kernel) : Object(kernel) {}
ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
// the emulated application.
// Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
// will be kept alive until after ClientDisconnected() returns.
std::shared_ptr<ServerSession> server = SharedFrom(parent->server);
if (server) {
std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
if (hle_handler)
hle_handler->ClientDisconnected(server);
// Clean up the list of client threads with pending requests, they are unneeded now that the
// client endpoint is closed.
server->pending_requesting_threads.clear();
server->currently_handling = nullptr;
}
parent->client = nullptr;
if (server) {
// Notify any threads waiting on the ServerSession that the endpoint has been closed. Note
// that this call has to happen after `Session::client` has been set to nullptr to let the
// ServerSession know that the client endpoint has been closed.
server->WakeupAllWaitingThreads();
}
}
Result ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread) {
// Keep ServerSession alive until we're done working with it.
std::shared_ptr<ServerSession> server = SharedFrom(parent->server);
R_UNLESS(server, ResultSessionClosed);
// Signal the server session that new data is available
return server->HandleSyncRequest(std::move(thread));
}
template <class Archive>
void ClientSession::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& name;
ar& parent;
}
SERIALIZE_IMPL(ClientSession)
} // namespace Kernel

View File

@@ -0,0 +1,60 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Kernel {
class Session;
class Thread;
class ClientSession final : public Object {
public:
explicit ClientSession(KernelSystem& kernel);
~ClientSession() override;
friend class KernelSystem;
std::string GetTypeName() const override {
return "ClientSession";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
/**
* Sends an SyncRequest from the current emulated thread.
* @param thread Thread that initiated the request.
* @return Result of the operation.
*/
Result SendSyncRequest(std::shared_ptr<Thread> thread);
std::string name; ///< Name of client port (optional)
/// The parent session, which links to the server endpoint.
std::shared_ptr<Session> parent;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ClientSession)
CONSTRUCT_KERNEL_OBJECT(Kernel::ClientSession)

View File

@@ -0,0 +1,42 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <boost/serialization/binary_object.hpp>
#include "common/archives.h"
#include "core/hle/kernel/config_mem.h"
SERIALIZE_EXPORT_IMPL(ConfigMem::Handler)
namespace ConfigMem {
Handler::Handler() {
std::memset(&config_mem, 0, sizeof(config_mem));
// Values extracted from firmware 11.17.0-50E
config_mem.kernel_version_min = 0x3a;
config_mem.kernel_version_maj = 0x2;
config_mem.ns_tid = 0x0004013000008002;
config_mem.sys_core_ver = 0x2;
config_mem.unit_info = 0x1; // Bit 0 set for Retail
config_mem.prev_firm = 0x1;
config_mem.ctr_sdk_ver = 0x0000F450;
config_mem.firm_version_min = 0x3a;
config_mem.firm_version_maj = 0x2;
config_mem.firm_sys_core_ver = 0x2;
config_mem.firm_ctr_sdk_ver = 0x0000F450;
}
ConfigMemDef& Handler::GetConfigMem() {
return config_mem;
}
template <class Archive>
void Handler::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<BackingMem>(*this);
ar& boost::serialization::make_binary_object(&config_mem, sizeof(config_mem));
}
SERIALIZE_IMPL(Handler)
} // namespace ConfigMem

View File

@@ -0,0 +1,79 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
// Configuration memory stores various hardware/kernel configuration settings. This memory page is
// read-only for ARM11 processes. I'm guessing this would normally be written to by the firmware/
// bootrom. Because we're not emulating this, and essentially just "stubbing" the functionality, I'm
// putting this as a subset of HLE for now.
#include <boost/serialization/export.hpp>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "common/swap.h"
#include "core/memory.h"
namespace ConfigMem {
struct ConfigMemDef {
u8 kernel_unk; // 0
u8 kernel_version_rev; // 1
u8 kernel_version_min; // 2
u8 kernel_version_maj; // 3
u32_le update_flag; // 4
u64_le ns_tid; // 8
u32_le sys_core_ver; // 10
u8 unit_info; // 14
u8 boot_firm; // 15
u8 prev_firm; // 16
INSERT_PADDING_BYTES(0x1); // 17
u32_le ctr_sdk_ver; // 18
INSERT_PADDING_BYTES(0x30 - 0x1C); // 1C
u32_le app_mem_type; // 30
INSERT_PADDING_BYTES(0x40 - 0x34); // 34
u32_le app_mem_alloc; // 40
u32_le sys_mem_alloc; // 44
u32_le base_mem_alloc; // 48
INSERT_PADDING_BYTES(0x60 - 0x4C); // 4C
u8 firm_unk; // 60
u8 firm_version_rev; // 61
u8 firm_version_min; // 62
u8 firm_version_maj; // 63
u32_le firm_sys_core_ver; // 64
u32_le firm_ctr_sdk_ver; // 68
INSERT_PADDING_BYTES(0x1000 - 0x6C); // 6C
};
static_assert(sizeof(ConfigMemDef) == Memory::CONFIG_MEMORY_SIZE,
"Config Memory structure size is wrong");
class Handler : public BackingMem {
public:
Handler();
ConfigMemDef& GetConfigMem();
u8* GetPtr() override {
return reinterpret_cast<u8*>(&config_mem);
}
const u8* GetPtr() const override {
return reinterpret_cast<const u8*>(&config_mem);
}
std::size_t GetSize() const override {
return sizeof(config_mem);
}
private:
ConfigMemDef config_mem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace ConfigMem
BOOST_CLASS_EXPORT_KEY(ConfigMem::Handler)

View File

@@ -0,0 +1,113 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
namespace Kernel {
namespace ErrCodes {
enum {
OutOfSharedMems = 11,
OutOfThreads = 12,
OutOfMutexes = 13,
OutOfSemaphores = 14,
OutOfEvents = 15,
OutOfTimers = 16,
OutOfHandles = 19,
SessionClosedByRemote = 26,
PortNameTooLong = 30,
WrongLockingThread = 31,
NoPendingSessions = 35,
WrongPermission = 46,
InvalidBufferDescriptor = 48,
OutOfAddressArbiters = 51,
MaxConnectionsReached = 52,
CommandTooLarge = 54,
};
}
// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always
// double check that the code matches before re-using the constant.
constexpr Result ResultOutOfHandles(ErrCodes::OutOfHandles, ErrorModule::Kernel,
ErrorSummary::OutOfResource,
ErrorLevel::Permanent); // 0xD8600413
constexpr Result ResultSessionClosed(ErrCodes::SessionClosedByRemote, ErrorModule::OS,
ErrorSummary::Canceled,
ErrorLevel::Status); // 0xC920181A
constexpr Result ResultPortNameTooLong(ErrCodes::PortNameTooLong, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E0181E
constexpr Result ResultWrongPermission(ErrCodes::WrongPermission, ErrorModule::OS,
ErrorSummary::WrongArgument, ErrorLevel::Permanent);
constexpr Result ResultInvalidBufferDescriptor(ErrCodes::InvalidBufferDescriptor, ErrorModule::OS,
ErrorSummary::WrongArgument, ErrorLevel::Permanent);
constexpr Result ResultMaxConnectionsReached(ErrCodes::MaxConnectionsReached, ErrorModule::OS,
ErrorSummary::WouldBlock,
ErrorLevel::Temporary); // 0xD0401834
constexpr Result ResultNotAuthorized(ErrorDescription::NotAuthorized, ErrorModule::OS,
ErrorSummary::WrongArgument,
ErrorLevel::Permanent); // 0xD9001BEA
constexpr Result ResultInvalidEnumValue(ErrorDescription::InvalidEnumValue, ErrorModule::Kernel,
ErrorSummary::InvalidArgument,
ErrorLevel::Permanent); // 0xD8E007ED
constexpr Result ResultInvalidEnumValueFnd(ErrorDescription::InvalidEnumValue, ErrorModule::FND,
ErrorSummary::InvalidArgument,
ErrorLevel::Permanent); // 0xD8E093ED
constexpr Result ResultInvalidCombination(ErrorDescription::InvalidCombination, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BEE
constexpr Result ResultInvalidCombinationKernel(ErrorDescription::InvalidCombination,
ErrorModule::Kernel, ErrorSummary::WrongArgument,
ErrorLevel::Permanent); // 0xD90007EE
constexpr Result ResultMisalignedAddress(ErrorDescription::MisalignedAddress, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BF1
constexpr Result ResultMisalignedSize(ErrorDescription::MisalignedSize, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BF2
constexpr Result ResultOutOfMemory(ErrorDescription::OutOfMemory, ErrorModule::Kernel,
ErrorSummary::OutOfResource,
ErrorLevel::Permanent); // 0xD86007F3
/// Returned when out of heap or linear heap memory when allocating
constexpr Result ResultOutOfHeapMemory(ErrorDescription::OutOfMemory, ErrorModule::OS,
ErrorSummary::OutOfResource,
ErrorLevel::Status); // 0xC860180A
constexpr Result ResultNotImplemented(ErrorDescription::NotImplemented, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BF4
constexpr Result ResultInvalidAddress(ErrorDescription::InvalidAddress, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BF5
constexpr Result ResultInvalidAddressState(ErrorDescription::InvalidAddress, ErrorModule::OS,
ErrorSummary::InvalidState,
ErrorLevel::Usage); // 0xE0A01BF5
constexpr Result ResultInvalidPointer(ErrorDescription::InvalidPointer, ErrorModule::Kernel,
ErrorSummary::InvalidArgument,
ErrorLevel::Permanent); // 0xD8E007F6
constexpr Result ResultInvalidHandle(ErrorDescription::InvalidHandle, ErrorModule::Kernel,
ErrorSummary::InvalidArgument,
ErrorLevel::Permanent); // 0xD8E007F7
/// Alternate code returned instead of ResultInvalidHandle in some code paths.
constexpr Result ResultInvalidHandleOs(ErrorDescription::InvalidHandle, ErrorModule::OS,
ErrorSummary::WrongArgument,
ErrorLevel::Permanent); // 0xD9001BF7
constexpr Result ResultNotFound(ErrorDescription::NotFound, ErrorModule::Kernel,
ErrorSummary::NotFound, ErrorLevel::Permanent); // 0xD88007FA
constexpr Result ResultOutOfRange(ErrorDescription::OutOfRange, ErrorModule::OS,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BFD
constexpr Result ResultOutOfRangeKernel(ErrorDescription::OutOfRange, ErrorModule::Kernel,
ErrorSummary::InvalidArgument,
ErrorLevel::Permanent); // 0xD8E007FD
constexpr Result ResultTimeout(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged, ErrorLevel::Info);
/// Returned when Accept() is called on a port with no sessions to be accepted.
constexpr Result ResultNoPendingSessions(ErrCodes::NoPendingSessions, ErrorModule::OS,
ErrorSummary::WouldBlock,
ErrorLevel::Permanent); // 0xD8401823
} // namespace Kernel

View File

@@ -0,0 +1,73 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::Event)
namespace Kernel {
Event::Event(KernelSystem& kernel) : WaitObject(kernel) {}
Event::~Event() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::Event, 1);
}
}
std::shared_ptr<Event> KernelSystem::CreateEvent(ResetType reset_type, std::string name) {
auto event = std::make_shared<Event>(*this);
event->signaled = false;
event->reset_type = reset_type;
event->name = std::move(name);
return event;
}
bool Event::ShouldWait(const Thread* thread) const {
return !signaled;
}
void Event::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
if (reset_type == ResetType::OneShot) {
signaled = false;
}
}
void Event::Signal() {
signaled = true;
WakeupAllWaitingThreads();
}
void Event::Clear() {
signaled = false;
}
void Event::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
if (reset_type == ResetType::Pulse) {
signaled = false;
}
}
template <class Archive>
void Event::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& reset_type;
ar& signaled;
ar& name;
ar& resource_limit;
}
SERIALIZE_IMPL(Event)
} // namespace Kernel

View File

@@ -0,0 +1,64 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/export.hpp>
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/wait_object.h"
namespace Kernel {
class Event final : public WaitObject {
public:
explicit Event(KernelSystem& kernel);
~Event() override;
std::string GetTypeName() const override {
return "Event";
}
std::string GetName() const override {
return name;
}
void SetName(const std::string& name_) {
name = name_;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Event;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
ResetType GetResetType() const {
return reset_type;
}
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
void WakeupAllWaitingThreads() override;
void Signal();
void Clear();
std::shared_ptr<ResourceLimit> resource_limit;
private:
ResetType reset_type; ///< Current ResetType
bool signaled; ///< Whether the event has already been signaled
std::string name; ///< Name of event (optional)
friend class KernelSystem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Event)
CONSTRUCT_KERNEL_OBJECT(Kernel::Event)

View File

@@ -0,0 +1,111 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <utility>
#include <boost/serialization/array.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::HandleTable)
namespace Kernel {
namespace {
constexpr u16 GetSlot(Handle handle) {
return handle >> 15;
}
constexpr u16 GetGeneration(Handle handle) {
return handle & 0x7FFF;
}
} // Anonymous namespace
HandleTable::HandleTable(KernelSystem& kernel) : kernel(kernel) {
next_generation = 1;
Clear();
}
HandleTable::~HandleTable() = default;
Result HandleTable::Create(Handle* out_handle, std::shared_ptr<Object> obj) {
DEBUG_ASSERT(obj != nullptr);
u16 slot = next_free_slot;
R_UNLESS(slot < generations.size(), ResultOutOfHandles);
next_free_slot = generations[slot];
u16 generation = next_generation++;
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
// CTR-OS doesn't use generation 0, so skip straight to 1.
if (next_generation >= (1 << 15)) {
next_generation = 1;
}
generations[slot] = generation;
objects[slot] = std::move(obj);
*out_handle = generation | (slot << 15);
return ResultSuccess;
}
Result HandleTable::Duplicate(Handle* out_handle, Handle handle) {
std::shared_ptr<Object> object = GetGeneric(handle);
R_UNLESS(object, ResultInvalidHandle);
return Create(out_handle, std::move(object));
}
Result HandleTable::Close(Handle handle) {
R_UNLESS(IsValid(handle), ResultInvalidHandle);
const u16 slot = GetSlot(handle);
objects[slot] = nullptr;
generations[slot] = next_free_slot;
next_free_slot = slot;
return ResultSuccess;
}
bool HandleTable::IsValid(Handle handle) const {
const u16 slot = GetSlot(handle);
const u16 generation = GetGeneration(handle);
return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation;
}
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
} else if (handle == CurrentProcess) {
return kernel.GetCurrentProcess();
}
if (!IsValid(handle)) {
return nullptr;
}
return objects[GetSlot(handle)];
}
void HandleTable::Clear() {
for (u16 i = 0; i < MAX_COUNT; ++i) {
generations[i] = i + 1;
objects[i] = nullptr;
}
next_free_slot = 0;
}
template <class Archive>
void HandleTable::serialize(Archive& ar, const unsigned int) {
ar& objects;
ar& generations;
ar& next_generation;
ar& next_free_slot;
}
SERIALIZE_IMPL(HandleTable)
} // namespace Kernel

View File

@@ -0,0 +1,129 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Kernel {
enum KernelHandle : Handle {
CurrentThread = 0xFFFF8000,
CurrentProcess = 0xFFFF8001,
};
/**
* This class allows the creation of Handles, which are references to objects that can be tested
* for validity and looked up. Here they are used to pass references to kernel objects to/from the
* emulated process. it has been designed so that it follows the same handle format and has
* approximately the same restrictions as the handle manager in the CTR-OS.
*
* Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0).
* The slot index is used to index into the arrays in this class to access the data corresponding
* to the Handle.
*
* To prevent accidental use of a freed Handle whose slot has already been reused, a global counter
* is kept and incremented every time a Handle is created. This is the Handle's "generation". The
* value of the counter is stored into the Handle as well as in the handle table (in the
* "generations" array). When looking up a handle, the Handle's generation must match with the
* value stored on the class, otherwise the Handle is considered invalid.
*
* To find free slots when allocating a Handle without needing to scan the entire object array, the
* generations field of unallocated slots is re-purposed as a linked list of indices to free slots.
* When a Handle is created, an index is popped off the list and used for the new Handle. When it
* is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is
* likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been
* verified and isn't likely to cause any problems.
*/
class HandleTable final : NonCopyable {
public:
explicit HandleTable(KernelSystem& kernel);
~HandleTable();
/**
* Allocates a handle for the given object.
* @return The created Handle or one of the following errors:
* - `ResultOutOfHandles`: the maximum number of handles has been exceeded.
*/
Result Create(Handle* out_handle, std::shared_ptr<Object> obj);
/**
* Returns a new handle that points to the same object as the passed in handle.
* @return The duplicated Handle or one of the following errors:
* - `ResultInvalidHandle`: an invalid handle was passed in.
* - Any errors returned by `Create()`.
*/
Result Duplicate(Handle* out_handle, Handle handle);
/**
* Closes a handle, removing it from the table and decreasing the object's ref-count.
* @return `ResultSuccess` or one of the following errors:
* - `ResultInvalidHandle`: an invalid handle was passed in.
*/
Result Close(Handle handle);
/// Checks if a handle is valid and points to an existing object.
bool IsValid(Handle handle) const;
/**
* Looks up a handle.
* @return Pointer to the looked-up object, or `nullptr` if the handle is not valid.
*/
std::shared_ptr<Object> GetGeneric(Handle handle) const;
/**
* Looks up a handle while verifying its type.
* @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its
* type differs from the requested one.
*/
template <class T>
std::shared_ptr<T> Get(Handle handle) const {
return DynamicObjectCast<T>(GetGeneric(handle));
}
/// Closes all handles held in this table.
void Clear();
private:
/**
* This is the maximum limit of handles allowed per process in CTR-OS. It can be further
* reduced by ExHeader values, but this is not emulated here.
*/
static const std::size_t MAX_COUNT = 4096;
/// Stores the Object referenced by the handle or null if the slot is empty.
std::array<std::shared_ptr<Object>, MAX_COUNT> objects;
/**
* The value of `next_generation` when the handle was created, used to check for validity. For
* empty slots, contains the index of the next free slot in the list.
*/
std::array<u16, MAX_COUNT> generations;
/**
* Global counter of the number of created handles. Stored in `generations` when a handle is
* created, and wraps around to 1 when it hits 0x8000.
*/
u16 next_generation;
/// Head of the free slots linked list.
u16 next_free_slot;
KernelSystem& kernel;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::HandleTable)
CONSTRUCT_KERNEL_OBJECT(Kernel::HandleTable)

View File

@@ -0,0 +1,358 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <vector>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
SERIALIZE_EXPORT_IMPL(Kernel::SessionRequestHandler)
SERIALIZE_EXPORT_IMPL(Kernel::SessionRequestHandler::SessionDataBase)
SERIALIZE_EXPORT_IMPL(Kernel::SessionRequestHandler::SessionInfo)
SERIALIZE_EXPORT_IMPL(Kernel::HLERequestContext)
SERIALIZE_EXPORT_IMPL(Kernel::HLERequestContext::ThreadCallback)
namespace Kernel {
class HLERequestContext::ThreadCallback : public Kernel::WakeupCallback {
public:
ThreadCallback(std::shared_ptr<HLERequestContext> context_,
std::shared_ptr<HLERequestContext::WakeupCallback> callback_)
: callback(std::move(callback_)), context(std::move(context_)) {}
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) {
ASSERT(thread->status == ThreadStatus::WaitHleEvent);
if (callback) {
callback->WakeUp(thread, *context, reason);
}
auto process = thread->owner_process.lock();
ASSERT(process);
// We must copy the entire command buffer *plus* the entire static buffers area, since
// the translation might need to read from it in order to retrieve the StaticBuffer
// target addresses.
std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buff;
Memory::MemorySystem& memory = context->kernel.memory;
memory.ReadBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
cmd_buff.size() * sizeof(u32));
context->WriteToOutgoingCommandBuffer(cmd_buff.data(), *process);
// Copy the translated command buffer back into the thread's command buffer area.
memory.WriteBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
cmd_buff.size() * sizeof(u32));
}
private:
ThreadCallback() = default;
std::shared_ptr<HLERequestContext::WakeupCallback> callback{};
std::shared_ptr<HLERequestContext> context{};
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::WakeupCallback>(*this);
ar& callback;
ar& context;
}
friend class boost::serialization::access;
};
SessionRequestHandler::SessionInfo::SessionInfo(std::shared_ptr<ServerSession> session,
std::unique_ptr<SessionDataBase> data)
: session(std::move(session)), data(std::move(data)) {}
void SessionRequestHandler::ClientConnected(std::shared_ptr<ServerSession> server_session) {
server_session->SetHleHandler(shared_from_this());
connected_sessions.emplace_back(std::move(server_session), MakeSessionData());
}
void SessionRequestHandler::ClientDisconnected(std::shared_ptr<ServerSession> server_session) {
server_session->SetHleHandler(nullptr);
connected_sessions.erase(
std::remove_if(connected_sessions.begin(), connected_sessions.end(),
[&](const SessionInfo& info) { return info.session == server_session; }),
connected_sessions.end());
}
template <class Archive>
void SessionRequestHandler::serialize(Archive& ar, const unsigned int) {
ar& connected_sessions;
}
SERIALIZE_IMPL(SessionRequestHandler)
template <class Archive>
void SessionRequestHandler::SessionDataBase::serialize(Archive& ar, const unsigned int) {}
SERIALIZE_IMPL(SessionRequestHandler::SessionDataBase)
template <class Archive>
void SessionRequestHandler::SessionInfo::serialize(Archive& ar, const unsigned int) {
ar& session;
ar& data;
}
SERIALIZE_IMPL(SessionRequestHandler::SessionInfo)
std::shared_ptr<Event> HLERequestContext::SleepClientThread(
const std::string& reason, std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->wakeup_callback = std::make_shared<ThreadCallback>(shared_from_this(), callback);
auto event = kernel.CreateEvent(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
thread->status = ThreadStatus::WaitHleEvent;
thread->wait_objects = {event};
event->AddWaitingThread(thread);
if (timeout.count() > 0)
thread->WakeAfterDelay(timeout.count());
return event;
}
HLERequestContext::HLERequestContext() : kernel(Core::Global<KernelSystem>()) {}
HLERequestContext::HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
std::shared_ptr<Thread> thread)
: kernel(kernel), session(std::move(session)), thread(thread) {
cmd_buf[0] = 0;
}
HLERequestContext::~HLERequestContext() = default;
std::shared_ptr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
ASSERT(id_from_cmdbuf < request_handles.size());
return request_handles[id_from_cmdbuf];
}
u32 HLERequestContext::AddOutgoingHandle(std::shared_ptr<Object> object) {
request_handles.push_back(std::move(object));
return static_cast<u32>(request_handles.size() - 1);
}
void HLERequestContext::ClearIncomingObjects() {
request_handles.clear();
}
const std::vector<u8>& HLERequestContext::GetStaticBuffer(u8 buffer_id) const {
return static_buffers[buffer_id];
}
void HLERequestContext::AddStaticBuffer(u8 buffer_id, std::vector<u8> data) {
static_buffers[buffer_id] = std::move(data);
}
Result HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
std::shared_ptr<Process> src_process_) {
auto& src_process = *src_process_;
IPC::Header header{src_cmdbuf[0]};
std::size_t untranslated_size = 1u + header.normal_params_size;
std::size_t command_size = untranslated_size + header.translate_params_size;
ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); // TODO(yuriks): Return error
std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin());
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
std::vector<u32> untranslated_cmdbuf;
if (should_record) {
untranslated_cmdbuf = std::vector<u32>{src_cmdbuf, src_cmdbuf + command_size};
}
std::size_t i = untranslated_size;
while (i < command_size) {
u32 descriptor = cmd_buf[i] = src_cmdbuf[i];
i += 1;
switch (IPC::GetDescriptorType(descriptor)) {
case IPC::DescriptorType::CopyHandle:
case IPC::DescriptorType::MoveHandle: {
u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error
for (u32 j = 0; j < num_handles; ++j) {
Handle handle = src_cmdbuf[i];
std::shared_ptr<Object> object = nullptr;
if (handle != 0) {
object = src_process.handle_table.GetGeneric(handle);
ASSERT(object != nullptr); // TODO(yuriks): Return error
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_process.handle_table.Close(handle);
}
}
cmd_buf[i++] = AddOutgoingHandle(std::move(object));
}
break;
}
case IPC::DescriptorType::CallingPid: {
cmd_buf[i++] = src_process.process_id;
break;
}
case IPC::DescriptorType::StaticBuffer: {
VAddr source_address = src_cmdbuf[i];
IPC::StaticBufferDescInfo buffer_info{descriptor};
// Copy the input buffer into our own vector and store it.
std::vector<u8> data(buffer_info.size);
kernel.memory.ReadBlock(src_process, source_address, data.data(), data.size());
AddStaticBuffer(buffer_info.buffer_id, std::move(data));
cmd_buf[i++] = source_address;
break;
}
case IPC::DescriptorType::MappedBuffer: {
u32 next_id = static_cast<u32>(request_mapped_buffers.size());
request_mapped_buffers.emplace_back(kernel.memory, src_process_, descriptor,
src_cmdbuf[i], next_id);
cmd_buf[i++] = next_id;
break;
}
default:
UNIMPLEMENTED_MSG("Unsupported handle translation: {:#010X}", descriptor);
}
}
if (should_record) {
std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
kernel.GetIPCRecorder().SetRequestInfo(thread, std::move(untranslated_cmdbuf),
std::move(translated_cmdbuf));
}
return ResultSuccess;
}
Result HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
Process& dst_process) const {
IPC::Header header{cmd_buf[0]};
std::size_t untranslated_size = 1u + header.normal_params_size;
std::size_t command_size = untranslated_size + header.translate_params_size;
ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH);
std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf);
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
std::vector<u32> untranslated_cmdbuf;
if (should_record) {
untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
}
std::size_t i = untranslated_size;
while (i < command_size) {
u32 descriptor = dst_cmdbuf[i] = cmd_buf[i];
i += 1;
switch (IPC::GetDescriptorType(descriptor)) {
case IPC::DescriptorType::CopyHandle:
case IPC::DescriptorType::MoveHandle: {
// HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally
u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
ASSERT(i + num_handles <= command_size);
for (u32 j = 0; j < num_handles; ++j) {
std::shared_ptr<Object> object = GetIncomingHandle(cmd_buf[i]);
Handle handle = 0;
if (object != nullptr) {
// TODO(yuriks): Figure out the proper error handling for if this fails
R_ASSERT(dst_process.handle_table.Create(std::addressof(handle), object));
}
dst_cmdbuf[i++] = handle;
}
break;
}
case IPC::DescriptorType::StaticBuffer: {
IPC::StaticBufferDescInfo buffer_info{descriptor};
const auto& data = GetStaticBuffer(buffer_info.buffer_id);
// Grab the address that the target thread set up to receive the response static buffer
// and write our data there. The static buffers area is located right after the command
// buffer area.
std::size_t static_buffer_offset =
IPC::COMMAND_BUFFER_LENGTH + 2 * buffer_info.buffer_id;
IPC::StaticBufferDescInfo target_descriptor{dst_cmdbuf[static_buffer_offset]};
VAddr target_address = dst_cmdbuf[static_buffer_offset + 1];
ASSERT_MSG(target_descriptor.size >= data.size(), "Static buffer data is too big");
kernel.memory.WriteBlock(dst_process, target_address, data.data(), data.size());
dst_cmdbuf[i++] = target_address;
break;
}
case IPC::DescriptorType::MappedBuffer: {
VAddr addr = request_mapped_buffers[cmd_buf[i]].address;
dst_cmdbuf[i++] = addr;
break;
}
default:
UNIMPLEMENTED_MSG("Unsupported handle translation: {:#010X}", descriptor);
}
}
if (should_record) {
std::vector<u32> translated_cmdbuf{dst_cmdbuf, dst_cmdbuf + command_size};
kernel.GetIPCRecorder().SetReplyInfo(thread, std::move(untranslated_cmdbuf),
std::move(translated_cmdbuf));
}
return ResultSuccess;
}
MappedBuffer& HLERequestContext::GetMappedBuffer(u32 id_from_cmdbuf) {
ASSERT_MSG(id_from_cmdbuf < request_mapped_buffers.size(), "Mapped Buffer ID out of range!");
return request_mapped_buffers[id_from_cmdbuf];
}
void HLERequestContext::ReportUnimplemented() const {
if (kernel.GetIPCRecorder().IsEnabled()) {
kernel.GetIPCRecorder().SetHLEUnimplemented(thread);
}
}
template <class Archive>
void HLERequestContext::serialize(Archive& ar, const unsigned int) {
ar& cmd_buf;
ar& session;
ar& thread;
ar& request_handles;
ar& static_buffers;
ar& request_mapped_buffers;
}
SERIALIZE_IMPL(HLERequestContext)
MappedBuffer::MappedBuffer() : memory(&Core::Global<Core::System>().Memory()) {}
MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process,
u32 descriptor, VAddr address, u32 id)
: memory(&memory), id(id), address(address), process(std::move(process)) {
IPC::MappedBufferDescInfo desc{descriptor};
size = desc.size;
perms = desc.perms;
}
void MappedBuffer::Read(void* dest_buffer, std::size_t offset, std::size_t size) {
ASSERT(perms & IPC::R);
ASSERT(offset + size <= this->size);
memory->ReadBlock(*process, address + static_cast<VAddr>(offset), dest_buffer, size);
}
void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t size) {
ASSERT(perms & IPC::W);
ASSERT(offset + size <= this->size);
memory->WriteBlock(*process, address + static_cast<VAddr>(offset), src_buffer, size);
}
} // namespace Kernel

View File

@@ -0,0 +1,399 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <array>
#include <chrono>
#include <future>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/small_vector.hpp>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/serialization/boost_small_vector.hpp"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
namespace Service {
class ServiceFrameworkBase;
}
namespace Memory {
class MemorySystem;
}
namespace Kernel {
class HandleTable;
class Process;
class Thread;
class Event;
class HLERequestContext;
class KernelSystem;
/**
* Interface implemented by HLE Session handlers.
* This can be provided to a ServerSession in order to hook into several relevant events
* (such as a new connection or a SyncRequest) so they can be implemented in the emulator.
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
virtual ~SessionRequestHandler() = default;
/**
* Handles a sync request from the emulated application.
* @param context holds all the information relevant to his request (ServerSession, Translated
* command buffer, etc).
*/
virtual void HandleSyncRequest(Kernel::HLERequestContext& context) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
* associated ServerSession alive for the duration of the connection.
* @param server_session Owning pointer to the ServerSession associated with the connection.
*/
virtual void ClientConnected(std::shared_ptr<ServerSession> server_session);
/**
* Signals that a client has just disconnected from this HLE handler and releases the
* associated ServerSession.
* @param server_session ServerSession associated with the connection.
*/
virtual void ClientDisconnected(std::shared_ptr<ServerSession> server_session);
/// Empty placeholder structure for services with no per-session data. The session data classes
/// in each service must inherit from this.
struct SessionDataBase {
virtual ~SessionDataBase() = default;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
struct SessionInfo {
SessionInfo(std::shared_ptr<ServerSession> session, std::unique_ptr<SessionDataBase> data);
std::shared_ptr<ServerSession> session;
std::unique_ptr<SessionDataBase> data;
private:
SessionInfo() = default;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
protected:
/// Creates the storage for the session data of the service.
virtual std::unique_ptr<SessionDataBase> MakeSessionData() = 0;
/// Returns the session data associated with the server session.
template <typename T>
T* GetSessionData(std::shared_ptr<ServerSession> session) {
static_assert(std::is_base_of<SessionDataBase, T>(),
"T is not a subclass of SessionDataBase");
auto itr = std::find_if(connected_sessions.begin(), connected_sessions.end(),
[&](const SessionInfo& info) { return info.session == session; });
ASSERT(itr != connected_sessions.end());
return static_cast<T*>(itr->data.get());
}
/// List of sessions that are connected to this handler. A ServerSession whose server endpoint
/// is an HLE implementation is kept alive by this list for the duration of the connection.
std::vector<SessionInfo> connected_sessions;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
// NOTE: The below classes are ephemeral and don't need serialization
class MappedBuffer {
public:
MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process, u32 descriptor,
VAddr address, u32 id);
// interface for service
void Read(void* dest_buffer, std::size_t offset, std::size_t size);
void Write(const void* src_buffer, std::size_t offset, std::size_t size);
std::size_t GetSize() const {
return size;
}
// interface for ipc helper
u32 GenerateDescriptor() const {
return IPC::MappedBufferDesc(size, perms);
}
u32 GetId() const {
return id;
}
private:
friend class HLERequestContext;
Memory::MemorySystem* memory;
u32 id;
VAddr address;
std::shared_ptr<Process> process;
u32 size;
IPC::MappedBufferPermissions perms;
MappedBuffer();
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& id;
ar& address;
ar& process;
ar& size;
ar& perms;
}
friend class boost::serialization::access;
};
/**
* Class containing information about an in-flight IPC request being handled by an HLE service
* implementation.
*
* HLE handle protocol
* ===================
*
* To avoid needing HLE services to keep a separate handle table, or having to directly modify the
* requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel
* will decode the incoming handles into object pointers and insert a id in the buffer where the
* handle would normally be. The service then calls GetIncomingHandle() with that id to get the
* pointer to the object. Similarly, instead of inserting a handle into the command buffer, the
* service calls AddOutgoingHandle() and stores the returned id where the handle would normally go.
*
* The end result is similar to just giving services their own real handle tables, but since these
* ids are local to a specific context, it avoids requiring services to manage handles for objects
* across multiple calls and ensuring that unneeded handles are cleaned up.
*
* HLE mapped buffer protocol
* ==========================
*
* HLE services don't have their own virtual memory space, a tweaked protocol is used to simulate
* memory mapping. The kernel will wrap the incoming buffers into a memory interface on which HLE
* services can operate, and insert a id in the buffer where the vaddr would normally be. The
* service then calls GetMappedBuffer with that id to get the memory interface. On response, like
* real services pushing back the mapped buffer address to unmap it, HLE services push back the
* id of the memory interface and let kernel convert it back to client vaddr. No real unmapping is
* needed in this case, though.
*/
class HLERequestContext : public std::enable_shared_from_this<HLERequestContext> {
public:
HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
std::shared_ptr<Thread> thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
u32* CommandBuffer() {
return cmd_buf.data();
}
/// Returns the command header from the IPC command buffer.
IPC::Header CommandHeader() const {
return {cmd_buf[0]};
}
/// Returns the Command ID from the IPC command buffer.
u16 CommandID() const {
return static_cast<u16>(CommandHeader().command_id.Value());
}
/**
* Returns the session through which this request was made. This can be used as a map key to
* access per-client data on services.
*/
std::shared_ptr<ServerSession> Session() const {
return session;
}
/**
* Returns the client thread that made the service request.
*/
std::shared_ptr<Thread> ClientThread() const {
return thread;
}
class WakeupCallback {
public:
virtual ~WakeupCallback() = default;
virtual void WakeUp(std::shared_ptr<Thread> thread, HLERequestContext& context,
ThreadWakeupReason reason) = 0;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {}
friend class boost::serialization::access;
};
/**
* Puts the specified guest thread to sleep until the returned event is signaled or until the
* specified timeout expires.
* @param reason Reason for pausing the thread, to be used for debugging purposes.
* @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
* invoked with a Timeout reason.
* @param callback Callback to be invoked when the thread is resumed. This callback must write
* the entire command response once again, regardless of the state of it before this function
* was called.
* @returns Event that when signaled will resume the thread and call the callback function.
*/
std::shared_ptr<Event> SleepClientThread(const std::string& reason,
std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback);
private:
template <typename ResultFunctor>
class AsyncWakeUpCallback : public WakeupCallback {
public:
explicit AsyncWakeUpCallback(ResultFunctor res_functor, std::future<void> fut)
: functor(res_functor) {
future = std::move(fut);
}
void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
functor(ctx);
}
private:
ResultFunctor functor;
std::future<void> future;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
if (!Archive::is_loading::value && future.valid()) {
future.wait();
}
ar& functor;
}
friend class boost::serialization::access;
};
public:
/**
* Puts the game thread to sleep and calls the specified async_section asynchronously.
* Once the execution of the async section finishes, result_function is called. Use this
* mechanism to run blocking IO operations, so that other game threads are allowed to run
* while the one performing the blocking operation waits.
* @param async_section Callable that takes Kernel::HLERequestContext& as argument
* and returns the amount of nanoseconds to wait before calling result_function.
* This callable is ran asynchronously.
* @param result_function Callable that takes Kernel::HLERequestContext& as argument
* and doesn't return anything. This callable is ran from the emulator thread
* and can be used to set the IPC result.
* @param really_async If set to false, it will call both async_section and result_function
* from the emulator thread.
*/
template <typename AsyncFunctor, typename ResultFunctor>
void RunAsync(AsyncFunctor async_section, ResultFunctor result_function,
bool really_async = true) {
if (really_async) {
this->SleepClientThread(
"RunAsync", std::chrono::nanoseconds(-1),
std::make_shared<AsyncWakeUpCallback<ResultFunctor>>(
result_function,
std::move(std::async(std::launch::async, [this, async_section] {
s64 sleep_for = async_section(*this);
this->thread->WakeAfterDelay(sleep_for, true);
}))));
} else {
s64 sleep_for = async_section(*this);
if (sleep_for > 0) {
auto parallel_wakeup = std::make_shared<AsyncWakeUpCallback<ResultFunctor>>(
result_function, std::move(std::future<void>()));
this->SleepClientThread("RunAsync", std::chrono::nanoseconds(sleep_for),
parallel_wakeup);
} else {
result_function(*this);
}
}
}
/**
* Resolves a object id from the request command buffer into a pointer to an object. See the
* "HLE handle protocol" section in the class documentation for more details.
*/
std::shared_ptr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const;
/**
* Adds an outgoing object to the response, returning the id which should be used to reference
* it. See the "HLE handle protocol" section in the class documentation for more details.
*/
u32 AddOutgoingHandle(std::shared_ptr<Object> object);
/**
* Discards all Objects from the context, invalidating all ids. This may be called after reading
* out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but
* this is not required.
*/
void ClearIncomingObjects();
/**
* Retrieves the static buffer identified by the input buffer_id. The static buffer *must* have
* been created in PopulateFromIncomingCommandBuffer by way of an input StaticBuffer descriptor.
*/
const std::vector<u8>& GetStaticBuffer(u8 buffer_id) const;
/**
* Sets up a static buffer that will be copied to the target process when the request is
* translated.
*/
void AddStaticBuffer(u8 buffer_id, std::vector<u8> data);
/**
* Gets a memory interface by the id from the request command buffer. See the "HLE mapped buffer
* protocol" section in the class documentation for more details.
*/
MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf);
/// Populates this context with data from the requesting process/thread.
Result PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
std::shared_ptr<Process> src_process);
/// Writes data from this context back to the requesting process/thread.
Result WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
/// Reports an unimplemented function.
void ReportUnimplemented() const;
class ThreadCallback;
friend class ThreadCallback;
private:
KernelSystem& kernel;
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
std::shared_ptr<ServerSession> session;
std::shared_ptr<Thread> thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<std::shared_ptr<Object>, 8> request_handles;
// The static buffers will be created when the IPC request is translated.
std::array<std::vector<u8>, IPC::MAX_STATIC_BUFFERS> static_buffers;
// The mapped buffers will be created when the IPC request is translated
boost::container::small_vector<MappedBuffer, 8> request_mapped_buffers;
HLERequestContext();
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::SessionRequestHandler)
BOOST_CLASS_EXPORT_KEY(Kernel::SessionRequestHandler::SessionDataBase)
BOOST_CLASS_EXPORT_KEY(Kernel::SessionRequestHandler::SessionInfo)
BOOST_CLASS_EXPORT_KEY(Kernel::HLERequestContext)
BOOST_CLASS_EXPORT_KEY(Kernel::HLERequestContext::ThreadCallback)

268
src/core/hle/kernel/ipc.cpp Normal file
View File

@@ -0,0 +1,268 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <boost/serialization/shared_ptr.hpp>
#include "common/alignment.h"
#include "common/archives.h"
#include "common/memory_ref.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/ipc.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::MappedBufferContext)
namespace Kernel {
Result TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
std::shared_ptr<Thread> src_thread,
std::shared_ptr<Thread> dst_thread, VAddr src_address,
VAddr dst_address,
std::vector<MappedBufferContext>& mapped_buffer_context, bool reply) {
auto src_process = src_thread->owner_process.lock();
auto dst_process = dst_thread->owner_process.lock();
ASSERT(src_process && dst_process);
IPC::Header header;
// TODO(Subv): Replace by Memory::Read32 when possible.
memory.ReadBlock(*src_process, src_address, &header.raw, sizeof(header.raw));
std::size_t untranslated_size = 1u + header.normal_params_size;
std::size_t command_size = untranslated_size + header.translate_params_size;
// Note: The real kernel does not check that the command length fits into the IPC buffer area.
ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH);
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
memory.ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32));
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
std::vector<u32> untranslated_cmdbuf;
if (should_record) {
untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
}
std::size_t i = untranslated_size;
while (i < command_size) {
u32 descriptor = cmd_buf[i];
i += 1;
switch (IPC::GetDescriptorType(descriptor)) {
case IPC::DescriptorType::CopyHandle:
case IPC::DescriptorType::MoveHandle: {
u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
// Note: The real kernel does not check that the number of handles fits into the command
// buffer before writing them, only after finishing.
if (i + num_handles > command_size) {
return Result(ErrCodes::CommandTooLarge, ErrorModule::OS,
ErrorSummary::InvalidState, ErrorLevel::Status);
}
for (u32 j = 0; j < num_handles; ++j) {
Handle handle = cmd_buf[i];
std::shared_ptr<Object> object = nullptr;
// Perform pseudo-handle detection here because by the time this function is called,
// the current thread and process are no longer the ones which created this IPC
// request, but the ones that are handling it.
if (handle == CurrentThread) {
object = src_thread;
} else if (handle == CurrentProcess) {
object = src_process;
} else if (handle != 0) {
object = src_process->handle_table.GetGeneric(handle);
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_process->handle_table.Close(handle);
}
}
if (object == nullptr) {
// Note: The real kernel sets invalid translated handles to 0 in the target
// command buffer.
cmd_buf[i++] = 0;
continue;
}
R_ASSERT(dst_process->handle_table.Create(std::addressof(cmd_buf[i++]),
std::move(object)));
}
break;
}
case IPC::DescriptorType::CallingPid: {
cmd_buf[i++] = src_process->process_id;
break;
}
case IPC::DescriptorType::StaticBuffer: {
IPC::StaticBufferDescInfo bufferInfo{descriptor};
VAddr static_buffer_src_address = cmd_buf[i];
std::vector<u8> data(bufferInfo.size);
memory.ReadBlock(*src_process, static_buffer_src_address, data.data(), data.size());
// Grab the address that the target thread set up to receive the response static buffer
// and write our data there. The static buffers area is located right after the command
// buffer area.
struct StaticBuffer {
IPC::StaticBufferDescInfo descriptor;
VAddr address;
};
static_assert(sizeof(StaticBuffer) == 8, "StaticBuffer struct has incorrect size.");
StaticBuffer target_buffer;
u32 static_buffer_offset = IPC::COMMAND_BUFFER_LENGTH * sizeof(u32) +
sizeof(StaticBuffer) * bufferInfo.buffer_id;
memory.ReadBlock(*dst_process, dst_address + static_buffer_offset, &target_buffer,
sizeof(target_buffer));
// Note: The real kernel doesn't seem to have any error recovery mechanisms for this
// case.
ASSERT_MSG(target_buffer.descriptor.size >= data.size(),
"Static buffer data is too big");
memory.WriteBlock(*dst_process, target_buffer.address, data.data(), data.size());
cmd_buf[i++] = target_buffer.address;
break;
}
case IPC::DescriptorType::MappedBuffer: {
IPC::MappedBufferDescInfo descInfo{descriptor};
VAddr source_address = cmd_buf[i];
u32 size = static_cast<u32>(descInfo.size);
IPC::MappedBufferPermissions permissions = descInfo.perms;
VAddr page_start = Common::AlignDown(source_address, Memory::CITRA_PAGE_SIZE);
u32 page_offset = source_address - page_start;
u32 num_pages = Common::AlignUp(page_offset + size, Memory::CITRA_PAGE_SIZE) >>
Memory::CITRA_PAGE_BITS;
// Skip when the size is zero and num_pages == 0
if (size == 0) {
cmd_buf[i++] = 0;
break;
}
ASSERT(num_pages >= 1);
if (reply) {
// Scan the target's command buffer for the matching mapped buffer.
// The real kernel panics if you try to reply with an unsolicited MappedBuffer.
auto found = std::find_if(
mapped_buffer_context.begin(), mapped_buffer_context.end(),
[permissions, size, source_address](const MappedBufferContext& context) {
// Note: reply's source_address is request's target_address
return context.permissions == permissions && context.size == size &&
context.target_address == source_address;
});
ASSERT(found != mapped_buffer_context.end());
if (permissions != IPC::MappedBufferPermissions::R) {
// Copy the modified buffer back into the target process
// NOTE: As this is a reply the "source" is the destination and the
// "target" is the source.
memory.CopyBlock(*dst_process, *src_process, found->source_address,
found->target_address, size);
}
VAddr prev_reserve = page_start - Memory::CITRA_PAGE_SIZE;
VAddr next_reserve = page_start + num_pages * Memory::CITRA_PAGE_SIZE;
auto& prev_vma = src_process->vm_manager.FindVMA(prev_reserve)->second;
auto& next_vma = src_process->vm_manager.FindVMA(next_reserve)->second;
ASSERT(prev_vma.meminfo_state == MemoryState::Reserved &&
next_vma.meminfo_state == MemoryState::Reserved);
// Unmap the buffer and guard pages from the source process
Result result =
src_process->vm_manager.UnmapRange(page_start - Memory::CITRA_PAGE_SIZE,
(num_pages + 2) * Memory::CITRA_PAGE_SIZE);
ASSERT(result == ResultSuccess);
mapped_buffer_context.erase(found);
i += 1;
break;
}
VAddr target_address = 0;
// TODO(Subv): Perform permission checks.
// Create a buffer which contains the mapped buffer and two additional guard pages.
std::shared_ptr<BackingMem> buffer =
std::make_shared<BufferMem>((num_pages + 2) * Memory::CITRA_PAGE_SIZE);
memory.ReadBlock(*src_process, source_address,
buffer->GetPtr() + Memory::CITRA_PAGE_SIZE + page_offset, size);
// Map the guard pages and mapped pages at once.
target_address =
dst_process->vm_manager
.MapBackingMemoryToBase(Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE,
buffer, static_cast<u32>(buffer->GetSize()),
Kernel::MemoryState::Shared)
.Unwrap();
// Change the permissions and state of the guard pages.
const VAddr low_guard_address = target_address;
const VAddr high_guard_address =
low_guard_address + static_cast<VAddr>(buffer->GetSize()) - Memory::CITRA_PAGE_SIZE;
ASSERT(dst_process->vm_manager.ChangeMemoryState(
low_guard_address, Memory::CITRA_PAGE_SIZE, Kernel::MemoryState::Shared,
Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Reserved,
Kernel::VMAPermission::None) == ResultSuccess);
ASSERT(dst_process->vm_manager.ChangeMemoryState(
high_guard_address, Memory::CITRA_PAGE_SIZE, Kernel::MemoryState::Shared,
Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Reserved,
Kernel::VMAPermission::None) == ResultSuccess);
// Get proper mapped buffer address and store it in the cmd buffer.
target_address += Memory::CITRA_PAGE_SIZE;
cmd_buf[i++] = target_address + page_offset;
mapped_buffer_context.push_back({permissions, size, source_address,
target_address + page_offset, std::move(buffer)});
break;
}
default:
UNIMPLEMENTED_MSG("Unsupported handle translation: {:#010X}", descriptor);
}
}
if (should_record) {
std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
if (reply) {
kernel.GetIPCRecorder().SetReplyInfo(dst_thread, std::move(untranslated_cmdbuf),
std::move(translated_cmdbuf));
} else {
kernel.GetIPCRecorder().SetRequestInfo(src_thread, std::move(untranslated_cmdbuf),
std::move(translated_cmdbuf), dst_thread);
}
}
memory.WriteBlock(*dst_process, dst_address, cmd_buf.data(), command_size * sizeof(u32));
return ResultSuccess;
}
template <class Archive>
void MappedBufferContext::serialize(Archive& ar, const unsigned int) {
ar& permissions;
ar& size;
ar& source_address;
ar& target_address;
ar& buffer;
}
SERIALIZE_IMPL(MappedBufferContext)
} // namespace Kernel

44
src/core/hle/kernel/ipc.h Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/thread.h"
namespace Memory {
class MemorySystem;
}
namespace Kernel {
class KernelSystem;
struct MappedBufferContext {
IPC::MappedBufferPermissions permissions;
u32 size;
VAddr source_address;
VAddr target_address;
std::shared_ptr<BackingMem> buffer;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
/// Performs IPC command buffer translation from one process to another.
Result TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory,
std::shared_ptr<Thread> src_thread,
std::shared_ptr<Thread> dst_thread, VAddr src_address,
VAddr dst_address,
std::vector<MappedBufferContext>& mapped_buffer_context, bool reply);
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::MappedBufferContext)

View File

@@ -0,0 +1,169 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/service.h"
namespace IPCDebugger {
namespace {
ObjectInfo GetObjectInfo(const Kernel::Object* object) {
if (object == nullptr) {
return {};
}
return {object->GetTypeName(), object->GetName(), static_cast<int>(object->GetObjectId())};
}
ObjectInfo GetObjectInfo(const Kernel::Thread* thread) {
if (thread == nullptr) {
return {};
}
return {thread->GetTypeName(), thread->GetName(), static_cast<int>(thread->GetThreadId())};
}
ObjectInfo GetObjectInfo(const Kernel::Process* process) {
if (process == nullptr) {
return {};
}
return {process->GetTypeName(), process->GetName(), static_cast<int>(process->process_id)};
}
} // namespace
Recorder::Recorder() = default;
Recorder::~Recorder() = default;
bool Recorder::IsEnabled() const {
return enabled.load(std::memory_order_relaxed);
}
void Recorder::RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
const std::shared_ptr<Kernel::Thread>& client_thread) {
const u32 thread_id = client_thread->GetThreadId();
if (auto owner_process = client_thread->owner_process.lock()) {
RequestRecord record = {/* id */ ++record_count,
/* status */ RequestStatus::Sent,
/* client_process */ GetObjectInfo(owner_process.get()),
/* client_thread */ GetObjectInfo(client_thread.get()),
/* client_session */ GetObjectInfo(client_session.get()),
/* client_port */ GetObjectInfo(client_session->parent->port.get()),
/* server_process */ {},
/* server_thread */ {},
/* server_session */ GetObjectInfo(client_session->parent->server)};
record_map.insert_or_assign(thread_id, std::make_unique<RequestRecord>(record));
client_session_map.insert_or_assign(thread_id, client_session);
InvokeCallbacks(record);
}
}
void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf,
const std::shared_ptr<Kernel::Thread>& server_thread) {
const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread");
return;
}
auto& record = *record_map[thread_id];
record.status = RequestStatus::Handling;
record.untranslated_request_cmdbuf = std::move(untranslated_cmdbuf);
record.translated_request_cmdbuf = std::move(translated_cmdbuf);
if (server_thread) {
if (auto owner_process = server_thread->owner_process.lock()) {
record.server_process = GetObjectInfo(owner_process.get());
}
record.server_thread = GetObjectInfo(server_thread.get());
} else {
record.is_hle = true;
}
// Function name
ASSERT_MSG(client_session_map.count(thread_id), "Client session is missing");
const auto& client_session = client_session_map[thread_id];
if (client_session->parent->port &&
client_session->parent->port->GetServerPort()->hle_handler) {
record.function_name = std::dynamic_pointer_cast<Service::ServiceFrameworkBase>(
client_session->parent->port->GetServerPort()->hle_handler)
->GetFunctionName({record.untranslated_request_cmdbuf[0]});
}
client_session_map.erase(thread_id);
InvokeCallbacks(record);
}
void Recorder::SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf) {
const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread");
return;
}
auto& record = *record_map[thread_id];
if (record.status != RequestStatus::HLEUnimplemented) {
record.status = RequestStatus::Handled;
}
record.untranslated_reply_cmdbuf = std::move(untranslated_cmdbuf);
record.translated_reply_cmdbuf = std::move(translated_cmdbuf);
InvokeCallbacks(record);
record_map.erase(thread_id);
}
void Recorder::SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread) {
const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started
LOG_ERROR(Kernel, "No request is associated with the thread");
return;
}
auto& record = *record_map[thread_id];
record.status = RequestStatus::HLEUnimplemented;
}
CallbackHandle Recorder::BindCallback(CallbackType callback) {
std::unique_lock lock(callback_mutex);
CallbackHandle handle = std::make_shared<CallbackType>(callback);
callbacks.emplace(handle);
return handle;
}
void Recorder::UnbindCallback(const CallbackHandle& handle) {
std::unique_lock lock(callback_mutex);
callbacks.erase(handle);
}
void Recorder::InvokeCallbacks(const RequestRecord& request) {
{
std::shared_lock lock(callback_mutex);
for (const auto& iter : callbacks) {
(*iter)(request);
}
}
}
void Recorder::SetEnabled(bool enabled_) {
enabled.store(enabled_, std::memory_order_relaxed);
}
} // namespace IPCDebugger

View File

@@ -0,0 +1,129 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <set>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
namespace Kernel {
class ClientSession;
class Thread;
} // namespace Kernel
namespace IPCDebugger {
/**
* Record of a kernel object, for debugging purposes.
*/
struct ObjectInfo {
std::string type;
std::string name;
int id = -1;
};
/**
* Status of a request.
*/
enum class RequestStatus {
Invalid, ///< Invalid status
Sent, ///< The request is sent to the kernel and is waiting to be handled
Handling, ///< The request is being handled
Handled, ///< The request is handled with reply sent
HLEUnimplemented, ///< The request is unimplemented by HLE, and unhandled
};
/**
* Record of an IPC request.
*/
struct RequestRecord {
int id;
RequestStatus status = RequestStatus::Invalid;
ObjectInfo client_process;
ObjectInfo client_thread;
ObjectInfo client_session;
ObjectInfo client_port; // Not available for portless
ObjectInfo server_process; // Only available for LLE requests
ObjectInfo server_thread; // Only available for LLE requests
ObjectInfo server_session;
std::string function_name; // Not available for LLE or portless
bool is_hle = false;
// Request info is only available when status is not `Invalid` or `Sent`
std::vector<u32> untranslated_request_cmdbuf;
std::vector<u32> translated_request_cmdbuf;
// Reply info is only available when status is `Handled`
std::vector<u32> untranslated_reply_cmdbuf;
std::vector<u32> translated_reply_cmdbuf;
};
using CallbackType = std::function<void(const RequestRecord&)>;
using CallbackHandle = std::shared_ptr<CallbackType>;
class Recorder {
public:
explicit Recorder();
~Recorder();
/**
* Returns whether the recorder is enabled.
*/
bool IsEnabled() const;
/**
* Registers a request into the recorder. The request is then assoicated with the client thread.
*/
void RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
const std::shared_ptr<Kernel::Thread>& client_thread);
/**
* Sets the request information of the request record associated with the client thread.
* When the server thread is empty, the request will be considered HLE.
*/
void SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf,
const std::shared_ptr<Kernel::Thread>& server_thread = {});
/**
* Sets the reply information of the request record assoicated with the client thread.
* The request is then unlinked from the client thread.
*/
void SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf);
/**
* Set the status of a record to HLEUnimplemented.
*/
void SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread);
/**
* Set the status of the debugger (enabled/disabled).
*/
void SetEnabled(bool enabled);
CallbackHandle BindCallback(CallbackType callback);
void UnbindCallback(const CallbackHandle& handle);
private:
void InvokeCallbacks(const RequestRecord& request);
std::unordered_map<u32, std::unique_ptr<RequestRecord>> record_map;
int record_count{};
// Temporary client session map for function name handling
std::unordered_map<u32, std::shared_ptr<Kernel::ClientSession>> client_session_map;
std::atomic_bool enabled{false};
std::set<CallbackHandle> callbacks;
mutable std::shared_mutex callback_mutex;
};
} // namespace IPCDebugger

View File

@@ -0,0 +1,210 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/serialization/atomic.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/config_mem.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
SERIALIZE_EXPORT_IMPL(Kernel::New3dsHwCapabilities)
namespace Kernel {
/// Initialize the kernel
KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback,
MemoryMode memory_mode, u32 num_cores,
const New3dsHwCapabilities& n3ds_hw_caps, u64 override_init_time)
: memory(memory), timing(timing),
prepare_reschedule_callback(std::move(prepare_reschedule_callback)), memory_mode(memory_mode),
n3ds_hw_caps(n3ds_hw_caps) {
std::generate(memory_regions.begin(), memory_regions.end(),
[] { return std::make_shared<MemoryRegionInfo>(); });
MemoryInit(memory_mode, n3ds_hw_caps.memory_mode, override_init_time);
resource_limits = std::make_unique<ResourceLimitList>(*this);
for (u32 core_id = 0; core_id < num_cores; ++core_id) {
thread_managers.push_back(std::make_unique<ThreadManager>(*this, core_id));
}
timer_manager = std::make_unique<TimerManager>(timing);
ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
stored_processes.assign(num_cores, nullptr);
next_thread_id = 1;
}
/// Shutdown the kernel
KernelSystem::~KernelSystem() {
ResetThreadIDs();
};
ResourceLimitList& KernelSystem::ResourceLimit() {
return *resource_limits;
}
const ResourceLimitList& KernelSystem::ResourceLimit() const {
return *resource_limits;
}
u32 KernelSystem::GenerateObjectID() {
return next_object_id++;
}
std::shared_ptr<Process> KernelSystem::GetCurrentProcess() const {
return current_process;
}
void KernelSystem::SetCurrentProcess(std::shared_ptr<Process> process) {
current_process = process;
SetCurrentMemoryPageTable(process->vm_manager.page_table);
}
void KernelSystem::SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id) {
if (current_cpu->GetID() == core_id) {
current_process = process;
SetCurrentMemoryPageTable(process->vm_manager.page_table);
} else {
stored_processes[core_id] = process;
thread_managers[core_id]->cpu->SetPageTable(process->vm_manager.page_table);
}
}
void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table) {
memory.SetCurrentPageTable(page_table);
if (current_cpu != nullptr) {
current_cpu->SetPageTable(page_table);
}
}
void KernelSystem::SetCPUs(std::vector<std::shared_ptr<Core::ARM_Interface>> cpus) {
ASSERT(cpus.size() == thread_managers.size());
for (u32 i = 0; i < cpus.size(); i++) {
thread_managers[i]->SetCPU(*cpus[i]);
}
}
void KernelSystem::SetRunningCPU(Core::ARM_Interface* cpu) {
if (current_process) {
stored_processes[current_cpu->GetID()] = current_process;
}
current_cpu = cpu;
timing.SetCurrentTimer(cpu->GetID());
if (stored_processes[current_cpu->GetID()]) {
SetCurrentProcess(stored_processes[current_cpu->GetID()]);
}
}
ThreadManager& KernelSystem::GetThreadManager(u32 core_id) {
return *thread_managers[core_id];
}
const ThreadManager& KernelSystem::GetThreadManager(u32 core_id) const {
return *thread_managers[core_id];
}
ThreadManager& KernelSystem::GetCurrentThreadManager() {
return *thread_managers[current_cpu->GetID()];
}
const ThreadManager& KernelSystem::GetCurrentThreadManager() const {
return *thread_managers[current_cpu->GetID()];
}
TimerManager& KernelSystem::GetTimerManager() {
return *timer_manager;
}
const TimerManager& KernelSystem::GetTimerManager() const {
return *timer_manager;
}
SharedPage::Handler& KernelSystem::GetSharedPageHandler() {
return *shared_page_handler;
}
const SharedPage::Handler& KernelSystem::GetSharedPageHandler() const {
return *shared_page_handler;
}
ConfigMem::Handler& KernelSystem::GetConfigMemHandler() {
return *config_mem_handler;
}
IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() {
return *ipc_recorder;
}
const IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() const {
return *ipc_recorder;
}
void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
named_ports.emplace(std::move(name), std::move(port));
}
u32 KernelSystem::NewThreadId() {
return next_thread_id++;
}
void KernelSystem::ResetThreadIDs() {
next_thread_id = 0;
}
template <class Archive>
void KernelSystem::serialize(Archive& ar, const unsigned int) {
ar& memory_regions;
ar& named_ports;
// current_cpu set externally
// NB: subsystem references and prepare_reschedule_callback are constant
ar&* resource_limits.get();
ar& next_object_id;
ar&* timer_manager.get();
ar& next_process_id;
ar& process_list;
ar& current_process;
// NB: core count checked in 'core'
for (auto& thread_manager : thread_managers) {
ar&* thread_manager.get();
}
ar& config_mem_handler;
ar& shared_page_handler;
ar& stored_processes;
ar& next_thread_id;
ar& memory_mode;
ar& n3ds_hw_caps;
ar& main_thread_extended_sleep;
// Deliberately don't include debugger info to allow debugging through loads
if (Archive::is_loading::value) {
for (auto& memory_region : memory_regions) {
memory_region->Unlock();
}
for (auto& process : process_list) {
process->vm_manager.Unlock();
}
}
}
SERIALIZE_IMPL(KernelSystem)
template <class Archive>
void New3dsHwCapabilities::serialize(Archive& ar, const unsigned int) {
ar& enable_l2_cache;
ar& enable_804MHz_cpu;
ar& memory_mode;
}
SERIALIZE_IMPL(New3dsHwCapabilities)
} // namespace Kernel

View File

@@ -0,0 +1,412 @@
// Copyright 2014 Citra Emulator Project / PPSSPP Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/result.h"
#include "core/memory.h"
namespace ConfigMem {
class Handler;
}
namespace SharedPage {
class Handler;
}
namespace Memory {
class MemorySystem;
}
namespace Core {
class ARM_Interface;
class Timing;
} // namespace Core
namespace IPCDebugger {
class Recorder;
}
namespace Kernel {
class AddressArbiter;
class Event;
class Mutex;
class CodeSet;
class Process;
class Thread;
class Semaphore;
class Timer;
class ClientPort;
class ServerPort;
class ClientSession;
class ServerSession;
class ResourceLimitList;
class SharedMemory;
class ThreadManager;
class TimerManager;
class VMManager;
struct AddressMapping;
enum class ResetType {
OneShot,
Sticky,
Pulse,
};
/// Permissions for mapped shared memory blocks
enum class MemoryPermission : u32 {
None = 0,
Read = (1u << 0),
Write = (1u << 1),
ReadWrite = (Read | Write),
Execute = (1u << 2),
ReadExecute = (Read | Execute),
WriteExecute = (Write | Execute),
ReadWriteExecute = (Read | Write | Execute),
DontCare = (1u << 28)
};
enum class MemoryRegion : u16 {
APPLICATION = 1,
SYSTEM = 2,
BASE = 3,
};
union CoreVersion {
CoreVersion(u32 version) : raw(version) {}
CoreVersion(u32 major_ver, u32 minor_ver, u32 revision_ver) {
revision.Assign(revision_ver);
minor.Assign(minor_ver);
major.Assign(major_ver);
}
u32 raw = 0;
BitField<8, 8, u32> revision;
BitField<16, 8, u32> minor;
BitField<24, 8, u32> major;
};
/// Common memory memory modes.
enum class MemoryMode : u8 {
Prod = 0, ///< 64MB app memory
Dev1 = 2, ///< 96MB app memory
Dev2 = 3, ///< 80MB app memory
Dev3 = 4, ///< 72MB app memory
Dev4 = 5, ///< 32MB app memory
};
/// New 3DS memory modes.
enum class New3dsMemoryMode : u8 {
Legacy = 0, ///< Use Old 3DS system mode.
NewProd = 1, ///< 124MB app memory
NewDev1 = 2, ///< 178MB app memory
NewDev2 = 3, ///< 124MB app memory
};
/// Structure containing N3DS hardware capability flags.
struct New3dsHwCapabilities {
bool enable_l2_cache; ///< Whether extra L2 cache should be enabled.
bool enable_804MHz_cpu; ///< Whether the CPU should run at 804MHz.
New3dsMemoryMode memory_mode; ///< The New 3DS memory mode.
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
class KernelSystem {
public:
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback, MemoryMode memory_mode,
u32 num_cores, const New3dsHwCapabilities& n3ds_hw_caps,
u64 override_init_time = 0);
~KernelSystem();
using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>;
using SessionPair = std::pair<std::shared_ptr<ServerSession>, std::shared_ptr<ClientSession>>;
/**
* Creates an address arbiter.
*
* @param name Optional name used for debugging.
* @returns The created AddressArbiter.
*/
std::shared_ptr<AddressArbiter> CreateAddressArbiter(std::string name = "Unknown");
/**
* Creates an event
* @param reset_type ResetType describing how to create event
* @param name Optional name of event
*/
std::shared_ptr<Event> CreateEvent(ResetType reset_type, std::string name = "Unknown");
/**
* Creates a mutex.
* @param initial_locked Specifies if the mutex should be locked initially
* @param name Optional name of mutex
* @return Pointer to new Mutex object
*/
std::shared_ptr<Mutex> CreateMutex(bool initial_locked, std::string name = "Unknown");
std::shared_ptr<CodeSet> CreateCodeSet(std::string name, u64 program_id);
std::shared_ptr<Process> CreateProcess(std::shared_ptr<CodeSet> code_set);
/**
* Terminates a process, killing its threads and removing it from the process list.
* @param process Process to terminate.
*/
void TerminateProcess(std::shared_ptr<Process> process);
/**
* Creates and returns a new thread. The new thread is immediately scheduled
* @param name The friendly name desired for the thread
* @param entry_point The address at which the thread should start execution
* @param priority The thread's priority
* @param arg User data to pass to the thread
* @param processor_id The ID(s) of the processors on which the thread is desired to be run
* @param stack_top The address of the thread's stack top
* @param owner_process The parent process for the thread
* @param make_ready If the thread should be put in the ready queue
* @return A shared pointer to the newly created thread
*/
ResultVal<std::shared_ptr<Thread>> CreateThread(std::string name, VAddr entry_point,
u32 priority, u32 arg, s32 processor_id,
VAddr stack_top,
std::shared_ptr<Process> owner_process,
bool make_ready = true);
/**
* Creates a semaphore.
* @param initial_count Number of slots reserved for other threads
* @param max_count Maximum number of slots the semaphore can have
* @param name Optional name of semaphore
* @return The created semaphore
*/
ResultVal<std::shared_ptr<Semaphore>> CreateSemaphore(s32 initial_count, s32 max_count,
std::string name = "Unknown");
/**
* Creates a timer
* @param reset_type ResetType describing how to create the timer
* @param name Optional name of timer
* @return The created Timer
*/
std::shared_ptr<Timer> CreateTimer(ResetType reset_type, std::string name = "Unknown");
/**
* Creates a pair of ServerPort and an associated ClientPort.
*
* @param max_sessions Maximum number of sessions to the port
* @param name Optional name of the ports
* @return The created port tuple
*/
PortPair CreatePortPair(u32 max_sessions, std::string name = "UnknownPort");
/**
* Creates a pair of ServerSession and an associated ClientSession.
* @param name Optional name of the ports.
* @param client_port Optional The ClientPort that spawned this session.
* @return The created session tuple
*/
SessionPair CreateSessionPair(const std::string& name = "Unknown",
std::shared_ptr<ClientPort> client_port = nullptr);
ResourceLimitList& ResourceLimit();
const ResourceLimitList& ResourceLimit() const;
/**
* Creates a shared memory object.
* @param owner_process Process that created this shared memory object.
* @param size Size of the memory block. Must be page-aligned.
* @param permissions Permission restrictions applied to the process which created the block.
* @param other_permissions Permission restrictions applied to other processes mapping the
* block.
* @param address The address from which to map the Shared Memory.
* @param region If the address is 0, the shared memory will be allocated in this region of the
* linear heap.
* @param name Optional object name, used for debugging purposes.
*/
ResultVal<std::shared_ptr<SharedMemory>> CreateSharedMemory(
std::shared_ptr<Process> owner_process, u32 size, MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address = 0,
MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown");
/**
* Creates a shared memory object from a block of memory managed by an HLE applet.
* @param offset The offset into the heap block that the SharedMemory will map.
* @param size Size of the memory block. Must be page-aligned.
* @param permissions Permission restrictions applied to the process which created the block.
* @param other_permissions Permission restrictions applied to other processes mapping the
* block.
* @param name Optional object name, used for debugging purposes.
*/
std::shared_ptr<SharedMemory> CreateSharedMemoryForApplet(u32 offset, u32 size,
MemoryPermission permissions,
MemoryPermission other_permissions,
std::string name = "Unknown Applet");
u32 GenerateObjectID();
/// Retrieves a process from the current list of processes.
std::shared_ptr<Process> GetProcessById(u32 process_id) const;
std::span<const std::shared_ptr<Process>> GetProcessList() const {
return process_list;
}
std::shared_ptr<Process> GetCurrentProcess() const;
void SetCurrentProcess(std::shared_ptr<Process> process);
void SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id);
void SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table);
void SetCPUs(std::vector<std::shared_ptr<Core::ARM_Interface>> cpu);
void SetRunningCPU(Core::ARM_Interface* cpu);
ThreadManager& GetThreadManager(u32 core_id);
const ThreadManager& GetThreadManager(u32 core_id) const;
ThreadManager& GetCurrentThreadManager();
const ThreadManager& GetCurrentThreadManager() const;
TimerManager& GetTimerManager();
const TimerManager& GetTimerManager() const;
void MapSharedPages(VMManager& address_space);
SharedPage::Handler& GetSharedPageHandler();
const SharedPage::Handler& GetSharedPageHandler() const;
ConfigMem::Handler& GetConfigMemHandler();
IPCDebugger::Recorder& GetIPCRecorder();
const IPCDebugger::Recorder& GetIPCRecorder() const;
std::shared_ptr<MemoryRegionInfo> GetMemoryRegion(MemoryRegion region);
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
std::array<std::shared_ptr<MemoryRegionInfo>, 3> memory_regions{};
/// Adds a port to the named port table
void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
void PrepareReschedule() {
prepare_reschedule_callback();
}
u32 NewThreadId();
void ResetThreadIDs();
MemoryMode GetMemoryMode() const {
return memory_mode;
}
const New3dsHwCapabilities& GetNew3dsHwCapabilities() const {
return n3ds_hw_caps;
}
std::recursive_mutex& GetHLELock() {
return hle_lock;
}
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort
std::unordered_map<std::string, std::shared_ptr<ClientPort>> named_ports;
Core::ARM_Interface* current_cpu = nullptr;
Memory::MemorySystem& memory;
Core::Timing& timing;
/// Sleep main thread of the first ever launched non-sysmodule process.
void SetAppMainThreadExtendedSleep(bool requires_sleep) {
main_thread_extended_sleep = requires_sleep;
}
bool GetAppMainThreadExtendedSleep() const {
return main_thread_extended_sleep;
}
private:
void MemoryInit(MemoryMode memory_mode, New3dsMemoryMode n3ds_mode, u64 override_init_time);
std::function<void()> prepare_reschedule_callback;
std::unique_ptr<ResourceLimitList> resource_limits;
std::atomic<u32> next_object_id{0};
// Note: keep the member order below in order to perform correct destruction.
// Thread manager is destructed before process list in order to Stop threads and clear thread
// info from their parent processes first. Timer manager is destructed after process list
// because timers are destructed along with process list and they need to clear info from the
// timer manager.
// TODO (wwylele): refactor the cleanup sequence to make this less complicated and sensitive.
std::unique_ptr<TimerManager> timer_manager;
// TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
// reserved for low-level services
u32 next_process_id = 10;
// Lists all processes that exist in the current session.
std::vector<std::shared_ptr<Process>> process_list;
std::shared_ptr<Process> current_process;
std::vector<std::shared_ptr<Process>> stored_processes;
std::vector<std::unique_ptr<ThreadManager>> thread_managers;
std::shared_ptr<ConfigMem::Handler> config_mem_handler;
std::shared_ptr<SharedPage::Handler> shared_page_handler;
std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
u32 next_thread_id;
MemoryMode memory_mode;
New3dsHwCapabilities n3ds_hw_caps;
/*
* Synchronizes access to the internal HLE kernel structures, it is acquired when a guest
* application thread performs a syscall. It should be acquired by any host threads that read or
* modify the HLE kernel state. Note: Any operation that directly or indirectly reads from or
* writes to the emulated memory is not protected by this mutex, and should be avoided in any
* threads other than the CPU thread.
*/
std::recursive_mutex hle_lock;
/*
* Flags non system module main threads to wait a bit before running. On real hardware,
* system modules have plenty of time to load before the game is loaded, but on citra they
* start at the same time as the game. The artificial wait gives system modules some time
* to load and setup themselves before the game starts.
*/
bool main_thread_extended_sleep = false;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::New3dsHwCapabilities)

View File

@@ -0,0 +1,286 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <boost/serialization/set.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/kernel/config_mem.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::MemoryRegionInfo)
namespace Kernel {
/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system
/// memory configuration type.
static const u32 memory_region_sizes[8][3] = {
// Old 3DS layouts
{0x04000000, 0x02C00000, 0x01400000}, // 0
{/* This appears to be unused. */}, // 1
{0x06000000, 0x00C00000, 0x01400000}, // 2
{0x05000000, 0x01C00000, 0x01400000}, // 3
{0x04800000, 0x02400000, 0x01400000}, // 4
{0x02000000, 0x04C00000, 0x01400000}, // 5
// New 3DS layouts
{0x07C00000, 0x06400000, 0x02000000}, // 6
{0x0B200000, 0x02E00000, 0x02000000}, // 7
};
void KernelSystem::MemoryInit(MemoryMode memory_mode, New3dsMemoryMode n3ds_mode,
u64 override_init_time) {
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
u32 mem_type_index = static_cast<u32>(memory_mode);
u32 reported_mem_type = static_cast<u32>(memory_mode);
if (is_new_3ds) {
if (n3ds_mode == New3dsMemoryMode::NewProd || n3ds_mode == New3dsMemoryMode::NewDev2) {
mem_type_index = 6;
reported_mem_type = 6;
} else if (n3ds_mode == New3dsMemoryMode::NewDev1) {
mem_type_index = 7;
reported_mem_type = 7;
} else {
// On the N3ds, all O3ds configurations (<=5) are forced to 6 instead.
mem_type_index = 6;
}
}
// The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
// the sizes specified in the memory_region_sizes table.
VAddr base = 0;
for (int i = 0; i < 3; ++i) {
memory_regions[i]->Reset(base, memory_region_sizes[mem_type_index][i]);
base += memory_regions[i]->size;
}
// We must've allocated the entire FCRAM by the end
ASSERT(base == (is_new_3ds ? Memory::FCRAM_N3DS_SIZE : Memory::FCRAM_SIZE));
config_mem_handler = std::make_shared<ConfigMem::Handler>();
auto& config_mem = config_mem_handler->GetConfigMem();
config_mem.app_mem_type = reported_mem_type;
config_mem.app_mem_alloc = memory_region_sizes[reported_mem_type][0];
config_mem.sys_mem_alloc = memory_regions[1]->size;
config_mem.base_mem_alloc = memory_regions[2]->size;
shared_page_handler = std::make_shared<SharedPage::Handler>(timing, override_init_time);
}
std::shared_ptr<MemoryRegionInfo> KernelSystem::GetMemoryRegion(MemoryRegion region) {
switch (region) {
case MemoryRegion::APPLICATION:
return memory_regions[0];
case MemoryRegion::SYSTEM:
return memory_regions[1];
case MemoryRegion::BASE:
return memory_regions[2];
default:
UNREACHABLE();
}
}
void KernelSystem::HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) {
using namespace Memory;
struct MemoryArea {
VAddr vaddr_base;
PAddr paddr_base;
u32 size;
};
// The order of entries in this array is important. The VRAM and IO VAddr ranges overlap, and
// VRAM must be tried first.
static constexpr MemoryArea memory_areas[] = {
{VRAM_VADDR, VRAM_PADDR, VRAM_SIZE},
{IO_AREA_VADDR, IO_AREA_PADDR, IO_AREA_SIZE},
{DSP_RAM_VADDR, DSP_RAM_PADDR, DSP_RAM_SIZE},
{N3DS_EXTRA_RAM_VADDR, N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE - 0x20000},
};
VAddr mapping_limit = mapping.address + mapping.size;
if (mapping_limit < mapping.address) {
LOG_CRITICAL(Loader, "Mapping size overflowed: address=0x{:08X} size=0x{:X}",
mapping.address, mapping.size);
return;
}
auto area =
std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) {
return mapping.address >= area.vaddr_base &&
mapping_limit <= area.vaddr_base + area.size;
});
if (area == std::end(memory_areas)) {
LOG_ERROR(Loader,
"Unhandled special mapping: address=0x{:08X} size=0x{:X}"
" read_only={} unk_flag={}",
mapping.address, mapping.size, mapping.read_only, mapping.unk_flag);
return;
}
u32 offset_into_region = mapping.address - area->vaddr_base;
if (area->paddr_base == IO_AREA_PADDR) {
LOG_ERROR(Loader, "MMIO mappings are not supported yet. phys_addr=0x{:08X}",
area->paddr_base + offset_into_region);
return;
}
auto target_pointer = memory.GetPhysicalRef(area->paddr_base + offset_into_region);
// TODO(yuriks): This flag seems to have some other effect, but it's unknown what
MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO;
auto vma =
address_space.MapBackingMemory(mapping.address, target_pointer, mapping.size, memory_state)
.Unwrap();
address_space.Reprotect(vma,
mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite);
}
void KernelSystem::MapSharedPages(VMManager& address_space) {
auto cfg_mem_vma = address_space
.MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, {config_mem_handler},
Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
.Unwrap();
address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);
auto shared_page_vma = address_space
.MapBackingMemory(Memory::SHARED_PAGE_VADDR, {shared_page_handler},
Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
.Unwrap();
address_space.Reprotect(shared_page_vma, VMAPermission::Read);
}
void MemoryRegionInfo::Reset(u32 base, u32 size) {
ASSERT(!is_locked);
this->base = base;
this->size = size;
used = 0;
free_blocks.clear();
// mark the entire region as free
free_blocks.insert(Interval::right_open(base, base + size));
}
MemoryRegionInfo::IntervalSet MemoryRegionInfo::HeapAllocate(u32 size) {
ASSERT(!is_locked);
IntervalSet result;
u32 rest = size;
// Try allocating from the higher address
for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) {
ASSERT(iter->bounds() == boost::icl::interval_bounds::right_open());
if (iter->upper() - iter->lower() >= rest) {
// Requested size is fulfilled with this block
result += Interval(iter->upper() - rest, iter->upper());
rest = 0;
break;
}
result += *iter;
rest -= iter->upper() - iter->lower();
}
if (rest != 0) {
// There is no enough free space
return {};
}
free_blocks -= result;
used += size;
return result;
}
bool MemoryRegionInfo::LinearAllocate(u32 offset, u32 size) {
ASSERT(!is_locked);
Interval interval(offset, offset + size);
if (!boost::icl::contains(free_blocks, interval)) {
// The requested range is already allocated
return false;
}
free_blocks -= interval;
used += size;
return true;
}
std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) {
ASSERT(!is_locked);
// Find the first sufficient continuous block from the lower address
for (const auto& interval : free_blocks) {
ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open());
if (interval.upper() - interval.lower() >= size) {
Interval allocated(interval.lower(), interval.lower() + size);
free_blocks -= allocated;
used += size;
return allocated.lower();
}
}
// No sufficient block found
return std::nullopt;
}
std::optional<u32> MemoryRegionInfo::RLinearAllocate(u32 size) {
ASSERT(!is_locked);
// Find the first sufficient continuous block from the upper address
for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) {
auto interval = *iter;
ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open());
if (interval.upper() - interval.lower() >= size) {
Interval allocated(interval.upper() - size, interval.upper());
free_blocks -= allocated;
used += size;
return allocated.lower();
}
}
// No sufficient block found
return std::nullopt;
}
void MemoryRegionInfo::Free(u32 offset, u32 size) {
if (is_locked) {
return;
}
Interval interval(offset, offset + size);
ASSERT(!boost::icl::intersects(free_blocks, interval)); // must be allocated blocks
free_blocks += interval;
used -= size;
}
void MemoryRegionInfo::Unlock() {
is_locked = false;
}
template <class Archive>
void MemoryRegionInfo::serialize(Archive& ar, const unsigned int) {
ar& base;
ar& size;
ar& used;
ar& free_blocks;
if (Archive::is_loading::value) {
is_locked = true;
}
}
SERIALIZE_IMPL(MemoryRegionInfo)
} // namespace Kernel

View File

@@ -0,0 +1,91 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <optional>
#include <boost/icl/interval_set.hpp>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/serialization/boost_interval_set.hpp"
namespace Kernel {
struct AddressMapping;
class VMManager;
struct MemoryRegionInfo {
u32 base; // Not an address, but offset from start of FCRAM
u32 size;
u32 used;
// The domain of the interval_set are offsets from start of FCRAM
using IntervalSet = boost::icl::interval_set<u32>;
using Interval = IntervalSet::interval_type;
IntervalSet free_blocks;
// When locked, Free calls will be ignored, while Allocate calls will hit an assert. A memory
// region locks itself after deserialization.
bool is_locked{};
/**
* Reset the allocator state
* @param base The base offset the beginning of FCRAM.
* @param size The region size this allocator manages
*/
void Reset(u32 base, u32 size);
/**
* Allocates memory from the heap.
* @param size The size of memory to allocate.
* @returns The set of blocks that make up the allocation request. Empty set if there is no
* enough space.
*/
IntervalSet HeapAllocate(u32 size);
/**
* Allocates memory from the linear heap with specific address and size.
* @param offset the address offset to the beginning of FCRAM.
* @param size size of the memory to allocate.
* @returns true if the allocation is successful. false if the requested region is not free.
*/
bool LinearAllocate(u32 offset, u32 size);
/**
* Allocates memory from the linear heap with only size specified.
* @param size size of the memory to allocate.
* @returns the address offset to the beginning of FCRAM; null if there is no enough space
*/
std::optional<u32> LinearAllocate(u32 size);
/**
* Allocates memory from the linear heap with only size specified.
* @param size size of the memory to allocate.
* @returns the address offset to the found block, searching from the end of FCRAM; null if
* there is no enough space
*/
std::optional<u32> RLinearAllocate(u32 size);
/**
* Frees one segment of memory. The memory must have been allocated as heap or linear heap.
* @param offset the region address offset to the beginning of FCRAM.
* @param size the size of the region to free.
*/
void Free(u32 offset, u32 size);
/**
* Unlock the MemoryRegion. Used after loading is completed.
*/
void Unlock();
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::MemoryRegionInfo)

View File

@@ -0,0 +1,144 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::Mutex)
namespace Kernel {
void ReleaseThreadMutexes(Thread* thread) {
for (auto& mtx : thread->held_mutexes) {
mtx->lock_count = 0;
mtx->holding_thread = nullptr;
mtx->WakeupAllWaitingThreads();
}
thread->held_mutexes.clear();
}
Mutex::Mutex(KernelSystem& kernel) : WaitObject(kernel), kernel(kernel) {}
Mutex::~Mutex() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::Mutex, 1);
}
}
std::shared_ptr<Mutex> KernelSystem::CreateMutex(bool initial_locked, std::string name) {
auto mutex = std::make_shared<Mutex>(*this);
mutex->lock_count = 0;
mutex->name = std::move(name);
mutex->holding_thread = nullptr;
// Acquire mutex with current thread if initialized as locked
if (initial_locked) {
mutex->Acquire(GetCurrentThreadManager().GetCurrentThread());
}
return mutex;
}
bool Mutex::ShouldWait(const Thread* thread) const {
return lock_count > 0 && thread != holding_thread.get();
}
void Mutex::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
// Actually "acquire" the mutex only if we don't already have it
if (lock_count == 0) {
priority = thread->current_priority;
thread->held_mutexes.insert(SharedFrom(this));
holding_thread = SharedFrom(thread);
thread->UpdatePriority();
kernel.PrepareReschedule();
}
lock_count++;
}
Result Mutex::Release(Thread* thread) {
// We can only release the mutex if it's held by the calling thread.
if (thread != holding_thread.get()) {
if (holding_thread) {
LOG_ERROR(
Kernel,
"Tried to release a mutex (owned by thread id {}) from a different thread id {}",
holding_thread->thread_id, thread->thread_id);
}
return Result(ErrCodes::WrongLockingThread, ErrorModule::Kernel,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
}
// Note: It should not be possible for the situation where the mutex has a holding thread with a
// zero lock count to occur. The real kernel still checks for this, so we do too.
if (lock_count <= 0)
return Result(ErrorDescription::InvalidResultValue, ErrorModule::Kernel,
ErrorSummary::InvalidState, ErrorLevel::Permanent);
lock_count--;
// Yield to the next thread only if we've fully released the mutex
if (lock_count == 0) {
holding_thread->held_mutexes.erase(SharedFrom(this));
holding_thread->UpdatePriority();
holding_thread = nullptr;
WakeupAllWaitingThreads();
kernel.PrepareReschedule();
}
return ResultSuccess;
}
void Mutex::AddWaitingThread(std::shared_ptr<Thread> thread) {
WaitObject::AddWaitingThread(thread);
thread->pending_mutexes.insert(SharedFrom(this));
UpdatePriority();
}
void Mutex::RemoveWaitingThread(Thread* thread) {
WaitObject::RemoveWaitingThread(thread);
thread->pending_mutexes.erase(SharedFrom(this));
UpdatePriority();
}
void Mutex::UpdatePriority() {
if (!holding_thread)
return;
u32 best_priority = ThreadPrioLowest;
for (auto& waiter : GetWaitingThreads()) {
if (waiter->current_priority < best_priority)
best_priority = waiter->current_priority;
}
if (best_priority != priority) {
priority = best_priority;
holding_thread->UpdatePriority();
}
}
template <class Archive>
void Mutex::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& lock_count;
ar& priority;
ar& name;
ar& holding_thread;
ar& resource_limit;
}
SERIALIZE_IMPL(Mutex)
} // namespace Kernel

View File

@@ -0,0 +1,79 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace Kernel {
class Thread;
class Mutex final : public WaitObject {
public:
explicit Mutex(KernelSystem& kernel);
~Mutex() override;
std::string GetTypeName() const override {
return "Mutex";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Mutex;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ResourceLimit> resource_limit;
int lock_count; ///< Number of times the mutex has been acquired
u32 priority; ///< The priority of the mutex, used for priority inheritance.
std::string name; ///< Name of mutex (optional)
std::shared_ptr<Thread> holding_thread; ///< Thread that has acquired the mutex
/**
* Elevate the mutex priority to the best priority
* among the priorities of all its waiting threads.
*/
void UpdatePriority();
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
void AddWaitingThread(std::shared_ptr<Thread> thread) override;
void RemoveWaitingThread(Thread* thread) override;
/**
* Attempts to release the mutex from the specified thread.
* @param thread Thread that wants to release the mutex.
* @returns The result code of the operation.
*/
Result Release(Thread* thread);
private:
KernelSystem& kernel;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
/**
* Releases all the mutexes held by the specified thread
* @param thread Thread that is holding the mutexes
*/
void ReleaseThreadMutexes(Thread* thread);
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Mutex)
CONSTRUCT_KERNEL_OBJECT(Kernel::Mutex)

View File

@@ -0,0 +1,50 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "common/archives.h"
SERIALIZE_EXPORT_IMPL(Kernel::Object)
namespace Kernel {
Object::Object(KernelSystem& kernel) : object_id{kernel.GenerateObjectID()} {}
Object::~Object() = default;
bool Object::IsWaitable() const {
switch (GetHandleType()) {
case HandleType::Event:
case HandleType::Mutex:
case HandleType::Thread:
case HandleType::Semaphore:
case HandleType::Timer:
case HandleType::ServerPort:
case HandleType::ServerSession:
return true;
case HandleType::Unknown:
case HandleType::SharedMemory:
case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
case HandleType::CodeSet:
case HandleType::ClientPort:
case HandleType::ClientSession:
return false;
}
UNREACHABLE();
}
template <class Archive>
void Object::serialize(Archive& ar, const unsigned int) {
ar& object_id;
}
SERIALIZE_IMPL(Object)
} // namespace Kernel

View File

@@ -0,0 +1,106 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/serialization/atomic.h"
#include "core/global.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
class KernelSystem;
using Handle = u32;
enum class HandleType : u32 {
Unknown,
Event,
Mutex,
SharedMemory,
Thread,
Process,
AddressArbiter,
Semaphore,
Timer,
ResourceLimit,
CodeSet,
ClientPort,
ServerPort,
ClientSession,
ServerSession,
};
enum {
DEFAULT_STACK_SIZE = 0x4000,
};
class Object : NonCopyable, public std::enable_shared_from_this<Object> {
public:
explicit Object(KernelSystem& kernel);
virtual ~Object();
/// Returns a unique identifier for the object. For debugging purposes only.
u32 GetObjectId() const {
return object_id.load(std::memory_order_relaxed);
}
virtual std::string GetTypeName() const {
return "[BAD KERNEL OBJECT TYPE]";
}
virtual std::string GetName() const {
return "[UNKNOWN KERNEL OBJECT]";
}
virtual HandleType GetHandleType() const = 0;
/**
* Check if a thread can wait on the object
* @return True if a thread can wait on the object, otherwise false
*/
bool IsWaitable() const;
private:
std::atomic<u32> object_id;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
template <typename T>
std::shared_ptr<T> SharedFrom(T* raw) {
if (raw == nullptr)
return nullptr;
return std::static_pointer_cast<T>(raw->shared_from_this());
}
/**
* Attempts to downcast the given Object pointer to a pointer to T.
* @return Derived pointer to the object, or `nullptr` if `object` isn't of type T.
*/
template <typename T>
inline std::shared_ptr<T> DynamicObjectCast(std::shared_ptr<Object> object) {
if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) {
return std::static_pointer_cast<T>(object);
}
return nullptr;
}
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Object)
#define CONSTRUCT_KERNEL_OBJECT(T) \
namespace boost::serialization { \
template <class Archive> \
void load_construct_data(Archive& ar, T* t, const unsigned int file_version) { \
::new (t) T(Core::Global<Kernel::KernelSystem>()); \
} \
}

View File

@@ -0,0 +1,656 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <boost/serialization/array.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/bitset.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "common/serialization/boost_vector.hpp"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/plgldr/plgldr.h"
#include "core/loader/loader.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::AddressMapping)
SERIALIZE_EXPORT_IMPL(Kernel::Process)
SERIALIZE_EXPORT_IMPL(Kernel::CodeSet)
SERIALIZE_EXPORT_IMPL(Kernel::CodeSet::Segment)
namespace Kernel {
template <class Archive>
void AddressMapping::serialize(Archive& ar, const unsigned int) {
ar& address;
ar& size;
ar& read_only;
ar& unk_flag;
}
SERIALIZE_IMPL(AddressMapping)
template <class Archive>
void Process::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& handle_table;
ar& codeset; // TODO: Replace with apploader reference
ar& resource_limit;
ar& svc_access_mask;
ar& handle_table_size;
ar&(boost::container::vector<AddressMapping, boost::container::dtl::static_storage_allocator<
AddressMapping, 8, 0, true>>&)address_mappings;
ar& flags.raw;
ar& no_thread_restrictions;
ar& kernel_version;
ar& ideal_processor;
ar& status;
ar& process_id;
ar& creation_time_ticks;
ar& vm_manager;
ar& memory_used;
ar& memory_region;
ar& holding_memory;
ar& holding_tls_memory;
ar& tls_slots;
}
SERIALIZE_IMPL(Process)
std::shared_ptr<CodeSet> KernelSystem::CreateCodeSet(std::string name, u64 program_id) {
auto codeset{std::make_shared<CodeSet>(*this)};
codeset->name = std::move(name);
codeset->program_id = program_id;
return codeset;
}
CodeSet::CodeSet(KernelSystem& kernel) : Object(kernel) {}
CodeSet::~CodeSet() {}
template <class Archive>
void CodeSet::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& memory;
ar& segments;
ar& entrypoint;
ar& name;
ar& program_id;
}
SERIALIZE_IMPL(CodeSet)
template <class Archive>
void CodeSet::Segment::serialize(Archive& ar, const unsigned int) {
ar& offset;
ar& addr;
ar& size;
}
SERIALIZE_IMPL(CodeSet::Segment)
std::shared_ptr<Process> KernelSystem::CreateProcess(std::shared_ptr<CodeSet> code_set) {
auto process{std::make_shared<Process>(*this)};
process->codeset = std::move(code_set);
process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->status = ProcessStatus::Created;
process->process_id = ++next_process_id;
process->creation_time_ticks = timing.GetTicks();
process_list.push_back(process);
return process;
}
void KernelSystem::TerminateProcess(std::shared_ptr<Process> process) {
LOG_INFO(Kernel_SVC, "Process {} exiting", process->process_id);
ASSERT_MSG(process->status == ProcessStatus::Running, "Process has already exited");
process->status = ProcessStatus::Exited;
// Stop all process threads.
for (u32 core = 0; core < Core::GetNumCores(); core++) {
GetThreadManager(core).TerminateProcessThreads(process);
}
process->Exit();
std::erase(process_list, process);
}
void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) {
for (std::size_t i = 0; i < len; ++i) {
u32 descriptor = kernel_caps[i];
u32 type = descriptor >> 20;
if (descriptor == 0xFFFFFFFF) {
// Unused descriptor entry
continue;
} else if ((type & 0xF00) == 0xE00) { // 0x0FFF
// Allowed interrupts list
LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored");
} else if ((type & 0xF80) == 0xF00) { // 0x07FF
// Allowed syscalls mask
unsigned int index = ((descriptor >> 24) & 7) * 24;
u32 bits = descriptor & 0xFFFFFF;
while (bits && index < svc_access_mask.size()) {
svc_access_mask.set(index, bits & 1);
++index;
bits >>= 1;
}
} else if ((type & 0xFF0) == 0xFE0) { // 0x00FF
// Handle table size
handle_table_size = descriptor & 0x3FF;
} else if ((type & 0xFF8) == 0xFF0) { // 0x007F
// Misc. flags
flags.raw = descriptor & 0xFFFF;
} else if ((type & 0xFFE) == 0xFF8) { // 0x001F
// Mapped memory range
if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) {
LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored.");
continue;
}
u32 end_desc = kernel_caps[i + 1];
++i; // Skip over the second descriptor on the next iteration
AddressMapping mapping;
mapping.address = descriptor << 12;
VAddr end_address = end_desc << 12;
if (mapping.address < end_address) {
mapping.size = end_address - mapping.address;
} else {
mapping.size = 0;
}
mapping.read_only = (descriptor & (1 << 20)) != 0;
mapping.unk_flag = (end_desc & (1 << 20)) != 0;
address_mappings.push_back(mapping);
} else if ((type & 0xFFF) == 0xFFE) { // 0x000F
// Mapped memory page
AddressMapping mapping;
mapping.address = descriptor << 12;
mapping.size = Memory::CITRA_PAGE_SIZE;
mapping.read_only = false;
mapping.unk_flag = false;
address_mappings.push_back(mapping);
} else if ((type & 0xFE0) == 0xFC0) { // 0x01FF
// Kernel version
kernel_version = descriptor & 0xFFFF;
int minor = kernel_version & 0xFF;
int major = (kernel_version >> 8) & 0xFF;
LOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor);
} else {
LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor);
}
}
}
void Process::Set3dsxKernelCaps() {
svc_access_mask.set();
address_mappings = {
{0x1FF50000, 0x8000, true}, // part of DSP RAM
{0x1FF70000, 0x8000, true}, // part of DSP RAM
{0x1F000000, 0x600000, false}, // entire VRAM
};
// Similar to Rosalina, we set kernel version to a recent one.
// This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp
// TODO: refactor kernel version out so it is configurable and consistent
// among all relevant places.
kernel_version = 0x23a;
}
void Process::Run(s32 main_thread_priority, u32 stack_size) {
memory_region = kernel.GetMemoryRegion(flags.memory_region);
// Ensure we can reserve a thread. Real kernel returns 0xC860180C if this fails.
if (!resource_limit->Reserve(ResourceLimitType::Thread, 1)) {
return;
}
VAddr out_addr{};
auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
HeapAllocate(std::addressof(out_addr), segment.addr, segment.size, permissions,
memory_state, true);
kernel.memory.WriteBlock(*this, segment.addr, codeset->memory.data() + segment.offset,
segment.size);
};
// Map CodeSet segments
MapSegment(codeset->CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
MapSegment(codeset->RODataSegment(), VMAPermission::Read, MemoryState::Code);
MapSegment(codeset->DataSegment(), VMAPermission::ReadWrite, MemoryState::Private);
// Allocate and map stack
HeapAllocate(std::addressof(out_addr), Memory::HEAP_VADDR_END - stack_size, stack_size,
VMAPermission::ReadWrite, MemoryState::Locked, true);
// Map special address mappings
kernel.MapSharedPages(vm_manager);
for (const auto& mapping : address_mappings) {
kernel.HandleSpecialMapping(vm_manager, mapping);
}
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
if (plgldr) {
plgldr->OnProcessRun(*this, kernel);
}
status = ProcessStatus::Running;
vm_manager.LogLayout(Common::Log::Level::Debug);
Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this));
}
void Process::Exit() {
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
if (plgldr) {
plgldr->OnProcessExit(*this, kernel);
}
}
VAddr Process::GetLinearHeapAreaAddress() const {
// Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of
// the extra RAM in the n3DS.
return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR;
}
VAddr Process::GetLinearHeapBase() const {
return GetLinearHeapAreaAddress() + memory_region->base;
}
VAddr Process::GetLinearHeapLimit() const {
return GetLinearHeapBase() + memory_region->size;
}
Result Process::HeapAllocate(VAddr* out_addr, VAddr target, u32 size, VMAPermission perms,
MemoryState memory_state, bool skip_range_check) {
LOG_DEBUG(Kernel, "Allocate heap target={:08X}, size={:08X}", target, size);
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
target + size < target) {
if (!skip_range_check) {
LOG_ERROR(Kernel, "Invalid heap address");
return ResultInvalidAddress;
}
}
{
auto vma = vm_manager.FindVMA(target);
if (vma->second.type != VMAType::Free ||
vma->second.base + vma->second.size < target + size) {
LOG_ERROR(Kernel, "Trying to allocate already allocated memory");
return ResultInvalidAddressState;
}
}
auto allocated_fcram = memory_region->HeapAllocate(size);
if (allocated_fcram.empty()) {
LOG_ERROR(Kernel, "Not enough space");
return ResultOutOfHeapMemory;
}
// Maps heap block by block
VAddr interval_target = target;
for (const auto& interval : allocated_fcram) {
u32 interval_size = interval.upper() - interval.lower();
LOG_DEBUG(Kernel, "Allocated FCRAM region lower={:08X}, upper={:08X}", interval.lower(),
interval.upper());
std::fill(kernel.memory.GetFCRAMPointer(interval.lower()),
kernel.memory.GetFCRAMPointer(interval.upper()), 0);
auto vma = vm_manager.MapBackingMemory(interval_target,
kernel.memory.GetFCRAMRef(interval.lower()),
interval_size, memory_state);
ASSERT(vma.Succeeded());
vm_manager.Reprotect(vma.Unwrap(), perms);
interval_target += interval_size;
}
holding_memory += allocated_fcram;
memory_used += size;
resource_limit->Reserve(ResourceLimitType::Commit, size);
*out_addr = target;
return ResultSuccess;
}
Result Process::HeapFree(VAddr target, u32 size) {
LOG_DEBUG(Kernel, "Free heap target={:08X}, size={:08X}", target, size);
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
target + size < target) {
LOG_ERROR(Kernel, "Invalid heap address");
return ResultInvalidAddress;
}
R_SUCCEED_IF(size == 0);
// Free heaps block by block
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size));
for (const auto& [backing_memory, block_size] : backing_blocks) {
const auto backing_offset = kernel.memory.GetFCRAMOffset(backing_memory.GetPtr());
memory_region->Free(backing_offset, block_size);
holding_memory -= MemoryRegionInfo::Interval(backing_offset, backing_offset + block_size);
}
Result result = vm_manager.UnmapRange(target, size);
ASSERT(result.IsSuccess());
memory_used -= size;
resource_limit->Release(ResourceLimitType::Commit, size);
return ResultSuccess;
}
Result Process::LinearAllocate(VAddr* out_addr, VAddr target, u32 size, VMAPermission perms) {
LOG_DEBUG(Kernel, "Allocate linear heap target={:08X}, size={:08X}", target, size);
u32 physical_offset;
if (target == 0) {
auto offset = memory_region->LinearAllocate(size);
if (!offset) {
LOG_ERROR(Kernel, "Not enough space");
return ResultOutOfHeapMemory;
}
physical_offset = *offset;
target = physical_offset + GetLinearHeapAreaAddress();
} else {
if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
target + size < target) {
LOG_ERROR(Kernel, "Invalid linear heap address");
return ResultInvalidAddress;
}
// Kernel would crash/return error when target doesn't meet some requirement.
// It seems that target is required to follow immediately after the allocated linear heap,
// or cover the entire hole if there is any.
// Right now we just ignore these checks because they are still unclear. Further more,
// games and homebrew only ever seem to pass target = 0 here (which lets the kernel decide
// the address), so this not important.
physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
if (!memory_region->LinearAllocate(physical_offset, size)) {
LOG_ERROR(Kernel, "Trying to allocate already allocated memory");
return ResultInvalidAddressState;
}
}
auto backing_memory = kernel.memory.GetFCRAMRef(physical_offset);
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + size, 0);
auto vma = vm_manager.MapBackingMemory(target, backing_memory, size, MemoryState::Continuous);
ASSERT(vma.Succeeded());
vm_manager.Reprotect(vma.Unwrap(), perms);
holding_memory += MemoryRegionInfo::Interval(physical_offset, physical_offset + size);
memory_used += size;
resource_limit->Reserve(ResourceLimitType::Commit, size);
LOG_DEBUG(Kernel, "Allocated at target={:08X}", target);
*out_addr = target;
return ResultSuccess;
}
Result Process::LinearFree(VAddr target, u32 size) {
LOG_DEBUG(Kernel, "Free linear heap target={:08X}, size={:08X}", target, size);
if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
target + size < target) {
LOG_ERROR(Kernel, "Invalid linear heap address");
return ResultInvalidAddress;
}
R_SUCCEED_IF(size == 0);
R_TRY(vm_manager.UnmapRange(target, size));
u32 physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
memory_region->Free(physical_offset, size);
holding_memory -= MemoryRegionInfo::Interval(physical_offset, physical_offset + size);
memory_used -= size;
resource_limit->Release(ResourceLimitType::Commit, size);
return ResultSuccess;
}
ResultVal<VAddr> Process::AllocateThreadLocalStorage() {
std::size_t tls_page;
std::size_t tls_slot;
bool needs_allocation = true;
// Iterate over all the allocated pages, and try to find one where not all slots are used.
for (tls_page = 0; tls_page < tls_slots.size(); ++tls_page) {
const auto& page_tls_slots = tls_slots[tls_page];
if (!page_tls_slots.all()) {
// We found a page with at least one free slot, find which slot it is.
for (tls_slot = 0; tls_slot < page_tls_slots.size(); ++tls_slot) {
if (!page_tls_slots.test(tls_slot)) {
needs_allocation = false;
break;
}
}
if (!needs_allocation) {
break;
}
}
}
if (needs_allocation) {
tls_page = tls_slots.size();
tls_slot = 0;
LOG_DEBUG(Kernel, "Allocating new TLS page in slot {}", tls_page);
// There are no already-allocated pages with free slots, lets allocate a new one.
// TLS pages are allocated from the BASE region in the linear heap.
auto base_memory_region = kernel.GetMemoryRegion(MemoryRegion::BASE);
// Allocate some memory from the end of the linear heap for this region.
auto offset = base_memory_region->LinearAllocate(Memory::CITRA_PAGE_SIZE);
if (!offset) {
LOG_ERROR(Kernel_SVC,
"Not enough space in BASE linear region to allocate a new TLS page");
return ResultOutOfMemory;
}
holding_tls_memory +=
MemoryRegionInfo::Interval(*offset, *offset + Memory::CITRA_PAGE_SIZE);
memory_used += Memory::CITRA_PAGE_SIZE;
// The page is completely available at the start.
tls_slots.emplace_back(0);
// Map the page to the current process' address space.
auto tls_page_addr =
Memory::TLS_AREA_VADDR + static_cast<VAddr>(tls_page) * Memory::CITRA_PAGE_SIZE;
vm_manager.MapBackingMemory(tls_page_addr, kernel.memory.GetFCRAMRef(*offset),
Memory::CITRA_PAGE_SIZE, MemoryState::Locked);
LOG_DEBUG(Kernel, "Allocated TLS page at addr={:08X}", tls_page_addr);
} else {
LOG_DEBUG(Kernel, "Allocating TLS in existing page slot {}", tls_page);
}
// Mark the slot as used
tls_slots[tls_page].set(tls_slot);
auto tls_address = Memory::TLS_AREA_VADDR +
static_cast<VAddr>(tls_page) * Memory::CITRA_PAGE_SIZE +
static_cast<VAddr>(tls_slot) * Memory::TLS_ENTRY_SIZE;
kernel.memory.ZeroBlock(*this, tls_address, Memory::TLS_ENTRY_SIZE);
return tls_address;
}
Result Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms, bool privileged) {
LOG_DEBUG(Kernel, "Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", target,
source, size, perms);
if (!privileged && (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END ||
source + size < source)) {
LOG_ERROR(Kernel, "Invalid source address");
return ResultInvalidAddress;
}
// TODO(wwylele): check target address range. Is it also restricted to heap region?
// Check range overlapping
if (source - target < size || target - source < size) {
if (privileged) {
if (source == target) {
// privileged Map allows identical source and target address, which simply changes
// the state and the permission of the memory
return vm_manager.ChangeMemoryState(source, size, MemoryState::Private,
VMAPermission::ReadWrite,
MemoryState::AliasCode, perms);
} else {
return ResultInvalidAddress;
}
} else {
return ResultInvalidAddressState;
}
}
auto vma = vm_manager.FindVMA(target);
if (vma->second.type != VMAType::Free || vma->second.base + vma->second.size < target + size) {
LOG_ERROR(Kernel, "Trying to map to already allocated memory");
return ResultInvalidAddressState;
}
MemoryState source_state = privileged ? MemoryState::Locked : MemoryState::Aliased;
MemoryState target_state = privileged ? MemoryState::AliasCode : MemoryState::Alias;
VMAPermission source_perm = privileged ? VMAPermission::None : VMAPermission::ReadWrite;
// Mark source region as Aliased
R_TRY(vm_manager.ChangeMemoryState(source, size, MemoryState::Private, VMAPermission::ReadWrite,
source_state, source_perm));
CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(source, size));
VAddr interval_target = target;
for (const auto& [backing_memory, block_size] : backing_blocks) {
auto target_vma =
vm_manager.MapBackingMemory(interval_target, backing_memory, block_size, target_state);
ASSERT(target_vma.Succeeded());
vm_manager.Reprotect(target_vma.Unwrap(), perms);
interval_target += block_size;
}
return ResultSuccess;
}
Result Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms, bool privileged) {
LOG_DEBUG(Kernel, "Unmap memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}",
target, source, size, perms);
if (!privileged && (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END ||
source + size < source)) {
LOG_ERROR(Kernel, "Invalid source address");
return ResultInvalidAddress;
}
// TODO(wwylele): check target address range. Is it also restricted to heap region?
if (source - target < size || target - source < size) {
if (privileged) {
if (source == target) {
// privileged Unmap allows identical source and target address, which simply changes
// the state and the permission of the memory
return vm_manager.ChangeMemoryState(source, size, MemoryState::AliasCode,
VMAPermission::None, MemoryState::Private,
perms);
} else {
return ResultInvalidAddress;
}
} else {
return ResultInvalidAddressState;
}
}
// TODO(wwylele): check that the source and the target are actually a pair created by Map
// Should return error 0xD8E007F5 in this case
MemoryState source_state = privileged ? MemoryState::Locked : MemoryState::Aliased;
R_TRY(vm_manager.UnmapRange(target, size));
// Change back source region state. Note that the permission is reprotected according to param
R_TRY(vm_manager.ChangeMemoryState(source, size, source_state, VMAPermission::None,
MemoryState::Private, perms));
return ResultSuccess;
}
void Process::FreeAllMemory() {
if (memory_region == nullptr || resource_limit == nullptr) {
return;
}
// Free any heap/linear memory allocations.
for (auto& entry : holding_memory) {
LOG_DEBUG(Kernel, "Freeing process memory region 0x{:08X} - 0x{:08X}", entry.lower(),
entry.upper());
auto size = entry.upper() - entry.lower();
memory_region->Free(entry.lower(), size);
memory_used -= size;
resource_limit->Release(ResourceLimitType::Commit, size);
}
holding_memory.clear();
// Free any TLS memory allocations.
auto base_memory_region = kernel.GetMemoryRegion(MemoryRegion::BASE);
for (auto& entry : holding_tls_memory) {
LOG_DEBUG(Kernel, "Freeing process TLS memory region 0x{:08X} - 0x{:08X}", entry.lower(),
entry.upper());
auto size = entry.upper() - entry.lower();
base_memory_region->Free(entry.lower(), size);
memory_used -= size;
}
holding_tls_memory.clear();
tls_slots.clear();
// Diagnostics for debugging.
// TODO: The way certain non-application shared memory is allocated can result in very slight
// leaks in these values still.
LOG_DEBUG(Kernel, "Remaining memory used after process cleanup: 0x{:08X}", memory_used);
LOG_DEBUG(Kernel, "Remaining memory resource commit after process cleanup: 0x{:08X}",
resource_limit->GetCurrentValue(ResourceLimitType::Commit));
}
Kernel::Process::Process(KernelSystem& kernel)
: Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) {
kernel.memory.RegisterPageTable(vm_manager.page_table);
}
Kernel::Process::~Process() {
LOG_INFO(Kernel, "Cleaning up process {}", process_id);
// Release all objects this process owns first so that their potential destructor can do clean
// up with this process before further destruction.
// TODO(wwylele): explicitly destroy or invalidate objects this process owns (threads, shared
// memory etc.) even if they are still referenced by other processes.
handle_table.Clear();
FreeAllMemory();
kernel.memory.UnregisterPageTable(vm_manager.page_table);
}
std::shared_ptr<Process> KernelSystem::GetProcessById(u32 process_id) const {
auto itr = std::find_if(
process_list.begin(), process_list.end(),
[&](const std::shared_ptr<Process>& process) { return process->process_id == process_id; });
if (itr == process_list.end())
return nullptr;
return *itr;
}
} // namespace Kernel

View File

@@ -0,0 +1,246 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <bitset>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/static_vector.hpp>
#include <boost/serialization/export.hpp>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/vm_manager.h"
namespace Kernel {
struct AddressMapping {
// Address and size must be page-aligned
VAddr address;
u32 size;
bool read_only;
bool unk_flag;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
union ProcessFlags {
u16 raw;
BitField<0, 1, u16>
allow_debug; ///< Allows other processes to attach to and debug this process.
BitField<1, 1, u16> force_debug; ///< Allows this process to attach to processes even if they
/// don't have allow_debug set.
BitField<2, 1, u16> allow_nonalphanum;
BitField<3, 1, u16> shared_page_writable; ///< Shared page is mapped with write permissions.
BitField<4, 1, u16> privileged_priority; ///< Can use priority levels higher than 24.
BitField<5, 1, u16> allow_main_args;
BitField<6, 1, u16> shared_device_mem;
BitField<7, 1, u16> runnable_on_sleep;
BitField<8, 4, MemoryRegion>
memory_region; ///< Default region for memory allocations for this process
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
};
enum class ProcessStatus { Created, Running, Exited };
class ResourceLimit;
struct MemoryRegionInfo;
class CodeSet final : public Object {
public:
explicit CodeSet(KernelSystem& kernel);
~CodeSet() override;
struct Segment {
std::size_t offset = 0;
VAddr addr = 0;
u32 size = 0;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
std::string GetTypeName() const override {
return "CodeSet";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::CodeSet;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
Segment& CodeSegment() {
return segments[0];
}
const Segment& CodeSegment() const {
return segments[0];
}
Segment& RODataSegment() {
return segments[1];
}
const Segment& RODataSegment() const {
return segments[1];
}
Segment& DataSegment() {
return segments[2];
}
const Segment& DataSegment() const {
return segments[2];
}
std::vector<u8> memory;
std::array<Segment, 3> segments;
VAddr entrypoint;
/// Name of the process
std::string name;
/// Title ID corresponding to the process
u64 program_id;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
class Process final : public Object {
public:
explicit Process(Kernel::KernelSystem& kernel);
~Process() override;
std::string GetTypeName() const override {
return "Process";
}
std::string GetName() const override {
return codeset->name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Process;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
HandleTable handle_table;
std::shared_ptr<CodeSet> codeset;
/// Resource limit descriptor for this process
std::shared_ptr<ResourceLimit> resource_limit;
/// The process may only call SVCs which have the corresponding bit set.
std::bitset<0x80> svc_access_mask;
/// Maximum size of the handle table for the process.
unsigned int handle_table_size = 0x200;
/// Special memory ranges mapped into this processes address space. This is used to give
/// processes access to specific I/O regions and device memory.
boost::container::static_vector<AddressMapping, 8> address_mappings;
ProcessFlags flags;
bool no_thread_restrictions = false;
/// Kernel compatibility version for this process
u16 kernel_version = 0;
/// The default CPU for this process, threads are scheduled on this cpu by default.
u8 ideal_processor = 0;
/// Current status of the process
ProcessStatus status;
/// The id of this process
u32 process_id;
// Creation time in ticks of the process.
u64 creation_time_ticks;
/**
* Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
* to this process.
*/
void ParseKernelCaps(const u32* kernel_caps, std::size_t len);
/**
* Set up the default kernel capability for 3DSX.
*/
void Set3dsxKernelCaps();
/**
* Applies address space changes and launches the process main thread.
*/
void Run(s32 main_thread_priority, u32 stack_size);
/**
* Called when the process exits by svc
*/
void Exit();
///////////////////////////////////////////////////////////////////////////////////////////////
// Memory Management
VMManager vm_manager;
u32 memory_used = 0;
std::shared_ptr<MemoryRegionInfo> memory_region = nullptr;
MemoryRegionInfo::IntervalSet holding_memory;
MemoryRegionInfo::IntervalSet holding_tls_memory;
/// The Thread Local Storage area is allocated as processes create threads,
/// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
/// holds the TLS for a specific thread. This vector contains which parts are in use for each
/// page as a bitmask.
/// This vector will grow as more pages are allocated for new threads.
std::vector<std::bitset<8>> tls_slots;
VAddr GetLinearHeapAreaAddress() const;
VAddr GetLinearHeapBase() const;
VAddr GetLinearHeapLimit() const;
Result HeapAllocate(VAddr* out_addr, VAddr target, u32 size, VMAPermission perms,
MemoryState memory_state = MemoryState::Private,
bool skip_range_check = false);
Result HeapFree(VAddr target, u32 size);
Result LinearAllocate(VAddr* out_addr, VAddr target, u32 size, VMAPermission perms);
Result LinearFree(VAddr target, u32 size);
ResultVal<VAddr> AllocateThreadLocalStorage();
Result Map(VAddr target, VAddr source, u32 size, VMAPermission perms, bool privileged = false);
Result Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
bool privileged = false);
private:
void FreeAllMemory();
KernelSystem& kernel;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::AddressMapping)
BOOST_CLASS_EXPORT_KEY(Kernel::CodeSet)
BOOST_CLASS_EXPORT_KEY(Kernel::CodeSet::Segment)
BOOST_CLASS_EXPORT_KEY(Kernel::Process)
CONSTRUCT_KERNEL_OBJECT(Kernel::CodeSet)
CONSTRUCT_KERNEL_OBJECT(Kernel::Process)

View File

@@ -0,0 +1,161 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/array.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/settings.h"
#include "core/hle/kernel/resource_limit.h"
SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimit)
SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimitList)
namespace Kernel {
ResourceLimit::ResourceLimit(KernelSystem& kernel) : Object(kernel) {}
ResourceLimit::~ResourceLimit() = default;
std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelSystem& kernel, std::string name) {
auto resource_limit = std::make_shared<ResourceLimit>(kernel);
resource_limit->m_name = std::move(name);
return resource_limit;
}
s32 ResourceLimit::GetCurrentValue(ResourceLimitType type) const {
const auto index = static_cast<std::size_t>(type);
return m_current_values[index];
}
s32 ResourceLimit::GetLimitValue(ResourceLimitType type) const {
const auto index = static_cast<std::size_t>(type);
return m_limit_values[index];
}
void ResourceLimit::SetLimitValue(ResourceLimitType type, s32 value) {
const auto index = static_cast<std::size_t>(type);
m_limit_values[index] = value;
}
bool ResourceLimit::Reserve(ResourceLimitType type, s32 amount) {
const auto index = static_cast<std::size_t>(type);
const s32 limit = m_limit_values[index];
const s32 new_value = m_current_values[index] + amount;
// TODO(PabloMK7): Fix all resource limit bugs and return an error, instead of ignoring it.
if (new_value > limit) {
LOG_ERROR(Kernel, "New value {} exceeds limit {} for resource type {}", new_value, limit,
type);
}
m_current_values[index] = new_value;
return true;
}
bool ResourceLimit::Release(ResourceLimitType type, s32 amount) {
const auto index = static_cast<std::size_t>(type);
const s32 value = m_current_values[index];
// TODO(PabloMK7): Fix all resource limit bugs and return an error, instead of ignoring it.
if (amount > value) {
LOG_ERROR(Kernel, "Amount {} exceeds current value {} for resource type {}", amount, value,
type);
}
m_current_values[index] = value - amount;
return true;
}
template <class Archive>
void ResourceLimit::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& m_name;
ar& m_limit_values;
ar& m_current_values;
}
SERIALIZE_IMPL(ResourceLimit)
ResourceLimitList::ResourceLimitList(KernelSystem& kernel) {
// PM makes APPMEMALLOC always match app RESLIMIT_COMMIT.
// See: https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/reslimit.c#L275
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
const auto& appmemalloc = kernel.GetMemoryRegion(MemoryRegion::APPLICATION);
// Create the Application resource limit
auto resource_limit = ResourceLimit::Create(kernel, "Applications");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x18);
resource_limit->SetLimitValue(ResourceLimitType::Commit, appmemalloc->size);
resource_limit->SetLimitValue(ResourceLimitType::Thread, 0x20);
resource_limit->SetLimitValue(ResourceLimitType::Event, 0x20);
resource_limit->SetLimitValue(ResourceLimitType::Mutex, 0x20);
resource_limit->SetLimitValue(ResourceLimitType::Semaphore, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x10);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x2);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x0);
resource_limits[static_cast<u8>(ResourceLimitCategory::Application)] = resource_limit;
// Create the SysApplet resource limit
resource_limit = ResourceLimit::Create(kernel, "System Applets");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x5E06000 : 0x2606000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0x1D : 0xE);
resource_limit->SetLimitValue(ResourceLimitType::Event, is_new_3ds ? 0xB : 0x8);
resource_limit->SetLimitValue(ResourceLimitType::Mutex, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::Semaphore, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x3);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x2710);
resource_limits[static_cast<u8>(ResourceLimitCategory::SysApplet)] = resource_limit;
// Create the LibApplet resource limit
resource_limit = ResourceLimit::Create(kernel, "Library Applets");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, 0x602000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, 0xE);
resource_limit->SetLimitValue(ResourceLimitType::Event, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::Mutex, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::Semaphore, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Timer, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, 0x8);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, 0x1);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x2710);
resource_limits[static_cast<u8>(ResourceLimitCategory::LibApplet)] = resource_limit;
// Create the Other resource limit
resource_limit = ResourceLimit::Create(kernel, "Others");
resource_limit->SetLimitValue(ResourceLimitType::Priority, 0x4);
resource_limit->SetLimitValue(ResourceLimitType::Commit, is_new_3ds ? 0x2182000 : 0x1682000);
resource_limit->SetLimitValue(ResourceLimitType::Thread, is_new_3ds ? 0xE1 : 0xCA);
resource_limit->SetLimitValue(ResourceLimitType::Event, is_new_3ds ? 0x108 : 0xF8);
resource_limit->SetLimitValue(ResourceLimitType::Mutex, is_new_3ds ? 0x25 : 0x23);
resource_limit->SetLimitValue(ResourceLimitType::Semaphore, is_new_3ds ? 0x43 : 0x40);
resource_limit->SetLimitValue(ResourceLimitType::Timer, is_new_3ds ? 0x2C : 0x2B);
resource_limit->SetLimitValue(ResourceLimitType::SharedMemory, is_new_3ds ? 0x1F : 0x1E);
resource_limit->SetLimitValue(ResourceLimitType::AddressArbiter, is_new_3ds ? 0x2D : 0x2B);
resource_limit->SetLimitValue(ResourceLimitType::CpuTime, 0x3E8);
resource_limits[static_cast<u8>(ResourceLimitCategory::Other)] = resource_limit;
}
ResourceLimitList::~ResourceLimitList() = default;
std::shared_ptr<ResourceLimit> ResourceLimitList::GetForCategory(ResourceLimitCategory category) {
switch (category) {
case ResourceLimitCategory::Application:
case ResourceLimitCategory::SysApplet:
case ResourceLimitCategory::LibApplet:
case ResourceLimitCategory::Other:
return resource_limits[static_cast<u8>(category)];
default:
UNREACHABLE_MSG("Unknown resource limit category");
}
}
template <class Archive>
void ResourceLimitList::serialize(Archive& ar, const unsigned int) {
ar& resource_limits;
}
SERIALIZE_IMPL(ResourceLimitList)
} // namespace Kernel

View File

@@ -0,0 +1,104 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
namespace Kernel {
enum class ResourceLimitCategory : u8 {
Application = 0,
SysApplet = 1,
LibApplet = 2,
Other = 3,
};
enum class ResourceLimitType : u32 {
Priority = 0,
Commit = 1,
Thread = 2,
Event = 3,
Mutex = 4,
Semaphore = 5,
Timer = 6,
SharedMemory = 7,
AddressArbiter = 8,
CpuTime = 9,
Max = 10,
};
class ResourceLimit final : public Object {
public:
explicit ResourceLimit(KernelSystem& kernel);
~ResourceLimit() override;
/**
* Creates a resource limit object.
*/
static std::shared_ptr<ResourceLimit> Create(KernelSystem& kernel,
std::string name = "Unknown");
std::string GetTypeName() const override {
return "ResourceLimit";
}
std::string GetName() const override {
return m_name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
s32 GetCurrentValue(ResourceLimitType type) const;
s32 GetLimitValue(ResourceLimitType type) const;
void SetLimitValue(ResourceLimitType name, s32 value);
bool Reserve(ResourceLimitType type, s32 amount);
bool Release(ResourceLimitType type, s32 amount);
private:
using ResourceArray = std::array<s32, static_cast<std::size_t>(ResourceLimitType::Max)>;
ResourceArray m_limit_values{};
ResourceArray m_current_values{};
std::string m_name;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
class ResourceLimitList {
public:
explicit ResourceLimitList(KernelSystem& kernel);
~ResourceLimitList();
/**
* Retrieves the resource limit associated with the specified resource limit category.
* @param category The resource limit category
* @returns The resource limit associated with the category
*/
std::shared_ptr<ResourceLimit> GetForCategory(ResourceLimitCategory category);
private:
std::array<std::shared_ptr<ResourceLimit>, 4> resource_limits;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ResourceLimit)
BOOST_CLASS_EXPORT_KEY(Kernel::ResourceLimitList)
CONSTRUCT_KERNEL_OBJECT(Kernel::ResourceLimit)
CONSTRUCT_KERNEL_OBJECT(Kernel::ResourceLimitList)

View File

@@ -0,0 +1,73 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::Semaphore)
namespace Kernel {
Semaphore::Semaphore(KernelSystem& kernel) : WaitObject(kernel) {}
Semaphore::~Semaphore() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::Semaphore, 1);
}
}
ResultVal<std::shared_ptr<Semaphore>> KernelSystem::CreateSemaphore(s32 initial_count,
s32 max_count,
std::string name) {
R_UNLESS(initial_count <= max_count, ResultInvalidCombinationKernel);
// When the semaphore is created, some slots are reserved for other threads,
// and the rest is reserved for the caller thread
auto semaphore = std::make_shared<Semaphore>(*this);
semaphore->max_count = max_count;
semaphore->available_count = initial_count;
semaphore->name = std::move(name);
return semaphore;
}
bool Semaphore::ShouldWait(const Thread* thread) const {
return available_count <= 0;
}
void Semaphore::Acquire(Thread* thread) {
if (available_count <= 0) {
return;
}
--available_count;
}
Result Semaphore::Release(s32* out_count, s32 release_count) {
R_UNLESS(max_count >= release_count + available_count, ResultOutOfRangeKernel);
*out_count = available_count;
available_count += release_count;
WakeupAllWaitingThreads();
return ResultSuccess;
}
template <class Archive>
void Semaphore::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& max_count;
ar& available_count;
ar& name;
ar& resource_limit;
}
SERIALIZE_IMPL(Semaphore)
} // namespace Kernel

View File

@@ -0,0 +1,59 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace Kernel {
class ResourceLimit;
class Semaphore final : public WaitObject {
public:
explicit Semaphore(KernelSystem& kernel);
~Semaphore() override;
std::string GetTypeName() const override {
return "Semaphore";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Semaphore;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ResourceLimit> resource_limit;
s32 max_count; ///< Maximum number of simultaneous holders the semaphore can have
s32 available_count; ///< Number of free slots left in the semaphore
std::string name; ///< Name of semaphore (optional)
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
/**
* Releases a certain number of slots from a semaphore.
* @param release_count The number of slots to release
* @return The number of free slots the semaphore had before this call
*/
Result Release(s32* out_count, s32 release_count);
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Semaphore)
CONSTRUCT_KERNEL_OBJECT(Kernel::Semaphore)

View File

@@ -0,0 +1,66 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <tuple>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::ServerPort)
namespace Kernel {
ServerPort::ServerPort(KernelSystem& kernel) : WaitObject(kernel) {}
ServerPort::~ServerPort() {}
Result ServerPort::Accept(std::shared_ptr<ServerSession>* out_server_session) {
R_UNLESS(!pending_sessions.empty(), ResultNoPendingSessions);
*out_server_session = std::move(pending_sessions.back());
pending_sessions.pop_back();
return ResultSuccess;
}
bool ServerPort::ShouldWait(const Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
}
void ServerPort::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
KernelSystem::PortPair KernelSystem::CreatePortPair(u32 max_sessions, std::string name) {
auto server_port{std::make_shared<ServerPort>(*this)};
auto client_port{std::make_shared<ClientPort>(*this)};
server_port->name = name + "_Server";
client_port->name = name + "_Client";
client_port->server_port = server_port;
client_port->max_sessions = max_sessions;
client_port->active_sessions = 0;
return std::make_pair(std::move(server_port), std::move(client_port));
}
template <class Archive>
void ServerPort::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& name;
ar& pending_sessions;
ar& hle_handler;
}
SERIALIZE_IMPL(ServerPort)
} // namespace Kernel

View File

@@ -0,0 +1,75 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <tuple>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace Kernel {
class ClientPort;
class ServerSession;
class SessionRequestHandler;
class ServerPort final : public WaitObject {
public:
explicit ServerPort(KernelSystem& kernel);
~ServerPort() override;
std::string GetTypeName() const override {
return "ServerPort";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ServerPort;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
/**
* Accepts a pending incoming connection on this port. If there are no pending sessions, will
* return ResultNoPendingSessions.
*/
Result Accept(std::shared_ptr<ServerSession>* out_server_session);
/**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
hle_handler = std::move(hle_handler_);
}
std::string name; ///< Name of port (optional)
/// ServerSessions waiting to be accepted by the port
std::vector<std::shared_ptr<ServerSession>> pending_sessions;
/// This session's HLE request handler template (optional)
/// ServerSessions created from this port inherit a reference to this handler.
std::shared_ptr<SessionRequestHandler> hle_handler;
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ServerPort)
CONSTRUCT_KERNEL_OBJECT(Kernel::ServerPort)

View File

@@ -0,0 +1,159 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <tuple>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::ServerSession)
namespace Kernel {
template <class Archive>
void ServerSession::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& name;
ar& parent;
ar& hle_handler;
ar& pending_requesting_threads;
ar& currently_handling;
ar& mapped_buffer_context;
}
SERIALIZE_IMPL(ServerSession)
ServerSession::ServerSession(KernelSystem& kernel) : WaitObject(kernel), kernel(kernel) {}
ServerSession::~ServerSession() {
// This destructor will be called automatically when the last ServerSession handle is closed by
// the emulated application.
// Decrease the port's connection count.
if (parent->port)
parent->port->ConnectionClosed();
// TODO(Subv): Wake up all the ClientSession's waiting threads and set
// the SendSyncRequest result to 0xC920181A.
parent->server = nullptr;
}
ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelSystem& kernel,
std::string name) {
auto server_session{std::make_shared<ServerSession>(kernel)};
server_session->name = std::move(name);
server_session->parent = nullptr;
return server_session;
}
bool ServerSession::ShouldWait(const Thread* thread) const {
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
if (parent->client == nullptr)
return false;
// Wait if we have no pending requests, or if we're currently handling a request.
return pending_requesting_threads.empty() || currently_handling != nullptr;
}
void ServerSession::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
// If the client endpoint was closed, don't do anything. This ServerSession is now useless and
// will linger until its last handle is closed by the running application.
if (parent->client == nullptr)
return;
// We are now handling a request, pop it from the stack.
ASSERT(!pending_requesting_threads.empty());
currently_handling = pending_requesting_threads.back();
pending_requesting_threads.pop_back();
}
Result ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
// similar.
// If this ServerSession has an associated HLE handler, forward the request to it.
if (hle_handler != nullptr) {
std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buf;
auto current_process = thread->owner_process.lock();
ASSERT(current_process);
kernel.memory.ReadBlock(*current_process, thread->GetCommandBufferAddress(), cmd_buf.data(),
cmd_buf.size() * sizeof(u32));
auto context =
std::make_shared<Kernel::HLERequestContext>(kernel, SharedFrom(this), thread);
context->PopulateFromIncomingCommandBuffer(cmd_buf.data(), current_process);
hle_handler->HandleSyncRequest(*context);
ASSERT(thread->status == Kernel::ThreadStatus::Running ||
thread->status == Kernel::ThreadStatus::WaitHleEvent);
// Only write the response immediately if the thread is still running. If the HLE handler
// put the thread to sleep then the writing of the command buffer will be deferred to the
// wakeup callback.
if (thread->status == Kernel::ThreadStatus::Running) {
context->WriteToOutgoingCommandBuffer(cmd_buf.data(), *current_process);
kernel.memory.WriteBlock(*current_process, thread->GetCommandBufferAddress(),
cmd_buf.data(), cmd_buf.size() * sizeof(u32));
}
}
if (thread->status == ThreadStatus::Running) {
// Put the thread to sleep until the server replies, it will be awoken in
// svcReplyAndReceive for LLE servers.
thread->status = ThreadStatus::WaitIPC;
if (hle_handler != nullptr) {
// For HLE services, we put the request threads to sleep for a short duration to
// simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for
// other reasons like an async callback. The IPC overhead is needed to prevent
// starvation when a thread only does sync requests to HLE services while a
// lower-priority thread is waiting to run.
// This delay was approximated in a homebrew application by measuring the average time
// it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC
// request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have
// a high variance and vary between models.
static constexpr u64 IPCDelayNanoseconds = 39000;
thread->WakeAfterDelay(IPCDelayNanoseconds);
} else {
// Add the thread to the list of threads that have issued a sync request with this
// server.
pending_requesting_threads.push_back(std::move(thread));
}
}
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
// on it.
WakeupAllWaitingThreads();
return ResultSuccess;
}
KernelSystem::SessionPair KernelSystem::CreateSessionPair(const std::string& name,
std::shared_ptr<ClientPort> port) {
auto server_session = ServerSession::Create(*this, name + "_Server").Unwrap();
auto client_session{std::make_shared<ClientSession>(*this)};
client_session->name = name + "_Client";
std::shared_ptr<Session> parent(new Session);
parent->client = client_session.get();
parent->server = server_session.get();
parent->port = port;
client_session->parent = parent;
server_session->parent = parent;
return std::make_pair(std::move(server_session), std::move(client_session));
}
} // namespace Kernel

View File

@@ -0,0 +1,117 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hle/kernel/ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
#include "core/memory.h"
namespace Kernel {
class ClientSession;
class ClientPort;
class ServerSession;
class Session;
class SessionRequestHandler;
class Thread;
/**
* Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
* primitive for communication between different processes, and are used to implement service calls
* to the various system services.
*
* To make a service call, the client must write the command header and parameters to the buffer
* located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest
* SVC call with its ClientSession handle. The kernel will read the command header, using it to
* marshall the parameters to the process at the server endpoint of the session.
* After the server replies to the request, the response is marshalled back to the caller's
* TLS buffer and control is transferred back to it.
*/
class ServerSession final : public WaitObject {
public:
~ServerSession() override;
explicit ServerSession(KernelSystem& kernel);
std::string GetName() const override {
return name;
}
std::string GetTypeName() const override {
return "ServerSession";
}
static constexpr HandleType HANDLE_TYPE = HandleType::ServerSession;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
/**
* Sets the HLE handler for the session. This handler will be called to service IPC requests
* instead of the regular IPC machinery. (The regular IPC machinery is currently not
* implemented.)
*/
void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
hle_handler = std::move(hle_handler_);
}
/**
* Handle a sync request from the emulated application.
* @param thread Thread that initiated the request.
* @returns Result from the operation.
*/
Result HandleSyncRequest(std::shared_ptr<Thread> thread);
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
std::string name; ///< The name of this session (optional)
std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
std::shared_ptr<SessionRequestHandler>
hle_handler; ///< This session's HLE request handler (optional)
/// List of threads that are pending a response after a sync request. This list is processed in
/// a LIFO manner, thus, the last request will be dispatched first.
/// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
std::vector<std::shared_ptr<Thread>> pending_requesting_threads;
/// Thread whose request is currently being handled. A request is considered "handled" when a
/// response is sent via svcReplyAndReceive.
/// TODO(Subv): Find a better name for this.
std::shared_ptr<Thread> currently_handling;
/// A temporary list holding mapped buffer info from IPC request, used for during IPC reply
std::vector<MappedBufferContext> mapped_buffer_context;
private:
/**
* Creates a server session. The server session can have an optional HLE handler,
* which will be invoked to handle the IPC requests that this session receives.
* @param kernel The kernel instance to create the server session on
* @param name Optional name of the server session.
* @return The created server session
*/
static ResultVal<std::shared_ptr<ServerSession>> Create(KernelSystem& kernel,
std::string name = "Unknown");
friend class KernelSystem;
KernelSystem& kernel;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ServerSession)
CONSTRUCT_KERNEL_OBJECT(Kernel::ServerSession)

View File

@@ -0,0 +1,24 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
SERIALIZE_EXPORT_IMPL(Kernel::Session)
namespace Kernel {
template <class Archive>
void Session::serialize(Archive& ar, const unsigned int file_version) {
ar& client;
ar& server;
ar& port;
}
SERIALIZE_IMPL(Session)
} // namespace Kernel

View File

@@ -0,0 +1,36 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <boost/serialization/export.hpp>
#include "core/hle/kernel/object.h"
namespace Kernel {
class ClientSession;
class ClientPort;
class ServerSession;
/**
* Parent structure to link the client and server endpoints of a session with their associated
* client port. The client port need not exist, as is the case for portless sessions like the
* FS File and Directory sessions. When one of the endpoints of a session is destroyed, its
* corresponding field in this structure will be set to nullptr.
*/
class Session final {
public:
ClientSession* client = nullptr; ///< The client endpoint of the session.
ServerSession* server = nullptr; ///< The server endpoint of the session.
std::shared_ptr<ClientPort> port; ///< The port that this session is associated with (optional).
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Session)

View File

@@ -0,0 +1,240 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/weak_ptr.hpp>
#include "common/archives.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::SharedMemory)
namespace Kernel {
SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
SharedMemory::~SharedMemory() {
for (const auto& interval : holding_memory) {
memory_region->Free(interval.lower(), interval.upper() - interval.lower());
}
auto process = owner_process.lock();
if (process) {
process->resource_limit->Release(ResourceLimitType::SharedMemory, 1);
if (base_address != 0) {
process->vm_manager.ChangeMemoryState(base_address, size, MemoryState::Locked,
VMAPermission::None, MemoryState::Private,
VMAPermission::ReadWrite);
} else {
process->memory_used -= size;
}
}
}
ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
std::shared_ptr<Process> owner_process, u32 size, MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) {
auto memory_region = GetMemoryRegion(region);
auto shared_memory = std::make_shared<SharedMemory>(*this);
shared_memory->owner_process = owner_process;
shared_memory->name = std::move(name);
shared_memory->size = size;
shared_memory->memory_region = memory_region;
shared_memory->permissions = permissions;
shared_memory->other_permissions = other_permissions;
if (address == 0) {
// We need to allocate a block from the Linear Heap ourselves.
// We'll manually allocate some memory from the linear heap in the specified region.
auto offset = memory_region->LinearAllocate(size);
ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!");
std::fill(memory.GetFCRAMPointer(*offset), memory.GetFCRAMPointer(*offset + size), 0);
shared_memory->backing_blocks = {{memory.GetFCRAMRef(*offset), size}};
shared_memory->holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size);
shared_memory->linear_heap_phys_offset = *offset;
// Increase the amount of used linear heap memory for the owner process.
if (owner_process != nullptr) {
owner_process->memory_used += size;
}
} else {
auto& vm_manager = owner_process->vm_manager;
// The memory is already available and mapped in the owner process.
R_TRY(vm_manager.ChangeMemoryState(address, size, MemoryState::Private,
VMAPermission::ReadWrite, MemoryState::Locked,
SharedMemory::ConvertPermissions(permissions)));
auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size);
ASSERT(backing_blocks.Succeeded()); // should success after verifying memory state above
shared_memory->backing_blocks = std::move(backing_blocks).Unwrap();
}
shared_memory->base_address = address;
return shared_memory;
}
std::shared_ptr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet(
u32 offset, u32 size, MemoryPermission permissions, MemoryPermission other_permissions,
std::string name) {
auto shared_memory{std::make_shared<SharedMemory>(*this)};
// Allocate memory in heap
auto memory_region = GetMemoryRegion(MemoryRegion::SYSTEM);
auto backing_blocks = memory_region->HeapAllocate(size);
ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!");
shared_memory->holding_memory = backing_blocks;
shared_memory->owner_process = std::weak_ptr<Process>();
shared_memory->name = std::move(name);
shared_memory->size = size;
shared_memory->memory_region = memory_region;
shared_memory->permissions = permissions;
shared_memory->other_permissions = other_permissions;
for (const auto& interval : backing_blocks) {
shared_memory->backing_blocks.emplace_back(memory.GetFCRAMRef(interval.lower()),
interval.upper() - interval.lower());
std::fill(memory.GetFCRAMPointer(interval.lower()),
memory.GetFCRAMPointer(interval.upper()), 0);
}
shared_memory->base_address = Memory::HEAP_VADDR + offset;
return shared_memory;
}
Result SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions) {
MemoryPermission own_other_permissions =
&target_process == owner_process.lock().get() ? this->permissions : this->other_permissions;
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
return ResultInvalidCombination;
}
// Error out if the requested permissions don't match what the creator process allows.
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match",
GetObjectId(), address, name);
return ResultInvalidCombination;
}
// Heap-backed memory blocks can not be mapped with other_permissions = DontCare
if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match",
GetObjectId(), address, name);
return ResultInvalidCombination;
}
// Error out if the provided permissions are not compatible with what the creator process needs.
if (other_permissions != MemoryPermission::DontCare &&
static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match",
GetObjectId(), address, name);
return ResultWrongPermission;
}
// TODO(Subv): Check for the Shared Device Mem flag in the creator process.
/*if (was_created_with_shared_device_mem && address != 0) {
return Result(ErrorDescription::InvalidCombination, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
}*/
// TODO(Subv): The same process that created a SharedMemory object
// can not map it in its own address space unless it was created with addr=0, result 0xD900182C.
if (address != 0) {
if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, invalid address",
GetObjectId(), address, name);
return ResultInvalidAddress;
}
}
VAddr target_address = address;
if (base_address == 0 && target_address == 0) {
// Calculate the address at which to map the memory block.
// Note: even on new firmware versions, the target address is still in the old linear heap
// region. This exception is made to keep the shared font compatibility. See
// APT:GetSharedFont for detail.
target_address = linear_heap_phys_offset + Memory::LINEAR_HEAP_VADDR;
}
{
auto vma = target_process.vm_manager.FindVMA(target_address);
if (vma->second.type != VMAType::Free ||
vma->second.base + vma->second.size < target_address + size) {
LOG_ERROR(
Kernel,
"cannot map id={}, address=0x{:08X} name={}, mapping to already allocated memory",
GetObjectId(), address, name);
return ResultInvalidAddressState;
}
}
// Map the memory block into the target process
VAddr interval_target = target_address;
for (const auto& interval : backing_blocks) {
auto vma = target_process.vm_manager.MapBackingMemory(interval_target, interval.first,
interval.second, MemoryState::Shared);
ASSERT(vma.Succeeded());
target_process.vm_manager.Reprotect(vma.Unwrap(), ConvertPermissions(permissions));
interval_target += interval.second;
}
return ResultSuccess;
}
Result SharedMemory::Unmap(Process& target_process, VAddr address) {
// TODO(Subv): Verify what happens if the application tries to unmap an address that is not
// mapped to a SharedMemory.
return target_process.vm_manager.UnmapRange(address, size);
}
VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
u32 masked_permissions =
static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
return static_cast<VMAPermission>(masked_permissions);
};
u8* SharedMemory::GetPointer(u32 offset) {
if (backing_blocks.size() != 1) {
LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory");
}
return backing_blocks[0].first + offset;
}
const u8* SharedMemory::GetPointer(u32 offset) const {
if (backing_blocks.size() != 1) {
LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory");
}
return backing_blocks[0].first + offset;
}
template <class Archive>
void SharedMemory::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& linear_heap_phys_offset;
ar& backing_blocks;
ar& size;
ar& memory_region;
ar& permissions;
ar& other_permissions;
ar& owner_process;
ar& base_address;
ar& name;
ar& holding_memory;
}
SERIALIZE_IMPL(SharedMemory)
} // namespace Kernel

View File

@@ -0,0 +1,120 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <utility>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
namespace Kernel {
class SharedMemory final : public Object {
public:
explicit SharedMemory(KernelSystem& kernel);
~SharedMemory() override;
std::string GetTypeName() const override {
return "SharedMemory";
}
std::string GetName() const override {
return name;
}
void SetName(std::string name_) {
name = std::move(name_);
}
static constexpr HandleType HANDLE_TYPE = HandleType::SharedMemory;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
/// Gets the size of the underlying memory block in bytes.
u64 GetSize() const {
return size;
}
/// Gets the linear heap physical offset
u64 GetLinearHeapPhysicalOffset() const {
return linear_heap_phys_offset;
}
/**
* Converts the specified MemoryPermission into the equivalent VMAPermission.
* @param permission The MemoryPermission to convert.
*/
static VMAPermission ConvertPermissions(MemoryPermission permission);
/**
* Maps a shared memory block to an address in the target process' address space
* @param target_process Process on which to map the memory block.
* @param address Address in system memory to map shared memory block to
* @param permissions Memory block map permissions (specified by SVC field)
* @param other_permissions Memory block map other permissions (specified by SVC field)
*/
Result Map(Process& target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions);
/**
* Unmaps a shared memory block from the specified address in system memory
* @param target_process Process from which to unmap the memory block.
* @param address Address in system memory where the shared memory block is mapped
* @return Result code of the unmap operation
*/
Result Unmap(Process& target_process, VAddr address);
/**
* Gets a pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
* @return A pointer to the shared memory block from the specified offset
*/
u8* GetPointer(u32 offset = 0);
/**
* Gets a constant pointer to the shared memory block
* @param offset Offset from the start of the shared memory block to get pointer
* @return A constant pointer to the shared memory block from the specified offset
*/
const u8* GetPointer(u32 offset = 0) const;
private:
/// Offset in FCRAM of the shared memory block in the linear heap if no address was specified
/// during creation.
PAddr linear_heap_phys_offset = 0;
/// Backing memory for this shared memory block.
std::vector<std::pair<MemoryRef, u32>> backing_blocks;
/// Size of the memory block. Page-aligned.
u32 size = 0;
/// Region of memory this block exists in.
std::shared_ptr<MemoryRegionInfo> memory_region = nullptr;
/// Permission restrictions applied to the process which created the block.
MemoryPermission permissions{};
/// Permission restrictions applied to other processes mapping the block.
MemoryPermission other_permissions{};
/// Process that created this shared memory block.
std::weak_ptr<Process> owner_process;
/// Address of shared memory block in the owner process if specified.
VAddr base_address = 0;
/// Name of shared memory object.
std::string name;
MemoryRegionInfo::IntervalSet holding_memory;
friend class KernelSystem;
KernelSystem& kernel;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::SharedMemory)
CONSTRUCT_KERNEL_OBJECT(Kernel::SharedMemory)

View File

@@ -0,0 +1,184 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <cstring>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/binary_object.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/movie.h"
SERIALIZE_EXPORT_IMPL(SharedPage::Handler)
namespace boost::serialization {
template <class Archive>
void load_construct_data(Archive& ar, SharedPage::Handler* t, const unsigned int) {
::new (t) SharedPage::Handler(Core::System::GetInstance().CoreTiming(),
Core::System::GetInstance().Movie().GetOverrideInitTime());
}
template void load_construct_data<iarchive>(iarchive& ar, SharedPage::Handler* t,
const unsigned int);
} // namespace boost::serialization
namespace SharedPage {
static std::chrono::seconds GetInitTime(u64 override_init_time) {
if (override_init_time != 0) {
// Override the clock init time with the one in the movie
return std::chrono::seconds(override_init_time);
}
switch (Settings::values.init_clock.GetValue()) {
case Settings::InitClock::SystemTime: {
auto now = std::chrono::system_clock::now();
// If the system time is in daylight saving, we give an additional hour to console time
std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
std::tm* now_tm = std::localtime(&now_time_t);
if (now_tm && now_tm->tm_isdst > 0)
now = now + std::chrono::hours(1);
// add the offset
s64 init_time_offset = Settings::values.init_time_offset.GetValue();
long long days_offset = init_time_offset / 86400;
long long days_offset_in_seconds = days_offset * 86400; // h/m/s truncated
unsigned long long seconds_offset =
std::abs(init_time_offset) - std::abs(days_offset_in_seconds);
now = now + std::chrono::seconds(seconds_offset);
now = now + std::chrono::seconds(days_offset_in_seconds);
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
}
case Settings::InitClock::FixedTime:
return std::chrono::seconds(Settings::values.init_time.GetValue());
default:
UNREACHABLE_MSG("Invalid InitClock value ({})", Settings::values.init_clock.GetValue());
}
}
Handler::Handler(Core::Timing& timing, u64 override_init_time) : timing(timing) {
std::memset(&shared_page, 0, sizeof(shared_page));
shared_page.running_hw = 0x1; // product
// Some games wait until this value becomes 0x1, before asking running_hw
shared_page.unknown_value = 0x1;
// Set to a completely full battery
shared_page.battery_state.charge_level.Assign(
static_cast<u8>(Service::PTM::ChargeLevels::CompletelyFull));
shared_page.battery_state.is_adapter_connected.Assign(1);
shared_page.battery_state.is_charging.Assign(1);
init_time = GetInitTime(override_init_time);
using namespace std::placeholders;
update_time_event = timing.RegisterEvent("SharedPage::UpdateTimeCallback",
std::bind(&Handler::UpdateTimeCallback, this, _1, _2));
timing.ScheduleEvent(0, update_time_event, 0, 0);
float slidestate = Settings::values.factor_3d.GetValue() / 100.0f;
shared_page.sliderstate_3d = static_cast<float_le>(slidestate);
// TODO(PabloMK7)
// Set wifi state to internet, to fake a connection from the NDM service.
// Remove once it is figured out how NDM uses AC to connect at console boot.
SetWifiLinkLevel(WifiLinkLevel::Best);
SetWifiState(WifiState::Internet);
}
u64 Handler::GetSystemTimeSince2000() const {
std::chrono::milliseconds now =
init_time + std::chrono::duration_cast<std::chrono::milliseconds>(timing.GetGlobalTimeUs());
// 3DS system does't allow user to set a time before Jan 1 2000,
// so we use it as an auxiliary epoch to calculate the console time.
std::tm epoch_tm;
epoch_tm.tm_sec = 0;
epoch_tm.tm_min = 0;
epoch_tm.tm_hour = 0;
epoch_tm.tm_mday = 1;
epoch_tm.tm_mon = 0;
epoch_tm.tm_year = 100;
epoch_tm.tm_isdst = 0;
s64 epoch = std::mktime(&epoch_tm) * 1000;
// Only when system time is after 2000, we set it as 3DS system time
if (now.count() > epoch) {
return now.count() - epoch;
} else {
return 0;
}
}
u64 Handler::GetSystemTimeSince1900() const {
// 3DS console time uses Jan 1 1900 as internal epoch,
// so we use the milliseconds between 1900 and 2000 as base console time
return 3155673600000ULL + GetSystemTimeSince2000();
}
void Handler::UpdateTimeCallback(std::uintptr_t user_data, int cycles_late) {
DateTime& date_time =
shared_page.date_time_counter % 2 ? shared_page.date_time_0 : shared_page.date_time_1;
date_time.date_time = GetSystemTimeSince1900();
date_time.update_tick = timing.GetTicks();
date_time.tick_to_second_coefficient = BASE_CLOCK_RATE_ARM11;
date_time.tick_offset = 0;
++shared_page.date_time_counter;
// system time is updated hourly
timing.ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event);
}
void Handler::SetMacAddress(const MacAddress& addr) {
std::memcpy(shared_page.wifi_macaddr, addr.data(), sizeof(MacAddress));
}
MacAddress Handler::GetMacAddress() {
MacAddress addr;
std::memcpy(addr.data(), shared_page.wifi_macaddr, sizeof(MacAddress));
return addr;
}
void Handler::SetWifiLinkLevel(WifiLinkLevel level) {
shared_page.wifi_link_level = static_cast<u8>(level);
}
WifiLinkLevel Handler::GetWifiLinkLevel() {
return static_cast<WifiLinkLevel>(shared_page.wifi_link_level);
}
void Handler::SetWifiState(WifiState state) {
shared_page.wifi_state = static_cast<u8>(state);
}
void Handler::Set3DLed(u8 state) {
shared_page.ledstate_3d = state;
}
void Handler::Set3DSlider(float slidestate) {
shared_page.sliderstate_3d = static_cast<float_le>(slidestate);
}
SharedPageDef& Handler::GetSharedPage() {
return shared_page;
}
template <class Archive>
void Handler::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<BackingMem>(*this);
ar& boost::serialization::make_binary_object(&shared_page, sizeof(shared_page));
}
SERIALIZE_IMPL(Handler)
} // namespace SharedPage

View File

@@ -0,0 +1,155 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
/**
* The shared page stores various runtime configuration settings. This memory page is
* read-only for user processes (there is a bit in the header that grants the process
* write access, according to 3dbrew; this is not emulated)
*/
#include <chrono>
#include <ctime>
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "common/swap.h"
#include "core/memory.h"
namespace Core {
struct TimingEventType;
class Timing;
} // namespace Core
namespace SharedPage {
// See http://3dbrew.org/wiki/Configuration_Memory#Shared_Memory_Page_For_ARM11_Processes
struct DateTime {
u64_le date_time; // 0
u64_le update_tick; // 8
u64_le tick_to_second_coefficient; // 10
u64_le tick_offset; // 18
};
static_assert(sizeof(DateTime) == 0x20, "Datetime size is wrong");
union BatteryState {
u8 raw;
BitField<0, 1, u8> is_adapter_connected;
BitField<1, 1, u8> is_charging;
BitField<2, 3, u8> charge_level;
};
using MacAddress = std::array<u8, 6>;
// Default MAC address in the Nintendo 3DS range
constexpr MacAddress DefaultMac = {0x40, 0xF4, 0x07, 0x00, 0x00, 0x00};
enum class WifiLinkLevel : u8 {
Off = 0,
Poor = 1,
Good = 2,
Best = 3,
};
enum class WifiState : u8 {
Invalid = 0,
Enabled = 1,
Internet = 2,
Local1 = 3,
Local2 = 4,
Local3 = 6,
Disabled = 7,
};
struct SharedPageDef {
// Most of these names are taken from the 3dbrew page linked above.
u32_le date_time_counter; // 0
u8 running_hw; // 4
/// "Microcontroller hardware info"
u8 mcu_hw_info; // 5
INSERT_PADDING_BYTES(0x20 - 0x6); // 6
DateTime date_time_0; // 20
DateTime date_time_1; // 40
u8 wifi_macaddr[6]; // 60
u8 wifi_link_level; // 66
u8 wifi_state; // 67
INSERT_PADDING_BYTES(0x80 - 0x68); // 68
float_le sliderstate_3d; // 80
u8 ledstate_3d; // 84
BatteryState battery_state; // 85
u8 unknown_value; // 86
INSERT_PADDING_BYTES(0xA0 - 0x87); // 87
u64_le menu_title_id; // A0
u64_le active_menu_title_id; // A8
INSERT_PADDING_BYTES(0x1000 - 0xB0); // B0
};
static_assert(sizeof(SharedPageDef) == Memory::SHARED_PAGE_SIZE,
"Shared page structure size is wrong");
class Handler : public BackingMem {
public:
Handler(Core::Timing& timing, u64 override_init_time);
void SetMacAddress(const MacAddress&);
MacAddress GetMacAddress();
void SetWifiLinkLevel(WifiLinkLevel);
WifiLinkLevel GetWifiLinkLevel();
void SetWifiState(WifiState);
void Set3DLed(u8);
void Set3DSlider(float);
SharedPageDef& GetSharedPage();
u8* GetPtr() override {
return reinterpret_cast<u8*>(&shared_page);
}
const u8* GetPtr() const override {
return reinterpret_cast<const u8*>(&shared_page);
}
std::size_t GetSize() const override {
return sizeof(shared_page);
}
/// Gets the system time in milliseconds since the year 2000.
u64 GetSystemTimeSince2000() const;
/// Gets the system time in milliseconds since the year 1900.
u64 GetSystemTimeSince1900() const;
private:
void UpdateTimeCallback(std::uintptr_t user_data, int cycles_late);
Core::Timing& timing;
Core::TimingEventType* update_time_event;
std::chrono::seconds init_time;
SharedPageDef shared_page;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
} // namespace SharedPage
namespace boost::serialization {
template <class Archive>
void load_construct_data(Archive& ar, SharedPage::Handler* t, const unsigned int);
} // namespace boost::serialization
BOOST_CLASS_EXPORT_KEY(SharedPage::Handler)

2390
src/core/hle/kernel/svc.cpp Normal file

File diff suppressed because it is too large Load Diff

35
src/core/hle/kernel/svc.h Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
namespace Core {
class System;
} // namespace Core
namespace Kernel {
class SVC;
class SVCContext {
public:
SVCContext(Core::System& system);
~SVCContext();
void CallSVC(u32 immediate);
private:
std::unique_ptr<SVC> impl;
};
class SVC_SyncCallback;
class SVC_IPCCallback;
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::SVC_SyncCallback)
BOOST_CLASS_EXPORT_KEY(Kernel::SVC_IPCCallback)

View File

@@ -0,0 +1,314 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstring>
#include <type_traits>
#include "common/common_types.h"
namespace Kernel {
/*
* This class defines the SVC ABI, and provides a wrapper template for translating between ARM
* registers and SVC parameters and return value. The SVC context class should inherit from this
* class using CRTP (`class SVC : public SVCWrapper<SVC> {...}`), and use the Wrap() template to
* convert a SVC function interface to a void()-type function that interacts with registers
* interface GetReg() and SetReg().
*/
template <typename Context>
class SVCWrapper {
protected:
template <auto F>
void Wrap() {
WrapHelper<decltype(F)>::Call(*static_cast<Context*>(this), F);
};
private:
// A special param index represents the return value
static constexpr std::size_t INDEX_RETURN = ~(std::size_t)0;
struct ParamSlot {
// whether the slot is used for a param
bool used;
// index of a param in the function signature, starting from 0, or special value
// INDEX_RETURN
std::size_t param_index;
// index of a 32-bit word within the param
std::size_t word_index;
};
// Size in words of the given type in ARM ABI
template <typename T>
static constexpr std::size_t WordSize() {
static_assert(std::is_trivially_copyable_v<T>);
if constexpr (std::is_pointer_v<T>) {
return 1;
} else if constexpr (std::is_same_v<T, bool>) {
return 1;
} else {
return (sizeof(T) - 1) / 4 + 1;
}
}
// Size in words of the given type in ARM ABI with pointer removed
template <typename T>
static constexpr std::size_t OutputWordSize() {
if constexpr (std::is_pointer_v<T>) {
return WordSize<std::remove_pointer_t<T>>();
} else {
return 0;
}
}
// Describes the register assignments of a SVC
struct SVCABI {
static constexpr std::size_t RegCount = 8;
// Register assignments for input params.
// For example, in[0] records which input param and word r0 stores
std::array<ParamSlot, RegCount> in{};
// Register assignments for output params.
// For example, out[0] records which output param (with pointer removed) and word r0 stores
std::array<ParamSlot, RegCount> out{};
// Search the register assignments for a word of a param
constexpr std::size_t GetRegisterIndex(std::size_t param_index,
std::size_t word_index) const {
for (std::size_t slot = 0; slot < RegCount; ++slot) {
if (in[slot].used && in[slot].param_index == param_index &&
in[slot].word_index == word_index) {
return slot;
}
if (out[slot].used && out[slot].param_index == param_index &&
out[slot].word_index == word_index) {
return slot;
}
}
// should throw, but gcc bugs out here
return 0x12345678;
}
};
// Generates ABI info for a given SVC signature R(Context::)(T...)
template <typename R, typename... T>
static constexpr SVCABI GetSVCABI() {
constexpr std::size_t param_count = sizeof...(T);
std::array<bool, param_count> param_is_output{{std::is_pointer_v<T>...}};
std::array<std::size_t, param_count> param_size{{WordSize<T>()...}};
std::array<std::size_t, param_count> output_param_size{{OutputWordSize<T>()...}};
std::array<bool, param_count> param_need_align{
{(std::is_same_v<T, u64> || std::is_same_v<T, s64>)...}};
// derives ARM ABI, which assigns all params to r0~r3 and then stack
std::array<ParamSlot, 4> armabi_reg{};
std::array<ParamSlot, 4> armabi_stack{};
std::size_t reg_pos = 0;
std::size_t stack_pos = 0;
for (std::size_t i = 0; i < param_count; ++i) {
if (param_need_align[i]) {
reg_pos += reg_pos % 2;
}
for (std::size_t j = 0; j < param_size[i]; ++j) {
if (reg_pos == 4) {
armabi_stack[stack_pos++] = ParamSlot{true, i, j};
} else {
armabi_reg[reg_pos++] = ParamSlot{true, i, j};
}
}
}
// now derives SVC ABI which is a modified version of ARM ABI
SVCABI mod_abi{};
std::size_t out_pos = 0; // next available output register
// assign return value to output registers
if constexpr (!std::is_void_v<R>) {
for (std::size_t j = 0; j < WordSize<R>(); ++j) {
mod_abi.out[out_pos++] = {true, INDEX_RETURN, j};
}
}
// assign output params (pointer-type params) to output registers
for (std::size_t i = 0; i < param_count; ++i) {
if (param_is_output[i]) {
for (std::size_t j = 0; j < output_param_size[i]; ++j) {
mod_abi.out[out_pos++] = ParamSlot{true, i, j};
}
}
}
// assign input params (non-pointer-type params) to input registers
stack_pos = 0; // next available stack param
for (std::size_t k = 0; k < mod_abi.in.size(); ++k) {
if (k < armabi_reg.size() && armabi_reg[k].used &&
!param_is_output[armabi_reg[k].param_index]) {
// If this is within the ARM ABI register range and it is a used input param, use
// the same register position
mod_abi.in[k] = armabi_reg[k];
} else {
// Otherwise, assign it with the next available stack input
// If all stack inputs have been allocated, this would do nothing
// and leaves the slot unused.
// Loop until an input stack param is found
while (stack_pos < armabi_stack.size() &&
(!armabi_stack[stack_pos].used ||
param_is_output[armabi_stack[stack_pos].param_index])) {
++stack_pos;
}
// Reassign the stack param to the free register
if (stack_pos < armabi_stack.size()) {
mod_abi.in[k] = armabi_stack[stack_pos++];
}
}
}
return mod_abi;
}
template <std::size_t param_index, std::size_t word_size, std::size_t... indices>
static constexpr std::array<std::size_t, word_size> GetRegIndicesImpl(
SVCABI abi, std::index_sequence<indices...>) {
return {{abi.GetRegisterIndex(param_index, indices)...}};
}
// Gets assigned register index for the param_index-th param of word_size in a function with
// signature R(Context::)(Ts...)
template <std::size_t param_index, std::size_t word_size, typename R, typename... Ts>
static constexpr std::array<std::size_t, word_size> GetRegIndices() {
constexpr SVCABI abi = GetSVCABI<R, Ts...>();
return GetRegIndicesImpl<param_index, word_size>(abi,
std::make_index_sequence<word_size>{});
}
// Gets the value for the param_index-th param of word_size in a function with signature
// R(Context::)(Ts...)
// T is the param type, which must be a non-pointer as it is an input param.
// Must hold: Ts[param_index] == T
template <std::size_t param_index, typename T, typename R, typename... Ts>
static void GetParam(Context& context, T& value) {
static_assert(!std::is_pointer_v<T>, "T should not be a pointer");
constexpr auto regi = GetRegIndices<param_index, WordSize<T>(), R, Ts...>();
if constexpr (std::is_class_v<T> || std::is_union_v<T>) {
std::array<u32, WordSize<T>()> buf;
for (std::size_t i = 0; i < WordSize<T>(); ++i) {
buf[i] = context.GetReg(regi[i]);
}
std::memcpy(&value, &buf, sizeof(T));
} else if constexpr (WordSize<T>() == 2) {
u64 l = context.GetReg(regi[0]);
u64 h = context.GetReg(regi[1]);
value = static_cast<T>(l | (h << 32));
} else if constexpr (std::is_same_v<T, bool>) {
// TODO: Is this correct or should only test the lowest byte?
value = context.GetReg(regi[0]) != 0;
} else {
value = static_cast<T>(context.GetReg(regi[0]));
}
}
// Sets the value for the param_index-th param of word_size in a function with signature
// R(Context::)(Ts...)
// T is the param type with pointer removed, which was originally a pointer-type output param
// Must hold: (Ts[param_index] == T*) || (R==T && param_index == INDEX_RETURN)
template <std::size_t param_index, typename T, typename R, typename... Ts>
static void SetParam(Context& context, const T& value) {
static_assert(!std::is_pointer_v<T>, "T should have pointer removed before passing in");
constexpr auto regi = GetRegIndices<param_index, WordSize<T>(), R, Ts...>();
if constexpr (std::is_class_v<T> || std::is_union_v<T>) {
std::array<u32, WordSize<T>()> buf;
std::memcpy(&buf, &value, sizeof(T));
for (std::size_t i = 0; i < WordSize<T>(); ++i) {
context.SetReg(regi[i], buf[i]);
}
} else if constexpr (WordSize<T>() == 2) {
u64 u = static_cast<u64>(value);
context.SetReg(regi[0], static_cast<u32>(u & 0xFFFFFFFF));
context.SetReg(regi[1], static_cast<u32>(u >> 32));
} else {
u32 u = static_cast<u32>(value);
context.SetReg(regi[0], u);
}
}
template <typename SVCT, typename R, typename... Ts>
struct WrapPass;
template <typename SVCT, typename R, typename T, typename... Ts>
struct WrapPass<SVCT, R, T, Ts...> {
// Do I/O for the param T in function R(Context::svc)(Us..., T, Ts...) and then move on to
// the next param.
// Us are params whose I/O is already handled.
// T is the current param to do I/O.
// Ts are params whose I/O is not handled yet.
template <typename... Us>
static void Call(Context& context, SVCT svc, Us... u) {
static_assert(std::is_same_v<SVCT, R (Context::*)(Us..., T, Ts...)>);
constexpr std::size_t current_param_index = sizeof...(Us);
if constexpr (std::is_pointer_v<T>) {
using OutputT = std::remove_pointer_t<T>;
OutputT output;
WrapPass<SVCT, R, Ts...>::Call(context, svc, u..., &output);
SetParam<current_param_index, OutputT, R, Us..., T, Ts...>(context, output);
} else {
T input;
GetParam<current_param_index, T, R, Us..., T, Ts...>(context, input);
WrapPass<SVCT, R, Ts...>::Call(context, svc, u..., input);
}
}
};
template <typename SVCT, typename R>
struct WrapPass<SVCT, R /*empty for T, Ts...*/> {
// Call function R(Context::svc)(Us...) and transfer the return value to registers
template <typename... Us>
static void Call(Context& context, SVCT svc, Us... u) {
static_assert(std::is_same_v<SVCT, R (Context::*)(Us...)>);
if constexpr (std::is_void_v<R>) {
(context.*svc)(u...);
} else {
R r = (context.*svc)(u...);
SetParam<INDEX_RETURN, R, R, Us...>(context, r);
}
}
};
template <typename SVCT>
struct WrapPass<SVCT, Result /*empty for T, Ts...*/> {
// Call function R(Context::svc)(Us...) and transfer the return value to registers
template <typename... Us>
static void Call(Context& context, SVCT svc, Us... u) {
static_assert(std::is_same_v<SVCT, Result (Context::*)(Us...)>);
if constexpr (std::is_void_v<Result>) {
(context.*svc)(u...);
} else {
Result r = (context.*svc)(u...);
if (r.IsError()) {
LOG_ERROR(Kernel_SVC, "level={} summary={} module={} description={}",
r.level.ExtractValue(r.raw), r.summary.ExtractValue(r.raw),
r.module.ExtractValue(r.raw), r.description.ExtractValue(r.raw));
}
SetParam<INDEX_RETURN, Result, Result, Us...>(context, r);
}
}
};
template <typename T>
struct WrapHelper;
template <typename R, typename... T>
struct WrapHelper<R (Context::*)(T...)> {
static void Call(Context& context, R (Context::*svc)(T...)) {
WrapPass<decltype(svc), R, T...>::Call(context, svc /*Empty for Us*/);
}
};
};
} // namespace Kernel

View File

@@ -0,0 +1,522 @@
// Copyright 2014 Citra Emulator Project / PPSSPP Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <climits>
#include <boost/serialization/string.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/weak_ptr.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/serialization/boost_flat_set.h"
#include "common/settings.h"
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::Thread)
SERIALIZE_EXPORT_IMPL(Kernel::WakeupCallback)
namespace Kernel {
template <class Archive>
void ThreadManager::serialize(Archive& ar, const unsigned int) {
ar& current_thread;
ar& ready_queue;
ar& wakeup_callback_table;
ar& thread_list;
}
SERIALIZE_IMPL(ThreadManager)
template <class Archive>
void Thread::serialize(Archive& ar, const unsigned int file_version) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& context;
ar& thread_id;
ar& status;
ar& entry_point;
ar& stack_top;
ar& nominal_priority;
ar& current_priority;
ar& last_running_ticks;
ar& processor_id;
ar& tls_address;
ar& held_mutexes;
ar& pending_mutexes;
ar& owner_process;
ar& wait_objects;
ar& wait_address;
ar& name;
ar& wakeup_callback;
}
SERIALIZE_IMPL(Thread)
template <class Archive>
void WakeupCallback::serialize(Archive& ar, const unsigned int) {}
SERIALIZE_IMPL(WakeupCallback)
bool Thread::ShouldWait(const Thread* thread) const {
return status != ThreadStatus::Dead;
}
void Thread::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
Thread::Thread(KernelSystem& kernel, u32 core_id)
: WaitObject(kernel), core_id(core_id), thread_manager(kernel.GetThreadManager(core_id)) {}
Thread::~Thread() = default;
Thread* ThreadManager::GetCurrentThread() const {
return current_thread.get();
}
void Thread::Stop() {
// Cancel any outstanding wakeup events for this thread
thread_manager.kernel.timing.UnscheduleEvent(thread_manager.ThreadWakeupEventType, thread_id);
thread_manager.wakeup_callback_table.erase(thread_id);
// Clean up thread from ready queue
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
if (status == ThreadStatus::Ready) {
thread_manager.ready_queue.remove(current_priority, this);
}
status = ThreadStatus::Dead;
WakeupAllWaitingThreads();
// Clean up any dangling references in objects that this thread was waiting for
for (auto& wait_object : wait_objects) {
wait_object->RemoveWaitingThread(this);
}
wait_objects.clear();
// Release all the mutexes that this thread holds
ReleaseThreadMutexes(this);
// Mark the TLS slot in the thread's page as free.
u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::CITRA_PAGE_SIZE;
u32 tls_slot =
((tls_address - Memory::TLS_AREA_VADDR) % Memory::CITRA_PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
if (auto process = owner_process.lock()) {
process->tls_slots[tls_page].reset(tls_slot);
process->resource_limit->Release(ResourceLimitType::Thread, 1);
}
}
void ThreadManager::SwitchContext(Thread* new_thread) {
Thread* previous_thread = GetCurrentThread();
std::shared_ptr<Process> previous_process = nullptr;
Core::Timing& timing = kernel.timing;
// Save context for previous thread
if (previous_thread) {
previous_process = previous_thread->owner_process.lock();
previous_thread->last_running_ticks = cpu->GetTimer().GetTicks();
cpu->SaveContext(previous_thread->context);
if (previous_thread->status == ThreadStatus::Running) {
// This is only the case when a reschedule is triggered without the current thread
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
ready_queue.push_front(previous_thread->current_priority, previous_thread);
previous_thread->status = ThreadStatus::Ready;
}
}
// Load context of new thread
if (new_thread) {
ASSERT_MSG(new_thread->status == ThreadStatus::Ready,
"Thread must be ready to become running.");
// Cancel any outstanding wakeup events for this thread
timing.UnscheduleEvent(ThreadWakeupEventType, new_thread->thread_id);
current_thread = SharedFrom(new_thread);
ready_queue.remove(new_thread->current_priority, new_thread);
new_thread->status = ThreadStatus::Running;
ASSERT(current_thread->owner_process.lock());
if (previous_process != current_thread->owner_process.lock()) {
kernel.SetCurrentProcessForCPU(current_thread->owner_process.lock(), cpu->GetID());
}
cpu->LoadContext(new_thread->context);
cpu->SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress());
} else {
current_thread = nullptr;
// Note: We do not reset the current process and current page table when idling because
// technically we haven't changed processes, our threads are just paused.
}
}
Thread* ThreadManager::PopNextReadyThread() {
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
if (thread && thread->status == ThreadStatus::Running) {
do {
// We have to do better than the current thread.
// This call returns null when that's not possible.
next = ready_queue.pop_first_better(thread->current_priority);
if (!next) {
// Otherwise just keep going with the current thread
next = thread;
break;
} else if (!next->can_schedule)
unscheduled_ready_queue.push_back(next);
} while (!next->can_schedule);
} else {
do {
next = ready_queue.pop_first();
if (next && !next->can_schedule)
unscheduled_ready_queue.push_back(next);
} while (next && !next->can_schedule);
}
while (!unscheduled_ready_queue.empty()) {
auto t = std::move(unscheduled_ready_queue.back());
ready_queue.push_back(t->current_priority, t);
unscheduled_ready_queue.pop_back();
}
return next;
}
void ThreadManager::WaitCurrentThread_Sleep() {
Thread* thread = GetCurrentThread();
thread->status = ThreadStatus::WaitSleep;
}
void ThreadManager::ExitCurrentThread() {
current_thread->Stop();
std::erase(thread_list, current_thread);
kernel.PrepareReschedule();
}
void ThreadManager::TerminateProcessThreads(std::shared_ptr<Process> process) {
auto iter = thread_list.begin();
while (iter != thread_list.end()) {
auto& thread = *iter;
if (thread == current_thread || thread->owner_process.lock() != process) {
iter++;
continue;
}
if (thread->status != ThreadStatus::WaitSynchAny &&
thread->status != ThreadStatus::WaitSynchAll) {
// TODO: How does the real kernel handle non-waiting threads?
LOG_WARNING(Kernel, "Terminating non-waiting thread {}", thread->thread_id);
}
thread->Stop();
iter = thread_list.erase(iter);
}
// Kill the current thread last, if applicable.
if (current_thread != nullptr && current_thread->owner_process.lock() == process) {
ExitCurrentThread();
}
}
void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) {
std::shared_ptr<Thread> thread = SharedFrom(wakeup_callback_table.at(thread_id));
if (thread == nullptr) {
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", thread_id);
return;
}
if (thread->status == ThreadStatus::WaitSynchAny ||
thread->status == ThreadStatus::WaitSynchAll || thread->status == ThreadStatus::WaitArb ||
thread->status == ThreadStatus::WaitHleEvent) {
// Invoke the wakeup callback before clearing the wait objects
if (thread->wakeup_callback)
thread->wakeup_callback->WakeUp(ThreadWakeupReason::Timeout, thread, nullptr);
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects)
object->RemoveWaitingThread(thread.get());
thread->wait_objects.clear();
}
thread->ResumeFromWait();
}
void Thread::WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode) {
// Don't schedule a wakeup if the thread wants to wait forever
if (nanoseconds == -1)
return;
std::size_t core = thread_safe_mode ? core_id : std::numeric_limits<std::size_t>::max();
thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds),
thread_manager.ThreadWakeupEventType, thread_id,
core, thread_safe_mode);
}
void Thread::ResumeFromWait() {
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
switch (status) {
case ThreadStatus::WaitSynchAll:
case ThreadStatus::WaitSynchAny:
case ThreadStatus::WaitHleEvent:
case ThreadStatus::WaitArb:
case ThreadStatus::WaitSleep:
case ThreadStatus::WaitIPC:
case ThreadStatus::Dormant:
break;
case ThreadStatus::Ready:
// The thread's wakeup callback must have already been cleared when the thread was first
// awoken.
ASSERT(wakeup_callback == nullptr);
// If the thread is waiting on multiple wait objects, it might be awoken more than once
// before actually resuming. We can ignore subsequent wakeups if the thread status has
// already been set to ThreadStatus::Ready.
return;
case ThreadStatus::Running:
DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId());
return;
case ThreadStatus::Dead:
// This should never happen, as threads must complete before being stopped.
DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.",
GetObjectId());
return;
}
wakeup_callback = nullptr;
thread_manager.ready_queue.push_back(current_priority, this);
status = ThreadStatus::Ready;
thread_manager.kernel.PrepareReschedule();
}
void ThreadManager::DebugThreadQueue() {
Thread* thread = GetCurrentThread();
if (!thread) {
LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD");
} else {
LOG_DEBUG(Kernel, "0x{:02X} {} (current)", thread->current_priority,
GetCurrentThread()->GetObjectId());
}
for (auto& t : thread_list) {
u32 priority = ready_queue.contains(t.get());
if (priority != UINT_MAX) {
LOG_DEBUG(Kernel, "0x{:02X} {}", priority, t->GetObjectId());
}
}
}
/**
* Resets a thread context, making it ready to be scheduled and run by the CPU
* @param context Thread context to reset
* @param stack_top Address of the top of the stack
* @param entry_point Address of entry point for execution
* @param arg User argument for thread
*/
static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, u32 stack_top,
u32 entry_point, u32 arg) {
context.cpu_registers[0] = arg;
context.SetProgramCounter(entry_point);
context.SetStackPointer(stack_top);
context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode
}
ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
std::string name, VAddr entry_point, u32 priority, u32 arg, s32 processor_id, VAddr stack_top,
std::shared_ptr<Process> owner_process, bool make_ready) {
// Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > ThreadPrioLowest) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
return ResultOutOfRange;
}
if (processor_id > ThreadProcessorIdMax) {
LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
return ResultOutOfRangeKernel;
}
// TODO(yuriks): Other checks, returning 0xD9001BEA
if (!memory.IsValidVirtualAddress(*owner_process, entry_point)) {
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point);
// TODO: Verify error
return Result(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
}
auto thread = std::make_shared<Thread>(*this, processor_id);
thread_managers[processor_id]->thread_list.push_back(thread);
thread_managers[processor_id]->ready_queue.prepare(priority);
thread->thread_id = NewThreadId();
thread->status = ThreadStatus::Dormant;
thread->entry_point = entry_point;
thread->stack_top = stack_top;
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = timing.GetTimer(processor_id)->GetTicks();
thread->processor_id = processor_id;
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
thread->owner_process = owner_process;
CASCADE_RESULT(thread->tls_address, owner_process->AllocateThreadLocalStorage());
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg);
if (make_ready) {
thread_managers[processor_id]->ready_queue.push_back(thread->current_priority,
thread.get());
thread->status = ThreadStatus::Ready;
}
return thread;
}
void Thread::SetPriority(u32 priority) {
ASSERT_MSG(priority <= ThreadPrioLowest && priority >= ThreadPrioHighest,
"Invalid priority value.");
// If thread was ready, adjust queues
if (status == ThreadStatus::Ready)
thread_manager.ready_queue.move(this, current_priority, priority);
else
thread_manager.ready_queue.prepare(priority);
nominal_priority = current_priority = priority;
}
void Thread::UpdatePriority() {
u32 best_priority = nominal_priority;
for (auto& mutex : held_mutexes) {
if (mutex->priority < best_priority)
best_priority = mutex->priority;
}
BoostPriority(best_priority);
}
void Thread::BoostPriority(u32 priority) {
// If thread was ready, adjust queues
if (status == ThreadStatus::Ready)
thread_manager.ready_queue.move(this, current_priority, priority);
else
thread_manager.ready_queue.prepare(priority);
current_priority = priority;
}
std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
std::shared_ptr<Process> owner_process) {
constexpr s64 sleep_app_thread_ns = 2'600'000'000LL;
constexpr u32 system_module_tid_high = 0x00040130;
const bool is_lle_service =
static_cast<u32>(owner_process->codeset->program_id >> 32) == system_module_tid_high;
s64 sleep_time_ns = 0;
if (!is_lle_service && kernel.GetAppMainThreadExtendedSleep()) {
if (Settings::values.delay_start_for_lle_modules) {
sleep_time_ns = sleep_app_thread_ns;
}
kernel.SetAppMainThreadExtendedSleep(false);
}
// Initialize new "main" thread
auto thread_res =
kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor,
Memory::HEAP_VADDR_END, owner_process, sleep_time_ns == 0);
std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
thread->context.fpscr =
FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
if (sleep_time_ns != 0) {
thread->status = ThreadStatus::WaitSleep;
thread->WakeAfterDelay(sleep_time_ns);
}
// Note: The newly created thread will be run when the scheduler fires.
return thread;
}
bool ThreadManager::HaveReadyThreads() {
return ready_queue.get_first() != nullptr;
}
void ThreadManager::Reschedule() {
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
if (cur && next) {
LOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
} else if (cur) {
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
} else if (next) {
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
} else {
LOG_TRACE(Kernel, "context switch idle -> idle, do nothing");
return;
}
SwitchContext(next);
}
void Thread::SetWaitSynchronizationResult(Result result) {
context.cpu_registers[0] = result.raw;
}
void Thread::SetWaitSynchronizationOutput(s32 output) {
context.cpu_registers[1] = output;
}
s32 Thread::GetWaitObjectIndex(const WaitObject* object) const {
ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
const auto match = std::find_if(wait_objects.rbegin(), wait_objects.rend(),
[object](const auto& p) { return p.get() == object; });
return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1);
}
VAddr Thread::GetCommandBufferAddress() const {
// Offset from the start of TLS at which the IPC command buffer begins.
constexpr u32 command_header_offset = 0x80;
return GetTLSAddress() + command_header_offset;
}
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) {
ThreadWakeupEventType = kernel.timing.RegisterEvent(
"ThreadWakeupCallback_" + std::to_string(core_id),
[this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); });
}
ThreadManager::~ThreadManager() {
for (auto& t : thread_list) {
t->Stop();
}
}
std::span<const std::shared_ptr<Thread>> ThreadManager::GetThreadList() {
return thread_list;
}
} // namespace Kernel

View File

@@ -0,0 +1,364 @@
// Copyright 2014 Citra Emulator Project / PPSSPP Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/container/flat_set.hpp>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core_timing.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
namespace Kernel {
class Mutex;
class Process;
enum ThreadPriority : u32 {
ThreadPrioHighest = 0, ///< Highest thread priority
ThreadPrioUserlandMax = 24, ///< Highest thread priority for userland apps
ThreadPrioDefault = 48, ///< Default thread priority for userland apps
ThreadPrioLowest = 63, ///< Lowest thread priority
};
enum ThreadProcessorId : s32 {
ThreadProcessorIdDefault = -2, ///< Run thread on default core specified by exheader
ThreadProcessorIdAll = -1, ///< Run thread on either core
ThreadProcessorId0 = 0, ///< Run thread on core 0 (AppCore)
ThreadProcessorId1 = 1, ///< Run thread on core 1 (SysCore)
ThreadProcessorId2 = 2, ///< Run thread on core 2 (additional n3ds core)
ThreadProcessorId3 = 3, ///< Run thread on core 3 (additional n3ds core)
ThreadProcessorIdMax = 4, ///< Processor ID must be less than this
};
enum class ThreadStatus {
Running, ///< Currently running
Ready, ///< Ready to run
WaitArb, ///< Waiting on an address arbiter
WaitSleep, ///< Waiting due to a SleepThread SVC
WaitIPC, ///< Waiting for the reply from an IPC request
WaitSynchAny, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
WaitSynchAll, ///< Waiting due to WaitSynchronizationN with wait_all = true
WaitHleEvent, ///< Waiting due to an HLE handler pausing the thread
Dormant, ///< Created but not yet made ready
Dead ///< Run to completion, or forcefully terminated
};
enum class ThreadWakeupReason {
Signal, // The thread was woken up by WakeupAllWaitingThreads due to an object signal.
Timeout // The thread was woken up due to a wait timeout.
};
class Thread;
class WakeupCallback {
public:
virtual ~WakeupCallback() = default;
virtual void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) = 0;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
class ThreadManager {
public:
explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id);
~ThreadManager();
/**
* Gets the current thread
*/
Thread* GetCurrentThread() const;
/**
* Reschedules to the next available thread (call after current thread is suspended)
*/
void Reschedule();
/**
* Prints the thread queue for debugging purposes
*/
void DebugThreadQueue();
/**
* Returns whether there are any threads that are ready to run.
*/
bool HaveReadyThreads();
/**
* Waits the current thread on a sleep
*/
void WaitCurrentThread_Sleep();
/**
* Stops the current thread and removes it from the thread_list
*/
void ExitCurrentThread();
/**
* Terminates all threads belonging to a specific process.
*/
void TerminateProcessThreads(std::shared_ptr<Process> process);
/**
* Get a const reference to the thread list for debug use
*/
std::span<const std::shared_ptr<Thread>> GetThreadList();
void SetCPU(Core::ARM_Interface& cpu_) {
cpu = &cpu_;
}
private:
/**
* Switches the CPU's active thread context to that of the specified thread
* @param new_thread The thread to switch to
*/
void SwitchContext(Thread* new_thread);
/**
* Pops and returns the next thread from the thread queue
* @return A pointer to the next ready thread
*/
Thread* PopNextReadyThread();
/**
* Callback that will wake up the thread it was scheduled for
* @param thread_id The ID of the thread that's been awoken
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
*/
void ThreadWakeupCallback(u64 thread_id, s64 cycles_late);
Kernel::KernelSystem& kernel;
Core::ARM_Interface* cpu;
std::shared_ptr<Thread> current_thread;
Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue;
std::deque<Thread*> unscheduled_ready_queue;
std::unordered_map<u64, Thread*> wakeup_callback_table;
/// Event type for the thread wake up event
Core::TimingEventType* ThreadWakeupEventType = nullptr;
// Lists all threadsthat aren't deleted.
std::vector<std::shared_ptr<Thread>> thread_list;
friend class Thread;
friend class KernelSystem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
class Thread final : public WaitObject {
public:
explicit Thread(KernelSystem&, u32 core_id);
~Thread() override;
std::string GetName() const override {
return name;
}
std::string GetTypeName() const override {
return "Thread";
}
static constexpr HandleType HANDLE_TYPE = HandleType::Thread;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
/**
* Gets the thread's current priority
* @return The current thread's priority
*/
u32 GetPriority() const {
return current_priority;
}
/**
* Sets the thread's current priority
* @param priority The new priority
*/
void SetPriority(u32 priority);
/**
* Boost's a thread's priority to the best priority among the thread's held mutexes.
* This prevents priority inversion via priority inheritance.
*/
void UpdatePriority();
/**
* Temporarily boosts the thread's priority until the next time it is scheduled
* @param priority The new priority
*/
void BoostPriority(u32 priority);
/**
* Gets the thread's thread ID
* @return The thread's ID
*/
u32 GetThreadId() const {
return thread_id;
}
/**
* Resumes a thread from waiting
*/
void ResumeFromWait();
/**
* Schedules an event to wake up the specified thread after the specified delay
* @param nanoseconds The time this thread will be allowed to sleep for
* @param thread_safe_mode Set to true if called from a different thread than the emulator
* thread, such as coroutines.
*/
void WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode = false);
/**
* Sets the result after the thread awakens (from either WaitSynchronization SVC)
* @param result Value to set to the returned result
*/
void SetWaitSynchronizationResult(Result result);
/**
* Sets the output parameter value after the thread awakens (from WaitSynchronizationN SVC only)
* @param output Value to set to the output parameter
*/
void SetWaitSynchronizationOutput(s32 output);
/**
* Retrieves the index that this particular object occupies in the list of objects
* that the thread passed to WaitSynchronizationN, starting the search from the last element.
* It is used to set the output value of WaitSynchronizationN when the thread is awakened.
* When a thread wakes up due to an object signal, the kernel will use the index of the last
* matching object in the wait objects list in case of having multiple instances of the same
* object in the list.
* @param object Object to query the index of.
*/
s32 GetWaitObjectIndex(const WaitObject* object) const;
/**
* Stops a thread, invalidating it from further use
*/
void Stop();
/**
* Returns the Thread Local Storage address of the current thread
* @returns VAddr of the thread's TLS
*/
VAddr GetTLSAddress() const {
return tls_address;
}
/**
* Returns the address of the current thread's command buffer, located in the TLS.
* @returns VAddr of the thread's command buffer.
*/
VAddr GetCommandBufferAddress() const;
/**
* Returns whether this thread is waiting for all the objects in
* its wait list to become ready, as a result of a WaitSynchronizationN call
* with wait_all = true.
*/
bool IsSleepingOnWaitAll() const {
return status == ThreadStatus::WaitSynchAll;
}
Core::ARM_Interface::ThreadContext context{};
u32 thread_id;
bool can_schedule{true};
ThreadStatus status;
VAddr entry_point;
VAddr stack_top;
u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application
u32 current_priority; ///< Current thread priority, can be temporarily changed
u64 last_running_ticks; ///< CPU tick when thread was last running
s32 processor_id;
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
/// Mutexes currently held by this thread, which will be released when it exits.
boost::container::flat_set<std::shared_ptr<Mutex>> held_mutexes{};
/// Mutexes that this thread is currently waiting for.
boost::container::flat_set<std::shared_ptr<Mutex>> pending_mutexes{};
std::weak_ptr<Process> owner_process{}; ///< Process that owns this thread
/// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization1/N.
std::vector<std::shared_ptr<WaitObject>> wait_objects{};
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
std::string name{};
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
/// was waiting via WaitSynchronizationN then the object will be the last object that became
/// available. In case of a timeout, the object will be nullptr.
std::shared_ptr<WakeupCallback> wakeup_callback{};
const u32 core_id;
private:
ThreadManager& thread_manager;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
/**
* Sets up the primary application thread
* @param kernel The kernel instance on which the thread is created
* @param entry_point The address at which the thread should start execution
* @param priority The priority to give the main thread
* @param owner_process The parent process for the main thread
* @return A shared pointer to the main thread
*/
std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
std::shared_ptr<Process> owner_process);
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Thread)
BOOST_CLASS_EXPORT_KEY(Kernel::WakeupCallback)
namespace boost::serialization {
template <class Archive>
void save_construct_data(Archive& ar, const Kernel::Thread* t, const unsigned int) {
ar << t->core_id;
}
template <class Archive>
void load_construct_data(Archive& ar, Kernel::Thread* t, const unsigned int) {
u32 core_id;
ar >> core_id;
::new (t) Kernel::Thread(Core::Global<Kernel::KernelSystem>(), core_id);
}
} // namespace boost::serialization

View File

@@ -0,0 +1,141 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/string.hpp>
#include <boost/serialization/unordered_map.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
SERIALIZE_EXPORT_IMPL(Kernel::Timer)
namespace Kernel {
Timer::Timer(KernelSystem& kernel)
: WaitObject(kernel), kernel(kernel), timer_manager(kernel.GetTimerManager()) {}
Timer::~Timer() {
Cancel();
timer_manager.timer_callback_table.erase(callback_id);
if (resource_limit) {
resource_limit->Release(ResourceLimitType::Timer, 1);
}
}
std::shared_ptr<Timer> KernelSystem::CreateTimer(ResetType reset_type, std::string name) {
auto timer = std::make_shared<Timer>(*this);
timer->reset_type = reset_type;
timer->signaled = false;
timer->name = std::move(name);
timer->initial_delay = 0;
timer->interval_delay = 0;
timer->callback_id = ++timer_manager->next_timer_callback_id;
timer_manager->timer_callback_table[timer->callback_id] = timer.get();
return timer;
}
bool Timer::ShouldWait(const Thread* thread) const {
return !signaled;
}
void Timer::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
if (reset_type == ResetType::OneShot)
signaled = false;
}
void Timer::Set(s64 initial, s64 interval) {
// Ensure we get rid of any previous scheduled event
Cancel();
initial_delay = initial;
interval_delay = interval;
if (initial == 0) {
// Immediately invoke the callback
Signal(0);
} else {
kernel.timing.ScheduleEvent(nsToCycles(initial), timer_manager.timer_callback_event_type,
callback_id);
}
}
void Timer::Cancel() {
kernel.timing.UnscheduleEvent(timer_manager.timer_callback_event_type, callback_id);
}
void Timer::Clear() {
signaled = false;
}
void Timer::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
if (reset_type == ResetType::Pulse)
signaled = false;
}
void Timer::Signal(s64 cycles_late) {
LOG_TRACE(Kernel, "Timer {} fired", GetObjectId());
signaled = true;
// Resume all waiting threads
WakeupAllWaitingThreads();
if (interval_delay != 0) {
// Reschedule the timer with the interval delay
kernel.timing.ScheduleEvent(nsToCycles(interval_delay) - cycles_late,
timer_manager.timer_callback_event_type, callback_id);
}
}
template <class Archive>
void Timer::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& reset_type;
ar& initial_delay;
ar& interval_delay;
ar& signaled;
ar& name;
ar& callback_id;
ar& resource_limit;
}
SERIALIZE_IMPL(Timer)
/// The timer callback event, called when a timer is fired
void TimerManager::TimerCallback(u64 callback_id, s64 cycles_late) {
std::shared_ptr<Timer> timer = SharedFrom(timer_callback_table.at(callback_id));
if (timer == nullptr) {
LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016x}", callback_id);
return;
}
timer->Signal(cycles_late);
}
TimerManager::TimerManager(Core::Timing& timing) : timing(timing) {
timer_callback_event_type =
timing.RegisterEvent("TimerCallback", [this](u64 thread_id, s64 cycle_late) {
TimerCallback(thread_id, cycle_late);
});
}
template <class Archive>
void TimerManager::serialize(Archive& ar, const unsigned int) {
ar& next_timer_callback_id;
ar& timer_callback_table;
}
SERIALIZE_IMPL(TimerManager)
} // namespace Kernel

124
src/core/hle/kernel/timer.h Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/core_timing.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
namespace Core {
class Timing;
}
namespace Kernel {
class TimerManager {
public:
TimerManager(Core::Timing& timing);
private:
/// The timer callback event, called when a timer is fired
void TimerCallback(u64 callback_id, s64 cycles_late);
Core::Timing& timing;
/// The event type of the generic timer callback event
Core::TimingEventType* timer_callback_event_type = nullptr;
u64 next_timer_callback_id = 0;
std::unordered_map<u64, Timer*> timer_callback_table;
friend class Timer;
friend class KernelSystem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
class ResourceLimit;
class Timer final : public WaitObject {
public:
explicit Timer(KernelSystem& kernel);
~Timer() override;
std::string GetTypeName() const override {
return "Timer";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Timer;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
ResetType GetResetType() const {
return reset_type;
}
u64 GetInitialDelay() const {
return initial_delay;
}
u64 GetIntervalDelay() const {
return interval_delay;
}
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
void WakeupAllWaitingThreads() override;
/**
* Starts the timer, with the specified initial delay and interval.
* @param initial Delay until the timer is first fired
* @param interval Delay until the timer is fired after the first time
*/
void Set(s64 initial, s64 interval);
void Cancel();
void Clear();
/**
* Signals the timer, waking up any waiting threads and rescheduling it
* for the next interval.
* This method should not be called from outside the timer callback handler,
* lest multiple callback events get scheduled.
*/
void Signal(s64 cycles_late);
std::shared_ptr<ResourceLimit> resource_limit;
private:
ResetType reset_type; ///< The ResetType of this timer
u64 initial_delay; ///< The delay until the timer fires for the first time
u64 interval_delay; ///< The delay until the timer fires after the first time
bool signaled; ///< Whether the timer has been signaled or not
std::string name; ///< Name of timer (optional)
/// ID used as userdata to reference this object when inserting into the CoreTiming queue.
u64 callback_id;
KernelSystem& kernel;
TimerManager& timer_manager;
friend class KernelSystem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Timer)
CONSTRUCT_KERNEL_OBJECT(Kernel::Timer)

View File

@@ -0,0 +1,410 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <iterator>
#include <boost/serialization/map.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/split_member.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/plgldr/plgldr.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::VirtualMemoryArea)
namespace Kernel {
static const char* GetMemoryStateName(MemoryState state) {
static const char* names[] = {
"Free", "Reserved", "IO", "Static", "Code", "Private",
"Shared", "Continuous", "Aliased", "Alias", "AliasCode", "Locked",
};
return names[(int)state];
}
bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
ASSERT(base + size == next.base);
if (permissions != next.permissions || meminfo_state != next.meminfo_state ||
type != next.type) {
return false;
}
if (type == VMAType::BackingMemory &&
backing_memory.GetPtr() + size != next.backing_memory.GetPtr()) {
return false;
}
return true;
}
template <class Archive>
void VirtualMemoryArea::serialize(Archive& ar, const unsigned int) {
ar& base;
ar& size;
ar& type;
ar& permissions;
ar& meminfo_state;
ar& backing_memory;
}
SERIALIZE_IMPL(VirtualMemoryArea)
VMManager::VMManager(Memory::MemorySystem& memory, Kernel::Process& proc)
: page_table(std::make_shared<Memory::PageTable>()), memory(memory), process(proc) {
Reset();
}
VMManager::~VMManager() = default;
void VMManager::Reset() {
ASSERT(!is_locked);
vma_map.clear();
// Initialize the map with a single free region covering the entire managed space.
VirtualMemoryArea initial_vma;
initial_vma.size = MAX_ADDRESS;
vma_map.emplace(initial_vma.base, initial_vma);
page_table->Clear();
UpdatePageTableForVMA(initial_vma);
}
VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
if (target >= MAX_ADDRESS) {
return vma_map.end();
} else {
return std::prev(vma_map.upper_bound(target));
}
}
ResultVal<VAddr> VMManager::MapBackingMemoryToBase(VAddr base, u32 region_size, MemoryRef memory,
u32 size, MemoryState state) {
ASSERT(!is_locked);
// Find the first Free VMA.
VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
if (vma.second.type != VMAType::Free)
return false;
VAddr vma_end = vma.second.base + vma.second.size;
return vma_end > base && vma_end >= base + size;
});
VAddr target = std::max(base, vma_handle->second.base);
// Do not try to allocate the block if there are no available addresses within the desired
// region.
if (vma_handle == vma_map.end() || target + size > base + region_size) {
return Result(ErrorDescription::OutOfMemory, ErrorModule::Kernel,
ErrorSummary::OutOfResource, ErrorLevel::Permanent);
}
auto result = MapBackingMemory(target, memory, size, state);
if (result.Failed())
return result.Code();
return target;
}
ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, MemoryRef memory,
u32 size, MemoryState state) {
ASSERT(!is_locked);
ASSERT(memory.GetPtr() != nullptr);
// This is the appropriately sized VMA that will turn into our allocation.
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
final_vma.type = VMAType::BackingMemory;
final_vma.permissions = VMAPermission::ReadWrite;
final_vma.meminfo_state = state;
final_vma.backing_memory = memory;
UpdatePageTableForVMA(final_vma);
return MergeAdjacent(vma_handle);
}
Result VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state,
VMAPermission expected_perms, MemoryState new_state,
VMAPermission new_perms) {
if (is_locked) {
return ResultSuccess;
}
VAddr target_end = target + size;
VMAIter begin_vma = StripIterConstness(FindVMA(target));
VMAIter i_end = vma_map.lower_bound(target_end);
if (begin_vma == vma_map.end())
return ResultInvalidAddress;
for (auto i = begin_vma; i != i_end; ++i) {
auto& vma = i->second;
if (vma.meminfo_state != expected_state) {
return ResultInvalidAddressState;
}
u32 perms = static_cast<u32>(expected_perms);
if ((static_cast<u32>(vma.permissions) & perms) != perms) {
return ResultInvalidAddressState;
}
}
CASCADE_RESULT(auto vma, CarveVMARange(target, size));
const VMAIter end = vma_map.end();
// The comparison against the end of the range must be done using addresses since VMAs can be
// merged during this process, causing invalidation of the iterators.
while (vma != end && vma->second.base < target_end) {
vma->second.permissions = new_perms;
vma->second.meminfo_state = new_state;
UpdatePageTableForVMA(vma->second);
vma = std::next(MergeAdjacent(vma));
}
return ResultSuccess;
}
VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
ASSERT(!is_locked);
VirtualMemoryArea& vma = vma_handle->second;
vma.type = VMAType::Free;
vma.permissions = VMAPermission::None;
vma.meminfo_state = MemoryState::Free;
vma.backing_memory = nullptr;
UpdatePageTableForVMA(vma);
return MergeAdjacent(vma_handle);
}
Result VMManager::UnmapRange(VAddr target, u32 size) {
ASSERT(!is_locked);
CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size));
const VAddr target_end = target + size;
const VMAIter end = vma_map.end();
// The comparison against the end of the range must be done using addresses since VMAs can be
// merged during this process, causing invalidation of the iterators.
while (vma != end && vma->second.base < target_end) {
vma = std::next(Unmap(vma));
}
ASSERT(FindVMA(target)->second.size >= size);
return ResultSuccess;
}
VMManager::VMAHandle VMManager::Reprotect(VMAHandle vma_handle, VMAPermission new_perms) {
ASSERT(!is_locked);
VMAIter iter = StripIterConstness(vma_handle);
VirtualMemoryArea& vma = iter->second;
vma.permissions = new_perms;
UpdatePageTableForVMA(vma);
return MergeAdjacent(iter);
}
Result VMManager::ReprotectRange(VAddr target, u32 size, VMAPermission new_perms) {
ASSERT(!is_locked);
CASCADE_RESULT(VMAIter vma, CarveVMARange(target, size));
const VAddr target_end = target + size;
const VMAIter end = vma_map.end();
// The comparison against the end of the range must be done using addresses since VMAs can be
// merged during this process, causing invalidation of the iterators.
while (vma != end && vma->second.base < target_end) {
vma = std::next(StripIterConstness(Reprotect(vma, new_perms)));
}
return ResultSuccess;
}
void VMManager::LogLayout(Common::Log::Level log_level) const {
for (const auto& p : vma_map) {
const VirtualMemoryArea& vma = p.second;
LOG_GENERIC(Common::Log::Class::Kernel, log_level, "{:08X} - {:08X} size: {:8X} {}{}{} {}",
vma.base, vma.base + vma.size, vma.size,
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
(u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
GetMemoryStateName(vma.meminfo_state));
}
}
void VMManager::Unlock() {
is_locked = false;
}
VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) {
// This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
// non-const access to its container.
return vma_map.erase(iter, iter); // Erases an empty range of elements
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u32 size) {
ASSERT_MSG((size & Memory::CITRA_PAGE_MASK) == 0, "non-page aligned size: {:#10X}", size);
ASSERT_MSG((base & Memory::CITRA_PAGE_MASK) == 0, "non-page aligned base: {:#010X}", base);
VMAIter vma_handle = StripIterConstness(FindVMA(base));
if (vma_handle == vma_map.end()) {
// Target address is outside the range managed by the kernel
return ResultInvalidAddress;
}
const VirtualMemoryArea& vma = vma_handle->second;
if (vma.type != VMAType::Free) {
// Region is already allocated
return ResultInvalidAddressState;
}
const VAddr start_in_vma = base - vma.base;
const VAddr end_in_vma = start_in_vma + size;
if (end_in_vma > vma.size) {
// Requested allocation doesn't fit inside VMA
return ResultInvalidAddressState;
}
if (end_in_vma != vma.size) {
// Split VMA at the end of the allocated region
SplitVMA(vma_handle, end_in_vma);
}
if (start_in_vma != 0) {
// Split VMA at the start of the allocated region
vma_handle = SplitVMA(vma_handle, start_in_vma);
}
return vma_handle;
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u32 size) {
ASSERT_MSG((size & Memory::CITRA_PAGE_MASK) == 0, "non-page aligned size: {:#10X}", size);
ASSERT_MSG((target & Memory::CITRA_PAGE_MASK) == 0, "non-page aligned base: {:#010X}", target);
const VAddr target_end = target + size;
ASSERT(target_end >= target);
ASSERT(target_end <= MAX_ADDRESS);
ASSERT(size > 0);
VMAIter begin_vma = StripIterConstness(FindVMA(target));
const VMAIter i_end = vma_map.lower_bound(target_end);
if (std::any_of(begin_vma, i_end,
[](const auto& entry) { return entry.second.type == VMAType::Free; })) {
return ResultInvalidAddressState;
}
if (target != begin_vma->second.base) {
begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
}
VMAIter end_vma = StripIterConstness(FindVMA(target_end));
if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
}
return begin_vma;
}
VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u32 offset_in_vma) {
VirtualMemoryArea& old_vma = vma_handle->second;
VirtualMemoryArea new_vma = old_vma; // Make a copy of the VMA
// For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
// a bug. This restriction might be removed later.
ASSERT(offset_in_vma < old_vma.size);
ASSERT(offset_in_vma > 0);
old_vma.size = offset_in_vma;
new_vma.base += offset_in_vma;
new_vma.size -= offset_in_vma;
switch (new_vma.type) {
case VMAType::Free:
break;
case VMAType::BackingMemory:
new_vma.backing_memory += offset_in_vma;
break;
}
ASSERT(old_vma.CanBeMergedWith(new_vma));
return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
}
VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
const VMAIter next_vma = std::next(iter);
if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
iter->second.size += next_vma->second.size;
vma_map.erase(next_vma);
}
if (iter != vma_map.begin()) {
VMAIter prev_vma = std::prev(iter);
if (prev_vma->second.CanBeMergedWith(iter->second)) {
prev_vma->second.size += iter->second.size;
vma_map.erase(iter);
iter = prev_vma;
}
}
return iter;
}
void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
switch (vma.type) {
case VMAType::Free:
memory.UnmapRegion(*page_table, vma.base, vma.size);
break;
case VMAType::BackingMemory:
memory.MapMemoryRegion(*page_table, vma.base, vma.size, vma.backing_memory);
break;
}
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
if (plgldr)
plgldr->OnMemoryChanged(process, Core::System::GetInstance().Kernel());
}
ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
u32 size) {
std::vector<std::pair<MemoryRef, u32>> backing_blocks;
VAddr interval_target = address;
while (interval_target != address + size) {
auto vma = FindVMA(interval_target);
if (vma->second.type != VMAType::BackingMemory) {
LOG_ERROR(Kernel, "Trying to use already freed memory");
return ResultInvalidAddressState;
}
VAddr interval_end = std::min(address + size, vma->second.base + vma->second.size);
u32 interval_size = interval_end - interval_target;
auto backing_memory = vma->second.backing_memory + (interval_target - vma->second.base);
backing_blocks.push_back({backing_memory, interval_size});
interval_target += interval_size;
}
return backing_blocks;
}
template <class Archive>
void VMManager::serialize(Archive& ar, const unsigned int) {
ar& vma_map;
ar& page_table;
if (Archive::is_loading::value) {
is_locked = true;
}
}
SERIALIZE_IMPL(VMManager)
} // namespace Kernel

View File

@@ -0,0 +1,237 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/result.h"
#include "core/memory.h"
namespace Kernel {
enum class VMAType : u8 {
/// VMA represents an unmapped region of the address space.
Free,
/// VMA is backed by a raw, unmanaged pointer.
BackingMemory,
};
/// Permissions for mapped memory blocks
enum class VMAPermission : u8 {
None = 0,
Read = 1,
Write = 2,
Execute = 4,
ReadWrite = Read | Write,
ReadExecute = Read | Execute,
WriteExecute = Write | Execute,
ReadWriteExecute = Read | Write | Execute,
};
/// Set of values returned in MemoryInfo.state by svcQueryMemory.
enum class MemoryState : u8 {
Free = 0,
Reserved = 1,
IO = 2,
Static = 3,
Code = 4,
Private = 5,
Shared = 6,
Continuous = 7,
Aliased = 8,
Alias = 9,
AliasCode = 10,
Locked = 11,
};
/**
* Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
* with homogeneous attributes across its extents. In this particular implementation each VMA is
* also backed by a single host memory allocation.
*/
struct VirtualMemoryArea {
/// Virtual base address of the region.
VAddr base = 0;
/// Size of the region.
u32 size = 0;
VMAType type = VMAType::Free;
VMAPermission permissions = VMAPermission::None;
/// Tag returned by svcQueryMemory. Not otherwise used.
MemoryState meminfo_state = MemoryState::Free;
/// Settings for type = BackingMemory
/// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed.
MemoryRef backing_memory{};
/// Tests if this area can be merged to the right with `next`.
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
/**
* Manages a process' virtual addressing space. This class maintains a list of allocated and free
* regions in the address space, along with their attributes, and allows kernel clients to
* manipulate it, adjusting the page table to match.
*
* This is similar in idea and purpose to the VM manager present in operating system kernels, with
* the main difference being that it doesn't have to support swapping or memory mapping of files.
* The implementation is also simplified by not having to allocate page frames. See these articles
* about the Linux kernel for an explantion of the concept and implementation:
* - http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory/
* - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
*/
class VMManager final {
public:
/**
* The maximum amount of address space managed by the kernel. Addresses above this are never
* used.
* @note This is the limit used by the New 3DS kernel. Old 3DS used 0x20000000.
*/
static const u32 MAX_ADDRESS = 0x40000000;
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
* `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
* merged when possible so that no two similar and adjacent regions exist that have not been
* merged.
*/
std::map<VAddr, VirtualMemoryArea> vma_map;
using VMAHandle = decltype(vma_map)::const_iterator;
explicit VMManager(Memory::MemorySystem& memory, Kernel::Process& proc);
~VMManager();
/// Clears the address space map, re-initializing with a single free area.
void Reset();
/// Finds the VMA in which the given address is included in, or `vma_map.end()`.
VMAHandle FindVMA(VAddr target) const;
// TODO(yuriks): Should these functions actually return the handle?
/**
* Maps part of a ref-counted block of memory at the first free address after the given base.
*
* @param base The base address to start the mapping at.
* @param region_size The max size of the region from where we'll try to find an address.
* @param memory The memory to be mapped.
* @param size Size of the mapping.
* @param state MemoryState tag to attach to the VMA.
* @returns The address at which the memory was mapped.
*/
ResultVal<VAddr> MapBackingMemoryToBase(VAddr base, u32 region_size, MemoryRef memory, u32 size,
MemoryState state);
/**
* Maps an unmanaged host memory pointer at a given address.
*
* @param target The guest address to start the mapping at.
* @param memory The memory to be mapped.
* @param size Size of the mapping.
* @param state MemoryState tag to attach to the VMA.
*/
ResultVal<VMAHandle> MapBackingMemory(VAddr target, MemoryRef memory, u32 size,
MemoryState state);
/**
* Updates the memory state and permissions of the specified range. The range's original memory
* state and permissions must match the `expected` parameters.
*
* @param target The guest address of the beginning of the range.
* @param size The size of the range
* @param expected_state Expected MemoryState of the range.
* @param expected_perms Expected VMAPermission of the range.
* @param new_state New MemoryState for the range.
* @param new_perms New VMAPermission for the range.
*/
Result ChangeMemoryState(VAddr target, u32 size, MemoryState expected_state,
VMAPermission expected_perms, MemoryState new_state,
VMAPermission new_perms);
/// Unmaps a range of addresses, splitting VMAs as necessary.
Result UnmapRange(VAddr target, u32 size);
/// Changes the permissions of the given VMA.
VMAHandle Reprotect(VMAHandle vma, VMAPermission new_perms);
/// Changes the permissions of a range of addresses, splitting VMAs as necessary.
Result ReprotectRange(VAddr target, u32 size, VMAPermission new_perms);
/// Dumps the address space layout to the log, for debugging
void LogLayout(Common::Log::Level log_level) const;
/// Gets a list of backing memory blocks for the specified range
ResultVal<std::vector<std::pair<MemoryRef, u32>>> GetBackingBlocksForRange(VAddr address,
u32 size);
/// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled.
std::shared_ptr<Memory::PageTable> page_table;
/**
* Unlock the VMManager. Used after loading is completed.
*/
void Unlock();
private:
using VMAIter = decltype(vma_map)::iterator;
/// Converts a VMAHandle to a mutable VMAIter.
VMAIter StripIterConstness(const VMAHandle& iter);
/// Unmaps the given VMA.
VMAIter Unmap(VMAIter vma);
/**
* Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
* the appropriate error checking.
*/
ResultVal<VMAIter> CarveVMA(VAddr base, u32 size);
/**
* Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
* end of the range.
*/
ResultVal<VMAIter> CarveVMARange(VAddr base, u32 size);
/**
* Splits a VMA in two, at the specified offset.
* @returns the right side of the split, with the original iterator becoming the left side.
*/
VMAIter SplitVMA(VMAIter vma, u32 offset_in_vma);
/**
* Checks for and merges the specified VMA with adjacent ones if possible.
* @returns the merged VMA or the original if no merging was possible.
*/
VMAIter MergeAdjacent(VMAIter vma);
/// Updates the pages corresponding to this VMA so they match the VMA's attributes.
void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
Memory::MemorySystem& memory;
Kernel::Process& process;
// When locked, ChangeMemoryState calls will be ignored, other modification calls will hit an
// assert. VMManager locks itself after deserialization.
bool is_locked{};
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::VirtualMemoryArea)

View File

@@ -0,0 +1,119 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <utility>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
SERIALIZE_EXPORT_IMPL(Kernel::WaitObject)
namespace Kernel {
template <class Archive>
void WaitObject::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& waiting_threads;
// NB: hle_notifier *not* serialized since it's a callback!
// Fortunately it's only used in one place (DSP) so we can reconstruct it there
}
SERIALIZE_IMPL(WaitObject)
void WaitObject::AddWaitingThread(std::shared_ptr<Thread> thread) {
auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
if (itr == waiting_threads.end())
waiting_threads.push_back(std::move(thread));
}
void WaitObject::RemoveWaitingThread(Thread* thread) {
auto itr = std::find_if(waiting_threads.begin(), waiting_threads.end(),
[thread](const auto& p) { return p.get() == thread; });
// If a thread passed multiple handles to the same object,
// the kernel might attempt to remove the thread from the object's
// waiting threads list multiple times.
if (itr != waiting_threads.end())
waiting_threads.erase(itr);
}
std::shared_ptr<Thread> WaitObject::GetHighestPriorityReadyThread() const {
Thread* candidate = nullptr;
u32 candidate_priority = ThreadPrioLowest + 1;
for (const auto& thread : waiting_threads) {
// The list of waiting threads must not contain threads that are not waiting to be awakened.
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
thread->status == ThreadStatus::WaitSynchAll ||
thread->status == ThreadStatus::WaitHleEvent,
"Inconsistent thread statuses in waiting_threads");
if (thread->current_priority >= candidate_priority)
continue;
if (ShouldWait(thread.get()))
continue;
// A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
// in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
bool ready_to_run = true;
if (thread->status == ThreadStatus::WaitSynchAll) {
ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
[&thread](const std::shared_ptr<WaitObject>& object) {
return object->ShouldWait(thread.get());
});
}
if (ready_to_run) {
candidate = thread.get();
candidate_priority = thread->current_priority;
}
}
return SharedFrom(candidate);
}
void WaitObject::WakeupAllWaitingThreads() {
while (auto thread = GetHighestPriorityReadyThread()) {
if (!thread->IsSleepingOnWaitAll()) {
Acquire(thread.get());
} else {
for (auto& object : thread->wait_objects) {
object->Acquire(thread.get());
}
}
// Invoke the wakeup callback before clearing the wait objects
if (thread->wakeup_callback)
thread->wakeup_callback->WakeUp(ThreadWakeupReason::Signal, thread, SharedFrom(this));
for (auto& object : thread->wait_objects)
object->RemoveWaitingThread(thread.get());
thread->wait_objects.clear();
thread->ResumeFromWait();
}
if (hle_notifier)
hle_notifier();
}
const std::vector<std::shared_ptr<Thread>>& WaitObject::GetWaitingThreads() const {
return waiting_threads;
}
void WaitObject::SetHLENotifier(std::function<void()> callback) {
hle_notifier = std::move(callback);
}
} // namespace Kernel

View File

@@ -0,0 +1,83 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
namespace Kernel {
class Thread;
/// Class that represents a Kernel object that a thread can be waiting on
class WaitObject : public Object {
public:
using Object::Object;
/**
* Check if the specified thread should wait until the object is available
* @param thread The thread about which we're deciding.
* @return True if the current thread should wait due to this object being unavailable
*/
virtual bool ShouldWait(const Thread* thread) const = 0;
/// Acquire/lock the object for the specified thread if it is available
virtual void Acquire(Thread* thread) = 0;
/**
* Add a thread to wait on this object
* @param thread Pointer to thread to add
*/
virtual void AddWaitingThread(std::shared_ptr<Thread> thread);
/**
* Removes a thread from waiting on this object (e.g. if it was resumed already)
* @param thread Pointer to thread to remove
*/
virtual void RemoveWaitingThread(Thread* thread);
/**
* Wake up all threads waiting on this object that can be awoken, in priority order,
* and set the synchronization result and output of the thread.
*/
virtual void WakeupAllWaitingThreads();
/// Obtains the highest priority thread that is ready to run from this object's waiting list.
std::shared_ptr<Thread> GetHighestPriorityReadyThread() const;
/// Get a const reference to the waiting threads list for debug use
const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
/// Sets a callback which is called when the object becomes available
void SetHLENotifier(std::function<void()> callback);
private:
/// Threads waiting for this object to become available
std::vector<std::shared_ptr<Thread>> waiting_threads;
/// Function to call when this object becomes available
std::function<void()> hle_notifier;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
// Specialization of DynamicObjectCast for WaitObjects
template <>
inline std::shared_ptr<WaitObject> DynamicObjectCast<WaitObject>(std::shared_ptr<Object> object) {
if (object != nullptr && object->IsWaitable()) {
return std::static_pointer_cast<WaitObject>(object);
}
return nullptr;
}
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::WaitObject)

30
src/core/hle/mii.cpp Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/crc.hpp>
#include <boost/serialization/binary_object.hpp>
#include "common/archives.h"
#include "core/hle/mii.h"
SERIALIZE_EXPORT_IMPL(Mii::MiiData)
SERIALIZE_EXPORT_IMPL(Mii::ChecksummedMiiData)
namespace Mii {
template <class Archive>
void MiiData::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::make_binary_object(this, sizeof(MiiData));
}
SERIALIZE_IMPL(MiiData)
template <class Archive>
void ChecksummedMiiData::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::make_binary_object(this, sizeof(ChecksummedMiiData));
}
SERIALIZE_IMPL(ChecksummedMiiData)
u16 ChecksummedMiiData::CalculateChecksum() {
// Calculate the checksum of the selected Mii, see https://www.3dbrew.org/wiki/Mii#Checksum
return boost::crc<16, 0x1021, 0, 0, false, false>(this, offsetof(ChecksummedMiiData, crc16));
}
} // namespace Mii

242
src/core/hle/mii.h Normal file
View File

@@ -0,0 +1,242 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/export.hpp>
#include "common/bit_field.h"
#include "common/common_types.h"
namespace Mii {
using Nickname = std::array<char16_t, 10>;
#pragma pack(push, 1)
// Reference: https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/mii.h
struct MiiData {
u8 version; ///< Always 3?
/// Mii options
union {
u8 raw;
BitField<0, 1, u8> allow_copying; ///< True if copying is allowed
BitField<1, 1, u8> is_private_name; ///< Private name?
BitField<2, 2, u8> region_lock; ///< Region lock (0=no lock, 1=JPN, 2=USA, 3=EUR)
BitField<4, 2, u8> char_set; ///< Character set (0=JPN+USA+EUR, 1=CHN, 2=KOR, 3=TWN)
} mii_options;
/// Mii position in Mii selector or Mii maker
union {
u8 raw;
BitField<0, 4, u8> page_index; ///< Page index of Mii
BitField<4, 4, u8> slot_index; ///< Slot offset of Mii on its Page
} mii_pos;
/// Console Identity
union {
u8 raw;
BitField<0, 4, u8> unknown0; ///< Mabye padding (always seems to be 0)?
BitField<4, 3, u8>
origin_console; ///< Console that the Mii was created on (1=WII, 2=DSI, 3=3DS)
} console_identity;
u64_be system_id; ///< Identifies the system that the Mii was created on (Determines pants)
u32_be mii_id; ///< ID of Mii
std::array<u8, 6> mac; ///< Creator's system's full MAC address
u16 pad; ///< Padding
/// Mii details
union {
u16_be raw;
BitField<0, 1, u16> gender; ///< Gender of Mii (0=Male, 1=Female)
BitField<1, 4, u16> bday_month; ///< Month of Mii's birthday
BitField<5, 5, u16> bday_day; ///< Day of Mii's birthday
BitField<10, 4, u16> favorite_color; ///< Color of Mii's shirt
BitField<14, 1, u16> favorite; ///< Whether the Mii is one of your 10 favorite Mii's
} mii_details;
Nickname mii_name; ///< Name of Mii (Encoded using UTF16)
u8 height; ///< How tall the Mii is
u8 width; ///< How wide the Mii is
/// Face style
union {
u8 raw;
BitField<0, 1, u8> disable_sharing; ///< Whether or not Sharing of the Mii is allowed
BitField<1, 4, u8> type; ///< Face type
BitField<5, 3, u8> skin_color; ///< Color of skin
} face_style;
/// Face details
union {
u8 raw;
BitField<0, 4, u8> wrinkles;
BitField<4, 4, u8> makeup;
} face_details;
u8 hair_style;
/// Hair details
union {
u8 raw;
BitField<0, 3, u8> color;
BitField<3, 1, u8> flip;
} hair_details;
/// Eye details
union {
u32_be raw;
BitField<0, 6, u32> type;
BitField<6, 3, u32> color;
BitField<9, 4, u32> scale;
BitField<13, 3, u32> aspect;
BitField<16, 5, u32> rotate;
BitField<21, 4, u32> x;
BitField<25, 5, u32> y;
} eye_details;
/// Eyebrow details
union {
u32_be raw;
BitField<0, 5, u32> style;
BitField<5, 3, u32> color;
BitField<8, 4, u32> scale;
BitField<12, 3, u32> aspect;
BitField<16, 5, u32> rotate;
BitField<21, 4, u32> x;
BitField<25, 5, u32> y;
} eyebrow_details;
/// Nose details
union {
u16_be raw;
BitField<0, 5, u16> type;
BitField<5, 4, u16> scale;
BitField<9, 5, u16> y;
} nose_details;
/// Mouth details
union {
u16_be raw;
BitField<0, 6, u16> type;
BitField<6, 3, u16> color;
BitField<9, 4, u16> scale;
BitField<13, 3, u16> aspect;
} mouth_details;
/// Mustache details
union {
u16_be raw;
BitField<0, 5, u16> mouth_y;
BitField<5, 3, u16> mustache_type;
} mustache_details;
/// Beard details
union {
u16_be raw;
BitField<0, 3, u16> type;
BitField<3, 3, u16> color;
BitField<6, 4, u16> scale;
BitField<10, 5, u16> y;
} beard_details;
/// Glasses details
union {
u16_be raw;
BitField<0, 4, u16> type;
BitField<4, 3, u16> color;
BitField<7, 4, u16> scale;
BitField<11, 5, u16> y;
} glasses_details;
/// Mole details
union {
u16_be raw;
BitField<0, 1, u16> type;
BitField<1, 4, u16> scale;
BitField<5, 5, u16> x;
BitField<10, 5, u16> y;
} mole_details;
Nickname author_name; ///< Name of Mii's author (Encoded using UTF16)
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size");
static_assert(std::is_trivial_v<MiiData>, "MiiData must be trivial.");
static_assert(std::is_trivially_copyable_v<MiiData>, "MiiData must be trivially copyable.");
struct ChecksummedMiiData {
private:
MiiData mii_data;
u16 padding;
u16_be crc16;
public:
ChecksummedMiiData& operator=(const MiiData& data) {
mii_data = data;
padding = 0;
FixChecksum();
return *this;
}
ChecksummedMiiData& operator=(MiiData&& data) {
mii_data = std::move(data);
padding = 0;
FixChecksum();
return *this;
}
void SetMiiData(MiiData data) {
mii_data = data;
FixChecksum();
}
operator MiiData() const {
return mii_data;
}
bool IsChecksumValid() {
return crc16 == CalculateChecksum();
}
u16 CalculateChecksum();
void FixChecksum() {
crc16 = CalculateChecksum();
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
#pragma pack(pop)
static_assert(sizeof(ChecksummedMiiData) == 0x60,
"ChecksummedMiiData structure has incorrect size");
static_assert(std::is_trivial_v<ChecksummedMiiData>, "ChecksummedMiiData must be trivial.");
static_assert(std::is_trivially_copyable_v<ChecksummedMiiData>,
"ChecksummedMiiData must be trivially copyable.");
} // namespace Mii
BOOST_CLASS_EXPORT_KEY(Mii::MiiData)
BOOST_CLASS_EXPORT_KEY(Mii::ChecksummedMiiData)

427
src/core/hle/result.h Normal file
View File

@@ -0,0 +1,427 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/access.hpp>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/expected.h"
// All the constants in this file come from http://3dbrew.org/wiki/Error_codes
/**
* Detailed description of the error. Code 0 always means success. Codes 1000 and above are
* considered "well-known" and have common values between all modules. The meaning of other codes
* vary by module.
*/
enum class ErrorDescription : u32 {
Success = 0,
// Codes 1000 and above are considered "well-known" and have common values between all modules.
InvalidSection = 1000,
TooLarge = 1001,
NotAuthorized = 1002,
AlreadyDone = 1003,
InvalidSize = 1004,
InvalidEnumValue = 1005,
InvalidCombination = 1006,
NoData = 1007,
Busy = 1008,
MisalignedAddress = 1009,
MisalignedSize = 1010,
OutOfMemory = 1011,
NotImplemented = 1012,
InvalidAddress = 1013,
InvalidPointer = 1014,
InvalidHandle = 1015,
NotInitialized = 1016,
AlreadyInitialized = 1017,
NotFound = 1018,
CancelRequested = 1019,
AlreadyExists = 1020,
OutOfRange = 1021,
Timeout = 1022,
InvalidResultValue = 1023,
};
/**
* Identifies the module which caused the error. Error codes can be propagated through a call
* chain, meaning that this doesn't always correspond to the module where the API call made is
* contained.
*/
enum class ErrorModule : u32 {
Common = 0,
Kernel = 1,
Util = 2,
FileServer = 3,
LoaderServer = 4,
TCB = 5,
OS = 6,
DBG = 7,
DMNT = 8,
PDN = 9,
GX = 10,
I2C = 11,
GPIO = 12,
DD = 13,
CODEC = 14,
SPI = 15,
PXI = 16,
FS = 17,
DI = 18,
HID = 19,
CAM = 20,
PI = 21,
PM = 22,
PM_LOW = 23,
FSI = 24,
SRV = 25,
NDM = 26,
NWM = 27,
SOC = 28,
LDR = 29,
ACC = 30,
RomFS = 31,
AM = 32,
HIO = 33,
Updater = 34,
MIC = 35,
FND = 36,
MP = 37,
MPWL = 38,
AC = 39,
HTTP = 40,
DSP = 41,
SND = 42,
DLP = 43,
HIO_LOW = 44,
CSND = 45,
SSL = 46,
AM_LOW = 47,
NEX = 48,
Friends = 49,
RDT = 50,
Applet = 51,
NIM = 52,
PTM = 53,
MIDI = 54,
MC = 55,
SWC = 56,
FatFS = 57,
NGC = 58,
CARD = 59,
CARDNOR = 60,
SDMC = 61,
BOSS = 62,
DBM = 63,
Config = 64,
PS = 65,
CEC = 66,
IR = 67,
UDS = 68,
PL = 69,
CUP = 70,
Gyroscope = 71,
MCU = 72,
NS = 73,
News = 74,
RO = 75,
GD = 76,
CardSPI = 77,
EC = 78,
WebBrowser = 79,
Test = 80,
ENC = 81,
PIA = 82,
ACT = 83,
VCTL = 84,
OLV = 85,
NEIA = 86,
NPNS = 87,
AVD = 90,
L2B = 91,
MVD = 92,
NFC = 93,
UART = 94,
SPM = 95,
QTM = 96,
NFP = 97,
Application = 254,
InvalidResult = 255
};
/// A less specific error cause.
enum class ErrorSummary : u32 {
Success = 0,
NothingHappened = 1,
WouldBlock = 2,
OutOfResource = 3, ///< There are no more kernel resources (memory, table slots) to
///< execute the operation.
NotFound = 4, ///< A file or resource was not found.
InvalidState = 5,
NotSupported = 6, ///< The operation is not supported or not implemented.
InvalidArgument = 7, ///< Returned when a passed argument is invalid in the current runtime
///< context. (Invalid handle, out-of-bounds pointer or size, etc.)
WrongArgument = 8, ///< Returned when a passed argument is in an incorrect format for use
///< with the function. (E.g. Invalid enum value)
Canceled = 9,
StatusChanged = 10,
Internal = 11,
InvalidResult = 63
};
/// The severity of the error.
enum class ErrorLevel : u32 {
Success = 0,
Info = 1,
Status = 25,
Temporary = 26,
Permanent = 27,
Usage = 28,
Reinitialize = 29,
Reset = 30,
Fatal = 31
};
/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields.
union Result {
u32 raw;
BitField<0, 10, u32> description;
BitField<10, 8, ErrorModule> module;
BitField<21, 6, ErrorSummary> summary;
BitField<27, 5, ErrorLevel> level;
// The last bit of `level` is checked by apps and the kernel to determine if a result code is an
// error
BitField<31, 1, u32> is_error;
constexpr explicit Result(u32 raw) : raw(raw) {}
constexpr Result(ErrorDescription description, ErrorModule module, ErrorSummary summary,
ErrorLevel level)
: Result(static_cast<u32>(description), module, summary, level) {}
constexpr Result(u32 description_, ErrorModule module_, ErrorSummary summary_,
ErrorLevel level_)
: raw(description.FormatValue(description_) | module.FormatValue(module_) |
summary.FormatValue(summary_) | level.FormatValue(level_)) {}
constexpr Result& operator=(const Result& o) = default;
constexpr bool IsSuccess() const {
return is_error.ExtractValue(raw) == 0;
}
constexpr bool IsError() const {
return is_error.ExtractValue(raw) == 1;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& raw;
}
friend class boost::serialization::access;
};
constexpr bool operator==(const Result& a, const Result& b) {
return a.raw == b.raw;
}
constexpr bool operator!=(const Result& a, const Result& b) {
return a.raw != b.raw;
}
// Convenience functions for creating some common kinds of errors:
/// The default success `Result`.
constexpr Result ResultSuccess(0);
/// Might be returned instead of a dummy success for unimplemented APIs.
constexpr Result UnimplementedFunction(ErrorModule module) {
return Result(ErrorDescription::NotImplemented, module, ErrorSummary::NotSupported,
ErrorLevel::Permanent);
}
/**
* Placeholder result code used for unknown error codes.
*
* @note This should only be used when a particular error code
* is not known yet.
*/
constexpr Result ResultUnknown(std::numeric_limits<u32>::max());
/**
* This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it
* also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying
* to access the inner value with operator* is undefined behavior and will assert with Unwrap().
* Users of this class must be cognizant to check the status of the ResultVal with operator bool(),
* Code(), Succeeded() or Failed() prior to accessing the inner value.
*
* An example of how it could be used:
* \code
* ResultVal<int> Frobnicate(float strength) {
* if (strength < 0.f || strength > 1.0f) {
* // Can't frobnicate too weakly or too strongly
* return Result(ErrorDescription::OutOfRange, ErrorModule::Common,
* ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
* } else {
* // Frobnicated! Give caller a cookie
* return 42;
* }
* }
* \endcode
*
* \code
* auto frob_result = Frobnicate(0.75f);
* if (frob_result) {
* // Frobbed ok
* printf("My cookie is %d\n", *frob_result);
* } else {
* printf("Guess I overdid it. :( Error code: %ux\n", frob_result.Code().raw);
* }
* \endcode
*/
template <typename T>
class ResultVal {
public:
constexpr ResultVal() : expected{} {}
constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {}
template <typename U>
constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {}
template <typename... Args>
constexpr ResultVal(Args&&... args) : expected{std::in_place, std::forward<Args>(args)...} {}
~ResultVal() = default;
constexpr ResultVal(const ResultVal&) = default;
constexpr ResultVal(ResultVal&&) = default;
ResultVal& operator=(const ResultVal&) = default;
ResultVal& operator=(ResultVal&&) = default;
[[nodiscard]] constexpr explicit operator bool() const noexcept {
return expected.has_value();
}
[[nodiscard]] constexpr Result Code() const {
return expected.has_value() ? ResultSuccess : expected.error();
}
[[nodiscard]] constexpr bool Succeeded() const {
return expected.has_value();
}
[[nodiscard]] constexpr bool Failed() const {
return !expected.has_value();
}
[[nodiscard]] constexpr T* operator->() {
return std::addressof(expected.value());
}
[[nodiscard]] constexpr const T* operator->() const {
return std::addressof(expected.value());
}
[[nodiscard]] constexpr T& operator*() & {
return *expected;
}
[[nodiscard]] constexpr const T& operator*() const& {
return *expected;
}
[[nodiscard]] constexpr T&& operator*() && {
return *expected;
}
[[nodiscard]] constexpr const T&& operator*() const&& {
return *expected;
}
[[nodiscard]] constexpr T& Unwrap() & {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return expected.value();
}
[[nodiscard]] constexpr const T& Unwrap() const& {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return expected.value();
}
[[nodiscard]] constexpr T&& Unwrap() && {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return std::move(expected.value());
}
[[nodiscard]] constexpr const T&& Unwrap() const&& {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return std::move(expected.value());
}
template <typename U>
[[nodiscard]] constexpr T ValueOr(U&& v) const& {
return expected.value_or(v);
}
template <typename U>
[[nodiscard]] constexpr T ValueOr(U&& v) && {
return expected.value_or(v);
}
private:
// TODO: Replace this with std::expected once it is standardized in the STL.
Common::Expected<T, Result> expected;
};
/**
* Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps
* the contained value and assigns it to `target`, which can be either an l-value expression or a
* variable declaration. If it fails the return code is returned from the current function. Thus it
* can be used to cascade errors out, achieving something akin to exception handling.
*/
#define CASCADE_RESULT(target, source) \
auto CONCAT2(check_result_L, __LINE__) = source; \
if (CONCAT2(check_result_L, __LINE__).Failed()) \
return CONCAT2(check_result_L, __LINE__).Code(); \
target = std::move(*CONCAT2(check_result_L, __LINE__))
#define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess())
#define R_FAILED(res) (static_cast<Result>(res).IsError())
/// Evaluates a boolean expression, and returns a result unless that expression is true.
#define R_UNLESS(expr, res) \
{ \
if (!(expr)) { \
return (res); \
} \
}
/// Evaluates an expression that returns a result, and returns the result if it would fail.
#define R_TRY(res_expr) \
{ \
const auto _tmp_r_try_rc = (res_expr); \
if (R_FAILED(_tmp_r_try_rc)) { \
return (_tmp_r_try_rc); \
} \
}
/// Evaluates a boolean expression, and succeeds if that expression is true.
#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), ResultSuccess)
/// Evaluates a boolean expression, and asserts if that expression is false.
#define R_ASSERT(expr) ASSERT(R_SUCCEEDED(expr))

112
src/core/hle/romfs.cpp Normal file
View File

@@ -0,0 +1,112 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/swap.h"
#include "core/hle/romfs.h"
namespace RomFS {
struct Header {
u32_le header_length;
u32_le dir_hash_table_offset;
u32_le dir_hash_table_length;
u32_le dir_table_offset;
u32_le dir_table_length;
u32_le file_hash_table_offset;
u32_le file_hash_table_length;
u32_le file_table_offset;
u32_le file_table_length;
u32_le data_offset;
};
static_assert(sizeof(Header) == 0x28, "Header has incorrect size");
struct DirectoryMetadata {
u32_le parent_dir_offset;
u32_le next_dir_offset;
u32_le first_child_dir_offset;
u32_le first_file_offset;
u32_le same_hash_next_dir_offset;
u32_le name_length; // in bytes
// followed by directory name
};
static_assert(sizeof(DirectoryMetadata) == 0x18, "DirectoryMetadata has incorrect size");
struct FileMetadata {
u32_le parent_dir_offset;
u32_le next_file_offset;
u64_le data_offset;
u64_le data_length;
u32_le same_hash_next_file_offset;
u32_le name_length; // in bytes
// followed by file name
};
static_assert(sizeof(FileMetadata) == 0x20, "FileMetadata has incorrect size");
static bool MatchName(const u8* buffer, u32 name_length, const std::u16string& name) {
std::vector<char16_t> name_buffer(name_length / sizeof(char16_t));
std::memcpy(name_buffer.data(), buffer, name_length);
return name == std::u16string(name_buffer.begin(), name_buffer.end());
}
RomFSFile::RomFSFile(const u8* data, u64 length) : data(data), length(length) {}
const u8* RomFSFile::Data() const {
return data;
}
u64 RomFSFile::Length() const {
return length;
}
const RomFSFile GetFile(const u8* romfs, const std::vector<std::u16string>& path) {
constexpr u32 INVALID_FIELD = 0xFFFFFFFF;
// Split path into directory names and file name
std::vector<std::u16string> dir_names = path;
dir_names.pop_back();
const std::u16string& file_name = path.back();
Header header;
std::memcpy(&header, romfs, sizeof(header));
// Find directories of each level
DirectoryMetadata dir;
const u8* current_dir = romfs + header.dir_table_offset;
std::memcpy(&dir, current_dir, sizeof(dir));
for (const std::u16string& dir_name : dir_names) {
u32 child_dir_offset;
child_dir_offset = dir.first_child_dir_offset;
while (true) {
if (child_dir_offset == INVALID_FIELD) {
return RomFSFile();
}
const u8* current_child_dir = romfs + header.dir_table_offset + child_dir_offset;
std::memcpy(&dir, current_child_dir, sizeof(dir));
if (MatchName(current_child_dir + sizeof(dir), dir.name_length, dir_name)) {
current_dir = current_child_dir;
break;
}
child_dir_offset = dir.next_dir_offset;
}
}
// Find the file
FileMetadata file;
u32 file_offset = dir.first_file_offset;
while (file_offset != INVALID_FIELD) {
const u8* current_file = romfs + header.file_table_offset + file_offset;
std::memcpy(&file, current_file, sizeof(file));
if (MatchName(current_file + sizeof(file), file.name_length, file_name)) {
return RomFSFile(romfs + header.data_offset + file.data_offset, file.data_length);
}
file_offset = file.next_file_offset;
}
return RomFSFile();
}
} // namespace RomFS

34
src/core/hle/romfs.h Normal file
View File

@@ -0,0 +1,34 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <vector>
#include "common/common_types.h"
namespace RomFS {
class RomFSFile {
public:
RomFSFile() = default;
RomFSFile(const u8* data, u64 length);
const u8* Data() const;
u64 Length() const;
private:
const u8* data = nullptr;
u64 length = 0;
};
/**
* Gets a RomFSFile class to a file in a RomFS image.
* @param romfs The pointer to the RomFS image
* @param path A vector containing the directory names and file name of the path to the file
* @return the RomFSFile to the file
* @todo reimplement this with a full RomFS manager
*/
const RomFSFile GetFile(const u8* romfs, const std::vector<std::u16string>& path);
} // namespace RomFS

View File

@@ -0,0 +1,217 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <vector>
#include "common/archives.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/result.h"
#include "core/hle/service/ac/ac.h"
#include "core/hle/service/ac/ac_i.h"
#include "core/hle/service/ac/ac_u.h"
#include "core/hle/service/soc/soc_u.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Service::AC::Module)
SERVICE_CONSTRUCT_IMPL(Service::AC::Module)
namespace Service::AC {
void Module::Interface::CreateDefaultConfig(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
std::vector<u8> buffer(sizeof(ACConfig));
std::memcpy(buffer.data(), &ac->default_config, buffer.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess);
rb.PushStaticBuffer(std::move(buffer), 0);
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::ConnectAsync(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(2, false); // ProcessId descriptor
ac->connect_event = rp.PopObject<Kernel::Event>();
rp.Skip(2, false); // Buffer descriptor
if (ac->connect_event) {
ac->connect_event->SetName("AC:connect_event");
ac->connect_event->Signal();
ac->ac_connected = true;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::GetConnectResult(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(2, false); // ProcessId descriptor
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::CloseAsync(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(2, false); // ProcessId descriptor
ac->close_event = rp.PopObject<Kernel::Event>();
if (ac->ac_connected && ac->disconnect_event) {
ac->disconnect_event->Signal();
}
if (ac->close_event) {
ac->close_event->SetName("AC:close_event");
ac->close_event->Signal();
}
ac->ac_connected = false;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::GetCloseResult(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(2, false); // ProcessId descriptor
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::GetWifiStatus(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
bool can_reach_internet = false;
std::shared_ptr<SOC::SOC_U> socu_module = SOC::GetService(ac->system);
if (socu_module) {
can_reach_internet = socu_module->GetDefaultInterfaceInfo().has_value();
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(can_reach_internet ? (Settings::values.is_new_3ds
? WifiStatus::STATUS_CONNECTED_N3DS
: WifiStatus::STATUS_CONNECTED_O3DS)
: WifiStatus::STATUS_DISCONNECTED));
}
void Module::Interface::GetInfraPriority(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
[[maybe_unused]] const std::vector<u8>& ac_config = rp.PopStaticBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(0); // Infra Priority, default 0
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::SetRequestEulaVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 major = rp.Pop<u8>();
u32 minor = rp.Pop<u8>();
const std::vector<u8>& ac_config = rp.PopStaticBuffer();
// TODO(Subv): Copy over the input ACConfig to the stored ACConfig.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess);
rb.PushStaticBuffer(std::move(ac_config), 0);
LOG_WARNING(Service_AC, "(STUBBED) called, major={}, minor={}", major, minor);
}
void Module::Interface::RegisterDisconnectEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
rp.Skip(2, false); // ProcessId descriptor
ac->disconnect_event = rp.PopObject<Kernel::Event>();
if (ac->disconnect_event) {
ac->disconnect_event->SetName("AC:disconnect_event");
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::GetConnectingProxyEnable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
constexpr bool proxy_enabled = false;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(proxy_enabled);
LOG_WARNING(Service_AC, "(STUBBED) called");
}
void Module::Interface::IsConnected(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 unk = rp.Pop<u32>();
u32 unk_descriptor = rp.Pop<u32>();
u32 unk_param = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(ac->ac_connected);
LOG_WARNING(Service_AC, "(STUBBED) called unk=0x{:08X} descriptor=0x{:08X} param=0x{:08X}", unk,
unk_descriptor, unk_param);
}
void Module::Interface::SetClientVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 version = rp.Pop<u32>();
rp.Skip(2, false); // ProcessId descriptor
LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x{:08X}", version);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
Module::Interface::Interface(std::shared_ptr<Module> ac, const char* name, u32 max_session)
: ServiceFramework(name, max_session), ac(std::move(ac)) {}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto ac = std::make_shared<Module>(system);
std::make_shared<AC_I>(ac)->InstallAsService(service_manager);
std::make_shared<AC_U>(ac)->InstallAsService(service_manager);
}
Module::Module(Core::System& system_) : system(system_) {}
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) {
ar& ac_connected;
ar& close_event;
ar& connect_event;
ar& disconnect_event;
// default_config is never written to
}
SERIALIZE_IMPL(Module)
} // namespace Service::AC

View File

@@ -0,0 +1,187 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class Event;
}
namespace Service::AC {
class Module final {
public:
explicit Module(Core::System& system_);
~Module() = default;
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> ac, const char* name, u32 max_session);
/**
* AC::CreateDefaultConfig service function
* Inputs:
* 64 : ACConfig size << 14 | 2
* 65 : pointer to ACConfig struct
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void CreateDefaultConfig(Kernel::HLERequestContext& ctx);
/**
* AC::ConnectAsync service function
* Inputs:
* 1 : ProcessId Header
* 3 : Copy Handle Header
* 4 : Connection Event handle
* 5 : ACConfig size << 14 | 2
* 6 : pointer to ACConfig struct
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void ConnectAsync(Kernel::HLERequestContext& ctx);
/**
* AC::GetConnectResult service function
* Inputs:
* 1 : ProcessId Header
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetConnectResult(Kernel::HLERequestContext& ctx);
/**
* AC::CloseAsync service function
* Inputs:
* 1 : ProcessId Header
* 3 : Copy Handle Header
* 4 : Event handle, should be signaled when AC connection is closed
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void CloseAsync(Kernel::HLERequestContext& ctx);
/**
* AC::GetCloseResult service function
* Inputs:
* 1 : ProcessId Header
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetCloseResult(Kernel::HLERequestContext& ctx);
/**
* AC::GetWifiStatus service function
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet.
*/
void GetWifiStatus(Kernel::HLERequestContext& ctx);
/**
* AC::GetInfraPriority service function
* Inputs:
* 1 : ACConfig size << 14 | 2
* 2 : pointer to ACConfig struct
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Infra Priority
*/
void GetInfraPriority(Kernel::HLERequestContext& ctx);
/**
* AC::SetRequestEulaVersion service function
* Inputs:
* 1 : Eula Version major
* 2 : Eula Version minor
* 3 : ACConfig size << 14 | 2
* 4 : Input pointer to ACConfig struct
* 64 : ACConfig size << 14 | 2
* 65 : Output pointer to ACConfig struct
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Infra Priority
*/
void SetRequestEulaVersion(Kernel::HLERequestContext& ctx);
/**
* AC::RegisterDisconnectEvent service function
* Inputs:
* 1 : ProcessId Header
* 3 : Copy Handle Header
* 4 : Event handle, should be signaled when AC connection is closed
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void RegisterDisconnectEvent(Kernel::HLERequestContext& ctx);
/**
* AC::GetConnectingProxyEnable service function
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : bool, is proxy enabled
*/
void GetConnectingProxyEnable(Kernel::HLERequestContext& ctx);
/**
* AC::IsConnected service function
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : bool, is connected
*/
void IsConnected(Kernel::HLERequestContext& ctx);
/**
* AC::SetClientVersion service function
* Inputs:
* 1 : Used SDK Version
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetClientVersion(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> ac;
};
protected:
enum class WifiStatus {
STATUS_DISCONNECTED = 0,
STATUS_CONNECTED_O3DS = 1,
STATUS_CONNECTED_N3DS = 2,
};
struct ACConfig {
std::array<u8, 0x200> data;
};
ACConfig default_config{};
bool ac_connected = false;
std::shared_ptr<Kernel::Event> close_event;
std::shared_ptr<Kernel::Event> connect_event;
std::shared_ptr<Kernel::Event> disconnect_event;
private:
Core::System& system;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
void InstallInterfaces(Core::System& system);
} // namespace Service::AC
BOOST_CLASS_EXPORT_KEY(Service::AC::Module)
SERVICE_CONSTRUCT(Service::AC::Module)

View File

@@ -0,0 +1,41 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/ac/ac_i.h"
namespace Service::AC {
AC_I::AC_I(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:i", 10) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &AC_I::CreateDefaultConfig, "CreateDefaultConfig"},
{0x0004, &AC_I::ConnectAsync, "ConnectAsync"},
{0x0005, &AC_I::GetConnectResult, "GetConnectResult"},
{0x0007, nullptr, "CancelConnectAsync"},
{0x0008, &AC_I::CloseAsync, "CloseAsync"},
{0x0009, &AC_I::GetCloseResult, "GetCloseResult"},
{0x000A, nullptr, "GetLastErrorCode"},
{0x000C, nullptr, "GetStatus"},
{0x000D, &AC_I::GetWifiStatus, "GetWifiStatus"},
{0x000E, nullptr, "GetCurrentAPInfo"},
{0x0010, nullptr, "GetCurrentNZoneInfo"},
{0x0011, nullptr, "GetNZoneApNumService"},
{0x001D, nullptr, "ScanAPs"},
{0x0024, nullptr, "AddDenyApType"},
{0x0027, &AC_I::GetInfraPriority, "GetInfraPriority"},
{0x002D, &AC_I::SetRequestEulaVersion, "SetRequestEulaVersion"},
{0x0030, &AC_I::RegisterDisconnectEvent, "RegisterDisconnectEvent"},
{0x0036, &AC_I::GetConnectingProxyEnable, "GetConnectingProxyEnable"},
{0x003C, nullptr, "GetAPSSIDList"},
{0x003E, &AC_I::IsConnected, "IsConnected"},
{0x0040, &AC_I::SetClientVersion, "SetClientVersion"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AC
SERIALIZE_EXPORT_IMPL(Service::AC::AC_I)

View File

@@ -0,0 +1,23 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hle/service/ac/ac.h"
namespace Service::AC {
class AC_I final : public Module::Interface {
public:
explicit AC_I(std::shared_ptr<Module> ac);
private:
SERVICE_SERIALIZATION(AC_I, ac, Module)
};
} // namespace Service::AC
BOOST_CLASS_EXPORT_KEY(Service::AC::AC_I)
BOOST_SERIALIZATION_CONSTRUCT(Service::AC::AC_I)

View File

@@ -0,0 +1,41 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/ac/ac_u.h"
namespace Service::AC {
AC_U::AC_U(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:u", 10) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &AC_U::CreateDefaultConfig, "CreateDefaultConfig"},
{0x0004, &AC_U::ConnectAsync, "ConnectAsync"},
{0x0005, &AC_U::GetConnectResult, "GetConnectResult"},
{0x0007, nullptr, "CancelConnectAsync"},
{0x0008, &AC_U::CloseAsync, "CloseAsync"},
{0x0009, &AC_U::GetCloseResult, "GetCloseResult"},
{0x000A, nullptr, "GetLastErrorCode"},
{0x000C, nullptr, "GetStatus"},
{0x000D, &AC_U::GetWifiStatus, "GetWifiStatus"},
{0x000E, nullptr, "GetCurrentAPInfo"},
{0x0010, nullptr, "GetCurrentNZoneInfo"},
{0x0011, nullptr, "GetNZoneApNumService"},
{0x001D, nullptr, "ScanAPs"},
{0x0024, nullptr, "AddDenyApType"},
{0x0027, &AC_U::GetInfraPriority, "GetInfraPriority"},
{0x002D, &AC_U::SetRequestEulaVersion, "SetRequestEulaVersion"},
{0x0030, &AC_U::RegisterDisconnectEvent, "RegisterDisconnectEvent"},
{0x0036, &AC_U::GetConnectingProxyEnable, "GetConnectingProxyEnable"},
{0x003C, nullptr, "GetAPSSIDList"},
{0x003E, &AC_U::IsConnected, "IsConnected"},
{0x0040, &AC_U::SetClientVersion, "SetClientVersion"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AC
SERIALIZE_EXPORT_IMPL(Service::AC::AC_U)

View File

@@ -0,0 +1,23 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/hle/service/ac/ac.h"
namespace Service::AC {
class AC_U final : public Module::Interface {
public:
explicit AC_U(std::shared_ptr<Module> ac);
private:
SERVICE_SERIALIZATION(AC_U, ac, Module)
};
} // namespace Service::AC
BOOST_CLASS_EXPORT_KEY(Service::AC::AC_U)
BOOST_SERIALIZATION_CONSTRUCT(Service::AC::AC_U)

View File

@@ -0,0 +1,69 @@
// Copyright 2016 Citra Emulator Project
// 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/kernel/shared_memory.h"
#include "core/hle/service/act/act.h"
#include "core/hle/service/act/act_a.h"
#include "core/hle/service/act/act_errors.h"
#include "core/hle/service/act/act_u.h"
namespace Service::ACT {
Module::Interface::Interface(std::shared_ptr<Module> act, const char* name)
: ServiceFramework(name, 3), act(std::move(act)) {}
Module::Interface::~Interface() = default;
void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto sdk_version = rp.Pop<u32>();
const auto shared_memory_size = rp.Pop<u32>();
const auto caller_pid = rp.PopPID();
[[maybe_unused]] const auto shared_memory = rp.PopObject<Kernel::SharedMemory>();
LOG_DEBUG(Service_ACT,
"(STUBBED) called sdk_version={:08X}, shared_memory_size={:08X}, caller_pid={}",
sdk_version, shared_memory_size, caller_pid);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto result = rp.Pop<Result>();
LOG_DEBUG(Service_ACT, "called result={:08X}", result.raw);
const u32 error_code = GetACTErrorCode(result);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(error_code);
}
void Module::Interface::GetAccountInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto account_slot = rp.Pop<u8>();
const auto size = rp.Pop<u32>();
const auto block_id = rp.Pop<u32>();
[[maybe_unused]] auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_ACT, "(STUBBED) called account_slot={:02X}, size={:08X}, block_id={:08X}",
account_slot, size, block_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto act = std::make_shared<Module>();
std::make_shared<ACT_A>(act)->InstallAsService(service_manager);
std::make_shared<ACT_U>(act)->InstallAsService(service_manager);
}
} // namespace Service::ACT

View File

@@ -0,0 +1,72 @@
// Copyright 2016 Citra 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::ACT {
/// Initializes all ACT services
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> act, const char* name);
~Interface();
protected:
std::shared_ptr<Module> act;
/**
* ACT::Initialize service function.
* Inputs:
* 1 : SDK version
* 2 : Shared Memory Size
* 3 : PID Translation Header (0x20)
* 4 : Caller PID
* 5 : Handle Translation Header (0x0)
* 6 : Shared Memory Handle
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void Initialize(Kernel::HLERequestContext& ctx);
/**
* ACT::GetErrorCode service function.
* Inputs:
* 1 : Result code
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Error code
*/
void GetErrorCode(Kernel::HLERequestContext& ctx);
/**
* ACT::GetAccountInfo service function.
* Inputs:
* 1 : Account slot
* 2 : Size
* 3 : Block ID
* 4 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 5 : Output Buffer Pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetAccountInfo(Kernel::HLERequestContext& ctx);
};
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {}
friend class boost::serialization::access;
};
void InstallInterfaces(Core::System& system);
} // namespace Service::ACT

View File

@@ -0,0 +1,77 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/act/act_a.h"
namespace Service::ACT {
ACT_A::ACT_A(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "act:a") {
const FunctionInfo functions[] = {
// act:u shared commands
// clang-format off
{0x0001, &ACT_A::Initialize, "Initialize"},
{0x0002, &ACT_A::GetErrorCode, "GetErrorCode"},
{0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_A::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{0x000D, nullptr, "GenerateUuid"},
{0x000F, nullptr, "FindSlotNoByUuid"},
{0x0010, nullptr, "SaveData"},
{0x0011, nullptr, "GetTransferableId"},
{0x0012, nullptr, "AcquireNexServiceToken"},
{0x0013, nullptr, "GetNexServiceToken"},
{0x0014, nullptr, "AcquireIndependentServiceToken"},
{0x0015, nullptr, "GetIndependentServiceToken"},
{0x0016, nullptr, "AcquireAccountInfo"},
{0x0017, nullptr, "AcquireAccountIdByPrincipalId"},
{0x0018, nullptr, "AcquirePrincipalIdByAccountId"},
{0x0019, nullptr, "AcquireMii"},
{0x001A, nullptr, "AcquireAccountInfoEx"},
{0x001D, nullptr, "InquireMailAddress"},
{0x001E, nullptr, "AcquireEula"},
{0x001F, nullptr, "AcquireEulaLanguageList"},
// act:a
{0x0402, nullptr, "CreateConsoleAccount"},
{0x0403, nullptr, "CommitConsoleAccount"},
{0x0404, nullptr, "UnbindServerAccount"},
{0x0405, nullptr, "DeleteConsoleAccount"},
{0x0407, nullptr, "UnloadConsoleAccount"},
{0x0408, nullptr, "EnableAccountPasswordCache"},
{0x0409, nullptr, "SetDefaultAccount"},
{0x040A, nullptr, "ReplaceAccountId"},
{0x040B, nullptr, "GetSupportContext"},
{0x0412, nullptr, "UpdateMii"},
{0x0413, nullptr, "UpdateMiiImage"},
{0x0414, nullptr, "InquireAccountIdAvailability"},
{0x0415, nullptr, "BindToNewServerAccount"},
{0x0416, nullptr, "BindToExistentServerAccount"},
{0x0417, nullptr, "InquireBindingToExistentServerAccount"},
{0x041A, nullptr, "AcquireAccountTokenEx"},
{0x041B, nullptr, "AgreeEula"},
{0x041C, nullptr, "SyncAccountInfo"},
{0x041E, nullptr, "UpdateAccountPassword"},
{0x041F, nullptr, "ReissueAccountPassword"},
{0x0420, nullptr, "SetAccountPasswordInput"},
{0x0421, nullptr, "UploadMii"},
{0x0423, nullptr, "ValidateMailAddress"},
{0x0423, nullptr, "SendConfirmationMail"},
{0x0428, nullptr, "ApproveByCreditCard"},
{0x0428, nullptr, "SendCoppaCodeMail"},
{0x042F, nullptr, "UpdateAccountInfoEx"},
{0x0430, nullptr, "UpdateAccountMailAddress"},
{0x0435, nullptr, "DeleteServerAccount"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::ACT
SERIALIZE_EXPORT_IMPL(Service::ACT::ACT_A)

View File

@@ -0,0 +1,22 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/act/act.h"
namespace Service::ACT {
class ACT_A final : public Module::Interface {
public:
explicit ACT_A(std::shared_ptr<Module> act);
private:
SERVICE_SERIALIZATION(ACT_A, act, Module)
};
} // namespace Service::ACT
BOOST_CLASS_EXPORT_KEY(Service::ACT::ACT_A)
BOOST_SERIALIZATION_CONSTRUCT(Service::ACT::ACT_A)

View File

@@ -0,0 +1,757 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/act/act_errors.h"
namespace Service::ACT {
u32 GetACTErrorCode(Result result) {
u32 error_code = ErrCodes::Unknown;
if (result.module == ErrorModule::ACT) {
switch (result.description) {
case ErrDescriptions::MySuccess:
error_code = ErrCodes::MySuccess;
break;
case ErrDescriptions::MailAddressNotConfirmed:
error_code = ErrCodes::MailAddressNotConfirmed;
break;
case ErrDescriptions::LibraryError:
error_code = ErrCodes::LibraryError;
break;
case ErrDescriptions::NotInitialized:
error_code = ErrCodes::NotInitialized;
break;
case ErrDescriptions::AlreadyInitialized:
error_code = ErrCodes::AlreadyInitialized;
break;
case ErrDescriptions::ErrDesc103:
error_code = ErrCodes::ErrCode225103;
break;
case ErrDescriptions::ErrDesc104:
error_code = ErrCodes::ErrCode225104;
break;
case ErrDescriptions::Busy:
error_code = ErrCodes::Busy;
break;
case ErrDescriptions::ErrDesc112:
error_code = ErrCodes::ErrCode225112;
break;
case ErrDescriptions::NotImplemented:
error_code = ErrCodes::NotImplemented;
break;
case ErrDescriptions::Deprecated:
error_code = ErrCodes::Deprecated;
break;
case ErrDescriptions::DevelopmentOnly:
error_code = ErrCodes::DevelopmentOnly;
break;
case ErrDescriptions::InvalidArgument:
error_code = ErrCodes::InvalidArgument;
break;
case ErrDescriptions::InvalidPointer:
error_code = ErrCodes::InvalidPointer;
break;
case ErrDescriptions::OutOfRange:
error_code = ErrCodes::OutOfRange;
break;
case ErrDescriptions::InvalidSize:
error_code = ErrCodes::InvalidSize;
break;
case ErrDescriptions::InvalidFormat:
error_code = ErrCodes::InvalidFormat;
break;
case ErrDescriptions::InvalidHandle:
error_code = ErrCodes::InvalidHandle;
break;
case ErrDescriptions::InvalidValue:
error_code = ErrCodes::InvalidValue;
break;
case ErrDescriptions::InternalError:
error_code = ErrCodes::InternalError;
break;
case ErrDescriptions::EndOfStream:
error_code = ErrCodes::EndOfStream;
break;
case ErrDescriptions::FileError:
error_code = ErrCodes::FileError;
break;
case ErrDescriptions::FileNotFound:
error_code = ErrCodes::FileNotFound;
break;
case ErrDescriptions::FileVersionMismatch:
error_code = ErrCodes::FileVersionMismatch;
break;
case ErrDescriptions::FileIOError:
error_code = ErrCodes::FileIOError;
break;
case ErrDescriptions::FileTypeMismatch:
error_code = ErrCodes::FileTypeMismatch;
break;
case ErrDescriptions::ErrDesc315:
error_code = ErrCodes::ErrCode225315;
break;
case ErrDescriptions::OutOfResource:
error_code = ErrCodes::OutOfResource;
break;
case ErrDescriptions::ShortOfBuffer:
error_code = ErrCodes::ShortOfBuffer;
break;
case ErrDescriptions::OutOfMemory:
error_code = ErrCodes::OutOfMemory;
break;
case ErrDescriptions::OutOfGlobalHeap:
error_code = ErrCodes::OutOfGlobalHeap;
break;
case ErrDescriptions::ErrDesc350:
error_code = ErrCodes::ErrCode225350;
break;
case ErrDescriptions::ErrDesc351:
error_code = ErrCodes::ErrCode225351;
break;
case ErrDescriptions::ErrDesc352:
error_code = ErrCodes::ErrCode225352;
break;
case ErrDescriptions::ErrDesc360:
error_code = ErrCodes::ErrCode225360;
break;
case ErrDescriptions::ErrDesc361:
error_code = ErrCodes::ErrCode225361;
break;
case ErrDescriptions::ErrDesc362:
error_code = ErrCodes::ErrCode225362;
break;
case ErrDescriptions::ErrDesc363:
error_code = ErrCodes::ErrCode225363;
break;
case ErrDescriptions::AccountManagementError:
error_code = ErrCodes::AccountManagementError;
break;
case ErrDescriptions::AccountNotFound:
error_code = ErrCodes::AccountNotFound;
break;
case ErrDescriptions::SlotsFull:
error_code = ErrCodes::SlotsFull;
break;
case ErrDescriptions::AccountNotLoaded:
error_code = ErrCodes::AccountNotLoaded;
break;
case ErrDescriptions::AccountAlreadyLoaded:
error_code = ErrCodes::AccountAlreadyLoaded;
break;
case ErrDescriptions::AccountLocked:
error_code = ErrCodes::AccountLocked;
break;
case ErrDescriptions::NotNetworkAccount:
error_code = ErrCodes::NotNetworkAccount;
break;
case ErrDescriptions::NotLocalAccount:
error_code = ErrCodes::NotLocalAccount;
break;
case ErrDescriptions::AccountNotCommited:
error_code = ErrCodes::AccountCommited;
break;
case ErrDescriptions::ErrDesc431:
error_code = ErrCodes::ErrCode225431;
break;
case ErrDescriptions::ErrDesc432:
error_code = ErrCodes::ErrCode225432;
break;
case ErrDescriptions::ErrDesc433:
error_code = ErrCodes::ErrCode225433;
break;
case ErrDescriptions::ErrDesc451:
error_code = ErrCodes::ErrCode221101;
break;
case ErrDescriptions::AuthenticationError:
error_code = ErrCodes::AuthenticationError;
break;
case ErrDescriptions::HttpError:
error_code = ErrCodes::HttpError;
break;
case ErrDescriptions::ErrDesc502:
error_code = ErrCodes::ErrCode225502;
break;
case ErrDescriptions::ErrDesc503:
error_code = ErrCodes::ErrCode225503;
break;
case ErrDescriptions::ErrDesc504:
error_code = ErrCodes::ErrCode225504;
break;
case ErrDescriptions::ErrDesc505:
error_code = ErrCodes::ErrCode225505;
break;
case ErrDescriptions::ErrDesc506:
error_code = ErrCodes::ErrCode225506;
break;
case ErrDescriptions::ErrDesc507:
error_code = ErrCodes::ErrCode225507;
break;
case ErrDescriptions::ErrDesc508:
error_code = ErrCodes::ErrCode225508;
break;
case ErrDescriptions::ErrDesc509:
error_code = ErrCodes::ErrCode225509;
break;
case ErrDescriptions::ErrDesc510:
error_code = ErrCodes::ErrCode225510;
break;
case ErrDescriptions::ErrDesc511:
error_code = ErrCodes::ErrCode225511;
break;
case ErrDescriptions::ErrDesc512:
error_code = ErrCodes::ErrCode225512;
break;
case ErrDescriptions::ErrDesc513:
error_code = ErrCodes::ErrCode225513;
break;
case ErrDescriptions::ErrDesc514:
error_code = ErrCodes::ErrCode225514;
break;
case ErrDescriptions::ErrDesc515:
error_code = ErrCodes::ErrCode225515;
break;
case ErrDescriptions::ErrDesc516:
error_code = ErrCodes::ErrCode225516;
break;
case ErrDescriptions::ErrDesc517:
error_code = ErrCodes::ErrCode225517;
break;
case ErrDescriptions::ErrDesc518:
error_code = ErrCodes::ErrCode225518;
break;
case ErrDescriptions::ErrDesc519:
error_code = ErrCodes::ErrCode225519;
break;
case ErrDescriptions::ErrDesc520:
error_code = ErrCodes::ErrCode225520;
break;
case ErrDescriptions::ErrDesc521:
error_code = ErrCodes::ErrCode225521;
break;
case ErrDescriptions::ErrDesc522:
error_code = ErrCodes::ErrCode225522;
break;
case ErrDescriptions::ErrDesc523:
error_code = ErrCodes::ErrCode225523;
break;
case ErrDescriptions::ErrDesc524:
error_code = ErrCodes::ErrCode225524;
break;
case ErrDescriptions::ErrDesc525:
error_code = ErrCodes::ErrCode225525;
break;
case ErrDescriptions::ErrDesc526:
error_code = ErrCodes::ErrCode225526;
break;
case ErrDescriptions::ErrDesc527:
error_code = ErrCodes::ErrCode225527;
break;
case ErrDescriptions::ErrDesc528:
error_code = ErrCodes::ErrCode225528;
break;
case ErrDescriptions::ErrDesc529:
error_code = ErrCodes::ErrCode225529;
break;
case ErrDescriptions::ErrDesc530:
error_code = ErrCodes::ErrCode225530;
break;
case ErrDescriptions::ErrDesc531:
error_code = ErrCodes::ErrCode225531;
break;
case ErrDescriptions::ErrDesc532:
error_code = ErrCodes::ErrCode225532;
break;
case ErrDescriptions::ErrDesc533:
error_code = ErrCodes::ErrCode225533;
break;
case ErrDescriptions::ErrDesc534:
error_code = ErrCodes::ErrCode225534;
break;
case ErrDescriptions::ErrDesc535:
error_code = ErrCodes::ErrCode225535;
break;
case ErrDescriptions::ErrDesc536:
error_code = ErrCodes::ErrCode225536;
break;
case ErrDescriptions::ErrDesc537:
error_code = ErrCodes::ErrCode225537;
break;
case ErrDescriptions::ErrDesc538:
error_code = ErrCodes::ErrCode225538;
break;
case ErrDescriptions::ErrDesc539:
error_code = ErrCodes::ErrCode225539;
break;
case ErrDescriptions::ErrDesc540:
error_code = ErrCodes::ErrCode225540;
break;
case ErrDescriptions::ErrDesc541:
error_code = ErrCodes::ErrCode225541;
break;
case ErrDescriptions::ErrDesc542:
error_code = ErrCodes::ErrCode225542;
break;
case ErrDescriptions::ErrDesc543:
error_code = ErrCodes::ErrCode225543;
break;
case ErrDescriptions::ErrDesc544:
error_code = ErrCodes::ErrCode225544;
break;
case ErrDescriptions::ErrDesc545:
error_code = ErrCodes::ErrCode225545;
break;
case ErrDescriptions::ErrDesc546:
error_code = ErrCodes::ErrCode225546;
break;
case ErrDescriptions::ErrDesc547:
error_code = ErrCodes::ErrCode225547;
break;
case ErrDescriptions::ErrDesc548:
error_code = ErrCodes::ErrCode225548;
break;
case ErrDescriptions::ErrDesc549:
error_code = ErrCodes::ErrCode225549;
break;
case ErrDescriptions::ErrDesc550:
error_code = ErrCodes::ErrCode225550;
break;
case ErrDescriptions::ErrDesc551:
error_code = ErrCodes::ErrCode225551;
break;
case ErrDescriptions::ErrDesc552:
error_code = ErrCodes::ErrCode225552;
break;
case ErrDescriptions::ErrDesc553:
error_code = ErrCodes::ErrCode225553;
break;
case ErrDescriptions::RequestError:
error_code = ErrCodes::RequestError;
break;
case ErrDescriptions::BadFormatParameter:
error_code = ErrCodes::BadFormatParameter;
break;
case ErrDescriptions::BadFormatRequest:
error_code = ErrCodes::BadFormatRequest;
break;
case ErrDescriptions::RequestParameterMissing:
error_code = ErrCodes::RequestParameterMissing;
break;
case ErrDescriptions::WrongHttpMethod:
error_code = ErrCodes::WrongHttpMethod;
break;
case ErrDescriptions::ResponseError:
error_code = ErrCodes::ResponseError;
break;
case ErrDescriptions::BadFormatResponse:
error_code = ErrCodes::BadFormatResponse;
break;
case ErrDescriptions::ResponseItemMissing:
error_code = ErrCodes::ResponseItemMissing;
break;
case ErrDescriptions::ResponseTooLarge:
error_code = ErrCodes::ResponseTooLarge;
break;
case ErrDescriptions::InvalidCommonParameter:
error_code = ErrCodes::InvalidCommonParameter;
break;
case ErrDescriptions::InvalidPlatformId:
error_code = ErrCodes::InvalidPlatformId;
break;
case ErrDescriptions::UnauthorizedDevice:
error_code = ErrCodes::UnauthorizedDevice;
break;
case ErrDescriptions::InvalidSerialId:
error_code = ErrCodes::InvalidSerialId;
break;
case ErrDescriptions::InvalidMacAddress:
error_code = ErrCodes::InvalidMacAddress;
break;
case ErrDescriptions::InvalidRegion:
error_code = ErrCodes::InvalidRegion;
break;
case ErrDescriptions::InvalidCountry:
error_code = ErrCodes::InvalidCountry;
break;
case ErrDescriptions::InvalidLanguage:
error_code = ErrCodes::InvalidLanguage;
break;
case ErrDescriptions::UnauthorizedClient:
error_code = ErrCodes::UnauthorizedClient;
break;
case ErrDescriptions::DeviceIdEmpty:
error_code = ErrCodes::DeviceIdEmpty;
break;
case ErrDescriptions::SerialIdEmpty:
error_code = ErrCodes::SerialIdEmpty;
break;
case ErrDescriptions::PlatformIdEmpty:
error_code = ErrCodes::PlatformIdEmpty;
break;
case ErrDescriptions::InvalidUniqueId:
error_code = ErrCodes::InvalidUniqueId;
break;
case ErrDescriptions::InvalidClientId:
error_code = ErrCodes::InvalidClientId;
break;
case ErrDescriptions::InvalidClientKey:
error_code = ErrCodes::InvalidClientKey;
break;
case ErrDescriptions::InvalidNexClientId:
error_code = ErrCodes::InvalidNexClientId;
break;
case ErrDescriptions::InvalidGameServerId:
error_code = ErrCodes::InvalidGameServerId;
break;
case ErrDescriptions::GameServerIdEnvironmentNotFound:
error_code = ErrCodes::GameServerIdEnvironmentNotFound;
break;
case ErrDescriptions::GameServerIdUniqueIdNotLinked:
error_code = ErrCodes::GameServerIdUniqueIdNotLinked;
break;
case ErrDescriptions::ClientIdUniqueIdNotLinked:
error_code = ErrCodes::ClientIdUniqueIdNotLinked;
break;
case ErrDescriptions::DeviceMismatch:
error_code = ErrCodes::DeviceMismatch;
break;
case ErrDescriptions::CountryMismatch:
error_code = ErrCodes::CountryMismatch;
break;
case ErrDescriptions::EulaNotAccepted:
error_code = ErrCodes::EulaNotAccepted;
break;
case ErrDescriptions::UpdateRequired:
error_code = ErrCodes::UpdateRequired;
break;
case ErrDescriptions::SystemUpdateRequired:
error_code = ErrCodes::SystemUpdateRequired;
break;
case ErrDescriptions::ApplicationUpdateRequired:
error_code = ErrCodes::ApplicationUpdateRequired;
break;
case ErrDescriptions::UnauthorizedRequest:
error_code = ErrCodes::UnauthorizedRequest;
break;
case ErrDescriptions::RequestForbidden:
error_code = ErrCodes::RequestForbidden;
break;
case ErrDescriptions::ResourceNotFound:
error_code = ErrCodes::ResourceNotFound;
break;
case ErrDescriptions::PidNotFound:
error_code = ErrCodes::PidNotFound;
break;
case ErrDescriptions::NexAccountNotFound:
error_code = ErrCodes::NexAccountNotFound;
break;
case ErrDescriptions::GenerateTokenFailure:
error_code = ErrCodes::GenerateTokenFailure;
break;
case ErrDescriptions::RequestNotFound:
error_code = ErrCodes::RequestNotFound;
break;
case ErrDescriptions::MasterPinNotFound:
error_code = ErrCodes::MasterPinNotFound;
break;
case ErrDescriptions::MailTextNotFound:
error_code = ErrCodes::MailTextNotFound;
break;
case ErrDescriptions::SendMailFailure:
error_code = ErrCodes::SendMailFailure;
break;
case ErrDescriptions::ApprovalIdNotFound:
error_code = ErrCodes::ApprovalIdNotFound;
break;
case ErrDescriptions::InvalidEulaParameter:
error_code = ErrCodes::InvalidEulaParameter;
break;
case ErrDescriptions::InvalidEulaCountry:
error_code = ErrCodes::InvalidEulaCountry;
break;
case ErrDescriptions::InvalidEulaCountryAndVersion:
error_code = ErrCodes::InvalidEulaCountryAndVersion;
break;
case ErrDescriptions::EulaNotFound:
error_code = ErrCodes::EulaNotFound;
break;
case ErrDescriptions::PhraseNotAcceptable:
error_code = ErrCodes::PhraseNotAcceptable;
break;
case ErrDescriptions::AccountIdAlreadyExists:
error_code = ErrCodes::AccountIdAlreadyExists;
break;
case ErrDescriptions::AccountIdNotAcceptable:
error_code = ErrCodes::AccountIdNotAcceptable;
break;
case ErrDescriptions::AccountPasswordNotAcceptable:
error_code = ErrCodes::AccountPasswordNotAcceptable;
break;
case ErrDescriptions::MiiNameNotAcceptable:
error_code = ErrCodes::MiiNameNotAcceptable;
break;
case ErrDescriptions::MailAddressNotAcceptable:
error_code = ErrCodes::MailAddressNotAcceptable;
break;
case ErrDescriptions::AccountIdFormatInvalid:
error_code = ErrCodes::AccountIdFormatInvalid;
break;
case ErrDescriptions::AccountIdPasswordSame:
error_code = ErrCodes::AccountIdPasswordSame;
break;
case ErrDescriptions::AccountIdCharNotAcceptable:
error_code = ErrCodes::AccountIdCharNotAcceptable;
break;
case ErrDescriptions::AccountIdSuccessiveSymbol:
error_code = ErrCodes::AccountIdSuccessiveSymbol;
break;
case ErrDescriptions::AccountIdSymbolPositionNotAcceptable:
error_code = ErrCodes::AccountIdSymbolPositionNotAcceptable;
break;
case ErrDescriptions::AccountIdTooManyDigit:
error_code = ErrCodes::AccountIdTooManyDigit;
break;
case ErrDescriptions::AccountPasswordCharNotAcceptable:
error_code = ErrCodes::AccountPasswordCharNotAcceptable;
break;
case ErrDescriptions::AccountPasswordTooFewCharTypes:
error_code = ErrCodes::AccountPasswordTooFewCharTypes;
break;
case ErrDescriptions::AccountPasswordSuccessiveSameChar:
error_code = ErrCodes::AccountPasswordSuccessiveSameChar;
break;
case ErrDescriptions::MailAddressDomainNameNotAcceptable:
error_code = ErrCodes::MailAddressDomainNameNotAcceptable;
break;
case ErrDescriptions::MailAddressDomainNameNotResolved:
error_code = ErrCodes::MailAddressDomainNameNotResolved;
break;
case ErrDescriptions::ErrDesc787:
error_code = ErrCodes::ErrCode222587;
break;
case ErrDescriptions::ReachedAssociationLimit:
error_code = ErrCodes::ReachedAssociationLimit;
break;
case ErrDescriptions::ReachedRegistrationLimit:
error_code = ErrCodes::ReachedRegistrationLimit;
break;
case ErrDescriptions::CoppaNotAccepted:
error_code = ErrCodes::CoppaNotAccepted;
break;
case ErrDescriptions::ParentalControlsRequired:
error_code = ErrCodes::ParentalControlsRequired;
break;
case ErrDescriptions::MiiNotRegistered:
error_code = ErrCodes::MiiNotRegistered;
break;
case ErrDescriptions::DeviceEulaCountryMismatch:
error_code = ErrCodes::DeviceEulaCountryMismatch;
break;
case ErrDescriptions::PendingMigration:
error_code = ErrCodes::PendingMigration;
break;
case ErrDescriptions::WrongUserInput:
error_code = ErrCodes::WrongUserInput;
break;
case ErrDescriptions::WrongAccountPassword:
error_code = ErrCodes::WrongAccountPassword;
break;
case ErrDescriptions::WrongMailAddress:
error_code = ErrCodes::WrongMailAddress;
break;
case ErrDescriptions::WrongAccountPasswordOrMailAddress:
error_code = ErrCodes::WrongAccountPasswordOrMailAddress;
break;
case ErrDescriptions::WrongConfirmationCode:
error_code = ErrCodes::WrongConfirmationCode;
break;
case ErrDescriptions::WrongBirthDateOrMailAddress:
error_code = ErrCodes::WrongBirthDateOrMailAddress;
break;
case ErrDescriptions::WrongAccountMail:
error_code = ErrCodes::WrongAccountMail;
break;
case ErrDescriptions::AccountAlreadyDeleted:
error_code = ErrCodes::AccountAlreadyDeleted;
break;
case ErrDescriptions::AccountIdChanged:
error_code = ErrCodes::AccountIdChanged;
break;
case ErrDescriptions::AuthenticationLocked:
error_code = ErrCodes::AuthenticationLocked;
break;
case ErrDescriptions::DeviceInactive:
error_code = ErrCodes::DeviceInactive;
break;
case ErrDescriptions::CoppaAgreementCanceled:
error_code = ErrCodes::CoppaAgreementCanceled;
break;
case ErrDescriptions::DomainAccountAlreadyExists:
error_code = ErrCodes::DomainAccountAlreadyExists;
break;
case ErrDescriptions::AccountTokenExpired:
error_code = ErrCodes::AccountTokenExpired;
break;
case ErrDescriptions::InvalidAccountToken:
error_code = ErrCodes::InvalidAccountToken;
break;
case ErrDescriptions::AuthenticationRequired:
error_code = ErrCodes::AuthenticationRequired;
break;
case ErrDescriptions::ErrDesc844:
error_code = ErrCodes::ErrCode225844;
break;
case ErrDescriptions::ConfirmationCodeExpired:
error_code = ErrCodes::ConfirmationCodeExpired;
break;
case ErrDescriptions::MailAddressNotValidated:
error_code = ErrCodes::MailAddressNotValidated;
break;
case ErrDescriptions::ExcessiveMailSendRequest:
error_code = ErrCodes::ExcessiveMailSendRequest;
break;
case ErrDescriptions::CreditCardError:
error_code = ErrCodes::CreditCardError;
break;
case ErrDescriptions::CreditCardGeneralFailure:
error_code = ErrCodes::CreditCardGeneralFailure;
break;
case ErrDescriptions::CreditCardDeclined:
error_code = ErrCodes::CreditCardDeclined;
break;
case ErrDescriptions::CreditCardBlacklisted:
error_code = ErrCodes::CreditCardBlacklisted;
break;
case ErrDescriptions::InvalidCreditCardNumber:
error_code = ErrCodes::InvalidCreditCardNumber;
break;
case ErrDescriptions::InvalidCreditCardDate:
error_code = ErrCodes::InvalidCreditCardDate;
break;
case ErrDescriptions::InvalidCreditCardPin:
error_code = ErrCodes::InvalidCreditCardPin;
break;
case ErrDescriptions::InvalidPostalCode:
error_code = ErrCodes::InvalidPostalCode;
break;
case ErrDescriptions::InvalidLocation:
error_code = ErrCodes::InvalidLocation;
break;
case ErrDescriptions::CreditCardDateExpired:
error_code = ErrCodes::CreditCardDateExpired;
break;
case ErrDescriptions::CreditCardNumberWrong:
error_code = ErrCodes::CreditCardNumberWrong;
break;
case ErrDescriptions::CreditCardPinWrong:
error_code = ErrCodes::CreditCardPinWrong;
break;
case ErrDescriptions::Banned:
error_code = ErrCodes::Banned;
break;
case ErrDescriptions::BannedAccount:
error_code = ErrCodes::BannedAccount;
break;
case ErrDescriptions::BannedAccountAll:
error_code = ErrCodes::BannedAccountAll;
break;
case ErrDescriptions::BannedAccountInApplication:
error_code = ErrCodes::BannedAccountInApplication;
break;
case ErrDescriptions::BannedAccountInNexService:
error_code = ErrCodes::BannedAccountInNexService;
break;
case ErrDescriptions::BannedAccountInIndependentService:
error_code = ErrCodes::BannedAccountInIndependentService;
break;
case ErrDescriptions::BannedDevice:
error_code = ErrCodes::BannedDevice;
break;
case ErrDescriptions::BannedDeviceAll:
error_code = ErrCodes::BannedDeviceAll;
break;
case ErrDescriptions::BannedDeviceInApplication:
error_code = ErrCodes::BannedDeviceInApplication;
break;
case ErrDescriptions::BannedDeviceInNexService:
error_code = ErrCodes::BannedDeviceInNexService;
break;
case ErrDescriptions::BannedDeviceInIndependentService:
error_code = ErrCodes::BannedDeviceInIndependentService;
break;
case ErrDescriptions::BannedAccountTemporarily:
error_code = ErrCodes::BannedAccountTemporarily;
break;
case ErrDescriptions::BannedAccountAllTemporarily:
error_code = ErrCodes::BannedAccountAllTemporarily;
break;
case ErrDescriptions::BannedAccountInApplicationTemporarily:
error_code = ErrCodes::BannedAccountInApplicationTemporarily;
break;
case ErrDescriptions::BannedAccountInNexServiceTemporarily:
error_code = ErrCodes::BannedAccountInNexServiceTemporarily;
break;
case ErrDescriptions::BannedAccountInIndependentServiceTemporarily:
error_code = ErrCodes::BannedAccountInIndependentServiceTemporarily;
break;
case ErrDescriptions::BannedDeviceTemporarily:
error_code = ErrCodes::BannedDeviceTemporarily;
break;
case ErrDescriptions::BannedDeviceAllTemporarily:
error_code = ErrCodes::BannedDeviceAllTemporarily;
break;
case ErrDescriptions::BannedDeviceInApplicationTemporarily:
error_code = ErrCodes::BannedDeviceInApplicationTemporarily;
break;
case ErrDescriptions::BannedDeviceInNexServiceTemporarily:
error_code = ErrCodes::BannedDeviceInNexServiceTemporarily;
break;
case ErrDescriptions::BannedDeviceInIndependentServiceTemporarily:
error_code = ErrCodes::BannedDeviceInIndependentServiceTemporarily;
break;
case ErrDescriptions::ServiceNotProvided:
error_code = ErrCodes::ServiceNotProvided;
break;
case ErrDescriptions::UnderMaintenance:
error_code = ErrCodes::UnderMaintenance;
break;
case ErrDescriptions::ServiceClosed:
error_code = ErrCodes::ServiceClosed;
break;
case ErrDescriptions::NintendoNetworkClosed:
error_code = ErrCodes::NintendoNetworkClosed;
break;
case ErrDescriptions::NotProvidedCountry:
error_code = ErrCodes::NotProvidedCountry;
break;
case ErrDescriptions::RestrictionError:
error_code = ErrCodes::RestrictionError;
break;
case ErrDescriptions::RestrictedByAge:
error_code = ErrCodes::RestrictedByAge;
break;
case ErrDescriptions::RestrictedByParentalControls:
error_code = ErrCodes::RestrictedByParentalControls;
break;
case ErrDescriptions::OnGameInternetCommunicationRestricted:
error_code = ErrCodes::OnGameInternetCommunicationRestricted;
break;
case ErrDescriptions::InternalServerError:
error_code = ErrCodes::InternalServerError;
break;
case ErrDescriptions::UnknownServerError:
error_code = ErrCodes::UnknownServerError;
break;
case ErrDescriptions::UnauthenticatedAfterSalvage:
error_code = ErrCodes::UnauthenticatedAfterSalvage;
break;
case ErrDescriptions::AuthenticationFailureUnknown:
error_code = ErrCodes::AuthenticationFailureUnknown;
break;
}
}
return error_code;
}
} // namespace Service::ACT

View File

@@ -0,0 +1,613 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::ACT {
namespace ErrDescriptions {
enum {
MySuccess = 0,
MailAddressNotConfirmed = 1,
// Library errors
LibraryError = 100,
NotInitialized = 101,
AlreadyInitialized = 102,
ErrDesc103 = 103,
ErrDesc104 = 104,
Busy = 111,
ErrDesc112 = 112,
NotImplemented = 191,
Deprecated = 192,
DevelopmentOnly = 193,
InvalidArgument = 200,
InvalidPointer = 201,
OutOfRange = 202,
InvalidSize = 203,
InvalidFormat = 204,
InvalidHandle = 205,
InvalidValue = 206,
InternalError = 300,
EndOfStream = 301,
FileError = 310,
FileNotFound = 311,
FileVersionMismatch = 312,
FileIOError = 313,
FileTypeMismatch = 314,
ErrDesc315 = 315,
OutOfResource = 330,
ShortOfBuffer = 331,
OutOfMemory = 340,
OutOfGlobalHeap = 341,
ErrDesc350 = 350,
ErrDesc351 = 351,
ErrDesc352 = 352,
ErrDesc360 = 360,
ErrDesc361 = 361,
ErrDesc362 = 362,
ErrDesc363 = 363,
// Account management errors
AccountManagementError = 400,
AccountNotFound = 401,
SlotsFull = 402,
AccountNotLoaded = 411,
AccountAlreadyLoaded = 412,
AccountLocked = 413,
NotNetworkAccount = 421,
NotLocalAccount = 422,
AccountNotCommited = 423,
ErrDesc431 = 431,
ErrDesc432 = 432,
ErrDesc433 = 433,
ErrDesc451 = 451,
AuthenticationError = 500,
// HTTP errors
HttpError = 501,
ErrDesc502 = 502,
ErrDesc503 = 503,
ErrDesc504 = 504,
ErrDesc505 = 505,
ErrDesc506 = 506,
ErrDesc507 = 507,
ErrDesc508 = 508,
ErrDesc509 = 509,
ErrDesc510 = 510,
ErrDesc511 = 511,
ErrDesc512 = 512,
ErrDesc513 = 513,
ErrDesc514 = 514,
ErrDesc515 = 515,
ErrDesc516 = 516,
ErrDesc517 = 517,
ErrDesc518 = 518,
ErrDesc519 = 519,
ErrDesc520 = 520,
ErrDesc521 = 521,
ErrDesc522 = 522,
ErrDesc523 = 523,
ErrDesc524 = 524,
ErrDesc525 = 525,
ErrDesc526 = 526,
ErrDesc527 = 527,
ErrDesc528 = 528,
ErrDesc529 = 529,
ErrDesc530 = 530,
ErrDesc531 = 531,
ErrDesc532 = 532,
ErrDesc533 = 533,
ErrDesc534 = 534,
ErrDesc535 = 535,
ErrDesc536 = 536,
ErrDesc537 = 537,
ErrDesc538 = 538,
ErrDesc539 = 539,
ErrDesc540 = 540,
ErrDesc541 = 541,
ErrDesc542 = 542,
ErrDesc543 = 543,
ErrDesc544 = 544,
ErrDesc545 = 545,
ErrDesc546 = 546,
ErrDesc547 = 547,
ErrDesc548 = 548,
ErrDesc549 = 549,
ErrDesc550 = 550,
ErrDesc551 = 551,
ErrDesc552 = 552,
ErrDesc553 = 553,
// Request errors
RequestError = 600,
BadFormatParameter = 601,
BadFormatRequest = 602,
RequestParameterMissing = 603,
WrongHttpMethod = 604,
// Response errors
ResponseError = 620,
BadFormatResponse = 621,
ResponseItemMissing = 622,
ResponseTooLarge = 623,
// Invalid parameter errors
InvalidCommonParameter = 650,
InvalidPlatformId = 651,
UnauthorizedDevice = 652,
InvalidSerialId = 653,
InvalidMacAddress = 654,
InvalidRegion = 655,
InvalidCountry = 656,
InvalidLanguage = 657,
UnauthorizedClient = 658,
DeviceIdEmpty = 659,
SerialIdEmpty = 660,
PlatformIdEmpty = 661,
InvalidUniqueId = 671,
InvalidClientId = 672,
InvalidClientKey = 673,
InvalidNexClientId = 681,
InvalidGameServerId = 682,
GameServerIdEnvironmentNotFound = 683,
GameServerIdUniqueIdNotLinked = 684,
ClientIdUniqueIdNotLinked = 685,
DeviceMismatch = 701,
CountryMismatch = 702,
EulaNotAccepted = 703,
// Update required errors
UpdateRequired = 710,
SystemUpdateRequired = 711,
ApplicationUpdateRequired = 712,
UnauthorizedRequest = 720,
RequestForbidden = 722,
// Resource not found errors
ResourceNotFound = 730,
PidNotFound = 731,
NexAccountNotFound = 732,
GenerateTokenFailure = 733,
RequestNotFound = 734,
MasterPinNotFound = 735,
MailTextNotFound = 736,
SendMailFailure = 737,
ApprovalIdNotFound = 738,
// EULA errors
InvalidEulaParameter = 740,
InvalidEulaCountry = 741,
InvalidEulaCountryAndVersion = 742,
EulaNotFound = 743,
// Not acceptable errors
PhraseNotAcceptable = 770,
AccountIdAlreadyExists = 771,
AccountIdNotAcceptable = 772,
AccountPasswordNotAcceptable = 773,
MiiNameNotAcceptable = 774,
MailAddressNotAcceptable = 775,
AccountIdFormatInvalid = 776,
AccountIdPasswordSame = 777,
AccountIdCharNotAcceptable = 778,
AccountIdSuccessiveSymbol = 779,
AccountIdSymbolPositionNotAcceptable = 780,
AccountIdTooManyDigit = 781,
AccountPasswordCharNotAcceptable = 782,
AccountPasswordTooFewCharTypes = 783,
AccountPasswordSuccessiveSameChar = 784,
MailAddressDomainNameNotAcceptable = 785,
MailAddressDomainNameNotResolved = 786,
ErrDesc787 = 787,
ReachedAssociationLimit = 791,
ReachedRegistrationLimit = 792,
CoppaNotAccepted = 793,
ParentalControlsRequired = 794,
MiiNotRegistered = 795,
DeviceEulaCountryMismatch = 796,
PendingMigration = 797,
// Wrong user input errors
WrongUserInput = 810,
WrongAccountPassword = 811,
WrongMailAddress = 812,
WrongAccountPasswordOrMailAddress = 813,
WrongConfirmationCode = 814,
WrongBirthDateOrMailAddress = 815,
WrongAccountMail = 816,
AccountAlreadyDeleted = 831,
AccountIdChanged = 832,
AuthenticationLocked = 833,
DeviceInactive = 834,
CoppaAgreementCanceled = 835,
DomainAccountAlreadyExists = 836,
AccountTokenExpired = 841,
InvalidAccountToken = 842,
AuthenticationRequired = 843,
ErrDesc844 = 844,
ConfirmationCodeExpired = 851,
MailAddressNotValidated = 861,
ExcessiveMailSendRequest = 862,
// Credit card errors
CreditCardError = 870,
CreditCardGeneralFailure = 871,
CreditCardDeclined = 872,
CreditCardBlacklisted = 873,
InvalidCreditCardNumber = 874,
InvalidCreditCardDate = 875,
InvalidCreditCardPin = 876,
InvalidPostalCode = 877,
InvalidLocation = 878,
CreditCardDateExpired = 879,
CreditCardNumberWrong = 880,
CreditCardPinWrong = 881,
// Ban errors
Banned = 900,
BannedAccount = 901,
BannedAccountAll = 902,
BannedAccountInApplication = 903,
BannedAccountInNexService = 904,
BannedAccountInIndependentService = 905,
BannedDevice = 911,
BannedDeviceAll = 912,
BannedDeviceInApplication = 913,
BannedDeviceInNexService = 914,
BannedDeviceInIndependentService = 915,
BannedAccountTemporarily = 921,
BannedAccountAllTemporarily = 922,
BannedAccountInApplicationTemporarily = 923,
BannedAccountInNexServiceTemporarily = 924,
BannedAccountInIndependentServiceTemporarily = 925,
BannedDeviceTemporarily = 931,
BannedDeviceAllTemporarily = 932,
BannedDeviceInApplicationTemporarily = 933,
BannedDeviceInNexServiceTemporarily = 934,
BannedDeviceInIndependentServiceTemporarily = 935,
// Service not provided errors
ServiceNotProvided = 950,
UnderMaintenance = 951,
ServiceClosed = 952,
NintendoNetworkClosed = 953,
NotProvidedCountry = 954,
// Restriction errors
RestrictionError = 970,
RestrictedByAge = 971,
RestrictedByParentalControls = 980,
OnGameInternetCommunicationRestricted = 981,
InternalServerError = 991,
UnknownServerError = 992,
UnauthenticatedAfterSalvage = 998,
AuthenticationFailureUnknown = 999,
};
}
namespace ErrCodes {
enum {
MySuccess = 220000, // 022-0000
MailAddressNotConfirmed = 220001, // 022-0001
// Library errors
LibraryError = 220500, // 022-0500
NotInitialized = 220501, // 022-0501
AlreadyInitialized = 220502, // 022-0502
ErrCode225103 = 225103, // 022-5103
ErrCode225104 = 225104, // 022-5104
Busy = 220511, // 022-0511
ErrCode225112 = 225112, // 022-5112
NotImplemented = 220591, // 022-0591
Deprecated = 220592, // 022-0592
DevelopmentOnly = 220593, // 022-0593
InvalidArgument = 220600, // 022-0600
InvalidPointer = 220601, // 022-0601
OutOfRange = 220602, // 022-0602
InvalidSize = 220603, // 022-0603
InvalidFormat = 220604, // 022-0604
InvalidHandle = 220605, // 022-0605
InvalidValue = 220606, // 022-0606
InternalError = 220700, // 022-0700
EndOfStream = 220701, // 022-0701
FileError = 220710, // 022-0710
FileNotFound = 220711, // 022-0711
FileVersionMismatch = 220712, // 022-0712
FileIOError = 220713, // 022-0713
FileTypeMismatch = 220714, // 022-0714
ErrCode225315 = 225315, // 022-5315
OutOfResource = 220730, // 022-0730
ShortOfBuffer = 220731, // 022-0731
OutOfMemory = 220740, // 022-0740
OutOfGlobalHeap = 220741, // 022-0741
ErrCode225350 = 225350, // 022-5350
ErrCode225351 = 225351, // 022-5351
ErrCode225352 = 225352, // 022-5352
ErrCode225360 = 225360, // 022-5360
ErrCode225361 = 225361, // 022-5361
ErrCode225362 = 225362, // 022-5362
ErrCode225363 = 225363, // 022-5363
// Account management errors
AccountManagementError = 221000, // 022-1000
AccountNotFound = 221001, // 022-1001
SlotsFull = 221002, // 022-1002
AccountNotLoaded = 221011, // 022-1011
AccountAlreadyLoaded = 221012, // 022-1012
AccountLocked = 221013, // 022-1013
NotNetworkAccount = 221021, // 022-1021
NotLocalAccount = 221022, // 022-1022
AccountCommited = 221023, // 022-1023
ErrCode225431 = 225431, // 022-5431
ErrCode225432 = 225432, // 022-5432
ErrCode225433 = 225433, // 022-5433
ErrCode221101 = 221101, // 022-1101
AuthenticationError = 222000, // 022-2000
// HTTP errors
HttpError = 222100, // 022-2100
ErrCode225502 = 225502, // 022-5502
ErrCode225503 = 225503, // 022-5503
ErrCode225504 = 225504, // 022-5504
ErrCode225505 = 225505, // 022-5505
ErrCode225506 = 225506, // 022-5506
ErrCode225507 = 225507, // 022-5507
ErrCode225508 = 225508, // 022-5508
ErrCode225509 = 225509, // 022-5509
ErrCode225510 = 225510, // 022-5510
ErrCode225511 = 225511, // 022-5511
ErrCode225512 = 225512, // 022-5512
ErrCode225513 = 225513, // 022-5513
ErrCode225514 = 225514, // 022-5514
ErrCode225515 = 225515, // 022-5515
ErrCode225516 = 225516, // 022-5516
ErrCode225517 = 225517, // 022-5517
ErrCode225518 = 225518, // 022-5518
ErrCode225519 = 225519, // 022-5519
ErrCode225520 = 225520, // 022-5520
ErrCode225521 = 225521, // 022-5521
ErrCode225522 = 225522, // 022-5522
ErrCode225523 = 225523, // 022-5523
ErrCode225524 = 225524, // 022-5524
ErrCode225525 = 225525, // 022-5525
ErrCode225526 = 225526, // 022-5526
ErrCode225527 = 225527, // 022-5527
ErrCode225528 = 225528, // 022-5528
ErrCode225529 = 225529, // 022-5529
ErrCode225530 = 225530, // 022-5530
ErrCode225531 = 225531, // 022-5531
ErrCode225532 = 225532, // 022-5532
ErrCode225533 = 225533, // 022-5533
ErrCode225534 = 225534, // 022-5534
ErrCode225535 = 225535, // 022-5535
ErrCode225536 = 225536, // 022-5536
ErrCode225537 = 225537, // 022-5537
ErrCode225538 = 225538, // 022-5538
ErrCode225539 = 225539, // 022-5539
ErrCode225540 = 225540, // 022-5540
ErrCode225541 = 225541, // 022-5541
ErrCode225542 = 225542, // 022-5542
ErrCode225543 = 225543, // 022-5543
ErrCode225544 = 225544, // 022-5544
ErrCode225545 = 225545, // 022-5545
ErrCode225546 = 225546, // 022-5546
ErrCode225547 = 225547, // 022-5547
ErrCode225548 = 225548, // 022-5548
ErrCode225549 = 225549, // 022-5549
ErrCode225550 = 225550, // 022-5550
ErrCode225551 = 225551, // 022-5551
ErrCode225552 = 225552, // 022-5552
ErrCode225553 = 225553, // 022-5553
// Request errors
RequestError = 222400, // 022-2400
BadFormatParameter = 222401, // 022-2401
BadFormatRequest = 222402, // 022-2402
RequestParameterMissing = 222403, // 022-2403
WrongHttpMethod = 222404, // 022-2404
// Response errors
ResponseError = 222420, // 022-2420
BadFormatResponse = 222421, // 022-2421
ResponseItemMissing = 222422, // 022-2422
ResponseTooLarge = 222423, // 022-2423
// Invalid parameter errors
InvalidCommonParameter = 222450, // 022-2450
InvalidPlatformId = 222451, // 022-2451
UnauthorizedDevice = 222452, // 022-2452
InvalidSerialId = 222453, // 022-2453
InvalidMacAddress = 222454, // 022-2454
InvalidRegion = 222455, // 022-2455
InvalidCountry = 222456, // 022-2456
InvalidLanguage = 222457, // 022-2457
UnauthorizedClient = 222458, // 022-2458
DeviceIdEmpty = 222459, // 022-2459
SerialIdEmpty = 222460, // 022-2460
PlatformIdEmpty = 222461, // 022-2461
InvalidUniqueId = 222471, // 022-2471
InvalidClientId = 222472, // 022-2472
InvalidClientKey = 222473, // 022-2473
InvalidNexClientId = 222481, // 022-2481
InvalidGameServerId = 222482, // 022-2482
GameServerIdEnvironmentNotFound = 222483, // 022-2483
GameServerIdUniqueIdNotLinked = 222484, // 022-2484
ClientIdUniqueIdNotLinked = 222485, // 022-2485
DeviceMismatch = 222501, // 022-2501
CountryMismatch = 222502, // 022-2502
EulaNotAccepted = 222503, // 022-2503
// Update required errors
UpdateRequired = 222510, // 022-2510
SystemUpdateRequired = 222511, // 022-2511
ApplicationUpdateRequired = 222512, // 022-2512
UnauthorizedRequest = 222520, // 022-2520
RequestForbidden = 222522, // 022-2522
// Resource not found errors
ResourceNotFound = 222530, // 022-2530
PidNotFound = 222531, // 022-2531
NexAccountNotFound = 222532, // 022-2532
GenerateTokenFailure = 222533, // 022-2533
RequestNotFound = 222534, // 022-2534
MasterPinNotFound = 222535, // 022-2535
MailTextNotFound = 222536, // 022-2536
SendMailFailure = 222537, // 022-2537
ApprovalIdNotFound = 222538, // 022-2538
// EULA errors
InvalidEulaParameter = 222540, // 022-2540
InvalidEulaCountry = 222541, // 022-2541
InvalidEulaCountryAndVersion = 222542, // 022-2542
EulaNotFound = 222543, // 022-2543
// Not acceptable errors
PhraseNotAcceptable = 222570, // 022-2570
AccountIdAlreadyExists = 222571, // 022-2571
AccountIdNotAcceptable = 222572, // 022-2572
AccountPasswordNotAcceptable = 222573, // 022-2573
MiiNameNotAcceptable = 222574, // 022-2574
MailAddressNotAcceptable = 222575, // 022-2575
AccountIdFormatInvalid = 222576, // 022-2576
AccountIdPasswordSame = 222577, // 022-2577
AccountIdCharNotAcceptable = 222578, // 022-2578
AccountIdSuccessiveSymbol = 222579, // 022-2579
AccountIdSymbolPositionNotAcceptable = 222580, // 022-2580
AccountIdTooManyDigit = 222581, // 022-2581
AccountPasswordCharNotAcceptable = 222582, // 022-2582
AccountPasswordTooFewCharTypes = 222583, // 022-2583
AccountPasswordSuccessiveSameChar = 222584, // 022-2584
MailAddressDomainNameNotAcceptable = 222585, // 022-2585
MailAddressDomainNameNotResolved = 222586, // 022-2586
ErrCode222587 = 222587, // 022-2587
ReachedAssociationLimit = 222591, // 022-2591
ReachedRegistrationLimit = 222592, // 022-2592
CoppaNotAccepted = 222593, // 022-2593
ParentalControlsRequired = 222594, // 022-2594
MiiNotRegistered = 222595, // 022-2595
DeviceEulaCountryMismatch = 222596, // 022-2596
PendingMigration = 222597, // 022-2597
// Wrong user input errors
WrongUserInput = 222610, // 022-2610
WrongAccountPassword = 222611, // 022-2611
WrongMailAddress = 222612, // 022-2612
WrongAccountPasswordOrMailAddress = 222613, // 022-2613
WrongConfirmationCode = 222614, // 022-2614
WrongBirthDateOrMailAddress = 222615, // 022-2615
WrongAccountMail = 222616, // 022-2616
AccountAlreadyDeleted = 222631, // 022-2631
AccountIdChanged = 222632, // 022-2632
AuthenticationLocked = 222633, // 022-2633
DeviceInactive = 222634, // 022-2634
CoppaAgreementCanceled = 222635, // 022-2635
DomainAccountAlreadyExists = 222636, // 022-2636
AccountTokenExpired = 222641, // 022-2641
InvalidAccountToken = 222642, // 022-2642
AuthenticationRequired = 222643, // 022-2643
ErrCode225844 = 225844, // 022-5844
ConfirmationCodeExpired = 222651, // 022-2651
MailAddressNotValidated = 222661, // 022-2661
ExcessiveMailSendRequest = 222662, // 022-2662
// Credit card errors
CreditCardError = 222670, // 022-2670
CreditCardGeneralFailure = 222671, // 022-2671
CreditCardDeclined = 222672, // 022-2672
CreditCardBlacklisted = 222673, // 022-2673
InvalidCreditCardNumber = 222674, // 022-2674
InvalidCreditCardDate = 222675, // 022-2675
InvalidCreditCardPin = 222676, // 022-2676
InvalidPostalCode = 222677, // 022-2677
InvalidLocation = 222678, // 022-2678
CreditCardDateExpired = 222679, // 022-2679
CreditCardNumberWrong = 222680, // 022-2680
CreditCardPinWrong = 222681, // 022-2681
// Ban errors
Banned = 222800, // 022-2800
BannedAccount = 222801, // 022-2801
BannedAccountAll = 222802, // 022-2802
BannedAccountInApplication = 222803, // 022-2803
BannedAccountInNexService = 222804, // 022-2804
BannedAccountInIndependentService = 222805, // 022-2805
BannedDevice = 222811, // 022-2811
BannedDeviceAll = 222812, // 022-2812
BannedDeviceInApplication = 222813, // 022-2813
BannedDeviceInNexService = 222814, // 022-2814
BannedDeviceInIndependentService = 222815, // 022-2815
BannedAccountTemporarily = 222821, // 022-2821
BannedAccountAllTemporarily = 222822, // 022-2822
BannedAccountInApplicationTemporarily = 222823, // 022-2823
BannedAccountInNexServiceTemporarily = 222824, // 022-2824
BannedAccountInIndependentServiceTemporarily = 222825, // 022-2825
BannedDeviceTemporarily = 222831, // 022-2831
BannedDeviceAllTemporarily = 222832, // 022-2832
BannedDeviceInApplicationTemporarily = 222833, // 022-2833
BannedDeviceInNexServiceTemporarily = 222834, // 022-2834
BannedDeviceInIndependentServiceTemporarily = 222835, // 022-2835
// Service not provided errors
ServiceNotProvided = 222880, // 022-2880
UnderMaintenance = 222881, // 022-2881
ServiceClosed = 222882, // 022-2882
NintendoNetworkClosed = 222883, // 022-2883
NotProvidedCountry = 222884, // 022-2884
// Restriction errors
RestrictionError = 222900, // 022-2900
RestrictedByAge = 222901, // 022-2901
RestrictedByParentalControls = 222910, // 022-2910
OnGameInternetCommunicationRestricted = 222911, // 022-2911
InternalServerError = 222931, // 022-2931
UnknownServerError = 222932, // 022-2932
UnauthenticatedAfterSalvage = 222998, // 022-2998
AuthenticationFailureUnknown = 222999, // 022-2999
Unknown = 229999, // 022-9999
};
}
/// Gets the ACT error code for the given result
u32 GetACTErrorCode(Result result);
} // namespace Service::ACT

View File

@@ -0,0 +1,46 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/act/act_u.h"
namespace Service::ACT {
ACT_U::ACT_U(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "act:u") {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &ACT_U::Initialize, "Initialize"},
{0x0002, &ACT_U::GetErrorCode, "GetErrorCode"},
{0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_U::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{0x000D, nullptr, "GenerateUuid"},
{0x000F, nullptr, "FindSlotNoByUuid"},
{0x0010, nullptr, "SaveData"},
{0x0011, nullptr, "GetTransferableId"},
{0x0012, nullptr, "AcquireNexServiceToken"},
{0x0013, nullptr, "GetNexServiceToken"},
{0x0014, nullptr, "AcquireIndependentServiceToken"},
{0x0015, nullptr, "GetIndependentServiceToken"},
{0x0016, nullptr, "AcquireAccountInfo"},
{0x0017, nullptr, "AcquireAccountIdByPrincipalId"},
{0x0018, nullptr, "AcquirePrincipalIdByAccountId"},
{0x0019, nullptr, "AcquireMii"},
{0x001A, nullptr, "AcquireAccountInfoEx"},
{0x001D, nullptr, "InquireMailAddress"},
{0x001E, nullptr, "AcquireEula"},
{0x001F, nullptr, "AcquireEulaLanguageList"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::ACT
SERIALIZE_EXPORT_IMPL(Service::ACT::ACT_U)

View File

@@ -0,0 +1,22 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/act/act.h"
namespace Service::ACT {
class ACT_U final : public Module::Interface {
public:
explicit ACT_U(std::shared_ptr<Module> act);
private:
SERVICE_SERIALIZATION(ACT_U, act, Module)
};
} // namespace Service::ACT
BOOST_CLASS_EXPORT_KEY(Service::ACT::ACT_U)
BOOST_SERIALIZATION_CONSTRUCT(Service::ACT::ACT_U)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,810 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <boost/serialization/array.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include "common/common_types.h"
#include "common/construct.h"
#include "common/swap.h"
#include "core/file_sys/cia_container.h"
#include "core/file_sys/file_backend.h"
#include "core/global.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "network/artic_base/artic_base_client.h"
namespace Core {
class System;
}
namespace FileUtil {
class IOFile;
}
namespace Service::FS {
enum class MediaType : u32;
}
namespace Kernel {
class Mutex;
}
namespace Service::AM {
namespace ErrCodes {
enum {
CIACurrentlyInstalling = 4,
InvalidTID = 31,
EmptyCIA = 32,
TryingToUninstallSystemApp = 44,
InvalidTIDInList = 60,
InvalidCIAHeader = 104,
};
} // namespace ErrCodes
enum class CIAInstallState : u32 {
InstallStarted,
HeaderLoaded,
CertLoaded,
TicketLoaded,
TMDLoaded,
ContentWritten,
};
enum class InstallStatus : u32 {
Success,
ErrorFailedToOpenFile,
ErrorFileNotFound,
ErrorAborted,
ErrorInvalid,
ErrorEncrypted,
};
enum class CTCertLoadStatus {
Loaded,
NotFound,
Invalid,
IOError,
};
struct CTCert {
u32_be signature_type{};
std::array<u8, 0x1E> signature_r{};
std::array<u8, 0x1E> signature_s{};
INSERT_PADDING_BYTES(0x40){};
std::array<char, 0x40> issuer{};
u32_be key_type{};
std::array<char, 0x40> key_id{};
u32_be expiration_time{};
std::array<u8, 0x1E> public_key_x{};
std::array<u8, 0x1E> public_key_y{};
INSERT_PADDING_BYTES(0x3C){};
bool IsValid() const;
u32 GetDeviceID() const;
};
static_assert(sizeof(CTCert) == 0x180, "Invalid CTCert size.");
// Title ID valid length
constexpr std::size_t TITLE_ID_VALID_LENGTH = 16;
constexpr u64 TWL_TITLE_ID_FLAG = 0x0000800000000000ULL;
// Progress callback for InstallCIA, receives bytes written and total bytes
using ProgressCallback = void(std::size_t, std::size_t);
// A file handled returned for CIAs to be written into and subsequently installed.
class CIAFile final : public FileSys::FileBackend {
public:
explicit CIAFile(Core::System& system_, Service::FS::MediaType media_type);
~CIAFile();
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
Result WriteTicket();
Result WriteTitleMetadata();
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
const u8* buffer) override;
u64 GetSize() const override;
bool SetSize(u64 size) const override;
bool Close() override;
void Flush() const override;
private:
Core::System& system;
// Whether it's installing an update, and what step of installation it is at
bool is_update = false;
CIAInstallState install_state = CIAInstallState::InstallStarted;
// How much has been written total, CIAContainer for the installing CIA, buffer of all data
// prior to content data, how much of each content index has been written, and where the CIA
// is being installed to
u64 written = 0;
FileSys::CIAContainer container;
std::vector<u8> data;
std::vector<u64> content_written;
std::vector<FileUtil::IOFile> content_files;
Service::FS::MediaType media_type;
class DecryptionState;
std::unique_ptr<DecryptionState> decryption_state;
};
// A file handled returned for Tickets to be written into and subsequently installed.
class TicketFile final : public FileSys::FileBackend {
public:
explicit TicketFile();
~TicketFile();
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
const u8* buffer) override;
u64 GetSize() const override;
bool SetSize(u64 size) const override;
bool Close() override;
void Flush() const override;
private:
u64 written = 0;
std::vector<u8> data;
};
/**
* Installs a CIA file from a specified file path.
* @param path file path of the CIA file to install
* @param update_callback callback function called during filesystem write
* @returns bool whether the install was successful
*/
InstallStatus InstallCIA(const std::string& path,
std::function<ProgressCallback>&& update_callback = nullptr);
/**
* Downloads and installs title form the Nintendo Update Service.
* @param title_id the title_id to download
* @returns whether the install was successful or error code
*/
InstallStatus InstallFromNus(u64 title_id, int version = -1);
/**
* Get the update title ID for a title
* @param titleId the title ID
* @returns The update title ID
*/
u64 GetTitleUpdateId(u64 title_id);
/**
* Get the mediatype for an installed title
* @param titleId the installed title ID
* @returns MediaType which the installed title will reside on
*/
Service::FS::MediaType GetTitleMediaType(u64 titleId);
/**
* Get the .tmd path for a title
* @param media_type the media the title exists on
* @param tid the title ID to get
* @param update set true if the incoming TMD should be used instead of the current TMD
* @returns string path to the .tmd file if it exists, otherwise a path to create one is given.
*/
std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, bool update = false);
/**
* Get the .app path for a title's installed content index.
* @param media_type the media the title exists on
* @param tid the title ID to get
* @param index the content index to get
* @param update set true if the incoming TMD should be used instead of the current TMD
* @returns string path to the .app file
*/
std::string GetTitleContentPath(FS::MediaType media_type, u64 tid, std::size_t index = 0,
bool update = false);
/**
* Get the folder for a title's installed content.
* @param media_type the media the title exists on
* @param tid the title ID to get
* @returns string path to the title folder
*/
std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid);
/**
* Get the title/ folder for a storage medium.
* @param media_type the storage medium to get the path for
* @returns string path to the folder
*/
std::string GetMediaTitlePath(Service::FS::MediaType media_type);
/**
* Uninstalls the specified title.
* @param media_type the storage medium the title is installed to
* @param title_id the title ID to uninstall
* @return result of the uninstall operation
*/
Result UninstallProgram(const FS::MediaType media_type, const u64 title_id);
class Module final {
public:
explicit Module(Core::System& system);
~Module();
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> am, const char* name, u32 max_session);
~Interface();
std::shared_ptr<Module> GetModule() const {
return am;
}
void UseArticClient(std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
}
protected:
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform);
/**
* AM::GetNumPrograms service function
* Gets the number of installed titles in the requested media type
* Inputs:
* 0 : Command header (0x00010040)
* 1 : Media type to load the titles from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : The number of titles in the requested media type
*/
void GetNumPrograms(Kernel::HLERequestContext& ctx);
/**
* AM::FindDLCContentInfos service function
* Explicitly checks that TID high value is 0004008C or an error is returned.
* Inputs:
* 1 : MediaType
* 2-3 : u64, Title ID
* 4 : Content count
* 6 : Content IDs pointer
* 8 : Content Infos pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void FindDLCContentInfos(Kernel::HLERequestContext& ctx);
/**
* AM::ListDLCContentInfos service function
* Explicitly checks that TID high value is 0004008C or an error is returned.
* Inputs:
* 1 : Content count
* 2 : MediaType
* 3-4 : u64, Title ID
* 5 : Start Index
* 7 : Content Infos pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Number of content infos returned
*/
void ListDLCContentInfos(Kernel::HLERequestContext& ctx);
/**
* AM::DeleteContents service function
* Inputs:
* 1 : MediaType
* 2-3 : u64, Title ID
* 4 : Content count
* 6 : Content IDs pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void DeleteContents(Kernel::HLERequestContext& ctx);
/**
* AM::GetProgramList service function
* Loads information about the desired number of titles from the desired media type into an
* array
* Inputs:
* 1 : Title count
* 2 : Media type to load the titles from
* 4 : Title IDs output pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : The number of titles loaded from the requested media type
*/
void GetProgramList(Kernel::HLERequestContext& ctx);
/**
* AM::GetProgramInfos service function
* Inputs:
* 1 : u8 Mediatype
* 2 : Total titles
* 4 : TitleIDList pointer
* 6 : TitleList pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetProgramInfos(Kernel::HLERequestContext& ctx);
/**
* AM::GetProgramInfosIgnorePlatform service function
* Inputs:
* 1 : u8 Mediatype
* 2 : Total titles
* 4 : TitleIDList pointer
* 6 : TitleList pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx);
/**
* AM::DeleteUserProgram service function
* Deletes a user program
* Inputs:
* 1 : Media Type
* 2-3 : Title ID
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void DeleteUserProgram(Kernel::HLERequestContext& ctx);
/**
* AM::GetProductCode service function
* Gets the product code of a title
* Inputs:
* 1 : Media Type
* 2-3 : Title ID
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-5 : Product Code
*/
void GetProductCode(Kernel::HLERequestContext& ctx);
/**
* AM::GetDLCTitleInfos service function
* Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004008C.
* Inputs:
* 1 : u8 Mediatype
* 2 : Total titles
* 4 : TitleIDList pointer
* 6 : TitleList pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetDLCTitleInfos(Kernel::HLERequestContext& ctx);
/**
* AM::GetPatchTitleInfos service function
* Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004000E.
* Inputs:
* 1 : u8 Mediatype
* 2 : Total titles
* 4 : TitleIDList input pointer
* 6 : TitleList output pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : TitleIDList input pointer
* 4 : TitleList output pointer
*/
void GetPatchTitleInfos(Kernel::HLERequestContext& ctx);
/**
* AM::ListDataTitleTicketInfos service function
* Inputs:
* 1 : Ticket count
* 2-3 : u64, Title ID
* 4 : Start Index?
* 5 : (TicketCount * 24) << 8 | 0x4
* 6 : Ticket Infos pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Number of ticket infos returned
*/
void ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx);
/**
* AM::GetDLCContentInfoCount service function
* Explicitly checks that TID high value is 0004008C or an error is returned.
* Inputs:
* 0 : Command header (0x100100C0)
* 1 : MediaType
* 2-3 : u64, Title ID
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Number of content infos plus one
*/
void GetDLCContentInfoCount(Kernel::HLERequestContext& ctx);
/**
* AM::DeleteTicket service function
* Inputs:
* 1-2 : u64, Title ID
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void DeleteTicket(Kernel::HLERequestContext& ctx);
/**
* AM::GetNumTickets service function
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Number of tickets
*/
void GetNumTickets(Kernel::HLERequestContext& ctx);
/**
* AM::GetTicketList service function
* Inputs:
* 1 : Number of TicketList
* 2 : Number to skip
* 4 : TicketList pointer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Total TicketList
*/
void GetTicketList(Kernel::HLERequestContext& ctx);
/**
* AM::GetDeviceID service function
* Inputs:
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Unknown
* 3 : DeviceID
*/
void GetDeviceID(Kernel::HLERequestContext& ctx);
/**
* AM::NeedsCleanup service function
* Inputs:
* 1 : Media Type
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : bool, Needs Cleanup
*/
void NeedsCleanup(Kernel::HLERequestContext& ctx);
/**
* AM::DoCleanup service function
* Inputs:
* 1 : Media Type
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void DoCleanup(Kernel::HLERequestContext& ctx);
/**
* AM::QueryAvailableTitleDatabase service function
* Inputs:
* 1 : Media Type
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Boolean, database availability
*/
void QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx);
/**
* AM::GetPersonalizedTicketInfoList service function
* Inputs:
* 1 : Count
* 2-3 : Buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Out count
*/
void GetPersonalizedTicketInfoList(Kernel::HLERequestContext& ctx);
/**
* AM::GetNumImportTitleContextsFiltered service function
* Inputs:
* 1 : Count
* 2 : Filter
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Num import titles
*/
void GetNumImportTitleContextsFiltered(Kernel::HLERequestContext& ctx);
/**
* AM::GetImportTitleContextListFiltered service function
* Inputs:
* 1 : Count
* 2 : Media type
* 3 : filter
* 4-5 : Buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Out count
* 3-4 : Out buffer
*/
void GetImportTitleContextListFiltered(Kernel::HLERequestContext& ctx);
/**
* AM::CheckContentRights service function
* Inputs:
* 1-2 : Title ID
* 3 : Content Index
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Boolean, whether we have rights to this content
*/
void CheckContentRights(Kernel::HLERequestContext& ctx);
/**
* AM::CheckContentRightsIgnorePlatform service function
* Inputs:
* 1-2 : Title ID
* 3 : Content Index
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Boolean, whether we have rights to this content
*/
void CheckContentRightsIgnorePlatform(Kernel::HLERequestContext& ctx);
/**
* AM::BeginImportProgram service function
* Begin importing from a CTR Installable Archive
* Inputs:
* 0 : Command header (0x04020040)
* 1 : Media type to install title to
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-3 : CIAFile handle for application to write to
*/
void BeginImportProgram(Kernel::HLERequestContext& ctx);
/**
* AM::BeginImportProgramTemporarily service function
* Begin importing from a CTR Installable Archive into the temporary title database
* Inputs:
* 0 : Command header (0x04030000)
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-3 : CIAFile handle for application to write to
*/
void BeginImportProgramTemporarily(Kernel::HLERequestContext& ctx);
/**
* AM::EndImportProgram service function
* Finish importing from a CTR Installable Archive
* Inputs:
* 0 : Command header (0x04050002)
* 1-2 : CIAFile handle application wrote to
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void EndImportProgram(Kernel::HLERequestContext& ctx);
/**
* AM::EndImportProgramWithoutCommit service function
* Finish importing from a CTR Installable Archive
* Inputs:
* 0 : Command header (0x04060002)
* 1-2 : CIAFile handle application wrote to
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void EndImportProgramWithoutCommit(Kernel::HLERequestContext& ctx);
/**
* AM::CommitImportPrograms service function
* Commits changes from the temporary title database to the real title database (title.db).
* This is a no-op for us, we don't use title.db
* Inputs:
* 0 : Command header (0x040700C2)
* 1 : Media type
* 2 : Title count
* 3 : Database type
* 4-5 : Title list buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void CommitImportPrograms(Kernel::HLERequestContext& ctx);
/**
* AM::GetProgramInfoFromCia service function
* Get TitleInfo from a CIA file handle
* Inputs:
* 0 : Command header (0x04080042)
* 1 : Media type of the title
* 2-3 : File handle CIA data can be read from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-8: TitleInfo structure
*/
void GetProgramInfoFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetSystemMenuDataFromCia service function
* Loads a CIA file's SMDH data into a specified buffer
* Inputs:
* 0 : Command header (0x04090004)
* 1-2 : File handle CIA data can be read from
* 3-4 : Output buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetSystemMenuDataFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetDependencyListFromCia service function
* Loads a CIA's dependency list into a specified buffer
* Inputs:
* 0 : Command header (0x040A0002)
* 1-2 : File handle CIA data can be read from
* 64-65 : Output buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetDependencyListFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetTransferSizeFromCia service function
* Returns the total expected transfer size up to the CIA meta offset from a CIA
* Inputs:
* 0 : Command header (0x040B0002)
* 1-2 : File handle CIA data can be read from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-3 : Transfer size
*/
void GetTransferSizeFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetCoreVersionFromCia service function
* Returns the core version from a CIA
* Inputs:
* 0 : Command header (0x040C0002)
* 1-2 : File handle CIA data can be read from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Core version
*/
void GetCoreVersionFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetRequiredSizeFromCia service function
* Returns the required amount of free space required to install a given CIA file
* Inputs:
* 0 : Command header (0x040D0042)
* 1 : Media type to install title to
* 2-3 : File handle CIA data can be read from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-3 : Required free space for CIA
*/
void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::DeleteProgram service function
* Deletes a program
* Inputs:
* 0 : Command header (0x041000C0)
* 1 : Media type
* 2-3 : Title ID
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void DeleteProgram(Kernel::HLERequestContext& ctx);
/**
* AM::GetSystemUpdaterMutex service function
* Inputs:
* 0 : Command header (0x04120000)
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Copy handle descriptor
* 3 : System updater mutex
*/
void GetSystemUpdaterMutex(Kernel::HLERequestContext& ctx);
/**
* AM::GetMetaSizeFromCia service function
* Returns the size of a given CIA's meta section
* Inputs:
* 0 : Command header (0x04130002)
* 1-2 : File handle CIA data can be read from
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Meta section size
*/
void GetMetaSizeFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::GetMetaDataFromCia service function
* Loads meta section data from a CIA file into a given buffer
* Inputs:
* 0 : Command header (0x04140044)
* 1-2 : File handle CIA data can be read from
* 3-4 : Output buffer
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void GetMetaDataFromCia(Kernel::HLERequestContext& ctx);
/**
* AM::BeginImportTicket service function
* Inputs:
* 1 : Media type to install title to
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2-3 : TicketHandle handle for application to write to
*/
void BeginImportTicket(Kernel::HLERequestContext& ctx);
/**
* AM::EndImportTicket service function
* Inputs:
* 1-2 : TicketHandle handle application wrote to
* Outputs:
* 1 : Result, 0 on success, otherwise error code
*/
void EndImportTicket(Kernel::HLERequestContext& ctx);
/**
* AM::GetDeviceCert service function
* Inputs:
* Outputs:
* 1 : Result, 0 on success, otherwise error code
* 2 : Unknown
* 3-4 : Device cert
*/
void GetDeviceCert(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> am;
// Placed on the interface level so that only am:net and am:app have it.
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
};
/**
* Gets the CTCert.bin path in the host filesystem
* @returns std::string CTCert.bin path in the host filesystem
*/
static std::string GetCTCertPath();
/**
* Loads the CTCert.bin file from the filesystem.
* @returns CTCertLoadStatus indicating the file load status.
*/
static CTCertLoadStatus LoadCTCertFile(CTCert& output);
private:
/**
* Scans the for titles in a storage medium for listing.
* @param media_type the storage medium to scan
*/
void ScanForTitles(Service::FS::MediaType media_type);
/**
* Scans all storage mediums for titles for listing.
*/
void ScanForAllTitles();
Core::System& system;
bool cia_installing = false;
std::array<std::vector<u64_le>, 3> am_title_list;
std::shared_ptr<Kernel::Mutex> system_updater_mutex;
CTCert ct_cert{};
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
std::shared_ptr<Module> GetModule(Core::System& system);
void InstallInterfaces(Core::System& system);
} // namespace Service::AM
BOOST_CLASS_EXPORT_KEY(Service::AM::Module)
SERVICE_CONSTRUCT(Service::AM::Module)

View File

@@ -0,0 +1,33 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/am/am_app.h"
namespace Service::AM {
AM_APP::AM_APP(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:app", 5) {
static const FunctionInfo functions[] = {
// clang-format off
{0x1001, &AM_APP::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
{0x1002, &AM_APP::FindDLCContentInfos, "FindDLCContentInfos"},
{0x1003, &AM_APP::ListDLCContentInfos, "ListDLCContentInfos"},
{0x1004, &AM_APP::DeleteContents, "DeleteContents"},
{0x1005, &AM_APP::GetDLCTitleInfos, "GetDLCTitleInfos"},
{0x1006, nullptr, "GetNumDataTitleTickets"},
{0x1007, &AM_APP::ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
{0x1008, nullptr, "GetItemRights"},
{0x1009, nullptr, "IsDataTitleInUse"},
{0x100A, nullptr, "IsExternalTitleDatabaseInitialized"},
{0x100B, nullptr, "GetNumExistingContentInfos"},
{0x100C, nullptr, "ListExistingContentInfos"},
{0x100D, &AM_APP::GetPatchTitleInfos, "GetPatchTitleInfos"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AM
SERIALIZE_EXPORT_IMPL(Service::AM::AM_APP)

View File

@@ -0,0 +1,22 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included..
#pragma once
#include "core/hle/service/am/am.h"
namespace Service::AM {
class AM_APP final : public Module::Interface {
public:
explicit AM_APP(std::shared_ptr<Module> am);
private:
SERVICE_SERIALIZATION(AM_APP, am, Module)
};
} // namespace Service::AM
BOOST_CLASS_EXPORT_KEY(Service::AM::AM_APP)
BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_APP)

View File

@@ -0,0 +1,130 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/am/am_net.h"
namespace Service::AM {
AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:net", 5) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &AM_NET::GetNumPrograms, "GetNumPrograms"},
{0x0002, &AM_NET::GetProgramList, "GetProgramList"},
{0x0003, &AM_NET::GetProgramInfos, "GetProgramInfos"},
{0x0004, &AM_NET::DeleteUserProgram, "DeleteUserProgram"},
{0x0005, &AM_NET::GetProductCode, "GetProductCode"},
{0x0006, nullptr, "GetStorageId"},
{0x0007, &AM_NET::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_NET::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_NET::GetTicketList, "GetTicketList"},
{0x000A, &AM_NET::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_NET::NeedsCleanup, "NeedsCleanup"},
{0x0014, nullptr, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"},
{0x0019, nullptr, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"},
{0x001D, nullptr, "DeleteAllTwlUserPrograms"},
{0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
{0x0021, &AM_NET::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
{0x0023, &AM_NET::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
{0x0024, &AM_NET::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
{0x0025, &AM_NET::CheckContentRights, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"},
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"},
{0x002C, &AM_NET::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
{0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
{0x0403, nullptr, "BeginImportProgramTemporarily"},
{0x0404, nullptr, "CancelImportProgram"},
{0x0405, &AM_NET::EndImportProgram, "EndImportProgram"},
{0x0406, nullptr, "EndImportProgramWithoutCommit"},
{0x0407, nullptr, "CommitImportPrograms"},
{0x0408, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
{0x0409, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
{0x040A, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"},
{0x040B, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"},
{0x0412, &AM_NET::GetSystemUpdaterMutex, "GetSystemUpdaterMutex"},
{0x0413, &AM_NET::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
{0x0414, &AM_NET::GetMetaDataFromCia, "GetMetaDataFromCia"},
{0x0415, nullptr, "CheckDemoLaunchRights"},
{0x0416, nullptr, "GetInternalTitleLocationInfo"},
{0x0417, nullptr, "PerpetuateAgbSaveData"},
{0x0418, nullptr, "BeginImportProgramForOverWrite"},
{0x0419, nullptr, "BeginImportSystemProgram"},
{0x0801, &AM_NET::BeginImportTicket, "BeginImportTicket"},
{0x0802, nullptr, "CancelImportTicket"},
{0x0803, &AM_NET::EndImportTicket, "EndImportTicket"},
{0x0804, nullptr, "BeginImportTitle"},
{0x0805, nullptr, "StopImportTitle"},
{0x0806, nullptr, "ResumeImportTitle"},
{0x0807, nullptr, "CancelImportTitle"},
{0x0808, nullptr, "EndImportTitle"},
{0x0809, nullptr, "CommitImportTitles"},
{0x080A, nullptr, "BeginImportTmd"},
{0x080B, nullptr, "CancelImportTmd"},
{0x080C, nullptr, "EndImportTmd"},
{0x080D, nullptr, "CreateImportContentContexts"},
{0x080E, nullptr, "BeginImportContent"},
{0x080F, nullptr, "StopImportContent"},
{0x0810, nullptr, "ResumeImportContent"},
{0x0811, nullptr, "CancelImportContent"},
{0x0812, nullptr, "EndImportContent"},
{0x0813, nullptr, "GetNumCurrentImportContentContexts"},
{0x0814, nullptr, "GetCurrentImportContentContextList"},
{0x0815, nullptr, "GetCurrentImportContentContexts"},
{0x0816, nullptr, "Sign"},
{0x0817, nullptr, "Verify"},
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
{0x0819, nullptr, "ImportCertificates"},
{0x081A, nullptr, "ImportCertificate"},
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
{0x081C, nullptr, "DeleteTicketId"},
{0x081D, nullptr, "GetNumTicketIds"},
{0x081E, nullptr, "GetTicketIdList"},
{0x081F, nullptr, "GetNumTicketsOfProgram"},
{0x0820, nullptr, "ListTicketInfos"},
{0x0821, nullptr, "GetRightsOnlyTicketData"},
{0x0822, nullptr, "GetNumCurrentContentInfos"},
{0x0823, nullptr, "FindCurrentContentInfos"},
{0x0824, nullptr, "ListCurrentContentInfos"},
{0x0825, nullptr, "CalculateContextRequiredSize"},
{0x0826, nullptr, "UpdateImportContentContexts"},
{0x0827, nullptr, "DeleteAllDemoLaunchInfos"},
{0x0828, nullptr, "BeginImportTitleForOverWrite"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AM
SERIALIZE_EXPORT_IMPL(Service::AM::AM_NET)

View File

@@ -0,0 +1,22 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included..
#pragma once
#include "core/hle/service/am/am.h"
namespace Service::AM {
class AM_NET final : public Module::Interface {
public:
explicit AM_NET(std::shared_ptr<Module> am);
private:
SERVICE_SERIALIZATION(AM_NET, am, Module)
};
} // namespace Service::AM
BOOST_CLASS_EXPORT_KEY(Service::AM::AM_NET)
BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_NET)

View File

@@ -0,0 +1,78 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/am/am_sys.h"
namespace Service::AM {
AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:sys", 5) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &AM_SYS::GetNumPrograms, "GetNumPrograms"},
{0x0002, &AM_SYS::GetProgramList, "GetProgramList"},
{0x0003, &AM_SYS::GetProgramInfos, "GetProgramInfos"},
{0x0004, &AM_SYS::DeleteUserProgram, "DeleteUserProgram"},
{0x0005, &AM_SYS::GetProductCode, "GetProductCode"},
{0x0006, nullptr, "GetStorageId"},
{0x0007, &AM_SYS::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_SYS::GetTicketList, "GetTicketList"},
{0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"},
{0x0014, &AM_SYS::DoCleanup, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"},
{0x0019, &AM_SYS::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"},
{0x001D, nullptr, "DeleteAllTwlUserPrograms"},
{0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
{0x0021, nullptr, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
{0x0024, nullptr, "GetImportTitleContextListFiltered"},
{0x0025, &AM_SYS::CheckContentRights, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"},
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"},
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
{0x002D, &AM_SYS::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x1001, &AM_SYS::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
{0x1002, &AM_SYS::FindDLCContentInfos, "FindDLCContentInfos"},
{0x1003, &AM_SYS::ListDLCContentInfos, "ListDLCContentInfos"},
{0x1004, &AM_SYS::DeleteContents, "DeleteContents"},
{0x1005, &AM_SYS::GetDLCTitleInfos, "GetDLCTitleInfos"},
{0x1006, nullptr, "GetNumDataTitleTickets"},
{0x1007, &AM_SYS::ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
{0x1008, nullptr, "GetItemRights"},
{0x1009, nullptr, "IsDataTitleInUse"},
{0x100A, nullptr, "IsExternalTitleDatabaseInitialized"},
{0x100B, nullptr, "GetNumExistingContentInfos"},
{0x100C, nullptr, "ListExistingContentInfos"},
{0x100D, &AM_SYS::GetPatchTitleInfos, "GetPatchTitleInfos"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AM
SERIALIZE_EXPORT_IMPL(Service::AM::AM_SYS)

View File

@@ -0,0 +1,22 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included..
#pragma once
#include "core/hle/service/am/am.h"
namespace Service::AM {
class AM_SYS final : public Module::Interface {
public:
explicit AM_SYS(std::shared_ptr<Module> am);
private:
SERVICE_SERIALIZATION(AM_SYS, am, Module)
};
} // namespace Service::AM
BOOST_CLASS_EXPORT_KEY(Service::AM::AM_SYS)
BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_SYS)

View File

@@ -0,0 +1,90 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/am/am_u.h"
namespace Service::AM {
AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u", 5) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &AM_U::GetNumPrograms, "GetNumPrograms"},
{0x0002, &AM_U::GetProgramList, "GetProgramList"},
{0x0003, &AM_U::GetProgramInfos, "GetProgramInfos"},
{0x0004, &AM_U::DeleteUserProgram, "DeleteUserProgram"},
{0x0005, &AM_U::GetProductCode, "GetProductCode"},
{0x0006, nullptr, "GetStorageId"},
{0x0007, &AM_U::DeleteTicket, "DeleteTicket"},
{0x0008, &AM_U::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_U::GetTicketList, "GetTicketList"},
{0x000A, &AM_U::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_U::NeedsCleanup, "NeedsCleanup"},
{0x0014, nullptr, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"},
{0x0019, nullptr, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"},
{0x001D, nullptr, "DeleteAllTwlUserPrograms"},
{0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
{0x0021, nullptr, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
{0x0024, nullptr, "GetImportTitleContextListFiltered"},
{0x0025, nullptr, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"},
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"},
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
{0x002D, nullptr, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"},
{0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
{0x0404, nullptr, "CancelImportProgram"},
{0x0405, &AM_U::EndImportProgram, "EndImportProgram"},
{0x0406, &AM_U::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"},
{0x0407, &AM_U::CommitImportPrograms, "CommitImportPrograms"},
{0x0408, &AM_U::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
{0x0409, &AM_U::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
{0x040A, &AM_U::GetDependencyListFromCia, "GetDependencyListFromCia"},
{0x040B, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_U::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"},
{0x0412, &AM_U::GetSystemUpdaterMutex, "GetSystemUpdaterMutex"},
{0x0413, &AM_U::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
{0x0414, &AM_U::GetMetaDataFromCia, "GetMetaDataFromCia"},
{0x0415, nullptr, "CheckDemoLaunchRights"},
{0x0416, nullptr, "GetInternalTitleLocationInfo"},
{0x0417, nullptr, "PerpetuateAgbSaveData"},
{0x0418, nullptr, "BeginImportProgramForOverWrite"},
{0x0419, nullptr, "BeginImportSystemProgram"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::AM
SERIALIZE_EXPORT_IMPL(Service::AM::AM_U)

View File

@@ -0,0 +1,22 @@
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included..
#pragma once
#include "core/hle/service/am/am.h"
namespace Service::AM {
class AM_U final : public Module::Interface {
public:
explicit AM_U(std::shared_ptr<Module> am);
private:
SERVICE_SERIALIZATION(AM_U, am, Module)
};
} // namespace Service::AM
BOOST_CLASS_EXPORT_KEY(Service::AM::AM_U)
BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_U)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <limits>
#include <memory>
#include <optional>
#include <vector>
#include <boost/serialization/array.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include "core/frontend/input.h"
#include "core/global.h"
#include "core/hle/kernel/event.h"
#include "core/hle/result.h"
#include "core/hle/service/fs/archive.h"
namespace Core {
class System;
}
namespace HLE::Applets {
class Applet;
}
namespace Service::APT {
/// Signals used by APT functions
enum class SignalType : u32 {
None = 0x0,
Wakeup = 0x1,
Request = 0x2,
Response = 0x3,
Exit = 0x4,
Message = 0x5,
HomeButtonSingle = 0x6,
HomeButtonDouble = 0x7,
DspSleep = 0x8,
DspWakeup = 0x9,
WakeupByExit = 0xA,
WakeupByPause = 0xB,
WakeupByCancel = 0xC,
WakeupByCancelAll = 0xD,
WakeupByPowerButtonClick = 0xE,
WakeupToJumpHome = 0xF,
RequestForSysApplet = 0x10,
WakeupToLaunchApplication = 0x11,
};
enum class Notification : u32 {
None = 0,
HomeButtonSingle = 1,
HomeButtonDouble = 2,
SleepQuery = 3,
SleepCancelledByOpen = 4,
SleepAccepted = 5,
SleepAwake = 6,
Shutdown = 7,
PowerButtonClick = 8,
PowerButtonClear = 9,
TrySleep = 10,
OrderToClose = 11,
};
/// App Id's used by APT functions
enum class AppletId : u32 {
None = 0,
AnySystemApplet = 0x100,
HomeMenu = 0x101,
AlternateMenu = 0x103,
Camera = 0x110,
FriendList = 0x112,
GameNotes = 0x113,
InternetBrowser = 0x114,
InstructionManual = 0x115,
Notifications = 0x116,
Miiverse = 0x117,
MiiversePost = 0x118,
AmiiboSettings = 0x119,
AnySysLibraryApplet = 0x200,
SoftwareKeyboard1 = 0x201,
Ed1 = 0x202,
PnoteApp = 0x204,
SnoteApp = 0x205,
Error = 0x206,
Mint = 0x207,
Extrapad = 0x208,
Memolib = 0x209,
Application = 0x300,
Tiger = 0x301,
AnyLibraryApplet = 0x400,
SoftwareKeyboard2 = 0x401,
Ed2 = 0x402,
PnoteApp2 = 0x404,
SnoteApp2 = 0x405,
Error2 = 0x406,
Mint2 = 0x407,
Extrapad2 = 0x408,
Memolib2 = 0x409,
};
/// Application Old/New 3DS target platforms
enum class TargetPlatform : u8 {
Old3ds = 0,
New3ds = 1,
};
/// Application Old/New 3DS running modes
enum class ApplicationRunningMode : u8 {
NoApplication = 0,
Old3dsRegistered = 1,
New3dsRegistered = 2,
Old3dsUnregistered = 3,
New3dsUnregistered = 4,
};
/// Holds information about the parameters used in Send/Glance/ReceiveParameter
struct MessageParameter {
AppletId sender_id = AppletId::None;
AppletId destination_id = AppletId::None;
SignalType signal = SignalType::None;
std::shared_ptr<Kernel::Object> object = nullptr;
std::vector<u8> buffer;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& sender_id;
ar& destination_id;
ar& signal;
ar& object;
ar& buffer;
}
friend class boost::serialization::access;
};
enum class AppletPos : u32 {
Application = 0,
Library = 1,
System = 2,
SysLibrary = 3,
Resident = 4,
AutoLibrary = 5,
Invalid = 0xFF,
};
union AppletAttributes {
u32 raw;
BitField<0, 3, AppletPos> applet_pos;
BitField<28, 1, u32> no_exit_on_system_applet;
BitField<29, 1, u32> is_home_menu;
AppletAttributes() : raw(0) {}
AppletAttributes(u32 attributes) : raw(attributes) {}
};
enum class ApplicationJumpFlags : u8 {
UseInputParameters = 0,
UseStoredParameters = 1,
UseCurrentParameters = 2
};
struct DeliverArg {
std::vector<u8> param;
std::vector<u8> hmac;
u64 source_program_id = std::numeric_limits<u64>::max();
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& param;
ar& hmac;
ar& source_program_id;
}
friend class boost::serialization::access;
};
struct ApplicationJumpParameters {
u64 next_title_id;
FS::MediaType next_media_type;
ApplicationJumpFlags flags;
u64 current_title_id;
FS::MediaType current_media_type;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& next_title_id;
ar& next_media_type;
ar& flags;
ar& current_title_id;
ar& current_media_type;
}
friend class boost::serialization::access;
};
struct ApplicationStartParameters {
u64 next_title_id;
FS::MediaType next_media_type;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& next_title_id;
ar& next_media_type;
}
friend class boost::serialization::access;
};
enum class DisplayBufferMode : u32_le {
R8G8B8A8 = 0,
R8G8B8 = 1,
R5G6B5 = 2,
R5G5B5A1 = 3,
R4G4B4A4 = 4,
Unimportable = 0xFFFFFFFF,
};
/// Used by the application to pass information about the current framebuffer to applets.
struct CaptureBufferInfo {
u32_le size;
u8 is_3d;
INSERT_PADDING_BYTES(0x3); // Padding for alignment
u32_le top_screen_left_offset;
u32_le top_screen_right_offset;
DisplayBufferMode top_screen_format;
u32_le bottom_screen_left_offset;
u32_le bottom_screen_right_offset;
DisplayBufferMode bottom_screen_format;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& size;
ar& is_3d;
ar& top_screen_left_offset;
ar& top_screen_right_offset;
ar& top_screen_format;
ar& bottom_screen_left_offset;
ar& bottom_screen_right_offset;
ar& bottom_screen_format;
}
friend class boost::serialization::access;
};
static_assert(sizeof(CaptureBufferInfo) == 0x20, "CaptureBufferInfo struct has incorrect size");
enum class SleepQueryReply : u32 {
Reject = 0,
Accept = 1,
Later = 2,
};
class AppletManager : public std::enable_shared_from_this<AppletManager> {
public:
explicit AppletManager(Core::System& system);
~AppletManager();
void ReloadInputDevices();
/**
* Clears any existing parameter and places a new one. This function is currently only used by
* HLE Applets and should be likely removed in the future
*/
void CancelAndSendParameter(const MessageParameter& parameter);
Result SendParameter(const MessageParameter& parameter);
ResultVal<MessageParameter> GlanceParameter(AppletId app_id);
ResultVal<MessageParameter> ReceiveParameter(AppletId app_id);
bool CancelParameter(bool check_sender, AppletId sender_appid, bool check_receiver,
AppletId receiver_appid);
struct GetLockHandleResult {
AppletAttributes corrected_attributes;
u32 state;
std::shared_ptr<Kernel::Mutex> lock;
};
ResultVal<GetLockHandleResult> GetLockHandle(AppletAttributes attributes);
struct InitializeResult {
std::shared_ptr<Kernel::Event> notification_event;
std::shared_ptr<Kernel::Event> parameter_event;
};
ResultVal<InitializeResult> Initialize(AppletId app_id, AppletAttributes attributes);
Result Enable(AppletAttributes attributes);
Result Finalize(AppletId app_id);
u32 CountRegisteredApplet();
bool IsRegistered(AppletId app_id);
ResultVal<AppletAttributes> GetAttribute(AppletId app_id);
ResultVal<Notification> InquireNotification(AppletId app_id);
Result SendNotification(Notification notification);
Result PrepareToStartLibraryApplet(AppletId applet_id);
Result PreloadLibraryApplet(AppletId applet_id);
Result FinishPreloadingLibraryApplet(AppletId applet_id);
Result StartLibraryApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
const std::vector<u8>& buffer);
Result PrepareToCloseLibraryApplet(bool not_pause, bool exiting, bool jump_home);
Result CloseLibraryApplet(std::shared_ptr<Kernel::Object> object,
const std::vector<u8>& buffer);
Result CancelLibraryApplet(bool app_exiting);
Result SendDspSleep(AppletId from_applet_id, std::shared_ptr<Kernel::Object> object);
Result SendDspWakeUp(AppletId from_applet_id, std::shared_ptr<Kernel::Object> object);
Result PrepareToStartSystemApplet(AppletId applet_id);
Result StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
const std::vector<u8>& buffer);
Result PrepareToCloseSystemApplet();
Result CloseSystemApplet(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
Result OrderToCloseSystemApplet();
Result PrepareToJumpToHomeMenu();
Result JumpToHomeMenu(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
Result PrepareToLeaveHomeMenu();
Result LeaveHomeMenu(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
Result OrderToCloseApplication();
Result PrepareToCloseApplication(bool return_to_sys);
Result CloseApplication(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
Result PrepareToDoApplicationJump(u64 title_id, FS::MediaType media_type,
ApplicationJumpFlags flags);
Result DoApplicationJump(const DeliverArg& arg);
boost::optional<DeliverArg> ReceiveDeliverArg() {
auto arg = deliver_arg;
deliver_arg = boost::none;
return arg;
}
void SetDeliverArg(boost::optional<DeliverArg> arg) {
deliver_arg = std::move(arg);
}
std::vector<u8> GetCaptureInfo() {
std::vector<u8> buffer;
if (capture_info) {
buffer.resize(sizeof(CaptureBufferInfo));
std::memcpy(buffer.data(), &capture_info.get(), sizeof(CaptureBufferInfo));
}
return buffer;
}
void SetCaptureInfo(std::vector<u8> buffer) {
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
capture_info.emplace();
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
}
std::vector<u8> ReceiveCaptureBufferInfo() {
std::vector<u8> buffer;
if (capture_buffer_info) {
buffer.resize(sizeof(CaptureBufferInfo));
std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo));
capture_buffer_info.reset();
}
return buffer;
}
void SendCaptureBufferInfo(std::vector<u8> buffer) {
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
capture_buffer_info.emplace();
std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
}
Result PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
Result StartApplication(const std::vector<u8>& parameter, const std::vector<u8>& hmac,
bool paused);
Result WakeupApplication(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
Result CancelApplication();
struct AppletManInfo {
AppletPos active_applet_pos;
AppletId requested_applet_id;
AppletId home_menu_applet_id;
AppletId active_applet_id;
};
ResultVal<AppletManInfo> GetAppletManInfo(AppletPos requested_applet_pos);
struct AppletInfo {
u64 title_id;
Service::FS::MediaType media_type;
bool registered;
bool loaded;
u32 attributes;
};
ResultVal<AppletInfo> GetAppletInfo(AppletId app_id);
ApplicationJumpParameters GetApplicationJumpParameters() const {
return app_jump_parameters;
}
ResultVal<Service::FS::MediaType> Unknown54(u32 in_param);
TargetPlatform GetTargetPlatform();
ApplicationRunningMode GetApplicationRunningMode();
private:
/// APT lock retrieved via GetLockHandle.
std::shared_ptr<Kernel::Mutex> lock;
/// Parameter data to be returned in the next call to Glance/ReceiveParameter.
// NOTE: A bug in gcc prevents serializing std::optional
boost::optional<MessageParameter> next_parameter;
/// This parameter will be sent to the application/applet once they register themselves by using
/// APT::Initialize.
boost::optional<MessageParameter> delayed_parameter;
ApplicationJumpParameters app_jump_parameters{};
boost::optional<ApplicationStartParameters> app_start_parameters{};
boost::optional<DeliverArg> deliver_arg{};
boost::optional<CaptureBufferInfo> capture_info;
boost::optional<CaptureBufferInfo> capture_buffer_info;
static constexpr std::size_t NumAppletSlot = 4;
enum class AppletSlot : u8 {
Application,
SystemApplet,
HomeMenu,
LibraryApplet,
// An invalid tag
Error,
};
struct AppletSlotData {
AppletId applet_id;
AppletSlot slot;
u64 title_id;
bool registered;
bool loaded;
AppletAttributes attributes;
Notification notification;
std::shared_ptr<Kernel::Event> notification_event;
std::shared_ptr<Kernel::Event> parameter_event;
void Reset() {
applet_id = AppletId::None;
registered = false;
title_id = 0;
attributes.raw = 0;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& applet_id;
ar& slot;
ar& title_id;
ar& registered;
ar& loaded;
ar& attributes.raw;
ar& notification;
ar& notification_event;
ar& parameter_event;
}
friend class boost::serialization::access;
};
// Holds data about the concurrently running applets in the system.
std::array<AppletSlotData, NumAppletSlot> applet_slots = {};
AppletSlot active_slot = AppletSlot::Error;
AppletSlot last_library_launcher_slot = AppletSlot::Error;
SignalType library_applet_closing_command = SignalType::None;
AppletId last_prepared_library_applet = AppletId::None;
AppletSlot last_system_launcher_slot = AppletSlot::Error;
AppletSlot last_jump_to_home_slot = AppletSlot::Error;
bool ordered_to_close_sys_applet = false;
bool ordered_to_close_application = false;
bool application_cancelled = false;
AppletSlot application_close_target = AppletSlot::Error;
// This flag is used to determine if an app that supports New 3DS capabilities should use them.
// It also affects the results of APT:GetTargetPlatform and APT:GetApplicationRunningMode.
bool new_3ds_mode_blocked = false;
std::unordered_map<AppletId, std::shared_ptr<HLE::Applets::Applet>> hle_applets;
Core::TimingEventType* hle_applet_update_event;
Core::TimingEventType* button_update_event;
std::atomic<bool> is_device_reload_pending{true};
std::unique_ptr<Input::ButtonDevice> home_button;
std::unique_ptr<Input::ButtonDevice> power_button;
bool last_home_button_state = false;
bool last_power_button_state = false;
Core::System& system;
AppletSlotData* GetAppletSlot(AppletSlot slot) {
return &applet_slots[static_cast<std::size_t>(slot)];
}
AppletId GetAppletSlotId(AppletSlot slot) {
return slot != AppletSlot::Error ? GetAppletSlot(slot)->applet_id : AppletId::None;
}
AppletSlot GetAppletSlotFromId(AppletId id);
AppletSlot GetAppletSlotFromAttributes(AppletAttributes attributes);
AppletSlot GetAppletSlotFromPos(AppletPos pos);
/// Checks if the Application slot has already been registered and sends the parameter to it,
/// otherwise it queues for sending when the application registers itself with APT::Enable.
void SendApplicationParameterAfterRegistration(const MessageParameter& parameter);
void SendNotificationToAll(Notification notification);
void EnsureHomeMenuLoaded();
void CaptureFrameBuffers();
Result CreateHLEApplet(AppletId id, AppletId parent, bool preload);
void HLEAppletUpdateEvent(std::uintptr_t user_data, s64 cycles_late);
void LoadInputDevices();
void ButtonUpdateEvent(std::uintptr_t user_data, s64 cycles_late);
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& next_parameter;
ar& app_jump_parameters;
ar& delayed_parameter;
ar& app_start_parameters;
ar& deliver_arg;
ar& capture_info;
ar& capture_buffer_info;
ar& active_slot;
ar& last_library_launcher_slot;
ar& last_prepared_library_applet;
ar& last_system_launcher_slot;
ar& last_jump_to_home_slot;
ar& ordered_to_close_sys_applet;
ar& ordered_to_close_application;
ar& application_cancelled;
ar& application_close_target;
ar& new_3ds_mode_blocked;
ar& lock;
ar& capture_info;
ar& applet_slots;
ar& library_applet_closing_command;
if (Archive::is_loading::value) {
LoadInputDevices();
}
}
friend class boost::serialization::access;
};
} // namespace Service::APT
BOOST_CLASS_VERSION(Service::APT::ApplicationJumpParameters, 1)
BOOST_CLASS_VERSION(Service::APT::AppletManager, 1)
SERVICE_CONSTRUCT(Service::APT::AppletManager)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/apt/apt_a.h"
namespace Service::APT {
APT_A::APT_A(std::shared_ptr<Module> apt)
: Module::APTInterface(std::move(apt), "APT:A", MaxAPTSessions) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, &APT_A::GetLockHandle, "GetLockHandle"},
{0x0002, &APT_A::Initialize, "Initialize"},
{0x0003, &APT_A::Enable, "Enable"},
{0x0004, &APT_A::Finalize, "Finalize"},
{0x0005, &APT_A::GetAppletManInfo, "GetAppletManInfo"},
{0x0006, &APT_A::GetAppletInfo, "GetAppletInfo"},
{0x0007, nullptr, "GetLastSignaledAppletId"},
{0x0008, &APT_A::CountRegisteredApplet, "CountRegisteredApplet"},
{0x0009, &APT_A::IsRegistered, "IsRegistered"},
{0x000A, &APT_A::GetAttribute, "GetAttribute"},
{0x000B, &APT_A::InquireNotification, "InquireNotification"},
{0x000C, &APT_A::SendParameter, "SendParameter"},
{0x000D, &APT_A::ReceiveParameter, "ReceiveParameter"},
{0x000E, &APT_A::GlanceParameter, "GlanceParameter"},
{0x000F, &APT_A::CancelParameter, "CancelParameter"},
{0x0010, nullptr, "DebugFunc"},
{0x0011, nullptr, "MapProgramIdForDebug"},
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x0013, nullptr, "GetPreparationState"},
{0x0014, nullptr, "SetPreparationState"},
{0x0015, &APT_A::PrepareToStartApplication, "PrepareToStartApplication"},
{0x0016, &APT_A::PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x0017, &APT_A::FinishPreloadingLibraryApplet, "FinishPreloadingLibraryApplet"},
{0x0018, &APT_A::PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x0019, &APT_A::PrepareToStartSystemApplet, "PrepareToStartSystemApplet"},
{0x001A, &APT_A::PrepareToStartNewestHomeMenu, "PrepareToStartNewestHomeMenu"},
{0x001B, &APT_A::StartApplication, "StartApplication"},
{0x001C, &APT_A::WakeupApplication, "WakeupApplication"},
{0x001D, &APT_A::CancelApplication, "CancelApplication"},
{0x001E, &APT_A::StartLibraryApplet, "StartLibraryApplet"},
{0x001F, &APT_A::StartSystemApplet, "StartSystemApplet"},
{0x0020, nullptr, "StartNewestHomeMenu"},
{0x0021, &APT_A::OrderToCloseApplication, "OrderToCloseApplication"},
{0x0022, &APT_A::PrepareToCloseApplication, "PrepareToCloseApplication"},
{0x0023, nullptr, "PrepareToJumpToApplication"},
{0x0024, nullptr, "JumpToApplication"},
{0x0025, &APT_A::PrepareToCloseLibraryApplet, "PrepareToCloseLibraryApplet"},
{0x0026, &APT_A::PrepareToCloseSystemApplet, "PrepareToCloseSystemApplet"},
{0x0027, &APT_A::CloseApplication, "CloseApplication"},
{0x0028, &APT_A::CloseLibraryApplet, "CloseLibraryApplet"},
{0x0029, &APT_A::CloseSystemApplet, "CloseSystemApplet"},
{0x002A, &APT_A::OrderToCloseSystemApplet, "OrderToCloseSystemApplet"},
{0x002B, &APT_A::PrepareToJumpToHomeMenu, "PrepareToJumpToHomeMenu"},
{0x002C, &APT_A::JumpToHomeMenu, "JumpToHomeMenu"},
{0x002D, &APT_A::PrepareToLeaveHomeMenu, "PrepareToLeaveHomeMenu"},
{0x002E, &APT_A::LeaveHomeMenu, "LeaveHomeMenu"},
{0x002F, nullptr, "PrepareToLeaveResidentApplet"},
{0x0030, nullptr, "LeaveResidentApplet"},
{0x0031, &APT_A::PrepareToDoApplicationJump, "PrepareToDoApplicationJump"},
{0x0032, &APT_A::DoApplicationJump, "DoApplicationJump"},
{0x0033, &APT_A::GetProgramIdOnApplicationJump, "GetProgramIdOnApplicationJump"},
{0x0034, &APT_A::SendDeliverArg, "SendDeliverArg"},
{0x0035, &APT_A::ReceiveDeliverArg, "ReceiveDeliverArg"},
{0x0036, &APT_A::LoadSysMenuArg, "LoadSysMenuArg"},
{0x0037, &APT_A::StoreSysMenuArg, "StoreSysMenuArg"},
{0x0038, nullptr, "PreloadResidentApplet"},
{0x0039, nullptr, "PrepareToStartResidentApplet"},
{0x003A, nullptr, "StartResidentApplet"},
{0x003B, &APT_A::CancelLibraryApplet, "CancelLibraryApplet"},
{0x003C, &APT_A::SendDspSleep, "SendDspSleep"},
{0x003D, &APT_A::SendDspWakeUp, "SendDspWakeUp"},
{0x003E, &APT_A::ReplySleepQuery, "ReplySleepQuery"},
{0x003F, &APT_A::ReplySleepNotificationComplete, "ReplySleepNotificationComplete"},
{0x0040, &APT_A::SendCaptureBufferInfo, "SendCaptureBufferInfo"},
{0x0041, &APT_A::ReceiveCaptureBufferInfo, "ReceiveCaptureBufferInfo"},
{0x0042, nullptr, "SleepSystem"},
{0x0043, &APT_A::NotifyToWait, "NotifyToWait"},
{0x0044, &APT_A::GetSharedFont, "GetSharedFont"},
{0x0045, &APT_A::GetWirelessRebootInfo, "GetWirelessRebootInfo"},
{0x0046, &APT_A::Wrap, "Wrap"},
{0x0047, &APT_A::Unwrap, "Unwrap"},
{0x0048, &APT_A::GetProgramInfo, "GetProgramInfo"},
{0x0049, &APT_A::Reboot, "Reboot"},
{0x004A, &APT_A::GetCaptureInfo, "GetCaptureInfo"},
{0x004B, &APT_A::AppletUtility, "AppletUtility"},
{0x004C, nullptr, "SetFatalErrDispMode"},
{0x004D, nullptr, "GetAppletProgramInfo"},
{0x004E, &APT_A::HardwareResetAsync, "HardwareResetAsync"},
{0x004F, &APT_A::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x0050, &APT_A::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
{0x0051, &APT_A::GetStartupArgument, "GetStartupArgument"},
{0x0052, nullptr, "Wrap1"},
{0x0053, nullptr, "Unwrap1"},
{0x0054, &APT_A::Unknown54, "Unknown54"},
{0x0055, &APT_A::SetScreenCapturePostPermission, "SetScreenCapturePostPermission"},
{0x0056, &APT_A::GetScreenCapturePostPermission, "GetScreenCapturePostPermission"},
{0x0057, &APT_A::WakeupApplication2, "WakeupApplication2"},
{0x0058, &APT_A::GetProgramId, "GetProgramId"},
{0x0101, &APT_A::GetTargetPlatform, "GetTargetPlatform"},
{0x0102, &APT_A::CheckNew3DS, "CheckNew3DS"},
{0x0103, &APT_A::GetApplicationRunningMode, "GetApplicationRunningMode"},
{0x0104, &APT_A::IsStandardMemoryLayout, "IsStandardMemoryLayout"},
{0x0105, &APT_A::IsTitleAllowed, "IsTitleAllowed"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::APT
SERIALIZE_EXPORT_IMPL(Service::APT::APT_A)

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