First Commit
This commit is contained in:
62
src/core/hle/applets/applet.cpp
Normal file
62
src/core/hle/applets/applet.cpp
Normal 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
|
||||
84
src/core/hle/applets/applet.h
Normal file
84
src/core/hle/applets/applet.h
Normal 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
|
||||
67
src/core/hle/applets/erreula.cpp
Normal file
67
src/core/hle/applets/erreula.cpp
Normal 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
|
||||
33
src/core/hle/applets/erreula.h
Normal file
33
src/core/hle/applets/erreula.h
Normal 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
|
||||
136
src/core/hle/applets/mii_selector.cpp
Normal file
136
src/core/hle/applets/mii_selector.cpp
Normal 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
|
||||
90
src/core/hle/applets/mii_selector.h
Normal file
90
src/core/hle/applets/mii_selector.h
Normal 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
|
||||
67
src/core/hle/applets/mint.cpp
Normal file
67
src/core/hle/applets/mint.cpp
Normal 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
|
||||
33
src/core/hle/applets/mint.h
Normal file
33
src/core/hle/applets/mint.h
Normal 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
|
||||
205
src/core/hle/applets/swkbd.cpp
Normal file
205
src/core/hle/applets/swkbd.cpp
Normal 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
|
||||
208
src/core/hle/applets/swkbd.h
Normal file
208
src/core/hle/applets/swkbd.h
Normal 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
155
src/core/hle/ipc.h
Normal 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
465
src/core/hle/ipc_helpers.h
Normal 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
|
||||
220
src/core/hle/kernel/address_arbiter.cpp
Normal file
220
src/core/hle/kernel/address_arbiter.cpp
Normal 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
|
||||
88
src/core/hle/kernel/address_arbiter.h
Normal file
88
src/core/hle/kernel/address_arbiter.h
Normal 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)
|
||||
63
src/core/hle/kernel/client_port.cpp
Normal file
63
src/core/hle/kernel/client_port.cpp
Normal 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
|
||||
73
src/core/hle/kernel/client_port.h
Normal file
73
src/core/hle/kernel/client_port.h
Normal 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)
|
||||
67
src/core/hle/kernel/client_session.cpp
Normal file
67
src/core/hle/kernel/client_session.cpp
Normal 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
|
||||
60
src/core/hle/kernel/client_session.h
Normal file
60
src/core/hle/kernel/client_session.h
Normal 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)
|
||||
42
src/core/hle/kernel/config_mem.cpp
Normal file
42
src/core/hle/kernel/config_mem.cpp
Normal 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
|
||||
79
src/core/hle/kernel/config_mem.h
Normal file
79
src/core/hle/kernel/config_mem.h
Normal 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)
|
||||
113
src/core/hle/kernel/errors.h
Normal file
113
src/core/hle/kernel/errors.h
Normal 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
|
||||
73
src/core/hle/kernel/event.cpp
Normal file
73
src/core/hle/kernel/event.cpp
Normal 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
|
||||
64
src/core/hle/kernel/event.h
Normal file
64
src/core/hle/kernel/event.h
Normal 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)
|
||||
111
src/core/hle/kernel/handle_table.cpp
Normal file
111
src/core/hle/kernel/handle_table.cpp
Normal 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
|
||||
129
src/core/hle/kernel/handle_table.h
Normal file
129
src/core/hle/kernel/handle_table.h
Normal 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)
|
||||
358
src/core/hle/kernel/hle_ipc.cpp
Normal file
358
src/core/hle/kernel/hle_ipc.cpp
Normal 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
|
||||
399
src/core/hle/kernel/hle_ipc.h
Normal file
399
src/core/hle/kernel/hle_ipc.h
Normal 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
268
src/core/hle/kernel/ipc.cpp
Normal 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
44
src/core/hle/kernel/ipc.h
Normal 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)
|
||||
169
src/core/hle/kernel/ipc_debugger/recorder.cpp
Normal file
169
src/core/hle/kernel/ipc_debugger/recorder.cpp
Normal 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
|
||||
129
src/core/hle/kernel/ipc_debugger/recorder.h
Normal file
129
src/core/hle/kernel/ipc_debugger/recorder.h
Normal 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
|
||||
210
src/core/hle/kernel/kernel.cpp
Normal file
210
src/core/hle/kernel/kernel.cpp
Normal 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
|
||||
412
src/core/hle/kernel/kernel.h
Normal file
412
src/core/hle/kernel/kernel.h
Normal 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)
|
||||
286
src/core/hle/kernel/memory.cpp
Normal file
286
src/core/hle/kernel/memory.cpp
Normal 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
|
||||
91
src/core/hle/kernel/memory.h
Normal file
91
src/core/hle/kernel/memory.h
Normal 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)
|
||||
144
src/core/hle/kernel/mutex.cpp
Normal file
144
src/core/hle/kernel/mutex.cpp
Normal 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
|
||||
79
src/core/hle/kernel/mutex.h
Normal file
79
src/core/hle/kernel/mutex.h
Normal 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)
|
||||
50
src/core/hle/kernel/object.cpp
Normal file
50
src/core/hle/kernel/object.cpp
Normal 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
|
||||
106
src/core/hle/kernel/object.h
Normal file
106
src/core/hle/kernel/object.h
Normal 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>()); \
|
||||
} \
|
||||
}
|
||||
656
src/core/hle/kernel/process.cpp
Normal file
656
src/core/hle/kernel/process.cpp
Normal 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
|
||||
246
src/core/hle/kernel/process.h
Normal file
246
src/core/hle/kernel/process.h
Normal 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)
|
||||
161
src/core/hle/kernel/resource_limit.cpp
Normal file
161
src/core/hle/kernel/resource_limit.cpp
Normal 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
|
||||
104
src/core/hle/kernel/resource_limit.h
Normal file
104
src/core/hle/kernel/resource_limit.h
Normal 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)
|
||||
73
src/core/hle/kernel/semaphore.cpp
Normal file
73
src/core/hle/kernel/semaphore.cpp
Normal 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
|
||||
59
src/core/hle/kernel/semaphore.h
Normal file
59
src/core/hle/kernel/semaphore.h
Normal 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)
|
||||
66
src/core/hle/kernel/server_port.cpp
Normal file
66
src/core/hle/kernel/server_port.cpp
Normal 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
|
||||
75
src/core/hle/kernel/server_port.h
Normal file
75
src/core/hle/kernel/server_port.h
Normal 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)
|
||||
159
src/core/hle/kernel/server_session.cpp
Normal file
159
src/core/hle/kernel/server_session.cpp
Normal 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
|
||||
117
src/core/hle/kernel/server_session.h
Normal file
117
src/core/hle/kernel/server_session.h
Normal 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)
|
||||
24
src/core/hle/kernel/session.cpp
Normal file
24
src/core/hle/kernel/session.cpp
Normal 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
|
||||
36
src/core/hle/kernel/session.h
Normal file
36
src/core/hle/kernel/session.h
Normal 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)
|
||||
240
src/core/hle/kernel/shared_memory.cpp
Normal file
240
src/core/hle/kernel/shared_memory.cpp
Normal 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
|
||||
120
src/core/hle/kernel/shared_memory.h
Normal file
120
src/core/hle/kernel/shared_memory.h
Normal 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)
|
||||
184
src/core/hle/kernel/shared_page.cpp
Normal file
184
src/core/hle/kernel/shared_page.cpp
Normal 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
|
||||
155
src/core/hle/kernel/shared_page.h
Normal file
155
src/core/hle/kernel/shared_page.h
Normal 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
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
35
src/core/hle/kernel/svc.h
Normal 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)
|
||||
314
src/core/hle/kernel/svc_wrapper.h
Normal file
314
src/core/hle/kernel/svc_wrapper.h
Normal 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
|
||||
522
src/core/hle/kernel/thread.cpp
Normal file
522
src/core/hle/kernel/thread.cpp
Normal 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
|
||||
364
src/core/hle/kernel/thread.h
Normal file
364
src/core/hle/kernel/thread.h
Normal 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
|
||||
141
src/core/hle/kernel/timer.cpp
Normal file
141
src/core/hle/kernel/timer.cpp
Normal 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
124
src/core/hle/kernel/timer.h
Normal 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)
|
||||
410
src/core/hle/kernel/vm_manager.cpp
Normal file
410
src/core/hle/kernel/vm_manager.cpp
Normal 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
|
||||
237
src/core/hle/kernel/vm_manager.h
Normal file
237
src/core/hle/kernel/vm_manager.h
Normal 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)
|
||||
119
src/core/hle/kernel/wait_object.cpp
Normal file
119
src/core/hle/kernel/wait_object.cpp
Normal 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
|
||||
83
src/core/hle/kernel/wait_object.h
Normal file
83
src/core/hle/kernel/wait_object.h
Normal 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
30
src/core/hle/mii.cpp
Normal 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
242
src/core/hle/mii.h
Normal 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
427
src/core/hle/result.h
Normal 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
112
src/core/hle/romfs.cpp
Normal 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
34
src/core/hle/romfs.h
Normal 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
|
||||
217
src/core/hle/service/ac/ac.cpp
Normal file
217
src/core/hle/service/ac/ac.cpp
Normal 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
|
||||
187
src/core/hle/service/ac/ac.h
Normal file
187
src/core/hle/service/ac/ac.h
Normal 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)
|
||||
41
src/core/hle/service/ac/ac_i.cpp
Normal file
41
src/core/hle/service/ac/ac_i.cpp
Normal 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)
|
||||
23
src/core/hle/service/ac/ac_i.h
Normal file
23
src/core/hle/service/ac/ac_i.h
Normal 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)
|
||||
41
src/core/hle/service/ac/ac_u.cpp
Normal file
41
src/core/hle/service/ac/ac_u.cpp
Normal 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)
|
||||
23
src/core/hle/service/ac/ac_u.h
Normal file
23
src/core/hle/service/ac/ac_u.h
Normal 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)
|
||||
69
src/core/hle/service/act/act.cpp
Normal file
69
src/core/hle/service/act/act.cpp
Normal 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
|
||||
72
src/core/hle/service/act/act.h
Normal file
72
src/core/hle/service/act/act.h
Normal 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
|
||||
77
src/core/hle/service/act/act_a.cpp
Normal file
77
src/core/hle/service/act/act_a.cpp
Normal 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)
|
||||
22
src/core/hle/service/act/act_a.h
Normal file
22
src/core/hle/service/act/act_a.h
Normal 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)
|
||||
757
src/core/hle/service/act/act_errors.cpp
Normal file
757
src/core/hle/service/act/act_errors.cpp
Normal 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
|
||||
613
src/core/hle/service/act/act_errors.h
Normal file
613
src/core/hle/service/act/act_errors.h
Normal 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
|
||||
46
src/core/hle/service/act/act_u.cpp
Normal file
46
src/core/hle/service/act/act_u.cpp
Normal 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)
|
||||
22
src/core/hle/service/act/act_u.h
Normal file
22
src/core/hle/service/act/act_u.h
Normal 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)
|
||||
2480
src/core/hle/service/am/am.cpp
Normal file
2480
src/core/hle/service/am/am.cpp
Normal file
File diff suppressed because it is too large
Load Diff
810
src/core/hle/service/am/am.h
Normal file
810
src/core/hle/service/am/am.h
Normal 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)
|
||||
33
src/core/hle/service/am/am_app.cpp
Normal file
33
src/core/hle/service/am/am_app.cpp
Normal 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)
|
||||
22
src/core/hle/service/am/am_app.h
Normal file
22
src/core/hle/service/am/am_app.h
Normal 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)
|
||||
130
src/core/hle/service/am/am_net.cpp
Normal file
130
src/core/hle/service/am/am_net.cpp
Normal 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)
|
||||
22
src/core/hle/service/am/am_net.h
Normal file
22
src/core/hle/service/am/am_net.h
Normal 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)
|
||||
78
src/core/hle/service/am/am_sys.cpp
Normal file
78
src/core/hle/service/am/am_sys.cpp
Normal 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)
|
||||
22
src/core/hle/service/am/am_sys.h
Normal file
22
src/core/hle/service/am/am_sys.h
Normal 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)
|
||||
90
src/core/hle/service/am/am_u.cpp
Normal file
90
src/core/hle/service/am/am_u.cpp
Normal 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)
|
||||
22
src/core/hle/service/am/am_u.h
Normal file
22
src/core/hle/service/am/am_u.h
Normal 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)
|
||||
1600
src/core/hle/service/apt/applet_manager.cpp
Normal file
1600
src/core/hle/service/apt/applet_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
564
src/core/hle/service/apt/applet_manager.h
Normal file
564
src/core/hle/service/apt/applet_manager.h
Normal 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)
|
||||
1483
src/core/hle/service/apt/apt.cpp
Normal file
1483
src/core/hle/service/apt/apt.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1097
src/core/hle/service/apt/apt.h
Normal file
1097
src/core/hle/service/apt/apt.h
Normal file
File diff suppressed because it is too large
Load Diff
114
src/core/hle/service/apt/apt_a.cpp
Normal file
114
src/core/hle/service/apt/apt_a.cpp
Normal 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
Reference in New Issue
Block a user