Compare commits
204 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9046f764bf | ||
|
|
29f082775b | ||
|
|
0e05a9d58f | ||
|
|
581406af18 | ||
|
|
481d8716e0 | ||
|
|
a9c25ab9e4 | ||
|
|
81ff9e2473 | ||
|
|
74bce4d68f | ||
|
|
e5a0a23553 | ||
|
|
ad45d68871 | ||
|
|
49cb4fa37b | ||
|
|
92fcc6d15a | ||
|
|
2c87f10267 | ||
|
|
7e59e907ef | ||
|
|
80e4dbdce7 | ||
|
|
2ba4d878e5 | ||
|
|
dd321dc85f | ||
|
|
54df9fe29e | ||
|
|
cdb19e71fe | ||
|
|
722e7c05de | ||
|
|
e10483a878 | ||
|
|
acdc770cfb | ||
|
|
19038db489 | ||
|
|
145ae36963 | ||
|
|
38c1c500ab | ||
|
|
d46e0acb3c | ||
|
|
e450b0bbac | ||
|
|
b2647dba33 | ||
|
|
1c4365d928 | ||
|
|
97187b7ef6 | ||
|
|
6664d7b2c5 | ||
|
|
03c26d3406 | ||
|
|
0bc323bafb | ||
|
|
60a184455c | ||
|
|
1069eced84 | ||
|
|
4aa9779ae1 | ||
|
|
cc1fe93297 | ||
|
|
aee93f98f9 | ||
|
|
31930a3334 | ||
|
|
678c18aa5c | ||
|
|
9afcbba8e4 | ||
|
|
7992dee8e9 | ||
|
|
de0ab806df | ||
|
|
d08457f879 | ||
|
|
86e70cf302 | ||
|
|
0b33d38e9b | ||
|
|
e7fc3d13ed | ||
|
|
a6830e61b8 | ||
|
|
aca218aea0 | ||
|
|
9b9c586dff | ||
|
|
76754f5705 | ||
|
|
5bb80ab009 | ||
|
|
da5fcbf501 | ||
|
|
c31412c433 | ||
|
|
352b56367c | ||
|
|
7a5eda5914 | ||
|
|
adf26ae668 | ||
|
|
6383653a8d | ||
|
|
0cc347462d | ||
|
|
938e45eb83 | ||
|
|
c5a849212f | ||
|
|
f1cb425d92 | ||
|
|
0270906dbf | ||
|
|
655694253a | ||
|
|
5d7167dfca | ||
|
|
9049aedd83 | ||
|
|
80cbd81276 | ||
|
|
e8b565b239 | ||
|
|
3aa8b644a9 | ||
|
|
dde3094058 | ||
|
|
f0e902a7d6 | ||
|
|
87f8181405 | ||
|
|
f4432b5d0c | ||
|
|
258f0f5c31 | ||
|
|
dc85e3bff1 | ||
|
|
bbf3b2da0c | ||
|
|
a973a049b7 | ||
|
|
b77f571d20 | ||
|
|
635d1e5651 | ||
|
|
b5f8a5f0a3 | ||
|
|
f50f065c31 | ||
|
|
351d5a2227 | ||
|
|
aa1cf608ed | ||
|
|
4ddbd9bbaf | ||
|
|
e5ca097e32 | ||
|
|
03150a560e | ||
|
|
585b6a6a50 | ||
|
|
2239d47112 | ||
|
|
b32be35173 | ||
|
|
72d10ce66c | ||
|
|
0d449b77e2 | ||
|
|
6f620b2441 | ||
|
|
1fca683388 | ||
|
|
0287b2be6d | ||
|
|
a9d60c6103 | ||
|
|
1fa31cf74d | ||
|
|
8d89e88d2c | ||
|
|
d63f5acb15 | ||
|
|
d8d557df86 | ||
|
|
a9dc34ea5c | ||
|
|
ed95ce6bb7 | ||
|
|
ac8231ed10 | ||
|
|
705300992e | ||
|
|
7c70746ec4 | ||
|
|
0bf24d310e | ||
|
|
3b1e4c0995 | ||
|
|
9024cbb5b8 | ||
|
|
85ed0af84e | ||
|
|
8eaf857d06 | ||
|
|
12c365b549 | ||
|
|
7de8e36343 | ||
|
|
6594853eb1 | ||
|
|
58444a0376 | ||
|
|
d278f25bda | ||
|
|
72e6b31a07 | ||
|
|
1f98dc30ea | ||
|
|
949d9a7136 | ||
|
|
debabf1fa6 | ||
|
|
476b9f8fc5 | ||
|
|
a94831f2a9 | ||
|
|
8cea598158 | ||
|
|
cebce2a93a | ||
|
|
c2049aa4e5 | ||
|
|
a609b6907a | ||
|
|
ef7b2237d9 | ||
|
|
a141b46b5c | ||
|
|
85285b09b0 | ||
|
|
5172354e29 | ||
|
|
bf7da804c5 | ||
|
|
8806e69f59 | ||
|
|
a6addb5332 | ||
|
|
2347e1b8c5 | ||
|
|
3c63cecb96 | ||
|
|
e54c9e19f3 | ||
|
|
bf0d3e1fea | ||
|
|
9a87ece837 | ||
|
|
2bc2b32662 | ||
|
|
0634aee267 | ||
|
|
942def7831 | ||
|
|
c94db0e071 | ||
|
|
f7a173de6c | ||
|
|
b43cfe6c02 | ||
|
|
fbbb58b226 | ||
|
|
1e3b139cd7 | ||
|
|
6f00628564 | ||
|
|
3ec90dc6ef | ||
|
|
9aa5c1894e | ||
|
|
3a6e76e9b5 | ||
|
|
ca142f35c0 | ||
|
|
abefe29398 | ||
|
|
29f748a658 | ||
|
|
69b35d7615 | ||
|
|
b723390ab1 | ||
|
|
ce2403d975 | ||
|
|
d9590d7dfa | ||
|
|
2694b43d3a | ||
|
|
e6e17a3fc6 | ||
|
|
ddff188c65 | ||
|
|
d14ba122e2 | ||
|
|
2eff8336f4 | ||
|
|
cdd499c261 | ||
|
|
e65f5e4d66 | ||
|
|
1fb4bebb63 | ||
|
|
239dfea34a | ||
|
|
474bc29208 | ||
|
|
b703aba622 | ||
|
|
afb7e5cc05 | ||
|
|
fcf8f53a63 | ||
|
|
77328b0f19 | ||
|
|
c7c346a15d | ||
|
|
6df09f5b76 | ||
|
|
1edf8660bc | ||
|
|
4a31f99a02 | ||
|
|
030847d5fa | ||
|
|
bed2d6c425 | ||
|
|
77e705a8fa | ||
|
|
e7ac42677b | ||
|
|
a1c85b8c55 | ||
|
|
47f081d513 | ||
|
|
2cbc284c2b | ||
|
|
6d27614994 | ||
|
|
7c9f7aeacc | ||
|
|
6949f73149 | ||
|
|
a3d1ede25f | ||
|
|
c7c594a6b8 | ||
|
|
c6529688fc | ||
|
|
257b7bbfee | ||
|
|
a97cdb5eb4 | ||
|
|
f80b80b922 | ||
|
|
6f5bede402 | ||
|
|
bfad41b0c1 | ||
|
|
45f2a2fe29 | ||
|
|
e408bbceed | ||
|
|
702622b8f1 | ||
|
|
19c5cf9c63 | ||
|
|
466960c8ab | ||
|
|
b2a8209c5b | ||
|
|
d3fbf45705 | ||
|
|
aeffd4b436 | ||
|
|
e7e3d5898e | ||
|
|
ed8ca608a0 | ||
|
|
e0ea2f5f6e | ||
|
|
a25d79cfaa | ||
|
|
f5f6292810 |
@@ -121,7 +121,8 @@ CubebSink::CubebSink(std::string target_device_name) {
|
||||
const auto collection_end{collection.device + collection.count};
|
||||
const auto device{
|
||||
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
|
||||
return target_device_name == info.friendly_name;
|
||||
return info.friendly_name != nullptr &&
|
||||
target_device_name == info.friendly_name;
|
||||
})};
|
||||
if (device != collection_end) {
|
||||
output_device = device->devid;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/settings.h"
|
||||
@@ -104,10 +103,7 @@ void Stream::PlayNextBuffer() {
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(AudioOutput, "Audio", "ReleaseActiveBuffer", MP_RGB(100, 100, 255));
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
MICROPROFILE_SCOPE(AudioOutput);
|
||||
ASSERT(active_buffer);
|
||||
released_buffers.push(std::move(active_buffer));
|
||||
release_callback();
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
|
||||
: m_sample_rate(sample_rate), m_channel_count(channel_count) {
|
||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
|
||||
m_sound_touch.setChannels(channel_count);
|
||||
m_sound_touch.setSampleRate(sample_rate);
|
||||
m_sound_touch.setPitch(1.0);
|
||||
@@ -33,10 +32,10 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
|
||||
// We were given actual_samples number of samples, and num_samples were requested from us.
|
||||
double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
|
||||
|
||||
const double max_latency = 1.0; // seconds
|
||||
const double max_latency = 0.25; // seconds
|
||||
const double max_backlog = m_sample_rate * max_latency;
|
||||
const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
|
||||
if (backlog_fullness > 5.0) {
|
||||
if (backlog_fullness > 4.0) {
|
||||
// Too many samples in backlog: Don't push anymore on
|
||||
num_in = 0;
|
||||
}
|
||||
@@ -50,7 +49,7 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
|
||||
|
||||
// This low-pass filter smoothes out variance in the calculated stretch ratio.
|
||||
// The time-scale determines how responsive this filter is.
|
||||
constexpr double lpf_time_scale = 2.0; // seconds
|
||||
constexpr double lpf_time_scale = 0.712; // seconds
|
||||
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
|
||||
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ public:
|
||||
|
||||
private:
|
||||
u32 m_sample_rate;
|
||||
u32 m_channel_count;
|
||||
soundtouch::SoundTouch m_sound_touch;
|
||||
double m_stretch_ratio = 1.0;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <share.h> // For _SH_DENYWR
|
||||
#include <share.h> // For _SH_DENYWR
|
||||
#include <windows.h> // For OutputDebugStringA
|
||||
#else
|
||||
#define _SH_DENYWR 0
|
||||
#endif
|
||||
@@ -139,12 +140,18 @@ void FileBackend::Write(const Entry& entry) {
|
||||
if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
|
||||
return;
|
||||
}
|
||||
bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
|
||||
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||
if (entry.log_level >= Level::Error) {
|
||||
file.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerBackend::Write(const Entry& entry) {
|
||||
#ifdef _WIN32
|
||||
::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
#define ALL_LOG_CLASSES() \
|
||||
CLS(Log) \
|
||||
@@ -196,6 +203,7 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NPNS) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, PCIE) \
|
||||
@@ -204,10 +212,12 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, PM) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, PSC) \
|
||||
SUB(Service, PSM) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, TCAP) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, USB) \
|
||||
SUB(Service, VI) \
|
||||
|
||||
@@ -103,6 +103,20 @@ private:
|
||||
std::size_t bytes_written;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to Visual Studio's output window
|
||||
*/
|
||||
class DebuggerBackend : public Backend {
|
||||
public:
|
||||
static const char* Name() {
|
||||
return "debugger";
|
||||
}
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend);
|
||||
|
||||
void RemoveBackend(std::string_view backend_name);
|
||||
|
||||
@@ -83,6 +83,7 @@ enum class Class : ClassType {
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NPNS, ///< The NPNS service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_PCIE, ///< The PCIe service
|
||||
@@ -96,6 +97,7 @@ enum class Class : ClassType {
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_TCAP, ///< The TCAP service.
|
||||
Service_Time, ///< The time service
|
||||
Service_USB, ///< The USB (Universal Serial Bus) service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
|
||||
@@ -153,6 +153,7 @@ struct VisitorInterface : NonCopyable {
|
||||
|
||||
/// Completion method, called once all fields have been visited
|
||||
virtual void Complete() = 0;
|
||||
virtual bool SubmitTestcase() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -178,6 +179,9 @@ struct NullVisitor : public VisitorInterface {
|
||||
void Visit(const Field<std::chrono::microseconds>& /*field*/) override {}
|
||||
|
||||
void Complete() override {}
|
||||
bool SubmitTestcase() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// Appends build-specific information to the given FieldCollection,
|
||||
|
||||
@@ -185,7 +185,7 @@ struct System::Impl {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
return ResultStatus::ErrorGetLoader;
|
||||
}
|
||||
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
|
||||
std::pair<std::optional<u32>, Loader::ResultStatus> system_mode =
|
||||
app_loader->LoadKernelSystemMode();
|
||||
|
||||
if (system_mode.second != Loader::ResultStatus::Success) {
|
||||
@@ -312,6 +312,10 @@ Cpu& System::CurrentCpuCore() {
|
||||
return impl->CurrentCpuCore();
|
||||
}
|
||||
|
||||
const Cpu& System::CurrentCpuCore() const {
|
||||
return impl->CurrentCpuCore();
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
return impl->RunLoop(tight_loop);
|
||||
}
|
||||
@@ -342,7 +346,11 @@ PerfStatsResults System::GetAndResetPerfStats() {
|
||||
return impl->GetAndResetPerfStats();
|
||||
}
|
||||
|
||||
Core::TelemetrySession& System::TelemetrySession() const {
|
||||
TelemetrySession& System::TelemetrySession() {
|
||||
return *impl->telemetry_session;
|
||||
}
|
||||
|
||||
const TelemetrySession& System::TelemetrySession() const {
|
||||
return *impl->telemetry_session;
|
||||
}
|
||||
|
||||
@@ -350,7 +358,11 @@ ARM_Interface& System::CurrentArmInterface() {
|
||||
return CurrentCpuCore().ArmInterface();
|
||||
}
|
||||
|
||||
std::size_t System::CurrentCoreIndex() {
|
||||
const ARM_Interface& System::CurrentArmInterface() const {
|
||||
return CurrentCpuCore().ArmInterface();
|
||||
}
|
||||
|
||||
std::size_t System::CurrentCoreIndex() const {
|
||||
return CurrentCpuCore().CoreIndex();
|
||||
}
|
||||
|
||||
@@ -358,6 +370,10 @@ Kernel::Scheduler& System::CurrentScheduler() {
|
||||
return CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::CurrentScheduler() const {
|
||||
return CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
@@ -378,6 +394,10 @@ ARM_Interface& System::ArmInterface(std::size_t core_index) {
|
||||
return CpuCore(core_index).ArmInterface();
|
||||
}
|
||||
|
||||
const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
|
||||
return CpuCore(core_index).ArmInterface();
|
||||
}
|
||||
|
||||
Cpu& System::CpuCore(std::size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return *impl->cpu_cores[core_index];
|
||||
@@ -392,6 +412,10 @@ ExclusiveMonitor& System::Monitor() {
|
||||
return *impl->cpu_exclusive_monitor;
|
||||
}
|
||||
|
||||
const ExclusiveMonitor& System::Monitor() const {
|
||||
return *impl->cpu_exclusive_monitor;
|
||||
}
|
||||
|
||||
Tegra::GPU& System::GPU() {
|
||||
return *impl->gpu_core;
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ public:
|
||||
*/
|
||||
bool IsPoweredOn() const;
|
||||
|
||||
/**
|
||||
* Returns a reference to the telemetry session for this emulation session.
|
||||
* @returns Reference to the telemetry session.
|
||||
*/
|
||||
Core::TelemetrySession& TelemetrySession() const;
|
||||
/// Gets a reference to the telemetry session for this emulation session.
|
||||
Core::TelemetrySession& TelemetrySession();
|
||||
|
||||
/// Gets a reference to the telemetry session for this emulation session.
|
||||
const Core::TelemetrySession& TelemetrySession() const;
|
||||
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
@@ -144,24 +144,36 @@ public:
|
||||
/// Gets an ARM interface to the CPU core that is currently running
|
||||
ARM_Interface& CurrentArmInterface();
|
||||
|
||||
/// Gets an ARM interface to the CPU core that is currently running
|
||||
const ARM_Interface& CurrentArmInterface() const;
|
||||
|
||||
/// Gets the index of the currently running CPU core
|
||||
std::size_t CurrentCoreIndex();
|
||||
std::size_t CurrentCoreIndex() const;
|
||||
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
Kernel::Scheduler& CurrentScheduler();
|
||||
|
||||
/// Gets an ARM interface to the CPU core with the specified index
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
const Kernel::Scheduler& CurrentScheduler() const;
|
||||
|
||||
/// Gets a reference to an ARM interface for the CPU core with the specified index
|
||||
ARM_Interface& ArmInterface(std::size_t core_index);
|
||||
|
||||
/// Gets a const reference to an ARM interface from the CPU core with the specified index
|
||||
const ARM_Interface& ArmInterface(std::size_t core_index) const;
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(std::size_t core_index);
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
const Cpu& CpuCore(std::size_t core_index) const;
|
||||
|
||||
/// Gets the exclusive monitor
|
||||
/// Gets a reference to the exclusive monitor
|
||||
ExclusiveMonitor& Monitor();
|
||||
|
||||
/// Gets a constant reference to the exclusive monitor
|
||||
const ExclusiveMonitor& Monitor() const;
|
||||
|
||||
/// Gets a mutable reference to the GPU interface
|
||||
Tegra::GPU& GPU();
|
||||
|
||||
@@ -230,6 +242,9 @@ private:
|
||||
/// Returns the currently running CPU core
|
||||
Cpu& CurrentCpuCore();
|
||||
|
||||
/// Returns the currently running CPU core
|
||||
const Cpu& CurrentCpuCore() const;
|
||||
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
|
||||
@@ -141,28 +141,28 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
|
||||
return mac_key;
|
||||
}
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed() {
|
||||
std::optional<Key128> DeriveSDSeed() {
|
||||
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000043",
|
||||
"rb+");
|
||||
if (!save_43.IsOpen())
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
const FileUtil::IOFile sd_private(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
|
||||
if (!sd_private.IsOpen())
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
std::array<u8, 0x10> private_seed{};
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<u8, 0x10> buffer{};
|
||||
std::size_t offset = 0;
|
||||
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
|
||||
if (!save_43.Seek(offset, SEEK_SET)) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
save_43.ReadBytes(buffer.data(), buffer.size());
|
||||
@@ -172,12 +172,12 @@ boost::optional<Key128> DeriveSDSeed() {
|
||||
}
|
||||
|
||||
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
Key128 seed{};
|
||||
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -291,26 +291,26 @@ static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
|
||||
static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
|
||||
u64 offset = 0;
|
||||
for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
|
||||
if (data[i] == 0x1) {
|
||||
offset = i + 1;
|
||||
break;
|
||||
} else if (data[i] != 0x0) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
const RSAKeyPair<2048>& key) {
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
const RSAKeyPair<2048>& key) {
|
||||
u32 cert_authority;
|
||||
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
|
||||
if (cert_authority == 0)
|
||||
return boost::none;
|
||||
return {};
|
||||
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
|
||||
LOG_INFO(Crypto,
|
||||
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
|
||||
@@ -321,7 +321,7 @@ boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
|
||||
|
||||
if (rights_id == Key128{})
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
Key128 key_temp{};
|
||||
|
||||
@@ -356,17 +356,17 @@ boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
|
||||
|
||||
if (m_0 != 0)
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
m_1 = m_1 ^ MGF1<0x20>(m_2);
|
||||
m_2 = m_2 ^ MGF1<0xDF>(m_1);
|
||||
|
||||
const auto offset = FindTicketOffset(m_2);
|
||||
if (offset == boost::none)
|
||||
return boost::none;
|
||||
ASSERT(offset.get() > 0);
|
||||
if (!offset)
|
||||
return {};
|
||||
ASSERT(*offset > 0);
|
||||
|
||||
std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
|
||||
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
|
||||
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
}
|
||||
@@ -395,7 +395,7 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
|
||||
if (base.size() < begin + length)
|
||||
return false;
|
||||
return std::all_of(base.begin() + begin, base.begin() + begin + length,
|
||||
[](u8 c) { return std::isdigit(c); });
|
||||
[](u8 c) { return std::isxdigit(c); });
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
@@ -661,8 +661,8 @@ void KeyManager::DeriveSDSeedLazy() {
|
||||
return;
|
||||
|
||||
const auto res = DeriveSDSeed();
|
||||
if (res != boost::none)
|
||||
SetKey(S128KeyType::SDSeed, res.get());
|
||||
if (res)
|
||||
SetKey(S128KeyType::SDSeed, *res);
|
||||
}
|
||||
|
||||
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
|
||||
@@ -713,7 +713,6 @@ void KeyManager::DeriveBase() {
|
||||
|
||||
const auto sbk = GetKey(S128KeyType::SecureBoot);
|
||||
const auto tsec = GetKey(S128KeyType::TSEC);
|
||||
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
@@ -890,9 +889,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
|
||||
for (const auto& raw : res) {
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
if (pair == boost::none)
|
||||
if (!pair)
|
||||
continue;
|
||||
const auto& [rid, key] = pair.value();
|
||||
const auto& [rid, key] = *pair;
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
@@ -191,14 +192,14 @@ Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master
|
||||
std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
|
||||
const Key128& key);
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed();
|
||||
std::optional<Key128> DeriveSDSeed();
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
|
||||
|
||||
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
|
||||
// 0x140-0x144 is zero)
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(
|
||||
const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
|
||||
std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
const RSAKeyPair<2048>& eticket_extended_key);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -168,10 +168,6 @@ VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
if (partitions[static_cast<std::size_t>(part)] == nullptr) {
|
||||
return Loader::ResultStatus::ErrorXCIMissingPartition;
|
||||
|
||||
@@ -94,9 +94,6 @@ public:
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
@@ -306,18 +305,18 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
|
||||
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
std::optional<Core::Crypto::Key128> key = {};
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
if (!key) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
if (!key) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
@@ -332,7 +331,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
|
||||
encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
|
||||
encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
|
||||
section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
@@ -388,11 +387,11 @@ u8 NCA::GetCryptoRevision() const {
|
||||
return master_key_id;
|
||||
}
|
||||
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
|
||||
std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
|
||||
const auto master_key_id = GetCryptoRevision();
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
@@ -416,25 +415,25 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
return out;
|
||||
}
|
||||
|
||||
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
|
||||
std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
|
||||
const auto master_key_id = GetCryptoRevision();
|
||||
|
||||
u128 rights_id{};
|
||||
memcpy(rights_id.data(), header.rights_id.data(), 16);
|
||||
if (rights_id == u128{}) {
|
||||
status = Loader::ResultStatus::ErrorInvalidRightsID;
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
|
||||
if (titlekey == Core::Crypto::Key128{}) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekek;
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
@@ -458,25 +457,25 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
|
||||
case NCASectionCryptoType::BKTR:
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
std::optional<Core::Crypto::Key128> key = {};
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
if (!key) {
|
||||
if (status == Loader::ResultStatus::Success)
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
|
||||
if (key == boost::none) {
|
||||
if (!key) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
|
||||
std::move(in), key.value(), starting_offset);
|
||||
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
|
||||
starting_offset);
|
||||
std::vector<u8> iv(16);
|
||||
for (u8 i = 0; i < 8; ++i)
|
||||
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
|
||||
@@ -546,7 +545,4 @@ u64 NCA::GetBaseIVFCOffset() const {
|
||||
return ivfc_offset;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
@@ -100,9 +101,6 @@ public:
|
||||
// Returns the base ivfc offset used in BKTR patching.
|
||||
u64 GetBaseIVFCOffset() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
bool CheckSupportedNCA(const NCAHeader& header);
|
||||
bool HandlePotentialHeaderDecryption();
|
||||
@@ -114,8 +112,8 @@ private:
|
||||
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
|
||||
|
||||
u8 GetCryptoRevision() const;
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
std::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/detail/container_fwd.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
|
||||
@@ -99,16 +99,16 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
u16 rle_size{};
|
||||
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
rle_size = Common::swap16(data_size);
|
||||
rle_size = Common::swap16(rle_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
const auto data = ips->ReadByte(offset++);
|
||||
if (data == boost::none)
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = static_cast<u16>(in_data.size() - real_offset);
|
||||
std::memset(in_data.data() + real_offset, data.get(), rle_size);
|
||||
std::memset(in_data.data() + real_offset, *data, rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
|
||||
@@ -83,7 +83,7 @@ std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
|
||||
return pfs_dirs;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string PartitionFilesystem::GetName() const {
|
||||
@@ -103,18 +103,4 @@ void PartitionFilesystem::PrintDebugInfo() const {
|
||||
pfs_files[i]->GetName(), pfs_files[i]->GetSize());
|
||||
}
|
||||
}
|
||||
|
||||
bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
const auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
|
||||
if (iter == pfs_files.end())
|
||||
return false;
|
||||
|
||||
const std::ptrdiff_t offset = std::distance(pfs_files.begin(), iter);
|
||||
pfs_files[offset] = std::move(pfs_files.back());
|
||||
pfs_files.pop_back();
|
||||
|
||||
pfs_dirs.emplace_back(std::move(dir));
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -35,9 +35,6 @@ public:
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
void PrintDebugInfo() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
u32_le magic;
|
||||
@@ -84,7 +81,6 @@ private:
|
||||
std::size_t content_offset = 0;
|
||||
|
||||
std::vector<VirtualFile> pfs_files;
|
||||
std::vector<VirtualDir> pfs_dirs;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -61,13 +61,12 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
|
||||
if (update != nullptr) {
|
||||
if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
|
||||
if (update != nullptr && update->GetExeFS() != nullptr &&
|
||||
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
|
||||
return exefs;
|
||||
@@ -237,7 +236,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
@@ -281,12 +280,11 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
} else {
|
||||
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = installed->GetEntryVersion(update_tid);
|
||||
if (meta_ver == boost::none || meta_ver.get() == 0) {
|
||||
if (meta_ver.value_or(0) == 0) {
|
||||
out.insert_or_assign("Update", "");
|
||||
} else {
|
||||
out.insert_or_assign(
|
||||
"Update",
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
|
||||
"Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
out.insert_or_assign("Update", "PACKED");
|
||||
|
||||
@@ -159,28 +159,28 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
return file;
|
||||
}
|
||||
|
||||
static boost::optional<NcaID> CheckMapForContentRecord(
|
||||
static std::optional<NcaID> CheckMapForContentRecord(
|
||||
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
|
||||
if (map.find(title_id) == map.end())
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
const auto& cnmt = map.at(title_id);
|
||||
|
||||
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
|
||||
[type](const ContentRecord& rec) { return rec.type == type; });
|
||||
if (iter == cnmt.GetContentRecords().end())
|
||||
return boost::none;
|
||||
return {};
|
||||
|
||||
return boost::make_optional(iter->nca_id);
|
||||
return std::make_optional(iter->nca_id);
|
||||
}
|
||||
|
||||
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
|
||||
ContentRecordType type) const {
|
||||
std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
|
||||
ContentRecordType type) const {
|
||||
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
|
||||
return meta_id.at(title_id);
|
||||
|
||||
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
|
||||
if (res1 != boost::none)
|
||||
if (res1)
|
||||
return res1;
|
||||
return CheckMapForContentRecord(meta, title_id, type);
|
||||
}
|
||||
@@ -283,17 +283,14 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
return nullptr;
|
||||
|
||||
return GetFileAtID(id.get());
|
||||
return id ? GetFileAtID(*id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
const auto meta_iter = meta.find(title_id);
|
||||
if (meta_iter != meta.end())
|
||||
return meta_iter->second.GetTitleVersion();
|
||||
@@ -302,15 +299,12 @@ boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
if (yuzu_meta_iter != yuzu_meta.end())
|
||||
return yuzu_meta_iter->second.GetTitleVersion();
|
||||
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
return nullptr;
|
||||
|
||||
return parser(GetFileAtID(id.get()), id.get());
|
||||
return id ? parser(GetFileAtID(*id), *id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
@@ -364,8 +358,8 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
@@ -373,11 +367,11 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
if (title_type && *title_type != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
if (record_type && *record_type != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
if (title_id && *title_id != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
@@ -459,7 +453,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType
|
||||
|
||||
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id) {
|
||||
std::optional<NcaID> override_id) {
|
||||
const auto in = nca->GetBaseFile();
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
|
||||
@@ -468,12 +462,12 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
|
||||
// game is massive), we're going to cheat and only hash the first MB of the NCA.
|
||||
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
|
||||
NcaID id{};
|
||||
if (override_id == boost::none) {
|
||||
if (override_id) {
|
||||
id = *override_id;
|
||||
} else {
|
||||
const auto& data = in->ReadBytes(0x100000);
|
||||
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
|
||||
memcpy(id.data(), hash.data(), 16);
|
||||
} else {
|
||||
id = override_id.get();
|
||||
}
|
||||
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true);
|
||||
@@ -543,14 +537,14 @@ bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryVersion(title_id);
|
||||
if (res != boost::none)
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
@@ -609,8 +603,8 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
@@ -619,11 +613,11 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
if (title_type && *title_type != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
if (record_type && *record_type != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
if (title_id && *title_id != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
@@ -96,11 +96,10 @@ public:
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
|
||||
// there is a meta NCA and all of them are accessible.
|
||||
@@ -125,12 +124,11 @@ private:
|
||||
std::vector<NcaID> AccumulateFiles() const;
|
||||
void ProcessFiles(const std::vector<NcaID>& ids);
|
||||
void AccumulateYuzuMeta();
|
||||
boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetFileAtID(NcaID id) const;
|
||||
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
|
||||
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id = boost::none);
|
||||
bool overwrite_if_exists, std::optional<NcaID> override_id = {});
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
@@ -153,7 +151,7 @@ public:
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
@@ -165,11 +163,10 @@ public:
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
private:
|
||||
std::vector<RegisteredCache*> caches;
|
||||
|
||||
@@ -205,10 +205,6 @@ VirtualDir NSP::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
exefs = pfs;
|
||||
|
||||
|
||||
@@ -55,9 +55,6 @@ public:
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
|
||||
void ReadNCAs(const std::vector<VirtualFile>& files);
|
||||
|
||||
@@ -167,13 +167,13 @@ std::string VfsFile::GetExtension() const {
|
||||
|
||||
VfsDirectory::~VfsDirectory() = default;
|
||||
|
||||
boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
|
||||
std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
|
||||
u8 out{};
|
||||
std::size_t size = Read(&out, 1, offset);
|
||||
if (size == 1)
|
||||
return out;
|
||||
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
|
||||
@@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo
|
||||
std::vector<u8> temp(std::min(block_size, src->GetSize()));
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
|
||||
const auto read = std::min(block_size, src->GetSize() - i);
|
||||
const auto block = src->Read(temp.data(), read, i);
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read)
|
||||
if (src->Read(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
@@ -103,8 +105,8 @@ public:
|
||||
// into file. Returns number of bytes successfully written.
|
||||
virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
|
||||
|
||||
// Reads exactly one byte at the offset provided, returning boost::none on error.
|
||||
virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const;
|
||||
// Reads exactly one byte at the offset provided, returning std::nullopt on error.
|
||||
virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
|
||||
// Reads size bytes starting at offset in file into a vector.
|
||||
virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
|
||||
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
|
||||
@@ -262,36 +264,8 @@ public:
|
||||
// item name -> type.
|
||||
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
|
||||
|
||||
// Interprets the file with name file instead as a directory of type directory.
|
||||
// The directory must have a constructor that takes a single argument of type
|
||||
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
|
||||
// subdirectory in one call.
|
||||
template <typename Directory>
|
||||
bool InterpretAsDirectory(std::string_view file) {
|
||||
auto file_p = GetFile(file);
|
||||
|
||||
if (file_p == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p));
|
||||
}
|
||||
|
||||
bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function,
|
||||
const std::string& file) {
|
||||
auto file_p = GetFile(file);
|
||||
if (file_p == nullptr)
|
||||
return false;
|
||||
return ReplaceFileWithSubdirectory(file_p, function(file_p));
|
||||
}
|
||||
|
||||
// Returns the full path of this directory as a string, recursively
|
||||
virtual std::string GetFullPath() const;
|
||||
|
||||
protected:
|
||||
// Backend for InterpretAsDirectory.
|
||||
// Removes all references to file and adds a reference to dir in the directory's implementation.
|
||||
virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
|
||||
};
|
||||
|
||||
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
|
||||
|
||||
@@ -126,7 +126,4 @@ bool LayeredVfsDirectory::Rename(std::string_view name_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -39,9 +39,6 @@ public:
|
||||
bool DeleteFile(std::string_view name) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::string name;
|
||||
|
||||
@@ -57,11 +57,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
|
||||
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
|
||||
}
|
||||
|
||||
boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
|
||||
std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
|
||||
if (r_offset < size)
|
||||
return file->ReadByte(offset + r_offset);
|
||||
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
bool IsReadable() const override;
|
||||
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
|
||||
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
|
||||
boost::optional<u8> ReadByte(std::size_t offset) const override;
|
||||
std::optional<u8> ReadByte(std::size_t offset) const override;
|
||||
std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
|
||||
std::vector<u8> ReadAllBytes() const override;
|
||||
bool WriteByte(u8 data, std::size_t offset) override;
|
||||
|
||||
@@ -430,7 +430,4 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
|
||||
return out;
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -100,9 +100,6 @@ public:
|
||||
std::string GetFullPath() const override;
|
||||
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
|
||||
@@ -53,10 +53,10 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
boost::optional<u8> ReadByte(std::size_t offset) const override {
|
||||
std::optional<u8> ReadByte(std::size_t offset) const override {
|
||||
if (offset < size)
|
||||
return value;
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
|
||||
|
||||
@@ -132,11 +132,4 @@ void VectorVfsDirectory::AddFile(VirtualFile file) {
|
||||
void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
|
||||
dirs.push_back(std::move(dir));
|
||||
}
|
||||
|
||||
bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
if (!DeleteFile(file->GetName()))
|
||||
return false;
|
||||
dirs.emplace_back(std::move(dir));
|
||||
return true;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -57,9 +57,6 @@ public:
|
||||
virtual void AddFile(VirtualFile file);
|
||||
virtual void AddDirectory(VirtualDir dir);
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
std::vector<VirtualFile> files;
|
||||
std::vector<VirtualDir> dirs;
|
||||
|
||||
@@ -163,7 +163,4 @@ std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -51,9 +51,6 @@ public:
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus Parse(std::string_view path);
|
||||
|
||||
|
||||
@@ -14,11 +14,6 @@ namespace IPC {
|
||||
/// Size of the command buffer area, in 32-bit words.
|
||||
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
|
||||
|
||||
// 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.
|
||||
constexpr auto ERR_INVALID_HANDLE = Kernel::ERR_INVALID_HANDLE_OS;
|
||||
|
||||
enum class ControlCommand : u32 {
|
||||
ConvertSessionToDomain = 0,
|
||||
ConvertDomainToSession = 1,
|
||||
|
||||
@@ -117,8 +117,7 @@ public:
|
||||
|
||||
AlignWithPadding();
|
||||
|
||||
const bool request_has_domain_header{context.GetDomainMessageHeader() != nullptr};
|
||||
if (context.Session()->IsDomain() && request_has_domain_header) {
|
||||
if (context.Session()->IsDomain() && context.HasDomainMessageHeader()) {
|
||||
IPC::DomainMessageHeader domain_header{};
|
||||
domain_header.num_objects = num_domain_objects;
|
||||
PushRaw(domain_header);
|
||||
|
||||
@@ -10,11 +10,6 @@ namespace Kernel {
|
||||
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
// TODO(Subv): Remove these 3DS OS error codes.
|
||||
SessionClosedByRemote = 26,
|
||||
NoPendingSessions = 35,
|
||||
InvalidBufferDescriptor = 48,
|
||||
|
||||
// Confirmed Switch OS error codes
|
||||
MaxConnectionsReached = 7,
|
||||
InvalidSize = 101,
|
||||
@@ -26,6 +21,7 @@ enum {
|
||||
InvalidThreadPriority = 112,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
InvalidPointer = 115,
|
||||
InvalidCombination = 116,
|
||||
Timeout = 117,
|
||||
SynchronizationCanceled = 118,
|
||||
@@ -33,6 +29,7 @@ enum {
|
||||
InvalidEnumValue = 120,
|
||||
NoSuchEntry = 121,
|
||||
AlreadyRegistered = 122,
|
||||
SessionClosed = 123,
|
||||
InvalidState = 125,
|
||||
ResourceLimitExceeded = 132,
|
||||
};
|
||||
@@ -41,18 +38,14 @@ enum {
|
||||
// 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.
|
||||
|
||||
// TODO(bunnei): Replace -1 with correct errors for Switch OS
|
||||
constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull);
|
||||
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
|
||||
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(ErrorModule::Kernel, ErrCodes::SessionClosed);
|
||||
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge);
|
||||
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrorModule::Kernel,
|
||||
ErrCodes::MaxConnectionsReached);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidCombination);
|
||||
constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
@@ -65,14 +58,8 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::Alrea
|
||||
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
|
||||
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidThreadPriority);
|
||||
constexpr ResultCode ERR_INVALID_POINTER(-1);
|
||||
constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
|
||||
constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
|
||||
/// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths.
|
||||
constexpr ResultCode ERR_INVALID_HANDLE_OS(-1);
|
||||
constexpr ResultCode ERR_INVALID_POINTER(ErrorModule::Kernel, ErrCodes::InvalidPointer);
|
||||
constexpr ResultCode ERR_NOT_FOUND(ErrorModule::Kernel, ErrCodes::NoSuchEntry);
|
||||
constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout);
|
||||
/// Returned when Accept() is called on a port with no sessions to be accepted.
|
||||
constexpr ResultCode ERR_NO_PENDING_SESSIONS(-1);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -161,8 +161,12 @@ public:
|
||||
return buffer_c_desciptors;
|
||||
}
|
||||
|
||||
const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
|
||||
return domain_message_header;
|
||||
const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
|
||||
return domain_message_header.get();
|
||||
}
|
||||
|
||||
bool HasDomainMessageHeader() const {
|
||||
return domain_message_header != nullptr;
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Kernel {
|
||||
*/
|
||||
static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_late) {
|
||||
const auto proper_handle = static_cast<Handle>(thread_handle);
|
||||
auto& system = Core::System::GetInstance();
|
||||
const auto& system = Core::System::GetInstance();
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
@@ -90,7 +90,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
/// The timer callback event, called when a timer is fired
|
||||
static void TimerCallback(u64 timer_handle, int cycles_late) {
|
||||
const auto proper_handle = static_cast<Handle>(timer_handle);
|
||||
auto& system = Core::System::GetInstance();
|
||||
const auto& system = Core::System::GetInstance();
|
||||
SharedPtr<Timer> timer = system.Kernel().RetrieveTimerFromCallbackHandleTable(proper_handle);
|
||||
|
||||
if (timer == nullptr) {
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
|
||||
@@ -232,6 +232,12 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
|
||||
// Clear instruction cache in CPU JIT
|
||||
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
|
||||
}
|
||||
|
||||
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
|
||||
|
||||
@@ -202,6 +202,16 @@ public:
|
||||
return is_64bit_process;
|
||||
}
|
||||
|
||||
/// Gets the total running time of the process instance in ticks.
|
||||
u64 GetCPUTimeTicks() const {
|
||||
return total_process_running_time_ticks;
|
||||
}
|
||||
|
||||
/// Updates the total running time, adding the given ticks to it.
|
||||
void UpdateCPUTimeTicks(u64 ticks) {
|
||||
total_process_running_time_ticks += ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads process-specifics configuration info with metadata provided
|
||||
* by an executable.
|
||||
@@ -305,6 +315,9 @@ private:
|
||||
/// specified by metadata provided to the process during loading.
|
||||
bool is_64bit_process = true;
|
||||
|
||||
/// Total running time for the process in ticks.
|
||||
u64 total_process_running_time_ticks = 0;
|
||||
|
||||
/// Per-process handle table for storing created object handles in.
|
||||
HandleTable handle_table;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
@@ -34,6 +35,10 @@ Thread* Scheduler::GetCurrentThread() const {
|
||||
return current_thread.get();
|
||||
}
|
||||
|
||||
u64 Scheduler::GetLastContextSwitchTicks() const {
|
||||
return last_context_switch_time;
|
||||
}
|
||||
|
||||
Thread* Scheduler::PopNextReadyThread() {
|
||||
Thread* next = nullptr;
|
||||
Thread* thread = GetCurrentThread();
|
||||
@@ -54,7 +59,10 @@ Thread* Scheduler::PopNextReadyThread() {
|
||||
}
|
||||
|
||||
void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
Thread* previous_thread = GetCurrentThread();
|
||||
Thread* const previous_thread = GetCurrentThread();
|
||||
Process* const previous_process = Core::CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
// Save context for previous thread
|
||||
if (previous_thread) {
|
||||
@@ -78,8 +86,6 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
new_thread->CancelWakeupTimer();
|
||||
|
||||
auto* const previous_process = Core::CurrentProcess();
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
ready_queue.remove(new_thread->GetPriority(), new_thread);
|
||||
@@ -102,6 +108,22 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = CoreTiming::GetTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
thread->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
if (process != nullptr) {
|
||||
process->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
last_context_switch_time = most_recent_switch_ticks;
|
||||
}
|
||||
|
||||
void Scheduler::Reschedule() {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ class ARM_Interface;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Process;
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::ARM_Interface& cpu_core);
|
||||
@@ -31,6 +33,9 @@ public:
|
||||
/// Gets the current running thread
|
||||
Thread* GetCurrentThread() const;
|
||||
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
u64 GetLastContextSwitchTicks() const;
|
||||
|
||||
/// Adds a new thread to the scheduler
|
||||
void AddThread(SharedPtr<Thread> thread, u32 priority);
|
||||
|
||||
@@ -64,6 +69,19 @@ private:
|
||||
*/
|
||||
void SwitchContext(Thread* new_thread);
|
||||
|
||||
/**
|
||||
* Called on every context switch to update the internal timestamp
|
||||
* This also updates the running time ticks for the given thread and
|
||||
* process using the following difference:
|
||||
*
|
||||
* ticks += most_recent_ticks - last_context_switch_ticks
|
||||
*
|
||||
* The internal tick timestamp for the scheduler is simply the
|
||||
* most recent tick count retrieved. No special arithmetic is
|
||||
* applied to it.
|
||||
*/
|
||||
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<SharedPtr<Thread>> thread_list;
|
||||
|
||||
@@ -73,6 +91,7 @@ private:
|
||||
SharedPtr<Thread> current_thread = nullptr;
|
||||
|
||||
Core::ARM_Interface& cpu_core;
|
||||
u64 last_context_switch_time = 0;
|
||||
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ ServerPort::~ServerPort() = default;
|
||||
|
||||
ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
|
||||
if (pending_sessions.empty()) {
|
||||
return ERR_NO_PENDING_SESSIONS;
|
||||
return ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto session = std::move(pending_sessions.back());
|
||||
@@ -28,7 +28,7 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
|
||||
|
||||
bool ServerPort::ShouldWait(Thread* thread) const {
|
||||
// If there are no pending sessions, we wait until a new one is added.
|
||||
return pending_sessions.size() == 0;
|
||||
return pending_sessions.empty();
|
||||
}
|
||||
|
||||
void ServerPort::Acquire(Thread* thread) {
|
||||
|
||||
@@ -63,7 +63,7 @@ void ServerSession::Acquire(Thread* thread) {
|
||||
}
|
||||
|
||||
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||
auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
auto* const domain_message_header = context.GetDomainMessageHeader();
|
||||
if (domain_message_header) {
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
@@ -111,7 +111,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
// If the session has been converted to a domain, handle the domain request
|
||||
if (IsDomain() && context.GetDomainMessageHeader()) {
|
||||
if (IsDomain() && context.HasDomainMessageHeader()) {
|
||||
result = HandleDomainSyncRequest(context);
|
||||
// If there is no domain header, the regular session handler is used
|
||||
} else if (hle_handler != nullptr) {
|
||||
|
||||
@@ -80,20 +80,19 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
|
||||
|
||||
ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
|
||||
MemoryPermission other_permissions) {
|
||||
|
||||
MemoryPermission own_other_permissions =
|
||||
const MemoryPermission own_other_permissions =
|
||||
target_process == owner_process ? 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 ERR_INVALID_COMBINATION;
|
||||
return ERR_INVALID_MEMORY_PERMISSIONS;
|
||||
}
|
||||
|
||||
// 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{:X} name={}, permissions don't match",
|
||||
GetObjectId(), address, name);
|
||||
return ERR_INVALID_COMBINATION;
|
||||
return ERR_INVALID_MEMORY_PERMISSIONS;
|
||||
}
|
||||
|
||||
// Error out if the provided permissions are not compatible with what the creator process needs.
|
||||
|
||||
@@ -395,16 +395,42 @@ struct BreakReason {
|
||||
/// Break program execution
|
||||
static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
BreakReason break_reason{reason};
|
||||
bool has_dumped_buffer{};
|
||||
|
||||
const auto handle_debug_buffer = [&](VAddr addr, u64 sz) {
|
||||
if (sz == 0 || addr == 0 || has_dumped_buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This typically is an error code so we're going to assume this is the case
|
||||
if (sz == sizeof(u32)) {
|
||||
LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr));
|
||||
} else {
|
||||
// We don't know what's in here so we'll hexdump it
|
||||
std::vector<u8> debug_buffer(sz);
|
||||
Memory::ReadBlock(addr, debug_buffer.data(), sz);
|
||||
std::string hexdump;
|
||||
for (std::size_t i = 0; i < debug_buffer.size(); i++) {
|
||||
hexdump += fmt::format("{:02X} ", debug_buffer[i]);
|
||||
if (i != 0 && i % 16 == 0) {
|
||||
hexdump += '\n';
|
||||
}
|
||||
}
|
||||
LOG_CRITICAL(Debug_Emulated, "debug_buffer=\n{}", hexdump);
|
||||
}
|
||||
has_dumped_buffer = true;
|
||||
};
|
||||
switch (break_reason.break_type) {
|
||||
case BreakType::Panic:
|
||||
LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}",
|
||||
info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
case BreakType::AssertionFailed:
|
||||
LOG_CRITICAL(Debug_Emulated,
|
||||
"Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
|
||||
info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
case BreakType::PreNROLoad:
|
||||
LOG_WARNING(
|
||||
@@ -433,6 +459,7 @@ static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
Debug_Emulated,
|
||||
"Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
static_cast<u32>(break_reason.break_type.Value()), info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -441,6 +468,7 @@ static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
ASSERT(false);
|
||||
|
||||
Core::CurrentProcess()->PrepareForTermination();
|
||||
@@ -467,6 +495,37 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
|
||||
info_sub_id, handle);
|
||||
|
||||
enum class GetInfoType : u64 {
|
||||
// 1.0.0+
|
||||
AllowedCpuIdBitmask = 0,
|
||||
AllowedThreadPrioBitmask = 1,
|
||||
MapRegionBaseAddr = 2,
|
||||
MapRegionSize = 3,
|
||||
HeapRegionBaseAddr = 4,
|
||||
HeapRegionSize = 5,
|
||||
TotalMemoryUsage = 6,
|
||||
TotalHeapUsage = 7,
|
||||
IsCurrentProcessBeingDebugged = 8,
|
||||
ResourceHandleLimit = 9,
|
||||
IdleTickCount = 10,
|
||||
RandomEntropy = 11,
|
||||
PerformanceCounter = 0xF0000002,
|
||||
// 2.0.0+
|
||||
ASLRRegionBaseAddr = 12,
|
||||
ASLRRegionSize = 13,
|
||||
NewMapRegionBaseAddr = 14,
|
||||
NewMapRegionSize = 15,
|
||||
// 3.0.0+
|
||||
IsVirtualAddressMemoryEnabled = 16,
|
||||
PersonalMmHeapUsage = 17,
|
||||
TitleId = 18,
|
||||
// 4.0.0+
|
||||
PrivilegedProcessId = 19,
|
||||
// 5.0.0+
|
||||
UserExceptionContextAddr = 20,
|
||||
ThreadTickCount = 0xF0000002,
|
||||
};
|
||||
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
@@ -529,6 +588,36 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
"(STUBBED) Attempted to query user exception context address, returned 0");
|
||||
*result = 0;
|
||||
break;
|
||||
case GetInfoType::ThreadTickCount: {
|
||||
constexpr u64 num_cpus = 4;
|
||||
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
|
||||
return ERR_INVALID_COMBINATION_KERNEL;
|
||||
}
|
||||
|
||||
const auto thread =
|
||||
current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto& system = Core::System::GetInstance();
|
||||
const auto& scheduler = system.CurrentScheduler();
|
||||
const auto* const current_thread = scheduler.GetCurrentThread();
|
||||
const bool same_thread = current_thread == thread;
|
||||
|
||||
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
|
||||
u64 out_ticks = 0;
|
||||
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
|
||||
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
|
||||
|
||||
out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
|
||||
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
|
||||
out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
|
||||
}
|
||||
|
||||
*result = out_ticks;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
@@ -594,16 +683,17 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
}
|
||||
|
||||
const auto* const current_process = Core::CurrentProcess();
|
||||
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
thread->SetPriority(priority);
|
||||
@@ -745,7 +835,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const ResourceLimit& resource_limit = current_process->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
if (processor_id == THREADPROCESSORID_DEFAULT) {
|
||||
|
||||
@@ -24,37 +24,6 @@ struct PageInfo {
|
||||
u64 flags;
|
||||
};
|
||||
|
||||
/// Values accepted by svcGetInfo
|
||||
enum class GetInfoType : u64 {
|
||||
// 1.0.0+
|
||||
AllowedCpuIdBitmask = 0,
|
||||
AllowedThreadPrioBitmask = 1,
|
||||
MapRegionBaseAddr = 2,
|
||||
MapRegionSize = 3,
|
||||
HeapRegionBaseAddr = 4,
|
||||
HeapRegionSize = 5,
|
||||
TotalMemoryUsage = 6,
|
||||
TotalHeapUsage = 7,
|
||||
IsCurrentProcessBeingDebugged = 8,
|
||||
ResourceHandleLimit = 9,
|
||||
IdleTickCount = 10,
|
||||
RandomEntropy = 11,
|
||||
PerformanceCounter = 0xF0000002,
|
||||
// 2.0.0+
|
||||
ASLRRegionBaseAddr = 12,
|
||||
ASLRRegionSize = 13,
|
||||
NewMapRegionBaseAddr = 14,
|
||||
NewMapRegionSize = 15,
|
||||
// 3.0.0+
|
||||
IsVirtualAddressMemoryEnabled = 16,
|
||||
PersonalMmHeapUsage = 17,
|
||||
TitleId = 18,
|
||||
// 4.0.0+
|
||||
PrivilegedProcessId = 19,
|
||||
// 5.0.0+
|
||||
UserExceptionContextAddr = 20,
|
||||
};
|
||||
|
||||
void CallSVC(u32 immediate);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -94,7 +94,7 @@ void Thread::CancelWakeupTimer() {
|
||||
CoreTiming::UnscheduleEventThreadsafe(kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
}
|
||||
|
||||
static boost::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
static std::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
|
||||
if (mask & (1ULL << index)) {
|
||||
if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) {
|
||||
@@ -142,7 +142,7 @@ void Thread::ResumeFromWait() {
|
||||
|
||||
status = ThreadStatus::Ready;
|
||||
|
||||
boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
|
||||
std::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
@@ -369,7 +369,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
|
||||
std::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
|
||||
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
|
||||
@@ -258,6 +258,14 @@ public:
|
||||
return last_running_ticks;
|
||||
}
|
||||
|
||||
u64 GetTotalCPUTimeTicks() const {
|
||||
return total_cpu_time_ticks;
|
||||
}
|
||||
|
||||
void UpdateCPUTimeTicks(u64 ticks) {
|
||||
total_cpu_time_ticks += ticks;
|
||||
}
|
||||
|
||||
s32 GetProcessorID() const {
|
||||
return processor_id;
|
||||
}
|
||||
@@ -378,7 +386,8 @@ private:
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
|
||||
s32 processor_id = 0;
|
||||
|
||||
|
||||
@@ -143,6 +143,26 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
|
||||
return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
|
||||
}
|
||||
|
||||
ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
// Find the first Free VMA.
|
||||
const VAddr base = GetASLRRegionBaseAddress();
|
||||
const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
|
||||
if (vma.second.type != VMAType::Free)
|
||||
return false;
|
||||
|
||||
const VAddr vma_end = vma.second.base + vma.second.size;
|
||||
return vma_end > base && vma_end >= base + size;
|
||||
});
|
||||
|
||||
if (vma_handle == vma_map.end()) {
|
||||
// TODO(Subv): Find the correct error code here.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
const VAddr target = std::max(base, vma_handle->second.base);
|
||||
return MakeResult<VAddr>(target);
|
||||
}
|
||||
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
|
||||
MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler) {
|
||||
|
||||
@@ -157,6 +157,14 @@ public:
|
||||
*/
|
||||
ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
|
||||
|
||||
/**
|
||||
* Finds the first free address that can hold a region of the desired size.
|
||||
*
|
||||
* @param size Size of the desired region.
|
||||
* @return The found free address.
|
||||
*/
|
||||
ResultVal<VAddr> FindFreeRegion(u64 size) const;
|
||||
|
||||
/**
|
||||
* Maps a memory-mapped IO region at a given address.
|
||||
*
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -16,6 +20,7 @@
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
// TODO: RE this structure
|
||||
struct UserData {
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -27,6 +32,29 @@ struct UserData {
|
||||
};
|
||||
static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
|
||||
|
||||
// Smallest JPEG https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
|
||||
// used as a backup should the one on disk not exist
|
||||
constexpr u32 backup_jpeg_size = 107;
|
||||
constexpr std::array<u8, backup_jpeg_size> backup_jpeg{{
|
||||
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
|
||||
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
|
||||
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
|
||||
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
|
||||
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
|
||||
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
|
||||
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
}};
|
||||
|
||||
static std::string GetImagePath(UUID uuid) {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
|
||||
}
|
||||
|
||||
static constexpr u32 SanitizeJPEGSize(std::size_t size) {
|
||||
constexpr std::size_t max_jpeg_image_size = 0x20000;
|
||||
return static_cast<u32>(std::min(size, max_jpeg_image_size));
|
||||
}
|
||||
|
||||
class IProfile final : public ServiceFramework<IProfile> {
|
||||
public:
|
||||
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
|
||||
@@ -73,32 +101,42 @@ private:
|
||||
}
|
||||
|
||||
void LoadImage(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
// smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
|
||||
// TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000
|
||||
constexpr u32 jpeg_size = 107;
|
||||
static constexpr std::array<u8, jpeg_size> jpeg{
|
||||
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03,
|
||||
0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a,
|
||||
0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10,
|
||||
0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
|
||||
0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
|
||||
0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
};
|
||||
ctx.WriteBuffer(jpeg);
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(jpeg_size);
|
||||
|
||||
const FileUtil::IOFile image(GetImagePath(user_id), "rb");
|
||||
if (!image.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC,
|
||||
"Failed to load user provided image! Falling back to built-in backup...");
|
||||
ctx.WriteBuffer(backup_jpeg);
|
||||
rb.Push<u32>(backup_jpeg_size);
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 size = SanitizeJPEGSize(image.GetSize());
|
||||
std::vector<u8> buffer(size);
|
||||
image.ReadBytes(buffer.data(), buffer.size());
|
||||
|
||||
ctx.WriteBuffer(buffer.data(), buffer.size());
|
||||
rb.Push<u32>(size);
|
||||
}
|
||||
|
||||
void GetImageSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
constexpr u32 jpeg_size = 107;
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(jpeg_size);
|
||||
|
||||
const FileUtil::IOFile image(GetImagePath(user_id), "rb");
|
||||
|
||||
if (!image.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC,
|
||||
"Failed to load user provided image! Falling back to built-in backup...");
|
||||
rb.Push<u32>(backup_jpeg_size);
|
||||
} else {
|
||||
rb.Push<u32>(SanitizeJPEGSize(image.GetSize()));
|
||||
}
|
||||
}
|
||||
|
||||
const ProfileManager& profile_manager;
|
||||
@@ -204,6 +242,28 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
}
|
||||
|
||||
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
|
||||
// access to use the network or not by the looks of it
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
if (profile_manager->GetUserCount() != 1) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<u128>(INVALID_UUID);
|
||||
return;
|
||||
}
|
||||
auto user_list = profile_manager->GetAllUsers();
|
||||
if (user_list.empty()) {
|
||||
rb.Push(ResultCode(-1)); // TODO(ogniK): Find the correct error code
|
||||
rb.PushRaw<u128>(INVALID_UUID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the first user we have
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<u128>(profile_manager->GetUser(0)->uuid);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager, const char* name)
|
||||
: ServiceFramework(name), module(std::move(module)),
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
|
||||
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
|
||||
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
|
||||
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
|
||||
@@ -17,7 +17,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
{5, &ACC_SU::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
|
||||
@@ -17,7 +17,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
{5, &ACC_U0::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
|
||||
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
|
||||
|
||||
@@ -17,7 +17,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
{5, &ACC_U1::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
|
||||
@@ -3,41 +3,66 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <random>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
struct UserRaw {
|
||||
UUID uuid;
|
||||
UUID uuid2;
|
||||
u64 timestamp;
|
||||
ProfileUsername username;
|
||||
INSERT_PADDING_BYTES(0x80);
|
||||
};
|
||||
static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
|
||||
|
||||
struct ProfileDataRaw {
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
std::array<UserRaw, MAX_USERS> users;
|
||||
};
|
||||
static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
|
||||
|
||||
// TODO(ogniK): Get actual error codes
|
||||
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
|
||||
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
|
||||
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
|
||||
|
||||
const UUID& UUID::Generate() {
|
||||
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
|
||||
|
||||
UUID UUID::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
uuid[0] = distribution(gen);
|
||||
uuid[1] = distribution(gen);
|
||||
return *this;
|
||||
return UUID{distribution(gen), distribution(gen)};
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager() {
|
||||
// TODO(ogniK): Create the default user we have for now until loading/saving users is added
|
||||
auto user_uuid = UUID{1, 0};
|
||||
ASSERT(CreateNewUser(user_uuid, Settings::values.username).IsSuccess());
|
||||
OpenUser(user_uuid);
|
||||
ParseUserSaveFile();
|
||||
|
||||
if (user_count == 0)
|
||||
CreateNewUser(UUID::Generate(), "yuzu");
|
||||
|
||||
auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
|
||||
if (UserExistsIndex(current))
|
||||
current = 0;
|
||||
|
||||
OpenUser(*GetUser(current));
|
||||
}
|
||||
|
||||
ProfileManager::~ProfileManager() = default;
|
||||
ProfileManager::~ProfileManager() {
|
||||
WriteUserSaveFile();
|
||||
}
|
||||
|
||||
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
|
||||
/// internal management of the users profiles
|
||||
boost::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
|
||||
std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
|
||||
if (user_count >= MAX_USERS) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
profiles[user_count] = user;
|
||||
profiles[user_count] = profile;
|
||||
return user_count++;
|
||||
}
|
||||
|
||||
@@ -56,7 +81,7 @@ bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
|
||||
|
||||
/// Helper function to register a user to the system
|
||||
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
|
||||
if (AddToProfiles(user) == boost::none) {
|
||||
if (!AddToProfiles(user)) {
|
||||
return ERROR_TOO_MANY_USERS;
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
@@ -101,31 +126,40 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
|
||||
return CreateNewUser(uuid, username_output);
|
||||
}
|
||||
|
||||
std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
|
||||
if (index >= MAX_USERS) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return profiles[index].user_uuid;
|
||||
}
|
||||
|
||||
/// Returns a users profile index based on their user id.
|
||||
boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
|
||||
std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
|
||||
if (!uuid) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
auto iter = std::find_if(profiles.begin(), profiles.end(),
|
||||
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
|
||||
|
||||
const auto iter = std::find_if(profiles.begin(), profiles.end(),
|
||||
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
|
||||
if (iter == profiles.end()) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
|
||||
}
|
||||
|
||||
/// Returns a users profile index based on their profile
|
||||
boost::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
|
||||
std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
|
||||
return GetUserIndex(user.user_uuid);
|
||||
}
|
||||
|
||||
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
|
||||
bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
|
||||
ProfileBase& profile) const {
|
||||
if (index == boost::none || index >= MAX_USERS) {
|
||||
bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
|
||||
if (!index || index >= MAX_USERS) {
|
||||
return false;
|
||||
}
|
||||
const auto& prof_info = profiles[index.get()];
|
||||
const auto& prof_info = profiles[*index];
|
||||
profile.user_uuid = prof_info.user_uuid;
|
||||
profile.username = prof_info.username;
|
||||
profile.timestamp = prof_info.creation_time;
|
||||
@@ -134,7 +168,7 @@ bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
|
||||
|
||||
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
|
||||
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
const auto idx = GetUserIndex(uuid);
|
||||
return GetProfileBase(idx, profile);
|
||||
}
|
||||
|
||||
@@ -161,26 +195,34 @@ std::size_t ProfileManager::GetOpenUserCount() const {
|
||||
|
||||
/// Checks if a user id exists in our profile manager
|
||||
bool ProfileManager::UserExists(UUID uuid) const {
|
||||
return (GetUserIndex(uuid) != boost::none);
|
||||
return GetUserIndex(uuid).has_value();
|
||||
}
|
||||
|
||||
bool ProfileManager::UserExistsIndex(std::size_t index) const {
|
||||
if (index >= MAX_USERS)
|
||||
return false;
|
||||
return profiles[index].user_uuid.uuid != INVALID_UUID;
|
||||
}
|
||||
|
||||
/// Opens a specific user
|
||||
void ProfileManager::OpenUser(UUID uuid) {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
if (idx == boost::none) {
|
||||
const auto idx = GetUserIndex(uuid);
|
||||
if (!idx) {
|
||||
return;
|
||||
}
|
||||
profiles[idx.get()].is_open = true;
|
||||
|
||||
profiles[*idx].is_open = true;
|
||||
last_opened_user = uuid;
|
||||
}
|
||||
|
||||
/// Closes a specific user
|
||||
void ProfileManager::CloseUser(UUID uuid) {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
if (idx == boost::none) {
|
||||
const auto idx = GetUserIndex(uuid);
|
||||
if (!idx) {
|
||||
return;
|
||||
}
|
||||
profiles[idx.get()].is_open = false;
|
||||
|
||||
profiles[*idx].is_open = false;
|
||||
}
|
||||
|
||||
/// Gets all valid user ids on the system
|
||||
@@ -210,10 +252,10 @@ UUID ProfileManager::GetLastOpenedUser() const {
|
||||
}
|
||||
|
||||
/// Return the users profile base and the unknown arbitary data.
|
||||
bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
|
||||
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
|
||||
ProfileData& data) const {
|
||||
if (GetProfileBase(index, profile)) {
|
||||
data = profiles[index.get()].data;
|
||||
data = profiles[*index].data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -222,7 +264,7 @@ bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, P
|
||||
/// Return the users profile base and the unknown arbitary data.
|
||||
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
|
||||
ProfileData& data) const {
|
||||
auto idx = GetUserIndex(uuid);
|
||||
const auto idx = GetUserIndex(uuid);
|
||||
return GetProfileBaseAndData(idx, profile, data);
|
||||
}
|
||||
|
||||
@@ -239,4 +281,96 @@ bool ProfileManager::CanSystemRegisterUser() const {
|
||||
// emulate qlaunch. Update this to dynamically change.
|
||||
}
|
||||
|
||||
bool ProfileManager::RemoveUser(UUID uuid) {
|
||||
const auto index = GetUserIndex(uuid);
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
profiles[*index] = ProfileInfo{};
|
||||
std::stable_partition(profiles.begin(), profiles.end(),
|
||||
[](const ProfileInfo& profile) { return profile.user_uuid; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
|
||||
const auto index = GetUserIndex(uuid);
|
||||
if (!index || profile_new.user_uuid == UUID(INVALID_UUID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& profile = profiles[*index];
|
||||
profile.user_uuid = profile_new.user_uuid;
|
||||
profile.username = profile_new.username;
|
||||
profile.creation_time = profile_new.timestamp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::ParseUserSaveFile() {
|
||||
FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
|
||||
"rb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
|
||||
"user 'yuzu' with random UUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileDataRaw data;
|
||||
if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) {
|
||||
LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
|
||||
"'yuzu' with random UUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < MAX_USERS; ++i) {
|
||||
const auto& user = data.users[i];
|
||||
|
||||
if (user.uuid != UUID(INVALID_UUID))
|
||||
AddUser({user.uuid, user.username, user.timestamp, {}, false});
|
||||
}
|
||||
|
||||
std::stable_partition(profiles.begin(), profiles.end(),
|
||||
[](const ProfileInfo& profile) { return profile.user_uuid; });
|
||||
}
|
||||
|
||||
void ProfileManager::WriteUserSaveFile() {
|
||||
ProfileDataRaw raw{};
|
||||
|
||||
for (std::size_t i = 0; i < MAX_USERS; ++i) {
|
||||
raw.users[i].username = profiles[i].username;
|
||||
raw.users[i].uuid2 = profiles[i].user_uuid;
|
||||
raw.users[i].uuid = profiles[i].user_uuid;
|
||||
raw.users[i].timestamp = profiles[i].creation_time;
|
||||
}
|
||||
|
||||
const auto raw_path =
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
|
||||
if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
|
||||
FileUtil::Delete(raw_path);
|
||||
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
|
||||
|
||||
if (!FileUtil::CreateFullPath(path)) {
|
||||
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
|
||||
"nand/system/save/8000000000000010/su/avators to mitigate this "
|
||||
"issue.");
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtil::IOFile save(path, "wb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
|
||||
"made in current session will be saved.");
|
||||
return;
|
||||
}
|
||||
|
||||
save.Resize(sizeof(ProfileDataRaw));
|
||||
save.WriteBytes(&raw, sizeof(ProfileDataRaw));
|
||||
}
|
||||
|
||||
}; // namespace Service::Account
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "boost/optional.hpp"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -36,7 +36,7 @@ struct UUID {
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate uuids based on RFC-4122
|
||||
const UUID& Generate();
|
||||
static UUID Generate();
|
||||
|
||||
// Set the UUID to {0,0} to be considered an invalid user
|
||||
void Invalidate() {
|
||||
@@ -45,10 +45,20 @@ struct UUID {
|
||||
std::string Format() const {
|
||||
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
|
||||
}
|
||||
|
||||
std::string FormatSwitch() const {
|
||||
std::array<u8, 16> s{};
|
||||
std::memcpy(s.data(), uuid.data(), sizeof(u128));
|
||||
return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
|
||||
":02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
|
||||
s[12], s[13], s[14], s[15]);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
using ProfileUsername = std::array<u8, 0x20>;
|
||||
constexpr std::size_t profile_username_size = 32;
|
||||
using ProfileUsername = std::array<u8, profile_username_size>;
|
||||
using ProfileData = std::array<u8, MAX_DATA>;
|
||||
using UserIDArray = std::array<UUID, MAX_USERS>;
|
||||
|
||||
@@ -81,18 +91,19 @@ static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
|
||||
/// objects
|
||||
class ProfileManager {
|
||||
public:
|
||||
ProfileManager(); // TODO(ogniK): Load from system save
|
||||
ProfileManager();
|
||||
~ProfileManager();
|
||||
|
||||
ResultCode AddUser(const ProfileInfo& user);
|
||||
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(UUID uuid, const std::string& username);
|
||||
boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
|
||||
boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
|
||||
bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const;
|
||||
std::optional<UUID> GetUser(std::size_t index) const;
|
||||
std::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
|
||||
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
|
||||
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
|
||||
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
|
||||
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
|
||||
bool GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
|
||||
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
|
||||
ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
|
||||
@@ -100,6 +111,7 @@ public:
|
||||
std::size_t GetUserCount() const;
|
||||
std::size_t GetOpenUserCount() const;
|
||||
bool UserExists(UUID uuid) const;
|
||||
bool UserExistsIndex(std::size_t index) const;
|
||||
void OpenUser(UUID uuid);
|
||||
void CloseUser(UUID uuid);
|
||||
UserIDArray GetOpenUsers() const;
|
||||
@@ -108,11 +120,17 @@ public:
|
||||
|
||||
bool CanSystemRegisterUser() const;
|
||||
|
||||
bool RemoveUser(UUID uuid);
|
||||
bool SetProfileBase(UUID uuid, const ProfileBase& profile_new);
|
||||
|
||||
private:
|
||||
void ParseUserSaveFile();
|
||||
void WriteUserSaveFile();
|
||||
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
|
||||
bool RemoveProfileAtIndex(std::size_t index);
|
||||
|
||||
std::array<ProfileInfo, MAX_USERS> profiles{};
|
||||
std::size_t user_count = 0;
|
||||
boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
|
||||
bool RemoveProfileAtIndex(std::size_t index);
|
||||
UUID last_opened_user{INVALID_UUID};
|
||||
};
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
@@ -26,6 +28,16 @@
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
|
||||
|
||||
struct LaunchParameters {
|
||||
u32_le magic;
|
||||
u32_le is_account_selected;
|
||||
u128 current_user;
|
||||
INSERT_PADDING_BYTES(0x70);
|
||||
};
|
||||
static_assert(sizeof(LaunchParameters) == 0x88);
|
||||
|
||||
IWindowController::IWindowController() : ServiceFramework("IWindowController") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -724,20 +736,23 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
|
||||
}
|
||||
|
||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
constexpr std::array<u8, 0x88> data{{
|
||||
0xca, 0x97, 0x94, 0xc7, // Magic
|
||||
1, 0, 0, 0, // IsAccountSelected (bool)
|
||||
1, 0, 0, 0, // User Id (word 0)
|
||||
0, 0, 0, 0, // User Id (word 1)
|
||||
0, 0, 0, 0, // User Id (word 2)
|
||||
0, 0, 0, 0 // User Id (word 3)
|
||||
}};
|
||||
LaunchParameters params{};
|
||||
|
||||
std::vector<u8> buffer(data.begin(), data.end());
|
||||
params.magic = POP_LAUNCH_PARAMETER_MAGIC;
|
||||
params.is_account_selected = 1;
|
||||
|
||||
Account::ProfileManager profile_manager{};
|
||||
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
|
||||
ASSERT(uuid);
|
||||
params.current_user = uuid->uuid;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameters));
|
||||
std::memcpy(buffer.data(), ¶ms, buffer.size());
|
||||
|
||||
rb.PushIpcInterface<AM::IStorage>(buffer);
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Service::AOC {
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
|
||||
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
|
||||
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
|
||||
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
}
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs() {
|
||||
@@ -74,7 +74,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
rb.Push<u32>(static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; })));
|
||||
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
|
||||
}
|
||||
|
||||
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -161,7 +161,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
|
||||
std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large");
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
||||
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||
|
||||
@@ -273,8 +273,8 @@ public:
|
||||
{0, &IFileSystem::CreateFile, "CreateFile"},
|
||||
{1, &IFileSystem::DeleteFile, "DeleteFile"},
|
||||
{2, &IFileSystem::CreateDirectory, "CreateDirectory"},
|
||||
{3, nullptr, "DeleteDirectory"},
|
||||
{4, nullptr, "DeleteDirectoryRecursively"},
|
||||
{3, &IFileSystem::DeleteDirectory, "DeleteDirectory"},
|
||||
{4, &IFileSystem::DeleteDirectoryRecursively, "DeleteDirectoryRecursively"},
|
||||
{5, &IFileSystem::RenameFile, "RenameFile"},
|
||||
{6, nullptr, "RenameDirectory"},
|
||||
{7, &IFileSystem::GetEntryType, "GetEntryType"},
|
||||
@@ -329,6 +329,30 @@ public:
|
||||
rb.Push(backend.CreateDirectory(name));
|
||||
}
|
||||
|
||||
void DeleteDirectory(Kernel::HLERequestContext& ctx) {
|
||||
const IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto file_buffer = ctx.ReadBuffer();
|
||||
std::string name = Common::StringFromBuffer(file_buffer);
|
||||
|
||||
LOG_DEBUG(Service_FS, "called directory {}", name);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.DeleteDirectory(name));
|
||||
}
|
||||
|
||||
void DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
|
||||
const IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto file_buffer = ctx.ReadBuffer();
|
||||
std::string name = Common::StringFromBuffer(file_buffer);
|
||||
|
||||
LOG_DEBUG(Service_FS, "called directory {}", name);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.DeleteDirectoryRecursively(name));
|
||||
}
|
||||
|
||||
void RenameFile(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
|
||||
@@ -392,8 +392,10 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
|
||||
}
|
||||
|
||||
void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
|
||||
styleset_changed_event->Signal();
|
||||
hold_type = joy_hold_type;
|
||||
}
|
||||
|
||||
Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
|
||||
return hold_type;
|
||||
}
|
||||
@@ -427,6 +429,9 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
|
||||
// TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
|
||||
// be signalled at least once, and signaled after a new controller is connected?
|
||||
styleset_changed_event->Signal();
|
||||
return styleset_changed_event;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ public:
|
||||
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
|
||||
|
||||
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
|
||||
|
||||
ReloadInputDevices();
|
||||
}
|
||||
|
||||
void ActivateController(HidController controller) {
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/ldr/ldr.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/loader/nro.h"
|
||||
|
||||
namespace Service::LDR {
|
||||
|
||||
@@ -59,16 +63,58 @@ public:
|
||||
explicit RelocatableObject() : ServiceFramework{"ldr:ro"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "LoadNro"},
|
||||
{0, &RelocatableObject::LoadNro, "LoadNro"},
|
||||
{1, nullptr, "UnloadNro"},
|
||||
{2, nullptr, "LoadNrr"},
|
||||
{2, &RelocatableObject::LoadNrr, "LoadNrr"},
|
||||
{3, nullptr, "UnloadNrr"},
|
||||
{4, nullptr, "Initialize"},
|
||||
{4, &RelocatableObject::Initialize, "Initialize"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void LoadNrr(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_LDR, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void LoadNro(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
rp.Skip(2, false);
|
||||
const VAddr nro_addr{rp.Pop<VAddr>()};
|
||||
const u64 nro_size{rp.Pop<u64>()};
|
||||
const VAddr bss_addr{rp.Pop<VAddr>()};
|
||||
const u64 bss_size{rp.Pop<u64>()};
|
||||
|
||||
// Read NRO data from memory
|
||||
std::vector<u8> nro_data(nro_size);
|
||||
Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
|
||||
|
||||
// Load NRO as new executable module
|
||||
const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)};
|
||||
Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr);
|
||||
|
||||
// TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party.
|
||||
// It is currently missing:
|
||||
// - Signature checks with LoadNRR
|
||||
// - Checking if a module has already been loaded
|
||||
// - Using/validating BSS, etc. params (these are used from NRO header instead)
|
||||
// - Error checking
|
||||
// - ...Probably other things
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(addr);
|
||||
LOG_WARNING(Service_LDR, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Initialize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_LDR, "(STUBBED) called");
|
||||
}
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm) {
|
||||
|
||||
@@ -328,13 +328,15 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushIpcInterface<IUser>(*this);
|
||||
}
|
||||
|
||||
void Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
|
||||
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
if (buffer.size() < sizeof(AmiiboFile)) {
|
||||
return; // Failed to load file
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
|
||||
nfc_tag_load->Signal();
|
||||
return true;
|
||||
}
|
||||
const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const {
|
||||
return nfc_tag_load;
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size");
|
||||
|
||||
void CreateUserInterface(Kernel::HLERequestContext& ctx);
|
||||
void LoadAmiibo(const std::vector<u8>& buffer);
|
||||
bool LoadAmiibo(const std::vector<u8>& buffer);
|
||||
const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const;
|
||||
const AmiiboFile& GetAmiiboBuffer() const;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
|
||||
boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
|
||||
std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
|
||||
// Only consider free buffers. Buffers become free once again after they've been Acquired
|
||||
// and Released by the compositor, see the NVFlinger::Compose method.
|
||||
@@ -44,7 +44,7 @@ boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
|
||||
});
|
||||
|
||||
if (itr == queue.end()) {
|
||||
return boost::none;
|
||||
return {};
|
||||
}
|
||||
|
||||
itr->status = Buffer::Status::Dequeued;
|
||||
@@ -70,12 +70,12 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
|
||||
itr->crop_rect = crop_rect;
|
||||
}
|
||||
|
||||
boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
|
||||
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
|
||||
return buffer.status == Buffer::Status::Queued;
|
||||
});
|
||||
if (itr == queue.end())
|
||||
return boost::none;
|
||||
return {};
|
||||
itr->status = Buffer::Status::Acquired;
|
||||
return *itr;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/swap.h"
|
||||
@@ -57,9 +58,9 @@ public:
|
||||
/// Rotate source image 90 degrees clockwise
|
||||
Rotate90 = 0x04,
|
||||
/// Rotate source image 180 degrees
|
||||
Roate180 = 0x03,
|
||||
Rotate180 = 0x03,
|
||||
/// Rotate source image 270 degrees clockwise
|
||||
Roate270 = 0x07,
|
||||
Rotate270 = 0x07,
|
||||
};
|
||||
|
||||
struct Buffer {
|
||||
@@ -73,11 +74,11 @@ public:
|
||||
};
|
||||
|
||||
void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
|
||||
boost::optional<u32> DequeueBuffer(u32 width, u32 height);
|
||||
std::optional<u32> DequeueBuffer(u32 width, u32 height);
|
||||
const IGBPBuffer& RequestBuffer(u32 slot) const;
|
||||
void QueueBuffer(u32 slot, BufferTransformFlags transform,
|
||||
const MathUtil::Rectangle<int>& crop_rect);
|
||||
boost::optional<const Buffer&> AcquireBuffer();
|
||||
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
|
||||
void ReleaseBuffer(u32 slot);
|
||||
u32 Query(QueryType type);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
@@ -134,7 +134,7 @@ void NVFlinger::Compose() {
|
||||
|
||||
MicroProfileFlip();
|
||||
|
||||
if (buffer == boost::none) {
|
||||
if (!buffer) {
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
|
||||
// There was no queued buffer to draw, render previous frame
|
||||
@@ -143,7 +143,7 @@ void NVFlinger::Compose() {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& igbp_buffer = buffer->igbp_buffer;
|
||||
auto& igbp_buffer = buffer->get().igbp_buffer;
|
||||
|
||||
// Now send the buffer to the GPU for drawing.
|
||||
// TODO(Subv): Support more than just disp0. The display device selection is probably based
|
||||
@@ -152,10 +152,10 @@ void NVFlinger::Compose() {
|
||||
ASSERT(nvdisp);
|
||||
|
||||
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
|
||||
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform,
|
||||
buffer->crop_rect);
|
||||
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
|
||||
buffer->get().transform, buffer->get().crop_rect);
|
||||
|
||||
buffer_queue->ReleaseBuffer(buffer->slot);
|
||||
buffer_queue->ReleaseBuffer(buffer->get().slot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,15 +67,15 @@ public:
|
||||
explicit IClientEpSession() : ServiceFramework{"IClientEpSession"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown1"},
|
||||
{1, nullptr, "Unknown2"},
|
||||
{2, nullptr, "Unknown3"},
|
||||
{3, nullptr, "Unknown4"},
|
||||
{0, nullptr, "Open"},
|
||||
{1, nullptr, "Close"},
|
||||
{2, nullptr, "Unknown1"},
|
||||
{3, nullptr, "Populate"},
|
||||
{4, nullptr, "PostBufferAsync"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{6, nullptr, "Unknown6"},
|
||||
{7, nullptr, "Unknown7"},
|
||||
{8, nullptr, "Unknown8"},
|
||||
{5, nullptr, "GetXferReport"},
|
||||
{6, nullptr, "Unknown2"},
|
||||
{7, nullptr, "Unknown3"},
|
||||
{8, nullptr, "Unknown4"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -89,15 +89,15 @@ public:
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown1"},
|
||||
{1, nullptr, "Unknown2"},
|
||||
{2, nullptr, "Unknown3"},
|
||||
{3, nullptr, "Unknown4"},
|
||||
{4, nullptr, "Unknown5"},
|
||||
{1, nullptr, "SetInterface"},
|
||||
{2, nullptr, "GetInterface"},
|
||||
{3, nullptr, "GetAlternateInterface"},
|
||||
{4, nullptr, "GetCurrentFrame"},
|
||||
{5, nullptr, "CtrlXferAsync"},
|
||||
{6, nullptr, "Unknown6"},
|
||||
{6, nullptr, "Unknown2"},
|
||||
{7, nullptr, "GetCtrlXferReport"},
|
||||
{8, nullptr, "Unknown7"},
|
||||
{9, nullptr, "GetClientEpSession"},
|
||||
{8, nullptr, "ResetDevice"},
|
||||
{9, nullptr, "OpenUsbEp"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -111,13 +111,14 @@ public:
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "BindClientProcess"},
|
||||
{1, nullptr, "Unknown1"},
|
||||
{2, nullptr, "Unknown2"},
|
||||
{3, nullptr, "Unknown3"},
|
||||
{4, nullptr, "Unknown4"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{1, nullptr, "QueryAllInterfaces"},
|
||||
{2, nullptr, "QueryAvailableInterfaces"},
|
||||
{3, nullptr, "QueryAcquiredInterfaces"},
|
||||
{4, nullptr, "CreateInterfaceAvailableEvent"},
|
||||
{5, nullptr, "DestroyInterfaceAvailableEvent"},
|
||||
{6, nullptr, "GetInterfaceStateChangeEvent"},
|
||||
{7, nullptr, "GetClientIfSession"},
|
||||
{7, nullptr, "AcquireUsbIf"},
|
||||
{8, nullptr, "Unknown1"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -131,11 +132,11 @@ public:
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "BindNoticeEvent"},
|
||||
{1, nullptr, "Unknown1"},
|
||||
{1, nullptr, "UnbindNoticeEvent"},
|
||||
{2, nullptr, "GetStatus"},
|
||||
{3, nullptr, "GetNotice"},
|
||||
{4, nullptr, "Unknown2"},
|
||||
{5, nullptr, "Unknown3"},
|
||||
{4, nullptr, "EnablePowerRequestNotice"},
|
||||
{5, nullptr, "DisablePowerRequestNotice"},
|
||||
{6, nullptr, "ReplyPowerRequest"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
@@ -506,9 +507,9 @@ private:
|
||||
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
|
||||
const u32 width{request.data.width};
|
||||
const u32 height{request.data.height};
|
||||
boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
|
||||
std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
|
||||
|
||||
if (slot != boost::none) {
|
||||
if (slot) {
|
||||
// Buffer is available
|
||||
IGBPDequeueBufferResponseParcel response{*slot};
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
@@ -520,7 +521,7 @@ private:
|
||||
Kernel::ThreadWakeupReason reason) {
|
||||
// Repeat TransactParcel DequeueBuffer when a buffer is available
|
||||
auto buffer_queue = nv_flinger->GetBufferQueue(id);
|
||||
boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
|
||||
std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
|
||||
IGBPDequeueBufferResponseParcel response{*slot};
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
@@ -145,7 +146,7 @@ public:
|
||||
* information.
|
||||
* @returns A pair with the optional system mode, and and the status.
|
||||
*/
|
||||
virtual std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() {
|
||||
virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
|
||||
// 96MB allocated to the application.
|
||||
return std::make_pair(2, ResultStatus::Success);
|
||||
}
|
||||
|
||||
@@ -127,18 +127,23 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
|
||||
// Read NSO header
|
||||
NroHeader nro_header{};
|
||||
if (sizeof(NroHeader) != file.ReadObject(&nro_header)) {
|
||||
/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name,
|
||||
VAddr load_base) {
|
||||
|
||||
if (data.size() < sizeof(NroHeader)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read NSO header
|
||||
NroHeader nro_header{};
|
||||
std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
|
||||
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Build program image
|
||||
std::vector<u8> program_image = file.ReadBytes(PageAlignSize(nro_header.file_size));
|
||||
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
|
||||
std::memcpy(program_image.data(), data.data(), program_image.size());
|
||||
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
|
||||
return {};
|
||||
}
|
||||
@@ -182,11 +187,15 @@ bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
|
||||
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
|
||||
GDBStub::RegisterModule(name, load_base, load_base);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
|
||||
return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base);
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
@@ -40,6 +41,8 @@ public:
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
bool IsRomFSUpdatable() const override;
|
||||
|
||||
static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base);
|
||||
|
||||
private:
|
||||
bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
|
||||
|
||||
|
||||
@@ -36,6 +36,16 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
|
||||
|
||||
if (nsp->IsExtractedType()) {
|
||||
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
|
||||
} else {
|
||||
if (title_id == 0)
|
||||
return;
|
||||
|
||||
secondary_loader = std::make_unique<AppLoader_NCA>(
|
||||
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
|
||||
}
|
||||
}
|
||||
|
||||
AppLoader_NSP::~AppLoader_NSP() = default;
|
||||
@@ -67,26 +77,19 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
|
||||
if (nsp->IsExtractedType()) {
|
||||
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
|
||||
} else {
|
||||
if (title_id == 0)
|
||||
return ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
if (title_id == 0)
|
||||
return ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
|
||||
secondary_loader = std::make_unique<AppLoader_NCA>(
|
||||
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
|
||||
if (nsp->GetStatus() != ResultStatus::Success)
|
||||
return nsp->GetStatus();
|
||||
|
||||
if (nsp->GetStatus() != ResultStatus::Success)
|
||||
return nsp->GetStatus();
|
||||
if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
|
||||
return nsp->GetProgramStatus(title_id);
|
||||
|
||||
if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
|
||||
return nsp->GetProgramStatus(title_id);
|
||||
|
||||
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
|
||||
if (!Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
return ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
}
|
||||
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
|
||||
if (!Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
return ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
}
|
||||
|
||||
const auto result = secondary_loader->Load(process);
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
@@ -18,19 +19,19 @@ namespace Memory {
|
||||
*
|
||||
* A hook may be mapped to multiple regions of memory.
|
||||
*
|
||||
* If a boost::none or false is returned from a function, the read/write request is passed through
|
||||
* If a std::nullopt or false is returned from a function, the read/write request is passed through
|
||||
* to the underlying memory region.
|
||||
*/
|
||||
class MemoryHook {
|
||||
public:
|
||||
virtual ~MemoryHook();
|
||||
|
||||
virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;
|
||||
virtual std::optional<bool> IsValidAddress(VAddr addr) = 0;
|
||||
|
||||
virtual boost::optional<u8> Read8(VAddr addr) = 0;
|
||||
virtual boost::optional<u16> Read16(VAddr addr) = 0;
|
||||
virtual boost::optional<u32> Read32(VAddr addr) = 0;
|
||||
virtual boost::optional<u64> Read64(VAddr addr) = 0;
|
||||
virtual std::optional<u8> Read8(VAddr addr) = 0;
|
||||
virtual std::optional<u16> Read16(VAddr addr) = 0;
|
||||
virtual std::optional<u32> Read32(VAddr addr) = 0;
|
||||
virtual std::optional<u64> Read64(VAddr addr) = 0;
|
||||
|
||||
virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
|
||||
|
||||
|
||||
@@ -74,10 +74,6 @@ double PerfStats::GetLastFrameTimeScale() {
|
||||
}
|
||||
|
||||
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
|
||||
// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
|
||||
// values increase the time needed to recover and limit framerate again after spikes.
|
||||
constexpr microseconds MAX_LAG_TIME_US = 25000us;
|
||||
|
||||
if (!Settings::values.use_frame_limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ struct Values {
|
||||
// System
|
||||
bool use_docked_mode;
|
||||
bool enable_nfc;
|
||||
std::string username;
|
||||
int current_user;
|
||||
int language_index;
|
||||
|
||||
// Controls
|
||||
|
||||
@@ -184,4 +184,13 @@ TelemetrySession::~TelemetrySession() {
|
||||
backend = nullptr;
|
||||
}
|
||||
|
||||
bool TelemetrySession::SubmitTestcase() {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
field_collection.Accept(*backend);
|
||||
return backend->SubmitTestcase();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -31,6 +31,12 @@ public:
|
||||
field_collection.AddField(type, name, std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a Testcase.
|
||||
* @returns A bool indicating whether the submission succeeded
|
||||
*/
|
||||
bool SubmitTestcase();
|
||||
|
||||
private:
|
||||
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
|
||||
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
|
||||
|
||||
@@ -64,11 +64,11 @@ void TestEnvironment::ClearWriteRecords() {
|
||||
|
||||
TestEnvironment::TestMemory::~TestMemory() {}
|
||||
|
||||
boost::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
|
||||
std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
|
||||
std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
|
||||
const auto iter = data.find(addr);
|
||||
|
||||
if (iter == data.end()) {
|
||||
@@ -79,15 +79,15 @@ boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
boost::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
|
||||
std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
|
||||
return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
|
||||
}
|
||||
|
||||
boost::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
|
||||
std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
|
||||
return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
|
||||
}
|
||||
|
||||
boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
|
||||
std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
|
||||
return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,12 +64,12 @@ private:
|
||||
|
||||
~TestMemory() override;
|
||||
|
||||
boost::optional<bool> IsValidAddress(VAddr addr) override;
|
||||
std::optional<bool> IsValidAddress(VAddr addr) override;
|
||||
|
||||
boost::optional<u8> Read8(VAddr addr) override;
|
||||
boost::optional<u16> Read16(VAddr addr) override;
|
||||
boost::optional<u32> Read32(VAddr addr) override;
|
||||
boost::optional<u64> Read64(VAddr addr) override;
|
||||
std::optional<u8> Read8(VAddr addr) override;
|
||||
std::optional<u16> Read16(VAddr addr) override;
|
||||
std::optional<u32> Read32(VAddr addr) override;
|
||||
std::optional<u64> Read64(VAddr addr) override;
|
||||
|
||||
bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
renderer_opengl/gl_rasterizer_cache.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
renderer_opengl/gl_resource_manager.h
|
||||
renderer_opengl/gl_shader_cache.cpp
|
||||
renderer_opengl/gl_shader_cache.h
|
||||
@@ -51,6 +52,10 @@ add_library(video_core STATIC
|
||||
renderer_opengl/maxwell_to_gl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
renderer_opengl/renderer_opengl.h
|
||||
renderer_opengl/utils.cpp
|
||||
renderer_opengl/utils.h
|
||||
surface.cpp
|
||||
surface.h
|
||||
textures/astc.cpp
|
||||
textures/astc.h
|
||||
textures/decoders.cpp
|
||||
|
||||
@@ -81,7 +81,7 @@ void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) {
|
||||
for (auto entry : commands) {
|
||||
Tegra::GPUVAddr address = entry.Address();
|
||||
u32 size = entry.sz;
|
||||
const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
|
||||
const std::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
|
||||
VAddr current_addr = *head_address;
|
||||
while (current_addr < *head_address + size * sizeof(CommandHeader)) {
|
||||
const CommandHeader header = {Memory::Read32(current_addr)};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -19,21 +20,56 @@ namespace Tegra::Engines {
|
||||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
|
||||
Maxwell3D::Maxwell3D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
|
||||
: memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {}
|
||||
: memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {
|
||||
InitializeRegisterDefaults();
|
||||
}
|
||||
|
||||
void Maxwell3D::InitializeRegisterDefaults() {
|
||||
// Initializes registers to their default values - what games expect them to be at boot. This is
|
||||
// for certain registers that may not be explicitly set by games.
|
||||
|
||||
// Reset all registers to zero
|
||||
std::memset(®s, 0, sizeof(regs));
|
||||
|
||||
// Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is
|
||||
// needed for ARMS.
|
||||
for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) {
|
||||
regs.viewport[viewport].depth_range_near = 0.0f;
|
||||
regs.viewport[viewport].depth_range_far = 1.0f;
|
||||
}
|
||||
// Doom and Bomberman seems to use the uninitialized registers and just enable blend
|
||||
// so initialize blend registers with sane values
|
||||
regs.blend.equation_rgb = Regs::Blend::Equation::Add;
|
||||
regs.blend.factor_source_rgb = Regs::Blend::Factor::One;
|
||||
regs.blend.factor_dest_rgb = Regs::Blend::Factor::Zero;
|
||||
regs.blend.equation_a = Regs::Blend::Equation::Add;
|
||||
regs.blend.factor_source_a = Regs::Blend::Factor::One;
|
||||
regs.blend.factor_dest_a = Regs::Blend::Factor::Zero;
|
||||
for (std::size_t blend_index = 0; blend_index < Regs::NumRenderTargets; blend_index++) {
|
||||
regs.independent_blend[blend_index].equation_rgb = Regs::Blend::Equation::Add;
|
||||
regs.independent_blend[blend_index].factor_source_rgb = Regs::Blend::Factor::One;
|
||||
regs.independent_blend[blend_index].factor_dest_rgb = Regs::Blend::Factor::Zero;
|
||||
regs.independent_blend[blend_index].equation_a = Regs::Blend::Equation::Add;
|
||||
regs.independent_blend[blend_index].factor_source_a = Regs::Blend::Factor::One;
|
||||
regs.independent_blend[blend_index].factor_dest_a = Regs::Blend::Factor::Zero;
|
||||
}
|
||||
}
|
||||
|
||||
void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
|
||||
// Reset the current macro.
|
||||
executing_macro = 0;
|
||||
|
||||
// The requested macro must have been uploaded already.
|
||||
auto macro_code = uploaded_macros.find(method);
|
||||
if (macro_code == uploaded_macros.end()) {
|
||||
LOG_ERROR(HW_GPU, "Macro {:04X} was not uploaded", method);
|
||||
// Lookup the macro offset
|
||||
const u32 entry{(method - MacroRegistersStart) >> 1};
|
||||
const auto& search{macro_offsets.find(entry)};
|
||||
if (search == macro_offsets.end()) {
|
||||
LOG_CRITICAL(HW_GPU, "macro not found for method 0x{:X}!", method);
|
||||
UNREACHABLE();
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the current macro.
|
||||
macro_interpreter.Execute(macro_code->second, std::move(parameters));
|
||||
macro_interpreter.Execute(search->second, std::move(parameters));
|
||||
}
|
||||
|
||||
void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
@@ -79,6 +115,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
ProcessMacroUpload(value);
|
||||
break;
|
||||
}
|
||||
case MAXWELL3D_REG_INDEX(macros.bind): {
|
||||
ProcessMacroBind(value);
|
||||
break;
|
||||
}
|
||||
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
|
||||
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
|
||||
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
|
||||
@@ -140,22 +180,25 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroUpload(u32 data) {
|
||||
// Store the uploaded macro code to interpret them when they're called.
|
||||
auto& macro = uploaded_macros[regs.macros.entry * 2 + MacroRegistersStart];
|
||||
macro.push_back(data);
|
||||
ASSERT_MSG(regs.macros.upload_address < macro_memory.size(),
|
||||
"upload_address exceeded macro_memory size!");
|
||||
macro_memory[regs.macros.upload_address++] = data;
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroBind(u32 data) {
|
||||
macro_offsets[regs.macros.entry] = data;
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessQueryGet() {
|
||||
GPUVAddr sequence_address = regs.query.QueryAddress();
|
||||
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
|
||||
// VAddr before writing.
|
||||
boost::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
|
||||
std::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
|
||||
|
||||
// TODO(Subv): Support the other query units.
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
"Units other than CROP are unimplemented");
|
||||
|
||||
u32 value = Memory::Read32(*address);
|
||||
u64 result = 0;
|
||||
|
||||
// TODO(Subv): Support the other query variables
|
||||
@@ -268,7 +311,7 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
// Don't allow writing past the end of the buffer.
|
||||
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
|
||||
|
||||
boost::optional<VAddr> address =
|
||||
std::optional<VAddr> address =
|
||||
memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
|
||||
|
||||
Memory::Write32(*address, value);
|
||||
@@ -281,7 +324,7 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
GPUVAddr tic_base_address = regs.tic.TICAddress();
|
||||
|
||||
GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
|
||||
boost::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
|
||||
std::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
@@ -305,7 +348,7 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
|
||||
|
||||
GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
|
||||
boost::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
|
||||
std::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
|
||||
|
||||
Texture::TSCEntry tsc_entry;
|
||||
Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
@@ -369,7 +412,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
|
||||
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
|
||||
|
||||
boost::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
|
||||
std::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
|
||||
Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
|
||||
@@ -462,6 +462,16 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct ColorMask {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 4, u32> R;
|
||||
BitField<4, 4, u32> G;
|
||||
BitField<8, 4, u32> B;
|
||||
BitField<12, 4, u32> A;
|
||||
};
|
||||
};
|
||||
|
||||
bool IsShaderConfigEnabled(std::size_t index) const {
|
||||
// The VertexB is always enabled.
|
||||
if (index == static_cast<std::size_t>(Regs::ShaderProgram::VertexB)) {
|
||||
@@ -475,12 +485,13 @@ public:
|
||||
INSERT_PADDING_WORDS(0x45);
|
||||
|
||||
struct {
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32 upload_address;
|
||||
u32 data;
|
||||
u32 entry;
|
||||
u32 bind;
|
||||
} macros;
|
||||
|
||||
INSERT_PADDING_WORDS(0x189);
|
||||
INSERT_PADDING_WORDS(0x188);
|
||||
|
||||
u32 tfb_enabled;
|
||||
|
||||
@@ -570,7 +581,11 @@ public:
|
||||
u32 stencil_back_mask;
|
||||
u32 stencil_back_func_mask;
|
||||
|
||||
INSERT_PADDING_WORDS(0x13);
|
||||
INSERT_PADDING_WORDS(0xC);
|
||||
|
||||
u32 color_mask_common;
|
||||
|
||||
INSERT_PADDING_WORDS(0x6);
|
||||
|
||||
u32 rt_separate_frag_data;
|
||||
|
||||
@@ -645,8 +660,14 @@ public:
|
||||
ComparisonOp depth_test_func;
|
||||
float alpha_test_ref;
|
||||
ComparisonOp alpha_test_func;
|
||||
|
||||
INSERT_PADDING_WORDS(0x9);
|
||||
u32 draw_tfb_stride;
|
||||
struct {
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
} blend_color;
|
||||
INSERT_PADDING_WORDS(0x4);
|
||||
|
||||
struct {
|
||||
u32 separate_alpha;
|
||||
@@ -723,7 +744,11 @@ public:
|
||||
StencilOp stencil_back_op_zpass;
|
||||
ComparisonOp stencil_back_func_func;
|
||||
|
||||
INSERT_PADDING_WORDS(0x17);
|
||||
INSERT_PADDING_WORDS(0x4);
|
||||
|
||||
u32 framebuffer_srgb;
|
||||
|
||||
INSERT_PADDING_WORDS(0x12);
|
||||
|
||||
union {
|
||||
BitField<2, 1, u32> coord_origin;
|
||||
@@ -751,7 +776,14 @@ public:
|
||||
};
|
||||
} draw;
|
||||
|
||||
INSERT_PADDING_WORDS(0x6B);
|
||||
INSERT_PADDING_WORDS(0xA);
|
||||
|
||||
struct {
|
||||
u32 enabled;
|
||||
u32 index;
|
||||
} primitive_restart;
|
||||
|
||||
INSERT_PADDING_WORDS(0x5F);
|
||||
|
||||
struct {
|
||||
u32 start_addr_high;
|
||||
@@ -829,8 +861,9 @@ public:
|
||||
BitField<6, 4, u32> RT;
|
||||
BitField<10, 11, u32> layer;
|
||||
} clear_buffers;
|
||||
|
||||
INSERT_PADDING_WORDS(0x4B);
|
||||
INSERT_PADDING_WORDS(0xB);
|
||||
std::array<ColorMask, NumRenderTargets> color_mask;
|
||||
INSERT_PADDING_WORDS(0x38);
|
||||
|
||||
struct {
|
||||
u32 query_address_high;
|
||||
@@ -983,10 +1016,25 @@ public:
|
||||
/// Returns the texture information for a specific texture in a specific shader stage.
|
||||
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
|
||||
|
||||
/// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
|
||||
/// we've seen used.
|
||||
using MacroMemory = std::array<u32, 0x40000>;
|
||||
|
||||
/// Gets a reference to macro memory.
|
||||
const MacroMemory& GetMacroMemory() const {
|
||||
return macro_memory;
|
||||
}
|
||||
|
||||
private:
|
||||
void InitializeRegisterDefaults();
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
std::unordered_map<u32, std::vector<u32>> uploaded_macros;
|
||||
/// Start offsets of each macro in macro_memory
|
||||
std::unordered_map<u32, u32> macro_offsets;
|
||||
|
||||
/// Memory for macro code
|
||||
MacroMemory macro_memory;
|
||||
|
||||
/// Macro method that is currently being executed / being fed parameters.
|
||||
u32 executing_macro = 0;
|
||||
@@ -1009,9 +1057,12 @@ private:
|
||||
*/
|
||||
void CallMacroMethod(u32 method, std::vector<u32> parameters);
|
||||
|
||||
/// Handles writes to the macro uploading registers.
|
||||
/// Handles writes to the macro uploading register.
|
||||
void ProcessMacroUpload(u32 data);
|
||||
|
||||
/// Handles writes to the macro bind register.
|
||||
void ProcessMacroBind(u32 data);
|
||||
|
||||
/// Handles a write to the CLEAR_BUFFERS register.
|
||||
void ProcessClearBuffers();
|
||||
|
||||
@@ -1045,6 +1096,7 @@ ASSERT_REG_POSITION(scissor_test, 0x380);
|
||||
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
|
||||
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
|
||||
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
|
||||
ASSERT_REG_POSITION(color_mask_common, 0x3E4);
|
||||
ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
|
||||
@@ -1057,6 +1109,10 @@ ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
|
||||
ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB);
|
||||
ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
|
||||
ASSERT_REG_POSITION(depth_test_func, 0x4C3);
|
||||
ASSERT_REG_POSITION(alpha_test_ref, 0x4C4);
|
||||
ASSERT_REG_POSITION(alpha_test_func, 0x4C5);
|
||||
ASSERT_REG_POSITION(draw_tfb_stride, 0x4C6);
|
||||
ASSERT_REG_POSITION(blend_color, 0x4C7);
|
||||
ASSERT_REG_POSITION(blend, 0x4CF);
|
||||
ASSERT_REG_POSITION(stencil_enable, 0x4E0);
|
||||
ASSERT_REG_POSITION(stencil_front_op_fail, 0x4E1);
|
||||
@@ -1077,14 +1133,17 @@ ASSERT_REG_POSITION(stencil_back_op_fail, 0x566);
|
||||
ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567);
|
||||
ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568);
|
||||
ASSERT_REG_POSITION(stencil_back_func_func, 0x569);
|
||||
ASSERT_REG_POSITION(framebuffer_srgb, 0x56E);
|
||||
ASSERT_REG_POSITION(point_coord_replace, 0x581);
|
||||
ASSERT_REG_POSITION(code_address, 0x582);
|
||||
ASSERT_REG_POSITION(draw, 0x585);
|
||||
ASSERT_REG_POSITION(primitive_restart, 0x591);
|
||||
ASSERT_REG_POSITION(index_array, 0x5F2);
|
||||
ASSERT_REG_POSITION(instanced_arrays, 0x620);
|
||||
ASSERT_REG_POSITION(cull, 0x646);
|
||||
ASSERT_REG_POSITION(logic_op, 0x671);
|
||||
ASSERT_REG_POSITION(clear_buffers, 0x674);
|
||||
ASSERT_REG_POSITION(color_mask, 0x680);
|
||||
ASSERT_REG_POSITION(query, 0x6C0);
|
||||
ASSERT_REG_POSITION(vertex_array[0], 0x700);
|
||||
ASSERT_REG_POSITION(independent_blend, 0x780);
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -79,6 +78,7 @@ union Attribute {
|
||||
constexpr explicit Attribute(u64 value) : value(value) {}
|
||||
|
||||
enum class Index : u64 {
|
||||
PointSize = 6,
|
||||
Position = 7,
|
||||
Attribute_0 = 8,
|
||||
Attribute_31 = 39,
|
||||
@@ -207,6 +207,16 @@ enum class UniformType : u64 {
|
||||
Double = 5,
|
||||
};
|
||||
|
||||
enum class StoreType : u64 {
|
||||
Unsigned8 = 0,
|
||||
Signed8 = 1,
|
||||
Unsigned16 = 2,
|
||||
Signed16 = 3,
|
||||
Bytes32 = 4,
|
||||
Bytes64 = 5,
|
||||
Bytes128 = 6,
|
||||
};
|
||||
|
||||
enum class IMinMaxExchange : u64 {
|
||||
None = 0,
|
||||
XLo = 1,
|
||||
@@ -567,6 +577,10 @@ union Instruction {
|
||||
BitField<55, 1, u64> saturate;
|
||||
} fmul32;
|
||||
|
||||
union {
|
||||
BitField<52, 1, u64> generates_cc;
|
||||
} op_32;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> is_signed;
|
||||
} shift;
|
||||
@@ -746,6 +760,18 @@ union Instruction {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} ld_c;
|
||||
|
||||
union {
|
||||
BitField<48, 3, StoreType> type;
|
||||
} ldst_sl;
|
||||
|
||||
union {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} ld_l;
|
||||
|
||||
union {
|
||||
BitField<44, 2, u64> unknown;
|
||||
} st_l;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
@@ -1208,6 +1234,8 @@ union Instruction {
|
||||
BitField<61, 1, u64> is_b_imm;
|
||||
BitField<60, 1, u64> is_b_gpr;
|
||||
BitField<59, 1, u64> is_c_gpr;
|
||||
BitField<20, 24, s64> smem_imm;
|
||||
BitField<0, 5, ControlCode> flow_control_code;
|
||||
|
||||
Attribute attribute;
|
||||
Sampler sampler;
|
||||
@@ -1231,8 +1259,12 @@ public:
|
||||
BRA,
|
||||
PBK,
|
||||
LD_A,
|
||||
LD_L,
|
||||
LD_S,
|
||||
LD_C,
|
||||
ST_A,
|
||||
ST_L,
|
||||
ST_S,
|
||||
LDG, // Load from global memory
|
||||
STG, // Store in global memory
|
||||
TEX,
|
||||
@@ -1428,7 +1460,7 @@ public:
|
||||
Type type;
|
||||
};
|
||||
|
||||
static boost::optional<const Matcher&> Decode(Instruction instr) {
|
||||
static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) {
|
||||
static const auto table{GetDecodeTable()};
|
||||
|
||||
const auto matches_instruction = [instr](const auto& matcher) {
|
||||
@@ -1436,7 +1468,8 @@ public:
|
||||
};
|
||||
|
||||
auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
|
||||
return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none;
|
||||
return iter != table.end() ? std::optional<std::reference_wrapper<const Matcher>>(*iter)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -1489,8 +1522,12 @@ private:
|
||||
INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
|
||||
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
|
||||
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
|
||||
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
|
||||
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
|
||||
INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
|
||||
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
|
||||
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
|
||||
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
|
||||
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
|
||||
@@ -1626,4 +1663,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Tegra::Shader
|
||||
} // namespace Tegra::Shader
|
||||
|
||||
@@ -96,6 +96,11 @@ struct Header {
|
||||
}
|
||||
} ps;
|
||||
};
|
||||
|
||||
u64 GetLocalMemorySize() {
|
||||
return (common1.shader_local_memory_low_size |
|
||||
(common2.shader_local_memory_high_size << 24));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tegra {
|
||||
|
||||
MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
|
||||
|
||||
void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) {
|
||||
void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) {
|
||||
Reset();
|
||||
registers[1] = parameters[0];
|
||||
this->parameters = std::move(parameters);
|
||||
@@ -19,7 +19,7 @@ void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> pa
|
||||
// Execute the code until we hit an exit condition.
|
||||
bool keep_executing = true;
|
||||
while (keep_executing) {
|
||||
keep_executing = Step(code, false);
|
||||
keep_executing = Step(offset, false);
|
||||
}
|
||||
|
||||
// Assert the the macro used all the input parameters
|
||||
@@ -29,7 +29,7 @@ void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> pa
|
||||
void MacroInterpreter::Reset() {
|
||||
registers = {};
|
||||
pc = 0;
|
||||
delayed_pc = boost::none;
|
||||
delayed_pc = {};
|
||||
method_address.raw = 0;
|
||||
parameters.clear();
|
||||
// The next parameter index starts at 1, because $r1 already has the value of the first
|
||||
@@ -37,17 +37,17 @@ void MacroInterpreter::Reset() {
|
||||
next_parameter_index = 1;
|
||||
}
|
||||
|
||||
bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
|
||||
bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) {
|
||||
u32 base_address = pc;
|
||||
|
||||
Opcode opcode = GetOpcode(code);
|
||||
Opcode opcode = GetOpcode(offset);
|
||||
pc += 4;
|
||||
|
||||
// Update the program counter if we were delayed
|
||||
if (delayed_pc != boost::none) {
|
||||
if (delayed_pc) {
|
||||
ASSERT(is_delay_slot);
|
||||
pc = *delayed_pc;
|
||||
delayed_pc = boost::none;
|
||||
delayed_pc = {};
|
||||
}
|
||||
|
||||
switch (opcode.operation) {
|
||||
@@ -108,7 +108,7 @@ bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
|
||||
|
||||
delayed_pc = base_address + opcode.GetBranchTarget();
|
||||
// Execute one more instruction due to the delay slot.
|
||||
return Step(code, true);
|
||||
return Step(offset, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -121,17 +121,18 @@ bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
|
||||
// Exit has a delay slot, execute the next instruction
|
||||
// Note: Executing an exit during a branch delay slot will cause the instruction at the
|
||||
// branch target to be executed before exiting.
|
||||
Step(code, true);
|
||||
Step(offset, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const {
|
||||
MacroInterpreter::Opcode MacroInterpreter::GetOpcode(u32 offset) const {
|
||||
const auto& macro_memory{maxwell3d.GetMacroMemory()};
|
||||
ASSERT((pc % sizeof(u32)) == 0);
|
||||
ASSERT(pc < code.size() * sizeof(u32));
|
||||
return {code[pc / sizeof(u32)]};
|
||||
ASSERT((pc + offset) < macro_memory.size() * sizeof(u32));
|
||||
return {macro_memory[offset + pc / sizeof(u32)]};
|
||||
}
|
||||
|
||||
u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const {
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -21,10 +22,10 @@ public:
|
||||
|
||||
/**
|
||||
* Executes the macro code with the specified input parameters.
|
||||
* @param code The macro byte code to execute
|
||||
* @param parameters The parameters of the macro
|
||||
* @param offset Offset to start execution at.
|
||||
* @param parameters The parameters of the macro.
|
||||
*/
|
||||
void Execute(const std::vector<u32>& code, std::vector<u32> parameters);
|
||||
void Execute(u32 offset, std::vector<u32> parameters);
|
||||
|
||||
private:
|
||||
enum class Operation : u32 {
|
||||
@@ -109,11 +110,11 @@ private:
|
||||
/**
|
||||
* Executes a single macro instruction located at the current program counter. Returns whether
|
||||
* the interpreter should keep running.
|
||||
* @param code The macro code to execute.
|
||||
* @param offset Offset to start execution at.
|
||||
* @param is_delay_slot Whether the current step is being executed due to a delay slot in a
|
||||
* previous instruction.
|
||||
*/
|
||||
bool Step(const std::vector<u32>& code, bool is_delay_slot);
|
||||
bool Step(u32 offset, bool is_delay_slot);
|
||||
|
||||
/// Calculates the result of an ALU operation. src_a OP src_b;
|
||||
u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const;
|
||||
@@ -126,7 +127,7 @@ private:
|
||||
bool EvaluateBranchCondition(BranchCondition cond, u32 value) const;
|
||||
|
||||
/// Reads an opcode at the current program counter location.
|
||||
Opcode GetOpcode(const std::vector<u32>& code) const;
|
||||
Opcode GetOpcode(u32 offset) const;
|
||||
|
||||
/// Returns the specified register's value. Register 0 is hardcoded to always return 0.
|
||||
u32 GetRegister(u32 register_id) const;
|
||||
@@ -149,7 +150,7 @@ private:
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
|
||||
u32 pc; ///< Current program counter
|
||||
boost::optional<u32>
|
||||
std::optional<u32>
|
||||
delayed_pc; ///< Program counter to execute at after the delay slot is executed.
|
||||
|
||||
static constexpr std::size_t NumMacroRegisters = 8;
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
|
||||
boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
|
||||
ASSERT(gpu_addr);
|
||||
const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, align, PageStatus::Unmapped)};
|
||||
|
||||
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot = PageSlot(*gpu_addr + offset);
|
||||
ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
|
||||
|
||||
for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot{PageSlot(*gpu_addr + offset)};
|
||||
|
||||
ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
|
||||
|
||||
slot = static_cast<u64>(PageStatus::Allocated);
|
||||
}
|
||||
|
||||
@@ -23,10 +26,11 @@ GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
|
||||
}
|
||||
|
||||
GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
|
||||
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot = PageSlot(gpu_addr + offset);
|
||||
for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot{PageSlot(gpu_addr + offset)};
|
||||
|
||||
ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
|
||||
|
||||
slot = static_cast<u64>(PageStatus::Allocated);
|
||||
}
|
||||
|
||||
@@ -34,17 +38,19 @@ GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
|
||||
}
|
||||
|
||||
GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
|
||||
boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
|
||||
ASSERT(gpu_addr);
|
||||
const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, PAGE_SIZE, PageStatus::Unmapped)};
|
||||
|
||||
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot = PageSlot(*gpu_addr + offset);
|
||||
ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
|
||||
|
||||
for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot{PageSlot(*gpu_addr + offset)};
|
||||
|
||||
ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
|
||||
|
||||
slot = cpu_addr + offset;
|
||||
}
|
||||
|
||||
MappedRegion region{cpu_addr, *gpu_addr, size};
|
||||
const MappedRegion region{cpu_addr, *gpu_addr, size};
|
||||
mapped_regions.push_back(region);
|
||||
|
||||
return *gpu_addr;
|
||||
@@ -53,14 +59,31 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
|
||||
GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
|
||||
ASSERT((gpu_addr & PAGE_MASK) == 0);
|
||||
|
||||
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot = PageSlot(gpu_addr + offset);
|
||||
if (PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Allocated)) {
|
||||
// Page has been already mapped. In this case, we must find a new area of memory to use that
|
||||
// is different than the specified one. Super Mario Odyssey hits this scenario when changing
|
||||
// areas, but we do not want to overwrite the old pages.
|
||||
// TODO(bunnei): We need to write a hardware test to confirm this behavior.
|
||||
|
||||
LOG_ERROR(HW_GPU, "attempting to map addr 0x{:016X}, which is not available!", gpu_addr);
|
||||
|
||||
const std::optional<GPUVAddr> new_gpu_addr{
|
||||
FindFreeBlock(gpu_addr, size, PAGE_SIZE, PageStatus::Allocated)};
|
||||
|
||||
ASSERT_MSG(new_gpu_addr, "unable to find available GPU memory");
|
||||
|
||||
gpu_addr = *new_gpu_addr;
|
||||
}
|
||||
|
||||
for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot{PageSlot(gpu_addr + offset)};
|
||||
|
||||
ASSERT(slot == static_cast<u64>(PageStatus::Allocated));
|
||||
|
||||
slot = cpu_addr + offset;
|
||||
}
|
||||
|
||||
MappedRegion region{cpu_addr, gpu_addr, size};
|
||||
const MappedRegion region{cpu_addr, gpu_addr, size};
|
||||
mapped_regions.push_back(region);
|
||||
|
||||
return gpu_addr;
|
||||
@@ -69,11 +92,12 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size)
|
||||
GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
|
||||
ASSERT((gpu_addr & PAGE_MASK) == 0);
|
||||
|
||||
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot = PageSlot(gpu_addr + offset);
|
||||
for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
|
||||
VAddr& slot{PageSlot(gpu_addr + offset)};
|
||||
|
||||
ASSERT(slot != static_cast<u64>(PageStatus::Allocated) &&
|
||||
slot != static_cast<u64>(PageStatus::Unmapped));
|
||||
|
||||
slot = static_cast<u64>(PageStatus::Unmapped);
|
||||
}
|
||||
|
||||
@@ -97,13 +121,14 @@ GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
|
||||
GPUVAddr gpu_addr = 0;
|
||||
u64 free_space = 0;
|
||||
std::optional<GPUVAddr> MemoryManager::FindFreeBlock(GPUVAddr region_start, u64 size, u64 align,
|
||||
PageStatus status) {
|
||||
GPUVAddr gpu_addr{region_start};
|
||||
u64 free_space{};
|
||||
align = (align + PAGE_MASK) & ~PAGE_MASK;
|
||||
|
||||
while (gpu_addr + free_space < MAX_ADDRESS) {
|
||||
if (!IsPageMapped(gpu_addr + free_space)) {
|
||||
if (PageSlot(gpu_addr + free_space) == static_cast<u64>(status)) {
|
||||
free_space += PAGE_SIZE;
|
||||
if (free_space >= size) {
|
||||
return gpu_addr;
|
||||
@@ -118,8 +143,8 @@ boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
|
||||
VAddr base_addr = PageSlot(gpu_addr);
|
||||
std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
|
||||
const VAddr base_addr{PageSlot(gpu_addr)};
|
||||
|
||||
if (base_addr == static_cast<u64>(PageStatus::Allocated) ||
|
||||
base_addr == static_cast<u64>(PageStatus::Unmapped)) {
|
||||
@@ -133,19 +158,15 @@ std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const {
|
||||
std::vector<GPUVAddr> results;
|
||||
for (const auto& region : mapped_regions) {
|
||||
if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) {
|
||||
u64 offset = cpu_addr - region.cpu_addr;
|
||||
const u64 offset{cpu_addr - region.cpu_addr};
|
||||
results.push_back(region.gpu_addr + offset);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
bool MemoryManager::IsPageMapped(GPUVAddr gpu_addr) {
|
||||
return PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Unmapped);
|
||||
}
|
||||
|
||||
VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) {
|
||||
auto& block = page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK];
|
||||
auto& block{page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]};
|
||||
if (!block) {
|
||||
block = std::make_unique<PageBlock>();
|
||||
block->fill(static_cast<VAddr>(PageStatus::Unmapped));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user