Compare commits

..

66 Commits

Author SHA1 Message Date
Lioncash
1833498617 es: Update service function tables
Updated based off information provided by Switchbrew.
2018-10-19 04:12:28 -04:00
Lioncash
a653be3510 audio: Update service function tables
Updated based off information provided by Switchbrew.
2018-10-19 04:09:12 -04:00
Lioncash
2b9fd23058 omm: Update service function tables
Updated based off information provided by Switchbrew.
2018-10-19 04:04:59 -04:00
Lioncash
4f52800822 nifm: Update service function tables
Updated based off information provided by switchbrew.
2018-10-19 04:00:41 -04:00
Lioncash
f6c5a48dd1 hid: Update service function tables
Updated based off information provided by Switchbrew.
2018-10-19 03:59:15 -04:00
Lioncash
d0cda7fe40 nim: Add the basic skeleton of the nim:eca service
Added based off information provided by Switchbrew
2018-10-19 03:46:18 -04:00
Lioncash
d16bafc99d ns: Update service function table
Updated based off information provided by Switchbrew.
2018-10-19 03:41:38 -04:00
Lioncash
a056b284cf set_cal: Update service function table
Updated based on information from Switchbrew.
2018-10-19 03:26:56 -04:00
bunnei
fdd82b754a Merge pull request #1523 from lioncash/lock
svc: Add missing error checks in svcArbitrateLock/svcArbitrateUnlock
2018-10-18 21:50:45 -04:00
bunnei
7f152f2273 Merge pull request #1511 from lioncash/content
content_archive: Minor reorganization changes
2018-10-18 21:48:09 -04:00
bunnei
e5d428cf1e Merge pull request #1521 from ogniK5377/imp-mmu
Used better names for mm:u and fixed a bad stub
2018-10-18 21:46:59 -04:00
bunnei
0291a86f60 Merge pull request #1522 from lioncash/core
core: Remove unnecessary assert in ArmInterface()
2018-10-18 21:46:19 -04:00
Lioncash
4b5ae8dbaa svc: Check for word alignment of addresses within svcArbitrateLock/svcArbitrateUnlock
The kernel itself checks whether or not the provided addresses are word
aligned before continuing, so we should be doing the same.
2018-10-18 13:01:29 -04:00
Lioncash
541e9624eb common: Add function for checking word alignment to alignment.h
This will be used in a following change to svcArbitrateLock() and
svcArbitrateUnlock()
2018-10-18 12:58:27 -04:00
Lioncash
d27f4a4928 common: Move Is4KBAligned() to alignment.h
Aligning on 4KB pages isn't a Switch-specific thing, so this can be
moved to common so it can be used with other things as well.
2018-10-18 12:57:02 -04:00
Lioncash
f109615be0 core: Remove unnecessary assert in ArmInterface()
CpuCore already does this sort of checking, so we can just call that
instead of duplicating the assertions.
2018-10-18 12:07:25 -04:00
bunnei
d4ff4152ad Merge pull request #1510 from lioncash/xci
XCI: Add function for checking the existence of the program NCA
2018-10-18 11:51:47 -04:00
bunnei
6acd8d166a Merge pull request #1505 from FernandoS27/tex-3d
Implemented 3D Textures
2018-10-18 11:50:42 -04:00
David Marcec
98c7a6d622 Used better names for mm:u and fixed bad stub
InitializeWithId needs to return an id which is a u32 which should be a non zero value
2018-10-19 01:09:34 +11:00
bunnei
7dee60d7d2 Merge pull request #1444 from ogniK5377/better-hid
"Better Hid" Rework Part 1
2018-10-17 20:25:17 -04:00
bunnei
77e2d68df7 Merge pull request #1489 from FernandoS27/fix-tlds
shader_decompiler: Fix TLDS
2018-10-17 18:58:38 -04:00
FernandoS27
caaa9914fd Clang format and other fixes 2018-10-17 18:52:11 -04:00
FernandoS27
cb9fdc7a26 Implement Reinterpret Surface, to accurately blit 3D textures 2018-10-17 18:52:10 -04:00
FernandoS27
dbc34db6ce Implement GetInRange in the Rasterizer Cache 2018-10-17 18:52:10 -04:00
FernandoS27
fd9e2d0073 Implement 3D Textures 2018-10-17 18:52:08 -04:00
bunnei
f912a82a8e Merge pull request #1497 from bunnei/flush-framebuffers
Implement flushing in the rasterizer cache
2018-10-17 18:40:34 -04:00
bunnei
6e8752881c Merge pull request #1498 from lioncash/aslr
svc: Clarify enum values for AddressSpaceBaseAddr and AddressSpaceSize in svcGetInfo()
2018-10-17 18:31:51 -04:00
bunnei
86dcf2942b Merge pull request #1496 from FernandoS27/tex-array
Implement Arrays on Tex Instruction
2018-10-17 18:30:44 -04:00
bunnei
afe22d8405 Merge pull request #1509 from DarkLordZach/device-save-data
savedata_factory: Add DeviceSaveData and fix TemporaryStorage
2018-10-17 18:22:05 -04:00
bunnei
648b55c6b9 gl_rasterizer_cache: Remove unnecessary block_depth=1 on Flush. 2018-10-17 18:20:15 -04:00
bunnei
2a035a1f6f gl_rasterizer_cache: Remove unnecessary temporary buffer with unswizzle. 2018-10-17 18:19:35 -04:00
David Marcec
8144fa42bd Using dual joycons as the default controller
Reason for the change is to allow both docked and undocked mode to work
2018-10-18 00:11:47 +11:00
bunnei
43b9494a0f gl_rasterizer_cache: Use AccurateCopySurface for use_accurate_gpu_emulation. 2018-10-16 17:20:49 -04:00
bunnei
ee7c2dbf5a config: Rename use_accurate_framebuffers -> use_accurate_gpu_emulation.
- This will be used as a catch-all for slow-but-accurate GPU emulation paths.
2018-10-16 17:02:29 -04:00
bunnei
91602de7f2 rasterizer_cache: Refactor to support in-order flushing. 2018-10-16 16:51:53 -04:00
Lioncash
871350ae35 content_archive: Simpify assignment of bktr_base_romfs in the constructor
std::move doesn't actually dereference the data, so it doesn't matter
whether or not the type is null.
2018-10-16 13:22:31 -04:00
Lioncash
441b5b97bd content_archive: Make IsValidNCA() an internally linked function
This is only ever used within the cpp file, so it can just be an
internal function.
2018-10-16 13:22:31 -04:00
Lioncash
53e77ffbfe content_archive: Simplify rights ID check
This is the same as using std::any_of with an inverted predicate.
2018-10-16 13:22:31 -04:00
Lioncash
d6604fa765 content_archive: Split loading into separate functions
The constructor alone is pretty large, the reading code should be split
into its consistuent parts to make it easier to understand it without
having to build a mental model of a 300+ line function.
2018-10-16 13:22:28 -04:00
Lioncash
4783ad54de content_archive: Pass and take NCASectionHeader instance by reference
Each header is 512 bytes in size, which is kind of an excessive amount
to copy all the time when it's possible to avoid doing so.
2018-10-16 12:08:17 -04:00
Lioncash
73e1e929a2 XCI: Add function for checking the existence of the program NCA
The only reason the getter existed was to check whether or not the
program NCA was null. Instead, we can just provide a function to query
for the existence of it, instead of exposing it entirely.
2018-10-16 11:36:58 -04:00
bunnei
0e59291310 gl_rasterizer_cache: Refactor to only call GetRegionEnd on surface creation. 2018-10-16 11:31:02 -04:00
bunnei
949d7832fa gl_rasterizer_cache: Only flush when use_accurate_framebuffers is enabled. 2018-10-16 11:31:02 -04:00
bunnei
5f79ba04bd gl_rasterizer_cache: Separate guest and host surface size managment. 2018-10-16 11:31:01 -04:00
bunnei
58be4dff79 gl_rasterizer_cache: Rename GetGLBytesPerPixel to GetBytesPerPixel.
- This does not really have anything to do with OpenGL.
2018-10-16 11:31:01 -04:00
bunnei
cf7b46c101 gl_rasterizer_cache: Remove unused FlushSurface method. 2018-10-16 11:31:01 -04:00
bunnei
3afdfd7bfa gl_rasterizer: Implement flushing. 2018-10-16 11:31:01 -04:00
bunnei
b4e29ccb81 gl_rasterizer_cache: Remove usage of Memory::Read/Write functions.
- These cannot be used within the cache, as they change cache state.
2018-10-16 11:31:00 -04:00
bunnei
4e9683e9d5 gl_rasterizer_cache: Clamp cached surface size to mapped GPU region size. 2018-10-16 11:31:00 -04:00
bunnei
37575eae65 memory_manager: Add a method for querying the end of a mapped GPU region. 2018-10-16 11:31:00 -04:00
bunnei
0be7e82289 rasterizer_cache: Reintroduce method for flushing. 2018-10-16 11:31:00 -04:00
bunnei
9b929e934b gl_rasterizer_cache: Reintroduce code for handling swizzle and flush to guest RAM. 2018-10-16 11:30:59 -04:00
Zach Hilman
9d4e6176eb savedata_factory: Add TemporaryStorage SaveDataSpaceId
Required for TemporaryStorage saves (in addition to SaveDataType)
2018-10-16 10:20:04 -04:00
Zach Hilman
74890cf2da savedata_factory: Add support for DeviceSaveData
Uses the same path as SaveData except with UID 0. Adds a warning if UID is not 0.
2018-10-16 10:19:21 -04:00
Lioncash
90f8474fc1 svc: Clarify enum values for AddressSpaceBaseAddr and AddressSpaceSize in svcGetInfo()
So, one thing that's puzzled me is why the kernel seemed to *not* use
the direct code address ranges in some cases for some service functions.
For example, in svcMapMemory, the full address space width is compared
against for validity, but for svcMapSharedMemory, it compares against
0xFFE00000, 0xFF8000000, and 0x7FF8000000 as upper bounds, and uses
either 0x200000 or 0x8000000 as the lower-bounds as the beginning of the
compared range. Coincidentally, these exact same values are also used in
svcGetInfo, and also when initializing the user address space, so this
is actually retrieving the ASLR extents, not the extents of the address
space in general.
2018-10-14 20:11:16 -04:00
FernandoS27
1d6559fbd3 Implement Arrays on Tex Instruction 2018-10-14 13:31:02 -04:00
FernandoS27
d880b77698 Fix TLDS 2018-10-13 22:14:25 -04:00
David Marcec
98b760c645 Wip 2018-10-12 16:28:00 +11:00
David Marcec
85b0d9a7be Dynamically decide handheld variant based on supported npad id priority
Kirby input still doesn't work, should fix a lot of other games
2018-10-12 02:56:49 +11:00
David Marcec
9e924f2ef2 Added BeginPermitVibrationSession and EndPermitVibrationSession
Used by Mario Party
2018-10-11 00:58:47 +11:00
David Marcec
3d75c9cd7a Added GetLedPattern and HandheldVariant
HandheldVariant is for specific games which expect handheld controllers to be at position 8(kirby), however this doesn't fix all games as some games require handhelds to be at position 0(snipperclips)
2018-10-10 21:38:43 +11:00
David Marcec
46cdeb4549 Kirby expects handheld controllers to be at position 8 2018-10-10 14:21:56 +11:00
David Marcec
f43815af5d Added the ability to "disconnect" individual npads
Fixes arms
2018-10-10 13:15:39 +11:00
David Marcec
b79c294c02 Removed unneeded forward declarations 2018-10-10 13:15:37 +11:00
David Marcec
5857aea94e Addressed changes for better hid 2018-10-10 13:15:37 +11:00
David Marcec
56f35ab262 "Better Hid" rework part 1 2018-10-10 13:15:35 +11:00
65 changed files with 2828 additions and 1317 deletions

View File

@@ -19,4 +19,16 @@ constexpr T AlignDown(T value, std::size_t size) {
return static_cast<T>(value - value % size);
}
template <typename T>
constexpr bool Is4KBAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0xFFF) == 0;
}
template <typename T>
constexpr bool IsWordAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0b11) == 0;
}
} // namespace Common

View File

@@ -236,6 +236,24 @@ add_library(core STATIC
hle/service/hid/irs.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/hid/controllers/controller_base.cpp
hle/service/hid/controllers/controller_base.h
hle/service/hid/controllers/debug_pad.cpp
hle/service/hid/controllers/debug_pad.h
hle/service/hid/controllers/gesture.cpp
hle/service/hid/controllers/gesture.h
hle/service/hid/controllers/keyboard.cpp
hle/service/hid/controllers/keyboard.h
hle/service/hid/controllers/mouse.cpp
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
hle/service/hid/controllers/stubbed.cpp
hle/service/hid/controllers/stubbed.h
hle/service/hid/controllers/touchscreen.cpp
hle/service/hid/controllers/touchscreen.h
hle/service/hid/controllers/xpad.cpp
hle/service/hid/controllers/xpad.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/ldn.cpp

View File

@@ -375,8 +375,7 @@ const Kernel::Process* System::CurrentProcess() const {
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
ASSERT(core_index < NUM_CPU_CORES);
return impl->cpu_cores[core_index]->ArmInterface();
return CpuCore(core_index).ArmInterface();
}
Cpu& System::CpuCore(std::size_t core_index) {

View File

@@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
return secure_partition->GetProgramTitleID();
}
std::shared_ptr<NCA> XCI::GetProgramNCA() const {
return program;
bool XCI::HasProgramNCA() const {
return program != nullptr;
}
VirtualFile XCI::GetProgramNCAFile() const {
if (GetProgramNCA() == nullptr)
if (!HasProgramNCA()) {
return nullptr;
return GetProgramNCA()->GetBaseFile();
}
return program->GetBaseFile();
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {

View File

@@ -80,7 +80,7 @@ public:
u64 GetProgramTitleID() const;
std::shared_ptr<NCA> GetProgramNCA() const;
bool HasProgramNCA() const;
VirtualFile GetProgramNCAFile() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;

View File

@@ -97,11 +97,288 @@ union NCASectionHeader {
};
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
bool IsValidNCA(const NCAHeader& header) {
static bool IsValidNCA(const NCAHeader& header) {
// TODO(DarkLordZach): Add NCA2/NCA0 support.
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
: file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
}
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
LOG_ERROR(Loader, "File reader errored out during header read.");
status = Loader::ResultStatus::ErrorBadNCAHeader;
return;
}
if (!HandlePotentialHeaderDecryption()) {
return;
}
has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(),
[](char c) { return c != '\0'; });
const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
});
if (!ReadSections(sections, bktr_base_ivfc_offset)) {
return;
}
status = Loader::ResultStatus::Success;
}
NCA::~NCA() = default;
bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
status = Loader::ResultStatus::ErrorNCA2;
return false;
}
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0;
return false;
}
return true;
}
bool NCA::HandlePotentialHeaderDecryption() {
if (IsValidNCA(header)) {
return true;
}
if (!CheckSupportedNCA(header)) {
return false;
}
NCAHeader dec_header{};
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
Core::Crypto::Op::Decrypt);
if (IsValidNCA(dec_header)) {
header = dec_header;
encrypted = true;
} else {
if (!CheckSupportedNCA(dec_header)) {
return false;
}
if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
} else {
status = Loader::ResultStatus::ErrorMissingHeaderKey;
}
return false;
}
return true;
}
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
const std::ptrdiff_t number_sections =
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
std::vector<NCASectionHeader> sections(number_sections);
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
if (encrypted) {
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
Core::Crypto::Op::Decrypt);
} else {
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
}
return sections;
}
bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
for (std::size_t i = 0; i < sections.size(); ++i) {
const auto& section = sections[i];
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
return false;
}
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
if (!ReadPFS0Section(section, header.section_tables[i])) {
return false;
}
}
}
return true;
}
bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset) {
const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
const std::size_t romfs_offset = base_offset + ivfc_offset;
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
auto dec = Decrypt(section, raw, romfs_offset);
if (dec == nullptr) {
if (status != Loader::ResultStatus::Success)
return false;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
status = Loader::ResultStatus::ErrorBadBKTRHeader;
return false;
}
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
section.bktr.subsection.offset) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
return false;
}
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
return false;
}
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
RelocationBlock relocation_block{};
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBlock;
return false;
}
SubsectionBlock subsection_block{};
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
return false;
}
std::vector<RelocationBucketRaw> relocation_buckets_raw(
(section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
if (dec->ReadBytes(relocation_buckets_raw.data(),
section.bktr.relocation.size - sizeof(RelocationBlock),
section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
section.bktr.relocation.size - sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
return false;
}
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
if (dec->ReadBytes(subsection_buckets_raw.data(),
section.bktr.subsection.size - sizeof(SubsectionBlock),
section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
return false;
}
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
u32 ctr_low;
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
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;
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return false;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return false;
}
}
}
if (bktr_base_romfs == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
return false;
}
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,
section.raw.section_ctr);
// BKTR applies to entire IVFC, so make an offset version to level 6
files.push_back(std::make_shared<OffsetVfsFile>(
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
} else {
files.push_back(std::move(dec));
}
romfs = files.back();
return true;
}
bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
section.pfs0.pfs0_header_offset;
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
if (dec != nullptr) {
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
dirs.push_back(std::move(npfs));
if (IsDirectoryExeFS(dirs.back()))
exefs = dirs.back();
} else {
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
} else {
if (status != Loader::ResultStatus::Success)
return false;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
return true;
}
u8 NCA::GetCryptoRevision() const {
u8 master_key_id = header.crypto_type;
if (header.crypto_type_2 > master_key_id)
@@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
return titlekey;
}
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
@@ -215,256 +492,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
}
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
: file(std::move(file_)),
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
status = Loader::ResultStatus::Success;
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
}
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
LOG_ERROR(Loader, "File reader errored out during header read.");
status = Loader::ResultStatus::ErrorBadNCAHeader;
return;
}
encrypted = false;
if (!IsValidNCA(header)) {
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
status = Loader::ResultStatus::ErrorNCA2;
return;
}
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0;
return;
}
NCAHeader dec_header{};
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
Core::Crypto::Op::Decrypt);
if (IsValidNCA(dec_header)) {
header = dec_header;
encrypted = true;
} else {
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
status = Loader::ResultStatus::ErrorNCA2;
return;
}
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0;
return;
}
if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
status = Loader::ResultStatus::ErrorMissingHeaderKey;
else
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
return;
}
}
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
[](char c) { return c == '\0'; }) != header.rights_id.end();
const std::ptrdiff_t number_sections =
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
std::vector<NCASectionHeader> sections(number_sections);
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
if (encrypted) {
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
Core::Crypto::Op::Decrypt);
} else {
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
}
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
}) != sections.end();
ivfc_offset = 0;
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
auto section = sections[i];
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
const std::size_t base_offset =
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
const std::size_t romfs_offset = base_offset + ivfc_offset;
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
auto dec = Decrypt(section, raw, romfs_offset);
if (dec == nullptr) {
if (status != Loader::ResultStatus::Success)
return;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return;
}
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
status = Loader::ResultStatus::ErrorBadBKTRHeader;
return;
}
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
section.bktr.subsection.offset) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
return;
}
const u64 size =
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
header.section_tables[i].media_offset);
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
return;
}
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
RelocationBlock relocation_block{};
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBlock;
return;
}
SubsectionBlock subsection_block{};
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
return;
}
std::vector<RelocationBucketRaw> relocation_buckets_raw(
(section.bktr.relocation.size - sizeof(RelocationBlock)) /
sizeof(RelocationBucketRaw));
if (dec->ReadBytes(relocation_buckets_raw.data(),
section.bktr.relocation.size - sizeof(RelocationBlock),
section.bktr.relocation.offset + sizeof(RelocationBlock) -
offset) !=
section.bktr.relocation.size - sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
return;
}
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
(section.bktr.subsection.size - sizeof(SubsectionBlock)) /
sizeof(SubsectionBucketRaw));
if (dec->ReadBytes(subsection_buckets_raw.data(),
section.bktr.subsection.size - sizeof(SubsectionBlock),
section.bktr.subsection.offset + sizeof(SubsectionBlock) -
offset) !=
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
return;
}
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
u32 ctr_low;
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
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;
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
if (key == boost::none) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return;
}
}
}
if (bktr_base_romfs == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
return;
}
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, section.raw.section_ctr);
// BKTR applies to entire IVFC, so make an offset version to level 6
files.push_back(std::make_shared<OffsetVfsFile>(
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
romfs = files.back();
} else {
files.push_back(std::move(dec));
romfs = files.back();
}
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
MEDIA_OFFSET_MULTIPLIER) +
section.pfs0.pfs0_header_offset;
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
header.section_tables[i].media_offset);
auto dec =
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
if (dec != nullptr) {
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
dirs.push_back(std::move(npfs));
if (IsDirectoryExeFS(dirs.back()))
exefs = dirs.back();
} else {
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return;
}
} else {
if (status != Loader::ResultStatus::Success)
return;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return;
}
}
}
status = Loader::ResultStatus::Success;
}
NCA::~NCA() = default;
Loader::ResultStatus NCA::GetStatus() const {
return status;
}

View File

@@ -73,8 +73,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
bool IsValidNCA(const NCAHeader& header);
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
@@ -106,10 +104,19 @@ protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
bool CheckSupportedNCA(const NCAHeader& header);
bool HandlePotentialHeaderDecryption();
std::vector<NCASectionHeader> ReadSectionHeaders() const;
bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset);
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();
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
@@ -118,15 +125,15 @@ private:
VirtualDir exefs = nullptr;
VirtualFile file;
VirtualFile bktr_base_romfs;
u64 ivfc_offset;
u64 ivfc_offset = 0;
NCAHeader header{};
bool has_rights_id{};
Loader::ResultStatus status{};
bool encrypted;
bool is_update;
bool encrypted = false;
bool is_update = false;
Core::Crypto::KeyManager keys;
};

View File

@@ -168,8 +168,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
load_dir == nullptr || load_dir->GetSize() <= 0) {
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
}
@@ -219,7 +218,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
title_id, static_cast<u8>(type))
.c_str();
if (type == ContentRecordType::Program || type == ContentRecordType::Data)
if (type == ContentRecordType::Program)
LOG_INFO(Loader, log_string);
else
LOG_DEBUG(Loader, log_string);

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
@@ -31,14 +30,6 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
}
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
return !operator==(lhs, rhs);
}
static bool FollowsTwoDigitDirFormat(std::string_view name) {
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
std::regex_constants::icase);
@@ -602,9 +593,6 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
},
[](const CNMT& c, const ContentRecord& r) { return true; });
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
@@ -628,9 +616,6 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
return true;
});
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
} // namespace FileSys

View File

@@ -50,10 +50,6 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
// std unique requires operator== to identify duplicates.
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
/*
* A class that catalogues NCAs in the registered directory structure.
* Nintendo's registered format follows this structure:
@@ -64,8 +60,8 @@ bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs
* | 00
* | 01 <- Actual content split along 4GB boundaries. (optional)
*
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
* when 4GB splitting can be ignored.)
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
* 4GB splitting can be ignored.)
*/
class RegisteredCache {
friend class RegisteredCacheUnion;

View File

@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
meta.title_id);
}
if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
LOG_WARNING(Service_FS,
"Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
"non-zero ({:016X}{:016X})",
meta.user_id[1], meta.user_id[0]);
}
std::string save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataSpaceId::NandUser:
out = "/user/";
break;
case SaveDataSpaceId::TemporaryStorage:
out = "/temp/";
break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
}
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::SystemSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
case SaveDataType::SaveData:
case SaveDataType::DeviceSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::TemporaryStorage:
return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));

View File

@@ -8,6 +8,7 @@
#include <mutex>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
@@ -36,9 +37,6 @@
namespace Kernel {
namespace {
constexpr bool Is4KBAligned(VAddr address) {
return (address & 0xFFF) == 0;
}
// Checks if address + size is greater than the given address
// This can return false if the size causes an overflow of a 64-bit type
@@ -69,11 +67,11 @@ bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) {
// in the same order.
ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
u64 size) {
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) {
return ERR_INVALID_ADDRESS;
}
if (size == 0 || !Is4KBAligned(size)) {
if (size == 0 || !Common::Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
@@ -352,6 +350,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
return ERR_INVALID_ADDRESS_STATE;
}
if (!Common::IsWordAligned(mutex_addr)) {
return ERR_INVALID_ADDRESS;
}
auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
requesting_thread_handle);
@@ -365,6 +367,10 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
return ERR_INVALID_ADDRESS_STATE;
}
if (!Common::IsWordAligned(mutex_addr)) {
return ERR_INVALID_ADDRESS;
}
return Mutex::Release(mutex_addr);
}
@@ -448,25 +454,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
case GetInfoType::RandomEntropy:
*result = 0;
break;
case GetInfoType::AddressSpaceBaseAddr:
*result = vm_manager.GetCodeRegionBaseAddress();
case GetInfoType::ASLRRegionBaseAddr:
*result = vm_manager.GetASLRRegionBaseAddress();
break;
case GetInfoType::AddressSpaceSize: {
const u64 width = vm_manager.GetAddressSpaceWidth();
switch (width) {
case 32:
*result = 0xFFE00000;
break;
case 36:
*result = 0xFF8000000;
break;
case 39:
*result = 0x7FF8000000;
break;
}
case GetInfoType::ASLRRegionSize:
*result = vm_manager.GetASLRRegionSize();
break;
}
case GetInfoType::NewMapRegionBaseAddr:
*result = vm_manager.GetNewMapRegionBaseAddress();
break;
@@ -583,11 +576,11 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shared_memory_handle, addr, size, permissions);
if (!Is4KBAligned(addr)) {
if (!Common::Is4KBAligned(addr)) {
return ERR_INVALID_ADDRESS;
}
if (size == 0 || !Is4KBAligned(size)) {
if (size == 0 || !Common::Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
@@ -612,11 +605,11 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
shared_memory_handle, addr, size);
if (!Is4KBAligned(addr)) {
if (!Common::Is4KBAligned(addr)) {
return ERR_INVALID_ADDRESS;
}
if (size == 0 || !Is4KBAligned(size)) {
if (size == 0 || !Common::Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}

View File

@@ -41,8 +41,8 @@ enum class GetInfoType : u64 {
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
// 2.0.0+
AddressSpaceBaseAddr = 12,
AddressSpaceSize = 13,
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
NewMapRegionBaseAddr = 14,
NewMapRegionSize = 15,
// 3.0.0+

View File

@@ -393,30 +393,35 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
switch (type) {
case FileSys::ProgramAddressSpaceType::Is32Bit:
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
address_space_width = 32;
code_region_base = 0x200000;
code_region_end = code_region_base + 0x3FE00000;
map_region_size = 0x40000000;
heap_region_size = 0x40000000;
aslr_region_base = 0x200000;
aslr_region_end = aslr_region_base + 0xFFE00000;
if (type == FileSys::ProgramAddressSpaceType::Is32Bit) {
map_region_size = 0x40000000;
heap_region_size = 0x40000000;
} else {
map_region_size = 0;
heap_region_size = 0x80000000;
}
break;
case FileSys::ProgramAddressSpaceType::Is36Bit:
address_space_width = 36;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x78000000;
aslr_region_base = 0x8000000;
aslr_region_end = aslr_region_base + 0xFF8000000;
map_region_size = 0x180000000;
heap_region_size = 0x180000000;
break;
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
address_space_width = 32;
code_region_base = 0x200000;
code_region_end = code_region_base + 0x3FE00000;
map_region_size = 0;
heap_region_size = 0x80000000;
break;
case FileSys::ProgramAddressSpaceType::Is39Bit:
address_space_width = 39;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x80000000;
aslr_region_base = 0x8000000;
aslr_region_end = aslr_region_base + 0x7FF8000000;
map_region_size = 0x1000000000;
heap_region_size = 0x180000000;
new_map_region_size = 0x80000000;
@@ -490,6 +495,18 @@ u64 VMManager::GetAddressSpaceWidth() const {
return address_space_width;
}
VAddr VMManager::GetASLRRegionBaseAddress() const {
return aslr_region_base;
}
VAddr VMManager::GetASLRRegionEndAddress() const {
return aslr_region_end;
}
u64 VMManager::GetASLRRegionSize() const {
return aslr_region_end - aslr_region_base;
}
VAddr VMManager::GetCodeRegionBaseAddress() const {
return code_region_base;
}

View File

@@ -205,6 +205,15 @@ public:
/// Gets the address space width in bits.
u64 GetAddressSpaceWidth() const;
/// Gets the base address of the ASLR region.
VAddr GetASLRRegionBaseAddress() const;
/// Gets the end address of the ASLR region.
VAddr GetASLRRegionEndAddress() const;
/// Gets the size of the ASLR region
u64 GetASLRRegionSize() const;
/// Gets the base address of the code region.
VAddr GetCodeRegionBaseAddress() const;
@@ -306,6 +315,9 @@ private:
VAddr address_space_base = 0;
VAddr address_space_end = 0;
VAddr aslr_region_base = 0;
VAddr aslr_region_end = 0;
VAddr code_region_base = 0;
VAddr code_region_end = 0;

View File

@@ -17,22 +17,24 @@ OMM::OMM() : ServiceFramework{"omm"} {
{5, nullptr, "GetCradleStatus"},
{6, nullptr, "FadeInDisplay"},
{7, nullptr, "FadeOutDisplay"},
{8, nullptr, "Unknown1"},
{9, nullptr, "Unknown2"},
{10, nullptr, "Unknown3"},
{11, nullptr, "Unknown4"},
{12, nullptr, "Unknown5"},
{13, nullptr, "Unknown6"},
{14, nullptr, "Unknown7"},
{15, nullptr, "Unknown8"},
{16, nullptr, "Unknown9"},
{17, nullptr, "Unknown10"},
{18, nullptr, "Unknown11"},
{19, nullptr, "Unknown12"},
{20, nullptr, "Unknown13"},
{21, nullptr, "Unknown14"},
{22, nullptr, "Unknown15"},
{23, nullptr, "Unknown16"},
{8, nullptr, "GetCradleFwVersion"},
{9, nullptr, "NotifyCecSettingsChanged"},
{10, nullptr, "SetOperationModePolicy"},
{11, nullptr, "GetDefaultDisplayResolution"},
{12, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
{13, nullptr, "UpdateDefaultDisplayResolution"},
{14, nullptr, "ShouldSleepOnBoot"},
{15, nullptr, "NotifyHdcpApplicationExecutionStarted"},
{16, nullptr, "NotifyHdcpApplicationExecutionFinished"},
{17, nullptr, "NotifyHdcpApplicationDrawingStarted"},
{18, nullptr, "NotifyHdcpApplicationDrawingFinished"},
{19, nullptr, "GetHdcpAuthenticationFailedEvent"},
{20, nullptr, "GetHdcpAuthenticationFailedEmulationEnabled"},
{21, nullptr, "SetHdcpAuthenticationFailedEmulation"},
{22, nullptr, "GetHdcpStateChangeEvent"},
{23, nullptr, "GetHdcpState"},
{24, nullptr, "ShowCardUpdateProcessing"},
{25, nullptr, "SetApplicationCecSettingsAndNotifyChanged"},
};
// clang-format on

View File

@@ -22,20 +22,22 @@ class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
: ServiceFramework("IAudioRenderer") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
{1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
{2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
{3, &IAudioRenderer::GetAudioRendererState, "GetAudioRendererState"},
{4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
{5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
{6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
{1, &IAudioRenderer::GetSampleCount, "GetSampleCount"},
{2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"},
{3, &IAudioRenderer::GetState, "GetState"},
{4, &IAudioRenderer::RequestUpdate, "RequestUpdate"},
{5, &IAudioRenderer::Start, "Start"},
{6, &IAudioRenderer::Stop, "Stop"},
{7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
{8, nullptr, "SetAudioRendererRenderingTimeLimit"},
{9, nullptr, "GetAudioRendererRenderingTimeLimit"},
{10, nullptr, "RequestUpdateAudioRendererAuto"},
{8, nullptr, "SetRenderingTimeLimit"},
{9, nullptr, "GetRenderingTimeLimit"},
{10, nullptr, "RequestUpdateAuto"},
{11, nullptr, "ExecuteAudioRendererRendering"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
@@ -49,42 +51,42 @@ private:
system_event->Signal();
}
void GetAudioRendererSampleRate(Kernel::HLERequestContext& ctx) {
void GetSampleRate(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleRate());
LOG_DEBUG(Service_Audio, "called");
}
void GetAudioRendererSampleCount(Kernel::HLERequestContext& ctx) {
void GetSampleCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleCount());
LOG_DEBUG(Service_Audio, "called");
}
void GetAudioRendererState(Kernel::HLERequestContext& ctx) {
void GetState(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(renderer->GetStreamState()));
LOG_DEBUG(Service_Audio, "called");
}
void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
void GetMixBufferCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetMixBufferCount());
LOG_DEBUG(Service_Audio, "called");
}
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
void RequestUpdate(Kernel::HLERequestContext& ctx) {
ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_Audio, "(STUBBED) called");
}
void StartAudioRenderer(Kernel::HLERequestContext& ctx) {
void Start(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -92,7 +94,7 @@ private:
LOG_WARNING(Service_Audio, "(STUBBED) called");
}
void StopAudioRenderer(Kernel::HLERequestContext& ctx) {
void Stop(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -129,6 +131,7 @@ public:
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
{11, nullptr, "QueryAudioDeviceInputEvent"},
{12, nullptr, "QueryAudioDeviceOutputEvent"},
{13, nullptr, "GetAudioSystemMasterVolumeSetting"},
};
RegisterHandlers(functions);

View File

@@ -9,6 +9,7 @@ namespace Service::ES {
class ETicket final : public ServiceFramework<ETicket> {
public:
explicit ETicket() : ServiceFramework{"es"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ImportTicket"},
{2, nullptr, "ImportTicketCertificateSet"},
@@ -37,15 +38,18 @@ public:
{25, nullptr, "DeletePrepurchaseRecord"},
{26, nullptr, "DeleteAllPrepurchaseRecord"},
{27, nullptr, "CountPrepurchaseRecord"},
{28, nullptr, "ListPrepurchaseRecord"},
{28, nullptr, "ListPrepurchaseRecordRightsIds"},
{29, nullptr, "ListPrepurchaseRecordInfo"},
{30, nullptr, "Unknown1"},
{31, nullptr, "Unknown2"},
{32, nullptr, "Unknown3"},
{33, nullptr, "Unknown4"},
{34, nullptr, "Unknown5"},
{35, nullptr, "Unknown6"},
{30, nullptr, "CountTicket"},
{31, nullptr, "ListTicketRightsIds"},
{32, nullptr, "CountPrepurchaseRecordEx"},
{33, nullptr, "ListPrepurchaseRecordRightsIdsEx"},
{34, nullptr, "GetEncryptedTicketSize"},
{35, nullptr, "GetEncryptedTicketData"},
{36, nullptr, "DeleteAllInactiveELicenseRequiredPersonalizedTicket"},
{503, nullptr, "GetTitleKey"},
};
// clang-format on
RegisterHandlers(functions);
}
};

View File

@@ -17,7 +17,6 @@
#include "core/file_sys/errors.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
@@ -631,7 +630,6 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
static_cast<u8>(storage_id), unknown, title_id);
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (data.Failed()) {
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
@@ -642,9 +640,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
return;
}
FileSys::PatchManager pm{title_id};
IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
IStorage storage(std::move(data.Unwrap()));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);

View File

@@ -0,0 +1,28 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
ControllerBase::~ControllerBase() = default;
void ControllerBase::ActivateController() {
if (is_activated) {
OnRelease();
}
is_activated = true;
OnInit();
}
void ControllerBase::DeactivateController() {
if (is_activated) {
OnRelease();
}
is_activated = false;
}
bool ControllerBase::IsControllerActivated() const {
return is_activated;
}
} // namespace Service::HID

View File

@@ -0,0 +1,45 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "common/swap.h"
namespace Service::HID {
class ControllerBase {
public:
ControllerBase() = default;
virtual ~ControllerBase() = 0;
// Called when the controller is initialized
virtual void OnInit() = 0;
// When the controller is released
virtual void OnRelease() = 0;
// When the controller is requesting an update for the shared memory
virtual void OnUpdate(u8* data, std::size_t size) = 0;
// Called when input devices should be loaded
virtual void OnLoadInputDevices() = 0;
void ActivateController();
void DeactivateController();
bool IsControllerActivated() const;
protected:
bool is_activated{false};
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
};
} // namespace Service::HID

View File

@@ -0,0 +1,42 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
namespace Service::HID {
Controller_DebugPad::Controller_DebugPad() = default;
void Controller_DebugPad::OnInit() {}
void Controller_DebugPad::OnRelease() {}
void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) {
shared_memory.header.timestamp = CoreTiming::GetTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update debug pad states
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
}
void Controller_DebugPad::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -0,0 +1,55 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
public:
Controller_DebugPad();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct AnalogStick {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogStick) == 0x8);
struct PadStates {
s64_le sampling_number;
s64_le sampling_number2;
u32_le attribute;
u32_le button_state;
AnalogStick r_stick;
AnalogStick l_stick;
};
static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
struct SharedMemory {
CommonHeader header;
std::array<PadStates, 17> pad_states;
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,43 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/gesture.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
Controller_Gesture::Controller_Gesture() = default;
void Controller_Gesture::OnInit() {}
void Controller_Gesture::OnRelease() {}
void Controller_Gesture::OnUpdate(u8* data, std::size_t size) {
shared_memory.header.timestamp = CoreTiming::GetTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update gesture states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_Gesture::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -0,0 +1,62 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_Gesture final : public ControllerBase {
public:
Controller_Gesture();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Locations {
s32_le x;
s32_le y;
};
struct GestureState {
s64_le sampling_number;
s64_le sampling_number2;
s64_le detection_count;
s32_le type;
s32_le dir;
s32_le x;
s32_le y;
s32_le delta_x;
s32_le delta_y;
f32 vel_x;
f32 vel_y;
s32_le attributes;
f32 scale;
f32 rotation;
s32_le location_count;
std::array<Locations, 4> locations;
};
static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<GestureState, 17> gesture_states;
};
SharedMemory shared_memory{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,43 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/keyboard.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
Controller_Keyboard::Controller_Keyboard() = default;
void Controller_Keyboard::OnInit() {}
void Controller_Keyboard::OnRelease() {}
void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) {
shared_memory.header.timestamp = CoreTiming::GetTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update keyboard states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_Keyboard::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -0,0 +1,49 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
public:
Controller_Keyboard();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct KeyboardState {
s64_le sampling_number;
s64_le sampling_number2;
s32_le modifier;
s32_le attribute;
std::array<u8, 32> key;
};
static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<KeyboardState, 17> pad_states;
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,43 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/mouse.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
Controller_Mouse::Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
void Controller_Mouse::OnRelease() {}
void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
shared_memory.header.timestamp = CoreTiming::GetTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
return;
}
shared_memory.header.entry_count = 16;
auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// TODO(ogniK): Update mouse states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_Mouse::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -0,0 +1,49 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
public:
Controller_Mouse();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct MouseState {
s64_le sampling_number;
s64_le sampling_number2;
s32_le x;
s32_le y;
s32_le delta_x;
s32_le delta_y;
s32_le mouse_wheel;
s32_le button;
s32_le attribute;
};
static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<MouseState, 17> mouse_states;
};
SharedMemory shared_memory{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,429 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <array>
#include <cstring>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/input.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/settings.h"
namespace Service::HID {
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
Controller_NPad::Controller_NPad() = default;
void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
const auto controller_type = connected_controllers[controller_idx].type;
auto& controller = shared_memory_entries[controller_idx];
if (controller_type == NPadControllerType::None) {
return;
}
controller.joy_styles.raw = 0; // Zero out
controller.device_type.raw = 0;
switch (controller_type) {
case NPadControllerType::Handheld:
controller.joy_styles.handheld.Assign(1);
controller.device_type.handheld.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::JoyDual:
controller.joy_styles.joycon_dual.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::JoyLeft:
controller.joy_styles.joycon_left.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::JoyRight:
controller.joy_styles.joycon_right.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::Pokeball:
controller.joy_styles.pokeball.Assign(1);
controller.device_type.pokeball.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
case NPadControllerType::ProController:
controller.joy_styles.pro_controller.Assign(1);
controller.device_type.pro_controller.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
}
controller.single_color_error = ColorReadError::ReadOk;
controller.single_color.body_color = 0;
controller.single_color.button_color = 0;
controller.dual_color_error = ColorReadError::ReadOk;
controller.left_color.body_color = JOYCON_BODY_NEON_BLUE;
controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE;
controller.right_color.body_color = JOYCON_BODY_NEON_RED;
controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED;
controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
controller.battery_level[2] = BATTERY_FULL;
}
void Controller_NPad::OnInit() {
auto& kernel = Core::System::GetInstance().Kernel();
styleset_changed_event =
Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
if (!IsControllerActivated())
return;
std::size_t controller{};
if (style.raw == 0) {
// We want to support all controllers
style.handheld.Assign(1);
style.joycon_left.Assign(1);
style.joycon_right.Assign(1);
style.joycon_dual.Assign(1);
style.pro_controller.Assign(1);
style.pokeball.Assign(1);
}
if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
[](const ControllerHolder& controller) { return controller.is_connected; })) {
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(u32));
AddNewController(NPadControllerType::JoyDual);
}
}
void Controller_NPad::OnLoadInputDevices() {
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
}
void Controller_NPad::OnRelease() {}
void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
if (!IsControllerActivated())
return;
for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
auto& npad = shared_memory_entries[i];
const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
&npad.handheld_states,
&npad.dual_states,
&npad.left_joy_states,
&npad.right_joy_states,
&npad.pokeball_states,
&npad.libnx};
for (auto* main_controller : controller_npads) {
main_controller->common.entry_count = 16;
main_controller->common.total_entry_count = 17;
const auto& last_entry =
main_controller->npad[main_controller->common.last_entry_index];
main_controller->common.timestamp = CoreTiming::GetTicks();
main_controller->common.last_entry_index =
(main_controller->common.last_entry_index + 1) % 17;
auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index];
cur_entry.timestamp = last_entry.timestamp + 1;
cur_entry.timestamp2 = cur_entry.timestamp;
}
const auto& controller_type = connected_controllers[i].type;
if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
continue;
}
// Pad states
ControllerPadState pad_state{};
using namespace Settings::NativeButton;
pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
AnalogPosition lstick_entry{};
AnalogPosition rstick_entry{};
const auto [stick_l_x_f, stick_l_y_f] =
sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] =
sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
auto& main_controller =
npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
auto& handheld_entry =
npad.handheld_states.npad[npad.handheld_states.common.last_entry_index];
auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index];
auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index];
auto& right_entry =
npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index];
auto& pokeball_entry =
npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index];
auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
if (hold_type == NpadHoldType::Horizontal) {
// TODO(ogniK): Remap buttons for different orientations
}
libnx_entry.connection_status.raw = 0;
switch (controller_type) {
case NPadControllerType::Handheld:
handheld_entry.connection_status.raw = 0;
handheld_entry.connection_status.IsConnected.Assign(1);
if (!Settings::values.use_docked_mode) {
handheld_entry.connection_status.IsWired.Assign(1);
}
handheld_entry.pad_states.raw = pad_state.raw;
handheld_entry.l_stick = lstick_entry;
handheld_entry.r_stick = rstick_entry;
break;
case NPadControllerType::JoyDual:
dual_entry.connection_status.raw = 0;
dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
dual_entry.connection_status.IsRightJoyConnected.Assign(1);
dual_entry.connection_status.IsConnected.Assign(1);
libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
libnx_entry.connection_status.IsConnected.Assign(1);
dual_entry.pad_states.raw = pad_state.raw;
dual_entry.l_stick = lstick_entry;
dual_entry.r_stick = rstick_entry;
case NPadControllerType::JoyLeft:
left_entry.connection_status.raw = 0;
left_entry.connection_status.IsConnected.Assign(1);
left_entry.pad_states.raw = pad_state.raw;
left_entry.l_stick = lstick_entry;
left_entry.r_stick = rstick_entry;
break;
case NPadControllerType::JoyRight:
right_entry.connection_status.raw = 0;
right_entry.connection_status.IsConnected.Assign(1);
right_entry.pad_states.raw = pad_state.raw;
right_entry.l_stick = lstick_entry;
right_entry.r_stick = rstick_entry;
break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
pokeball_entry.connection_status.IsConnected.Assign(1);
pokeball_entry.connection_status.IsWired.Assign(1);
pokeball_entry.pad_states.raw = pad_state.raw;
pokeball_entry.l_stick = lstick_entry;
pokeball_entry.r_stick = rstick_entry;
break;
case NPadControllerType::ProController:
main_controller.connection_status.raw = 0;
main_controller.connection_status.IsConnected.Assign(1);
main_controller.connection_status.IsWired.Assign(1);
main_controller.pad_states.raw = pad_state.raw;
main_controller.l_stick = lstick_entry;
main_controller.r_stick = rstick_entry;
break;
}
// LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
// any controllers.
libnx_entry.pad_states.raw = pad_state.raw;
libnx_entry.l_stick = lstick_entry;
libnx_entry.r_stick = rstick_entry;
}
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
shared_memory_entries.size() * sizeof(NPadEntry));
} // namespace Service::HID
void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
style.raw = style_set.raw;
}
Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
return style;
}
void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
std::memcpy(supported_npad_id_types.data(), data, length);
}
const void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
ASSERT(max_length < supported_npad_id_types.size());
std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
}
std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
return supported_npad_id_types.size();
}
void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
hold_type = joy_hold_type;
}
Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
return hold_type;
}
void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
ASSERT(npad_id < shared_memory_entries.size());
shared_memory_entries[npad_id].pad_assignment = assignment_mode;
}
void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
const std::vector<Vibration>& vibrations) {
if (!can_controllers_vibrate) {
return;
}
for (std::size_t i = 0; i < controller_ids.size(); i++) {
std::size_t controller_pos = i;
// Handheld controller conversion
if (controller_pos == 32) {
controller_pos = 8;
}
// Unknown controller conversion
if (controller_pos == 16) {
controller_pos = 9;
}
if (connected_controllers[controller_pos].is_connected) {
// TODO(ogniK): Vibrate the physical controller
}
}
LOG_WARNING(Service_HID, "(STUBBED) called");
last_processed_vibration = vibrations.back();
}
Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
return styleset_changed_event;
}
Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
return last_processed_vibration;
}
void Controller_NPad::AddNewController(NPadControllerType controller) {
if (controller == NPadControllerType::Handheld) {
connected_controllers[8] = {controller, true};
InitNewlyAddedControler(8);
return;
}
const auto pos =
std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
[](const ControllerHolder& holder) { return !holder.is_connected; });
if (pos == connected_controllers.end() - 2) {
LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
return;
}
const auto controller_id = std::distance(connected_controllers.begin(), pos);
connected_controllers[controller_id] = {controller, true};
InitNewlyAddedControler(controller_id);
}
void Controller_NPad::ConnectNPad(u32 npad_id) {
if (npad_id >= connected_controllers.size())
return;
connected_controllers[npad_id].is_connected = true;
}
void Controller_NPad::DisconnectNPad(u32 npad_id) {
if (npad_id >= connected_controllers.size())
return;
connected_controllers[npad_id].is_connected = false;
}
Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
// These are controllers without led patterns
return LedPattern{0, 0, 0, 0};
}
switch (npad_id) {
case 0:
return LedPattern{1, 0, 0, 0};
case 1:
return LedPattern{0, 1, 0, 0};
case 2:
return LedPattern{0, 0, 1, 0};
case 3:
return LedPattern{0, 0, 0, 1};
case 4:
return LedPattern{1, 0, 0, 1};
case 5:
return LedPattern{1, 0, 1, 0};
case 6:
return LedPattern{1, 0, 1, 1};
case 7:
return LedPattern{0, 1, 1, 0};
default:
UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
return LedPattern{0, 0, 0, 0};
};
}
void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
can_controllers_vibrate = can_vibrate;
}
} // namespace Service::HID

View File

@@ -0,0 +1,288 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
namespace Service::HID {
class Controller_NPad final : public ControllerBase {
public:
Controller_NPad();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
struct NPadType {
union {
u32_le raw{};
BitField<0, 1, u32_le> pro_controller;
BitField<1, 1, u32_le> handheld;
BitField<2, 1, u32_le> joycon_dual;
BitField<3, 1, u32_le> joycon_left;
BitField<4, 1, u32_le> joycon_right;
BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
};
};
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
struct Vibration {
f32 amp_low;
f32 freq_low;
f32 amp_high;
f32 freq_high;
};
static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
enum class NpadHoldType : u64 {
Vertical = 0,
Horizontal = 1,
};
enum class NPadAssignments : u32_le {
Dual = 0,
Single = 1,
};
enum class NPadControllerType {
None,
ProController,
Handheld,
JoyDual,
JoyLeft,
JoyRight,
Pokeball,
};
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position1.Assign(light2);
position1.Assign(light3);
position1.Assign(light4);
};
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
void SetSupportedStyleSet(NPadType style_set);
NPadType GetSupportedStyleSet() const;
void SetSupportedNPadIdTypes(u8* data, std::size_t length);
const void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
std::size_t GetSupportedNPadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
void VibrateController(const std::vector<u32>& controller_ids,
const std::vector<Vibration>& vibrations);
Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const;
Vibration GetLastVibration() const;
void AddNewController(NPadControllerType controller);
void ConnectNPad(u32 npad_id);
void DisconnectNPad(u32 npad_id);
LedPattern GetLedPattern(u32 npad_id);
void SetVibrationEnabled(bool can_vibrate);
private:
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
struct ControllerColor {
u32_le body_color;
u32_le button_color;
};
static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
struct ControllerPadState {
union {
u64_le raw{};
// Button states
BitField<0, 1, u64_le> a;
BitField<1, 1, u64_le> b;
BitField<2, 1, u64_le> x;
BitField<3, 1, u64_le> y;
BitField<4, 1, u64_le> l_stick;
BitField<5, 1, u64_le> r_stick;
BitField<6, 1, u64_le> l;
BitField<7, 1, u64_le> r;
BitField<8, 1, u64_le> zl;
BitField<9, 1, u64_le> zr;
BitField<10, 1, u64_le> plus;
BitField<11, 1, u64_le> minus;
// D-Pad
BitField<12, 1, u64_le> d_left;
BitField<13, 1, u64_le> d_up;
BitField<14, 1, u64_le> d_right;
BitField<15, 1, u64_le> d_down;
// Left JoyStick
BitField<16, 1, u64_le> l_stick_left;
BitField<17, 1, u64_le> l_stick_up;
BitField<18, 1, u64_le> l_stick_right;
BitField<19, 1, u64_le> l_stick_down;
// Right JoyStick
BitField<20, 1, u64_le> r_stick_left;
BitField<21, 1, u64_le> r_stick_up;
BitField<22, 1, u64_le> r_stick_right;
BitField<23, 1, u64_le> r_stick_down;
// Not always active?
BitField<24, 1, u64_le> sl;
BitField<25, 1, u64_le> sr;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
struct AnalogPosition {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
struct ConnectionState {
union {
u32_le raw{};
BitField<0, 1, u32_le> IsConnected;
BitField<1, 1, u32_le> IsWired;
BitField<2, 1, u32_le> IsLeftJoyConnected;
BitField<3, 1, u32_le> IsLeftJoyWired;
BitField<4, 1, u32_le> IsRightJoyConnected;
BitField<5, 1, u32_le> IsRightJoyWired;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
struct GenericStates {
s64_le timestamp;
s64_le timestamp2;
ControllerPadState pad_states;
AnalogPosition l_stick;
AnalogPosition r_stick;
ConnectionState connection_status;
};
static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
struct NPadGeneric {
CommonHeader common;
std::array<GenericStates, 17> npad;
};
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
enum class ColorReadError : u32_le {
ReadOk = 0,
ColorDoesntExist = 1,
NoController = 2,
};
struct NPadProperties {
union {
s64_le raw{};
BitField<11, 1, s64_le> is_vertical;
BitField<12, 1, s64_le> is_horizontal;
BitField<13, 1, s64_le> use_plus;
BitField<14, 1, s64_le> use_minus;
};
};
struct NPadDevice {
union {
u32_le raw{};
BitField<0, 1, s32_le> pro_controller;
BitField<1, 1, s32_le> handheld;
BitField<2, 1, s32_le> handheld_left;
BitField<3, 1, s32_le> handheld_right;
BitField<4, 1, s32_le> joycon_left;
BitField<5, 1, s32_le> joycon_right;
BitField<6, 1, s32_le> pokeball;
};
};
struct NPadEntry {
NPadType joy_styles;
NPadAssignments pad_assignment;
ColorReadError single_color_error;
ControllerColor single_color;
ColorReadError dual_color_error;
ControllerColor left_color;
ControllerColor right_color;
NPadGeneric main_controller_states;
NPadGeneric handheld_states;
NPadGeneric dual_states;
NPadGeneric left_joy_states;
NPadGeneric right_joy_states;
NPadGeneric pokeball_states;
NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
// relying on this for the time being
INSERT_PADDING_BYTES(
0x708 *
6); // TODO(ogniK): SixAxis states, require more information before implementation
NPadDevice device_type;
NPadProperties properties;
INSERT_PADDING_WORDS(1);
std::array<u32, 3> battery_level;
INSERT_PADDING_BYTES(0x5c);
INSERT_PADDING_BYTES(0xdf8);
};
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
struct ControllerHolder {
Controller_NPad::NPadControllerType type;
bool is_connected;
};
NPadType style{};
std::array<NPadEntry, 10> shared_memory_entries{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
Kernel::SharedPtr<Kernel::Event> styleset_changed_event;
std::size_t dump_idx{};
Vibration last_processed_vibration{};
static constexpr std::array<u32, 10> npad_id_list{0, 1, 2, 3, 4, 5, 6, 7, 32, 16};
std::array<ControllerHolder, 10> connected_controllers{};
bool can_controllers_vibrate{true};
void InitNewlyAddedControler(std::size_t controller_idx);
};
} // namespace Service::HID

View File

@@ -0,0 +1,39 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/stubbed.h"
namespace Service::HID {
Controller_Stubbed::Controller_Stubbed() = default;
void Controller_Stubbed::OnInit() {}
void Controller_Stubbed::OnRelease() {}
void Controller_Stubbed::OnUpdate(u8* data, std::size_t size) {
if (!smart_update) {
return;
}
CommonHeader header{};
header.timestamp = CoreTiming::GetTicks();
header.total_entry_count = 17;
header.entry_count = 0;
header.last_entry_index = 0;
std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
}
void Controller_Stubbed::OnLoadInputDevices() {}
void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
common_offset = off;
smart_update = true;
}
} // namespace Service::HID

View File

@@ -0,0 +1,33 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_Stubbed final : public ControllerBase {
public:
Controller_Stubbed();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
void SetCommonHeaderOffset(std::size_t off);
private:
bool smart_update{};
std::size_t common_offset{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,65 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/settings.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
Controller_Touchscreen::Controller_Touchscreen() = default;
void Controller_Touchscreen::OnInit() {}
void Controller_Touchscreen::OnRelease() {}
void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
shared_memory.header.timestamp = CoreTiming::GetTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry =
shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
const auto [x, y, pressed] = touch_device->GetStatus();
auto& touch_entry = cur_entry.states[0];
if (pressed) {
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
touch_entry.diameter_x = 15;
touch_entry.diameter_y = 15;
touch_entry.rotation_angle = 0;
const u64 tick = CoreTiming::GetTicks();
touch_entry.delta_time = tick - last_touch;
last_touch = tick;
touch_entry.finger = 0;
cur_entry.entry_count = 1;
} else {
cur_entry.entry_count = 0;
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
}
void Controller_Touchscreen::OnLoadInputDevices() {
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
}
} // namespace Service::HID

View File

@@ -0,0 +1,62 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
Controller_Touchscreen();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct TouchState {
u64_le delta_time;
u32_le attribute;
u32_le finger;
u32_le x;
u32_le y;
u32_le diameter_x;
u32_le diameter_y;
u32_le rotation_angle;
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
struct TouchScreenEntry {
s64_le sampling_number;
s64_le sampling_number2;
s32_le entry_count;
std::array<TouchState, 16> states;
};
static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
struct TouchScreenSharedMemory {
CommonHeader header;
std::array<TouchScreenEntry, 17> shared_memory_entries{};
INSERT_PADDING_BYTES(0x3c8);
};
static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
"TouchScreenSharedMemory is an invalid size");
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_device;
s64_le last_touch{};
};
} // namespace Service::HID

View File

@@ -0,0 +1,45 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/controllers/xpad.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
Controller_XPad::Controller_XPad() = default;
void Controller_XPad::OnInit() {}
void Controller_XPad::OnRelease() {}
void Controller_XPad::OnUpdate(u8* data, std::size_t size) {
for (auto& xpad_entry : shared_memory.shared_memory_entries) {
xpad_entry.header.timestamp = CoreTiming::GetTicks();
xpad_entry.header.total_entry_count = 17;
if (!IsControllerActivated()) {
xpad_entry.header.entry_count = 0;
xpad_entry.header.last_entry_index = 0;
return;
}
xpad_entry.header.entry_count = 16;
const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
}
// TODO(ogniK): Update xpad states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
void Controller_XPad::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -0,0 +1,60 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
class Controller_XPad final : public ControllerBase {
public:
Controller_XPad();
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct AnalogStick {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
struct XPadState {
s64_le sampling_number;
s64_le sampling_number2;
s32_le attributes;
u32_le pad_states;
AnalogStick x_stick;
AnalogStick y_stick;
};
static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
struct XPadEntry {
CommonHeader header;
std::array<XPadState, 17> pad_states{};
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
struct SharedMemory {
std::array<XPadEntry, 4> shared_memory_entries{};
};
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
};
} // namespace Service::HID

View File

@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -19,6 +21,16 @@
#include "core/hle/service/service.h"
#include "core/settings.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
#include "core/hle/service/hid/controllers/gesture.h"
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/controllers/stubbed.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/xpad.h"
namespace Service::HID {
// Updating period for each HID device.
@@ -26,6 +38,22 @@ namespace Service::HID {
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
enum class HidController : std::size_t {
DebugPad,
Touchscreen,
Mouse,
Keyboard,
XPad,
Unknown1,
Unknown2,
Unknown3,
SixAxisSensor,
NPad,
Gesture,
MaxControllers,
};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
@@ -37,19 +65,57 @@ public:
auto& kernel = Core::System::GetInstance().Kernel();
shared_mem = Kernel::SharedMemory::Create(
kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite,
kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
MakeController<Controller_DebugPad>(HidController::DebugPad);
MakeController<Controller_Touchscreen>(HidController::Touchscreen);
MakeController<Controller_Mouse>(HidController::Mouse);
MakeController<Controller_Keyboard>(HidController::Keyboard);
MakeController<Controller_XPad>(HidController::XPad);
MakeController<Controller_Stubbed>(HidController::Unknown1);
MakeController<Controller_Stubbed>(HidController::Unknown2);
MakeController<Controller_Stubbed>(HidController::Unknown3);
MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
MakeController<Controller_NPad>(HidController::NPad);
MakeController<Controller_Gesture>(HidController::Gesture);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
// Register update callbacks
pad_update_event = CoreTiming::RegisterEvent(
"HID::UpdatePadCallback",
[this](u64 userdata, int cycles_late) { UpdatePadCallback(userdata, cycles_late); });
[this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); });
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
void ActivateController(HidController controller) {
controllers[static_cast<size_t>(controller)]->ActivateController();
}
void DeactivateController(HidController controller) {
controllers[static_cast<size_t>(controller)]->DeactivateController();
}
template <typename T>
void MakeController(HidController controller) {
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
}
template <typename T>
T& GetController(HidController controller) {
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
}
~IAppletResource() {
CoreTiming::UnscheduleEvent(pad_update_event, 0);
}
@@ -62,200 +128,15 @@ private:
LOG_DEBUG(Service_HID, "called");
}
void LoadInputDevices() {
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
// TODO(shinyquagsire23): gyro, mouse, keyboard
}
void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMemory mem{};
std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
if (Settings::values.is_device_reload_pending.exchange(false))
LoadInputDevices();
// Set up controllers as neon red+blue Joy-Con attached to console
ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
controller_header.type = ControllerType_Handheld;
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) {
for (auto& layout : mem.controllers[controller].layouts) {
layout.header.num_entries = HID_NUM_ENTRIES;
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
// each with a timestamp in number of samples since boot.
const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
layout.header.timestamp_ticks = CoreTiming::GetTicks();
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
entry.timestamp = last_entry.timestamp + 1;
// TODO(shinyquagsire23): Is this always identical to timestamp?
entry.timestamp_2 = entry.timestamp;
// TODO(shinyquagsire23): More than just handheld input
if (controller != Controller_Handheld)
continue;
entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
// TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
// For now everything is just the default handheld layout, but split Joy-Con will
// rotate the face buttons and directions for certain layouts.
ControllerPadState& state = entry.buttons;
using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
void UpdateControllers(u64 userdata, int cycles_late) {
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
for (const auto& controller : controllers) {
if (should_reload) {
controller->OnLoadInputDevices();
}
controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
TouchScreen& touchscreen = mem.touchscreen;
const u64 last_entry = touchscreen.header.latest_entry;
const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
const u64 timestamp = CoreTiming::GetTicks();
const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
touchscreen.header.timestamp_ticks = timestamp;
touchscreen.header.num_entries = touchscreen.entries.size();
touchscreen.header.latest_entry = curr_entry;
touchscreen.header.max_entry_index = touchscreen.entries.size();
touchscreen.header.timestamp = timestamp;
touchscreen.entries[curr_entry].header.timestamp = sample_counter;
TouchScreenEntryTouch touch_entry{};
auto [x, y, pressed] = touch_device->GetStatus();
touch_entry.timestamp = timestamp;
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
touch_entry.touch_index = 0;
// TODO(DarkLordZach): Maybe try to derive these from EmuWindow?
touch_entry.diameter_x = 15;
touch_entry.diameter_y = 15;
touch_entry.angle = 0;
// TODO(DarkLordZach): Implement multi-touch support
if (pressed) {
touchscreen.entries[curr_entry].header.num_touches = 1;
touchscreen.entries[curr_entry].touches[0] = touch_entry;
} else {
touchscreen.entries[curr_entry].header.num_touches = 0;
}
// TODO(shinyquagsire23): Properly implement mouse
Mouse& mouse = mem.mouse;
const u64 last_mouse_entry = mouse.header.latest_entry;
const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
mouse.header.timestamp_ticks = timestamp;
mouse.header.num_entries = mouse.entries.size();
mouse.header.max_entry_index = mouse.entries.size();
mouse.header.latest_entry = curr_mouse_entry;
mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
// TODO(shinyquagsire23): Properly implement keyboard
Keyboard& keyboard = mem.keyboard;
const u64 last_keyboard_entry = keyboard.header.latest_entry;
const u64 curr_keyboard_entry =
(keyboard.header.latest_entry + 1) % keyboard.entries.size();
const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
keyboard.header.timestamp_ticks = timestamp;
keyboard.header.num_entries = keyboard.entries.size();
keyboard.header.latest_entry = last_keyboard_entry;
keyboard.header.max_entry_index = keyboard.entries.size();
keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
// TODO(shinyquagsire23): Figure out what any of these are
for (auto& input : mem.unk_input_1) {
const u64 last_input_entry = input.header.latest_entry;
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
input.header.timestamp_ticks = timestamp;
input.header.num_entries = input.entries.size();
input.header.latest_entry = last_input_entry;
input.header.max_entry_index = input.entries.size();
input.entries[curr_input_entry].timestamp = input_sample_counter;
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
}
for (auto& input : mem.unk_input_2) {
input.header.timestamp_ticks = timestamp;
input.header.num_entries = 17;
input.header.latest_entry = 0;
input.header.max_entry_index = 0;
}
UnkInput3& input = mem.unk_input_3;
const u64 last_input_entry = input.header.latest_entry;
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
input.header.timestamp_ticks = timestamp;
input.header.num_entries = input.entries.size();
input.header.latest_entry = last_input_entry;
input.header.max_entry_index = input.entries.size();
input.entries[curr_input_entry].timestamp = input_sample_counter;
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
// TODO(shinyquagsire23): Signal events
std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
// Reschedule recurrent event
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
}
@@ -265,11 +146,8 @@ private:
// CoreTiming update events
CoreTiming::EventType* pad_update_event;
// Stored input state info
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
std::unique_ptr<Input::TouchDevice> touch_device;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
controllers{};
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
@@ -299,9 +177,10 @@ public:
{11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
{21, &Hid::ActivateMouse, "ActivateMouse"},
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
{32, nullptr, "SendKeyboardLockKeyEvent"},
{40, nullptr, "AcquireXpadIdEventHandle"},
{41, nullptr, "ReleaseXpadIdEventHandle"},
{51, nullptr, "ActivateXpad"},
{51, &Hid::ActivateXpad, "ActivateXpad"},
{55, nullptr, "GetXpadIds"},
{56, nullptr, "ActivateJoyXpad"},
{58, nullptr, "GetJoyXpadLifoHandle"},
@@ -329,6 +208,7 @@ public:
{80, nullptr, "GetGyroscopeZeroDriftMode"},
{81, nullptr, "ResetGyroscopeZeroDriftMode"},
{82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
{83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
{91, &Hid::ActivateGesture, "ActivateGesture"},
{100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
{101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
@@ -362,8 +242,8 @@ public:
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
{207, nullptr, "SendVibrationGcErmCommand"},
{208, nullptr, "GetActualVibrationGcErmCommand"},
{209, nullptr, "BeginPermitVibrationSession"},
{210, nullptr, "EndPermitVibrationSession"},
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, nullptr, "StopConsoleSixAxisSensor"},
@@ -374,6 +254,7 @@ public:
{307, nullptr, "FinalizeSevenSixAxisSensor"},
{308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
{309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
{310, nullptr, "ResetSevenSixAxisSensorTimestamp"},
{400, nullptr, "IsUsbFullKeyControllerEnabled"},
{401, nullptr, "EnableUsbFullKeyController"},
{402, nullptr, "IsUsbFullKeyControllerConnected"},
@@ -389,28 +270,35 @@ public:
{505, nullptr, "SetPalmaFrModeType"},
{506, nullptr, "ReadPalmaStep"},
{507, nullptr, "EnablePalmaStep"},
{508, nullptr, "SuspendPalmaStep"},
{509, nullptr, "ResetPalmaStep"},
{510, nullptr, "ReadPalmaApplicationSection"},
{511, nullptr, "WritePalmaApplicationSection"},
{512, nullptr, "ReadPalmaUniqueCode"},
{513, nullptr, "SetPalmaUniqueCodeInvalid"},
{508, nullptr, "ResetPalmaStep"},
{509, nullptr, "ReadPalmaApplicationSection"},
{510, nullptr, "WritePalmaApplicationSection"},
{511, nullptr, "ReadPalmaUniqueCode"},
{512, nullptr, "SetPalmaUniqueCodeInvalid"},
{513, nullptr, "WritePalmaActivityEntry"},
{514, nullptr, "WritePalmaRgbLedPatternEntry"},
{515, nullptr, "WritePalmaWaveEntry"},
{516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
{517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
{518, nullptr, "SuspendPalmaFeature"},
{519, nullptr, "GetPalmaOperationResult"},
{520, nullptr, "ReadPalmaPlayLog"},
{521, nullptr, "ResetPalmaPlayLog"},
{522, nullptr, "SetIsPalmaAllConnectable"},
{523, nullptr, "SetIsPalmaPairedConnectable"},
{524, nullptr, "PairPalma"},
{525, nullptr, "SetPalmaBoostMode"},
{1000, nullptr, "SetNpadCommunicationMode"},
{1001, nullptr, "GetNpadCommunicationMode"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle");
}
~Hid() = default;
private:
std::shared_ptr<IAppletResource> applet_resource;
u32 joy_hold_type{0};
Kernel::SharedPtr<Kernel::Event> event;
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
if (applet_resource == nullptr) {
@@ -423,31 +311,59 @@ private:
LOG_DEBUG(Service_HID, "called");
}
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
void ActivateXpad(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::XPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void ActivateMouse(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void ActivateGesture(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::Gesture);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
// Should have no effect with how our npad sets up the data
applet_resource->ActivateController(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto handle = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -468,84 +384,168 @@ private:
}
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto supported_styleset = rp.PopRaw<u32>();
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedStyleSet({supported_styleset});
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.Push<u32>(controller.GetSupportedStyleSet().raw);
LOG_DEBUG(Service_HID, "called");
}
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
applet_resource->ActivateController(HidController::NPad);
LOG_DEBUG(Service_HID, "called");
}
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetStyleSetChangedEvent());
LOG_DEBUG(Service_HID, "called");
}
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto npad_id = rp.PopRaw<u32>();
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.DisconnectNPad(npad_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
IPC::RequestParser rp{ctx};
auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetLedPattern(npad_id)
.raw);
LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::RequestParser rp{ctx};
const auto hold_type = rp.PopRaw<u64>();
controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
const auto& controller =
applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(joy_hold_type);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetVibrationEnabled(true);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetVibrationEnabled(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto controller_id = rp.PopRaw<u32>();
const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.VibrateController({controller_id}, {vibration_values});
LOG_DEBUG(Service_HID, "called");
}
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
const auto controllers = ctx.ReadBuffer(0);
const auto vibrations = ctx.ReadBuffer(1);
std::vector<u32> controller_list(controllers.size() / sizeof(u32));
std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
sizeof(Controller_NPad::Vibration));
std::memcpy(controller_list.data(), controllers.data(), controllers.size());
std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
[](u32 controller_id) { return controller_id - 3; });
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.VibrateController(controller_list, vibration_list);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_HID, "called");
}
void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.PushRaw<Controller_NPad::Vibration>(
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetLastVibration());
LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id = rp.PopRaw<u32>();
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
LOG_DEBUG(Service_HID, "called");
}
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
@@ -555,6 +555,8 @@ private:
}
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto mode = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -563,8 +565,9 @@ private:
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
LOG_WARNING(Service_HID, "(STUBBED) called");
rb.Push<u32>(1);
rb.Push<u32>(0);
LOG_DEBUG(Service_HID, "called");
}
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
@@ -574,12 +577,6 @@ private:
LOG_DEBUG(Service_HID, "called");
}
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -597,18 +594,6 @@ private:
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateGesture(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
};
class HidDbg final : public ServiceFramework<HidDbg> {
@@ -650,6 +635,7 @@ public:
{140, nullptr, "DeactivateConsoleSixAxisSensor"},
{141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"},
{142, nullptr, "DeactivateSevenSixAxisSensor"},
{143, nullptr, "GetConsoleSixAxisSensorCountStates"},
{201, nullptr, "ActivateFirmwareUpdate"},
{202, nullptr, "DeactivateFirmwareUpdate"},
{203, nullptr, "StartFirmwareUpdate"},
@@ -660,12 +646,23 @@ public:
{208, nullptr, "StartFirmwareUpdateForRevert"},
{209, nullptr, "GetAvailableFirmwareVersionForRevert"},
{210, nullptr, "IsFirmwareUpdatingDevice"},
{211, nullptr, "StartFirmwareUpdateIndividual"},
{215, nullptr, "SetUsbFirmwareForceUpdateEnabled"},
{216, nullptr, "SetAllKuinaDevicesToFirmwareUpdateMode"},
{221, nullptr, "UpdateControllerColor"},
{222, nullptr, "ConnectUsbPadsAsync"},
{223, nullptr, "DisconnectUsbPadsAsync"},
{224, nullptr, "UpdateDesignInfo"},
{225, nullptr, "GetUniquePadDriverState"},
{226, nullptr, "GetSixAxisSensorDriverStates"},
{227, nullptr, "GetRxPacketHistory"},
{228, nullptr, "AcquireOperationEventHandle"},
{229, nullptr, "ReadSerialFlash"},
{230, nullptr, "WriteSerialFlash"},
{231, nullptr, "GetOperationResult"},
{232, nullptr, "EnableShipmentMode"},
{233, nullptr, "ClearPairingInfo"},
{234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
{301, nullptr, "GetAbstractedPadHandles"},
{302, nullptr, "GetAbstractedPadState"},
{303, nullptr, "GetAbstractedPadsState"},
@@ -673,6 +670,8 @@ public:
{322, nullptr, "UnsetAutoPilotVirtualPadState"},
{323, nullptr, "UnsetAllAutoPilotVirtualPadState"},
{350, nullptr, "AddRegisteredDevice"},
{400, nullptr, "DisableExternalMcuOnNxDevice"},
{401, nullptr, "DisableRailDeviceFiltering"},
};
// clang-format on
@@ -708,7 +707,9 @@ public:
{307, nullptr, "GetNpadSystemExtStyle"},
{308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
{309, nullptr, "GetNpadFullKeyGripColor"},
{310, nullptr, "GetMaskedSupportedNpadStyleSet"},
{311, nullptr, "SetNpadPlayerLedBlinkingDevice"},
{312, nullptr, "SetSupportedNpadStyleSetAll"},
{321, nullptr, "GetUniquePadsFromNpad"},
{322, nullptr, "GetIrSensorState"},
{323, nullptr, "GetXcdHandleForNpadWithIrSensor"},
@@ -733,6 +734,7 @@ public:
{546, nullptr, "AcquireDeviceRegisteredEventForControllerSupport"},
{547, nullptr, "GetAllowedBluetoothLinksCount"},
{548, nullptr, "GetRegisteredDevices"},
{549, nullptr, "GetConnectableRegisteredDevices"},
{700, nullptr, "ActivateUniquePad"},
{702, nullptr, "AcquireUniquePadConnectionEventHandle"},
{703, nullptr, "GetUniquePadIds"},
@@ -761,6 +763,7 @@ public:
{850, nullptr, "IsUsbFullKeyControllerEnabled"},
{851, nullptr, "EnableUsbFullKeyController"},
{852, nullptr, "IsUsbConnected"},
{870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
{900, nullptr, "ActivateInputDetector"},
{901, nullptr, "NotifyInputDetector"},
{1000, nullptr, "InitializeFirmwareUpdate"},
@@ -780,6 +783,12 @@ public:
{1052, nullptr, "CancelSixAxisSensorAccurateUserCalibration"},
{1053, nullptr, "GetSixAxisSensorAccurateUserCalibrationState"},
{1100, nullptr, "GetHidbusSystemServiceObject"},
{1120, nullptr, "SetFirmwareHotfixUpdateSkipEnabled"},
{1130, nullptr, "InitializeUsbFirmwareUpdate"},
{1131, nullptr, "FinalizeUsbFirmwareUpdate"},
{1132, nullptr, "CheckUsbFirmwareUpdateRequired"},
{1133, nullptr, "StartUsbFirmwareUpdate"},
{1134, nullptr, "GetUsbFirmwareUpdateState"},
};
// clang-format on

View File

@@ -4,408 +4,12 @@
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/service/service.h"
namespace SM {
class ServiceManager;
}
namespace Service::HID {
// Begin enums and output structs
constexpr u32 HID_NUM_ENTRIES = 17;
constexpr u32 HID_NUM_LAYOUTS = 7;
constexpr s32 HID_JOYSTICK_MAX = 0x8000;
constexpr s32 HID_JOYSTICK_MIN = -0x8000;
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
enum ControllerType : u32 {
ControllerType_ProController = 1 << 0,
ControllerType_Handheld = 1 << 1,
ControllerType_JoyconPair = 1 << 2,
ControllerType_JoyconLeft = 1 << 3,
ControllerType_JoyconRight = 1 << 4,
};
enum ControllerLayoutType : u32 {
Layout_ProController = 0, // Pro Controller or HID gamepad
Layout_Handheld = 1, // Two Joy-Con docked to rails
Layout_Single = 2, // Horizontal single Joy-Con or pair of Joy-Con, adjusted for orientation
Layout_Left = 3, // Only raw left Joy-Con state, no orientation adjustment
Layout_Right = 4, // Only raw right Joy-Con state, no orientation adjustment
Layout_DefaultDigital = 5, // Same as next, but sticks have 8-direction values only
Layout_Default = 6, // Safe default, single Joy-Con have buttons/sticks rotated for orientation
};
enum ControllerColorDescription {
ColorDesc_ColorsNonexistent = 1 << 1,
};
enum ControllerConnectionState {
ConnectionState_Connected = 1 << 0,
ConnectionState_Wired = 1 << 1,
};
enum ControllerJoystick {
Joystick_Left = 0,
Joystick_Right = 1,
};
enum ControllerID {
Controller_Player1 = 0,
Controller_Player2 = 1,
Controller_Player3 = 2,
Controller_Player4 = 3,
Controller_Player5 = 4,
Controller_Player6 = 5,
Controller_Player7 = 6,
Controller_Player8 = 7,
Controller_Handheld = 8,
Controller_Unknown = 9,
};
// End enums and output structs
// Begin UnkInput3
struct UnkInput3Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
struct UnkInput3Entry {
u64 timestamp;
u64 timestamp_2;
u64 unk_8;
u64 unk_10;
u64 unk_18;
};
static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
struct UnkInput3 {
UnkInput3Header header;
std::array<UnkInput3Entry, 17> entries;
std::array<u8, 0x138> padding;
};
static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
// End UnkInput3
// Begin TouchScreen
struct TouchScreenHeader {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
u64 timestamp;
};
static_assert(sizeof(TouchScreenHeader) == 0x28,
"HID touch screen header structure has incorrect size");
struct TouchScreenEntryHeader {
u64 timestamp;
u64 num_touches;
};
static_assert(sizeof(TouchScreenEntryHeader) == 0x10,
"HID touch screen entry header structure has incorrect size");
struct TouchScreenEntryTouch {
u64 timestamp;
u32 padding;
u32 touch_index;
u32 x;
u32 y;
u32 diameter_x;
u32 diameter_y;
u32 angle;
u32 padding_2;
};
static_assert(sizeof(TouchScreenEntryTouch) == 0x28,
"HID touch screen touch structure has incorrect size");
struct TouchScreenEntry {
TouchScreenEntryHeader header;
std::array<TouchScreenEntryTouch, 16> touches;
u64 unk;
};
static_assert(sizeof(TouchScreenEntry) == 0x298,
"HID touch screen entry structure has incorrect size");
struct TouchScreen {
TouchScreenHeader header;
std::array<TouchScreenEntry, 17> entries;
std::array<u8, 0x3c0> padding;
};
static_assert(sizeof(TouchScreen) == 0x3000, "HID touch screen structure has incorrect size");
// End TouchScreen
// Begin Mouse
struct MouseHeader {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(MouseHeader) == 0x20, "HID mouse header structure has incorrect size");
struct MouseButtonState {
union {
u64 hex{};
// Buttons
BitField<0, 1, u64> left;
BitField<1, 1, u64> right;
BitField<2, 1, u64> middle;
BitField<3, 1, u64> forward;
BitField<4, 1, u64> back;
};
};
struct MouseEntry {
u64 timestamp;
u64 timestamp_2;
u32 x;
u32 y;
u32 velocity_x;
u32 velocity_y;
u32 scroll_velocity_x;
u32 scroll_velocity_y;
MouseButtonState buttons;
};
static_assert(sizeof(MouseEntry) == 0x30, "HID mouse entry structure has incorrect size");
struct Mouse {
MouseHeader header;
std::array<MouseEntry, 17> entries;
std::array<u8, 0xB0> padding;
};
static_assert(sizeof(Mouse) == 0x400, "HID mouse structure has incorrect size");
// End Mouse
// Begin Keyboard
struct KeyboardHeader {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(KeyboardHeader) == 0x20, "HID keyboard header structure has incorrect size");
struct KeyboardModifierKeyState {
union {
u64 hex{};
// Buttons
BitField<0, 1, u64> lctrl;
BitField<1, 1, u64> lshift;
BitField<2, 1, u64> lalt;
BitField<3, 1, u64> lmeta;
BitField<4, 1, u64> rctrl;
BitField<5, 1, u64> rshift;
BitField<6, 1, u64> ralt;
BitField<7, 1, u64> rmeta;
BitField<8, 1, u64> capslock;
BitField<9, 1, u64> scrolllock;
BitField<10, 1, u64> numlock;
};
};
struct KeyboardEntry {
u64 timestamp;
u64 timestamp_2;
KeyboardModifierKeyState modifier;
u32 keys[8];
};
static_assert(sizeof(KeyboardEntry) == 0x38, "HID keyboard entry structure has incorrect size");
struct Keyboard {
KeyboardHeader header;
std::array<KeyboardEntry, 17> entries;
std::array<u8, 0x28> padding;
};
static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect size");
// End Keyboard
// Begin UnkInput1
struct UnkInput1Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
struct UnkInput1Entry {
u64 timestamp;
u64 timestamp_2;
u64 unk_8;
u64 unk_10;
u64 unk_18;
};
static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
struct UnkInput1 {
UnkInput1Header header;
std::array<UnkInput1Entry, 17> entries;
std::array<u8, 0x138> padding;
};
static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
// End UnkInput1
// Begin UnkInput2
struct UnkInput2Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
struct UnkInput2 {
UnkInput2Header header;
std::array<u8, 0x1E0> padding;
};
static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
// End UnkInput2
// Begin Controller
struct ControllerMAC {
u64 timestamp;
std::array<u8, 0x8> mac;
u64 unk;
u64 timestamp_2;
};
static_assert(sizeof(ControllerMAC) == 0x20, "HID controller MAC structure has incorrect size");
struct ControllerHeader {
u32 type;
u32 is_half;
u32 single_colors_descriptor;
u32 single_color_body;
u32 single_color_buttons;
u32 split_colors_descriptor;
u32 left_color_body;
u32 left_color_buttons;
u32 right_color_body;
u32 right_color_buttons;
};
static_assert(sizeof(ControllerHeader) == 0x28,
"HID controller header structure has incorrect size");
struct ControllerLayoutHeader {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(ControllerLayoutHeader) == 0x20,
"HID controller layout header structure has incorrect size");
struct ControllerPadState {
union {
u64 hex{};
// Buttons
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> lstick;
BitField<5, 1, u64> rstick;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-pad buttons
BitField<12, 1, u64> dleft;
BitField<13, 1, u64> dup;
BitField<14, 1, u64> dright;
BitField<15, 1, u64> ddown;
// Left stick directions
BitField<16, 1, u64> lstick_left;
BitField<17, 1, u64> lstick_up;
BitField<18, 1, u64> lstick_right;
BitField<19, 1, u64> lstick_down;
// Right stick directions
BitField<20, 1, u64> rstick_left;
BitField<21, 1, u64> rstick_up;
BitField<22, 1, u64> rstick_right;
BitField<23, 1, u64> rstick_down;
BitField<24, 1, u64> sl;
BitField<25, 1, u64> sr;
};
};
struct ControllerInputEntry {
u64 timestamp;
u64 timestamp_2;
ControllerPadState buttons;
s32 joystick_left_x;
s32 joystick_left_y;
s32 joystick_right_x;
s32 joystick_right_y;
u64 connection_state;
};
static_assert(sizeof(ControllerInputEntry) == 0x30,
"HID controller input entry structure has incorrect size");
struct ControllerLayout {
ControllerLayoutHeader header;
std::array<ControllerInputEntry, 17> entries;
};
static_assert(sizeof(ControllerLayout) == 0x350,
"HID controller layout structure has incorrect size");
struct Controller {
ControllerHeader header;
std::array<ControllerLayout, HID_NUM_LAYOUTS> layouts;
std::array<u8, 0x2a70> unk_1;
ControllerMAC mac_left;
ControllerMAC mac_right;
std::array<u8, 0xdf8> unk_2;
};
static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorrect size");
// End Controller
struct SharedMemory {
UnkInput3 unk_input_3;
TouchScreen touchscreen;
Mouse mouse;
Keyboard keyboard;
std::array<UnkInput1, 4> unk_input_1;
std::array<UnkInput2, 3> unk_input_2;
std::array<u8, 0x800> unk_section_8;
std::array<u8, 0x4000> controller_serials;
std::array<Controller, 10> controllers;
std::array<u8, 0x4600> unk_section_9;
};
static_assert(sizeof(SharedMemory) == 0x40000, "HID Shared Memory structure has incorrect size");
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();

View File

@@ -14,14 +14,14 @@ public:
explicit MM_U() : ServiceFramework{"mm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MM_U::Initialize, "InitializeOld"},
{1, &MM_U::Finalize, "FinalizeOld"},
{2, &MM_U::SetAndWait, "SetAndWaitOld"},
{3, &MM_U::Get, "GetOld"},
{4, &MM_U::Initialize, "Initialize"},
{5, &MM_U::Finalize, "Finalize"},
{6, &MM_U::SetAndWait, "SetAndWait"},
{7, &MM_U::Get, "Get"},
{0, &MM_U::Initialize, "Initialize"},
{1, &MM_U::Finalize, "Finalize"},
{2, &MM_U::SetAndWait, "SetAndWait"},
{3, &MM_U::Get, "Get"},
{4, &MM_U::InitializeWithId, "InitializeWithId"},
{5, &MM_U::FinalizeWithId, "FinalizeWithId"},
{6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"},
{7, &MM_U::GetWithId, "GetWithId"},
};
// clang-format on
@@ -59,9 +59,43 @@ private:
rb.Push(current);
}
void InitializeWithId(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(id); // Any non zero value
}
void FinalizeWithId(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetAndWaitWithId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 input_id = rp.Pop<u32>();
min = rp.Pop<u32>();
max = rp.Pop<u32>();
current = min;
LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}",
input_id, min, max);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetWithId(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_MM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(current);
}
u32 min{0};
u32 max{0};
u32 current{0};
u32 id{1};
};
void InstallInterfaces(SM::ServiceManager& service_manager) {

View File

@@ -144,7 +144,7 @@ private:
}
const u64 device_handle{0xDEAD};
const HID::ControllerID npad_id{HID::Controller_Player1};
const u32 npad_id{0}; // This is the first player controller id
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
Kernel::SharedPtr<Kernel::Event> activate_event;

View File

@@ -219,6 +219,7 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
{35, nullptr, "GetScanData"},
{36, nullptr, "GetCurrentAccessPoint"},
{37, nullptr, "Shutdown"},
{38, nullptr, "GetAllowedChannels"},
};
RegisterHandlers(functions);
}

View File

@@ -71,6 +71,22 @@ public:
}
};
class NIM_ECA final : public ServiceFramework<NIM_ECA> {
public:
explicit NIM_ECA() : ServiceFramework{"nim:eca"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateServerInterface"},
{1, nullptr, "RefreshDebugAvailability"},
{2, nullptr, "ClearDebugResponse"},
{3, nullptr, "RegisterDebugResponse"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class NIM_SHP final : public ServiceFramework<NIM_SHP> {
public:
explicit NIM_SHP() : ServiceFramework{"nim:shp"} {
@@ -214,6 +230,7 @@ private:
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<NIM>()->InstallAsService(sm);
std::make_shared<NIM_ECA>()->InstallAsService(sm);
std::make_shared<NIM_SHP>()->InstallAsService(sm);
std::make_shared<NTC>()->InstallAsService(sm);
}

View File

@@ -93,13 +93,23 @@ public:
{86, nullptr, "EnableApplicationCrashReport"},
{87, nullptr, "IsApplicationCrashReportEnabled"},
{90, nullptr, "BoostSystemMemoryResourceLimit"},
{91, nullptr, "Unknown1"},
{92, nullptr, "Unknown2"},
{93, nullptr, "GetMainApplicationProgramIndex"},
{94, nullptr, "LaunchApplication2"},
{95, nullptr, "GetApplicationLaunchInfo"},
{96, nullptr, "AcquireApplicationLaunchInfo"},
{97, nullptr, "GetMainApplicationProgramIndex2"},
{98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{100, nullptr, "ResetToFactorySettings"},
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
{200, nullptr, "CalculateUserSaveDataStatistics"},
{201, nullptr, "DeleteUserSaveDataAll"},
{210, nullptr, "DeleteUserSystemSaveData"},
{211, nullptr, "DeleteSaveData"},
{220, nullptr, "UnregisterNetworkServiceAccount"},
{221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
{300, nullptr, "GetApplicationShellEvent"},
{301, nullptr, "PopApplicationShellEventInfo"},
{302, nullptr, "LaunchLibraryApplet"},
@@ -114,6 +124,7 @@ public:
{403, nullptr, "GetMaxApplicationControlCacheCount"},
{404, nullptr, "InvalidateApplicationControlCache"},
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
{406, nullptr, "GetApplicationControlProperty"},
{502, nullptr, "RequestCheckGameCardRegistration"},
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
{504, nullptr, "RequestRegisterGameCard"},
@@ -129,6 +140,7 @@ public:
{604, nullptr, "RegisterContentsExternalKey"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
{606, nullptr, "GetContentMetaStorage"},
{607, nullptr, "ListAvailableAddOnContent"},
{700, nullptr, "PushDownloadTaskList"},
{701, nullptr, "ClearTaskStatusList"},
{702, nullptr, "RequestDownloadTaskList"},
@@ -148,6 +160,9 @@ public:
{907, nullptr, "WithdrawApplicationUpdateRequest"},
{908, nullptr, "ListApplicationRecordInstalledContentMeta"},
{909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
{910, nullptr, "Unknown3"},
{911, nullptr, "SetPreInstalledApplication"},
{912, nullptr, "ClearPreInstalledApplicationFlag"},
{1000, nullptr, "RequestVerifyApplicationDeprecated"},
{1001, nullptr, "CorruptApplicationForDebug"},
{1002, nullptr, "RequestVerifyAddOnContentsRights"},
@@ -162,6 +177,8 @@ public:
{1305, nullptr, "TryDeleteRunningApplicationEntity"},
{1306, nullptr, "TryDeleteRunningApplicationCompletely"},
{1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
{1308, nullptr, "DeleteApplicationCompletelyForDebug"},
{1309, nullptr, "CleanupUnavailableAddOnContents"},
{1400, nullptr, "PrepareShutdown"},
{1500, nullptr, "FormatSdCard"},
{1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
@@ -199,6 +216,28 @@ public:
{2015, nullptr, "CompareSystemDeliveryInfo"},
{2016, nullptr, "ListNotCommittedContentMeta"},
{2017, nullptr, "CreateDownloadTask"},
{2018, nullptr, "Unknown4"},
{2050, nullptr, "Unknown5"},
{2100, nullptr, "Unknown6"},
{2101, nullptr, "Unknown7"},
{2150, nullptr, "CreateRightsEnvironment"},
{2151, nullptr, "DestroyRightsEnvironment"},
{2152, nullptr, "ActivateRightsEnvironment"},
{2153, nullptr, "DeactivateRightsEnvironment"},
{2154, nullptr, "ForceActivateRightsContextForExit"},
{2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
{2161, nullptr, "SetUsersToRightsEnvironment"},
{2170, nullptr, "GetRightsEnvironmentStatus"},
{2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
{2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
{2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
{2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
{2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
{2199, nullptr, "GetRightsEnvironmentCountForDebug"},
{2200, nullptr, "Unknown8"},
{2201, nullptr, "Unknown9"},
{2250, nullptr, "Unknown10"},
{2300, nullptr, "Unknown11"},
};
// clang-format on
@@ -348,12 +387,15 @@ public:
{0, nullptr, "LaunchProgram"},
{1, nullptr, "TerminateProcess"},
{2, nullptr, "TerminateProgram"},
{3, nullptr, "GetShellEventHandle"},
{4, nullptr, "GetShellEventInfo"},
{5, nullptr, "TerminateApplication"},
{6, nullptr, "PrepareLaunchProgramFromHost"},
{7, nullptr, "LaunchApplication"},
{8, nullptr, "LaunchApplicationWithStorageId"},
{4, nullptr, "GetShellEventHandle"},
{5, nullptr, "GetShellEventInfo"},
{6, nullptr, "TerminateApplication"},
{7, nullptr, "PrepareLaunchProgramFromHost"},
{8, nullptr, "LaunchApplication"},
{9, nullptr, "LaunchApplicationWithStorageId"},
{10, nullptr, "TerminateApplication2"},
{11, nullptr, "GetRunningApplicationProcessId"},
{12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActive"},
};
// clang-format on
@@ -388,6 +430,7 @@ public:
{19, nullptr, "GetReceivedEulaDataSize"},
{20, nullptr, "GetReceivedEulaData"},
{21, nullptr, "SetupToReceiveSystemUpdate"},
{22, nullptr, "RequestCheckLatestUpdateIncludesRebootlessUpdate"},
};
// clang-format on

View File

@@ -39,7 +39,8 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
{29, nullptr, "GetAmiiboEcqvBlsKey"},
{30, nullptr, "GetAmiiboEcqvBlsCertificate"},
{31, nullptr, "GetAmiiboEcqvBlsRootCertificate"},
{32, nullptr, "GetUnknownId"},
{32, nullptr, "GetUsbTypeCPowerSourceCircuitVersion"},
{33, nullptr, "GetBatteryVersion"},
};
RegisterHandlers(functions);
}

View File

@@ -59,8 +59,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
return xci->GetProgramNCAStatus();
const auto nca = xci->GetProgramNCA();
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
const auto result = nca_loader->Load(process);

View File

@@ -136,7 +136,7 @@ struct Values {
float resolution_factor;
bool use_frame_limit;
u16 frame_limit;
bool use_accurate_framebuffers;
bool use_accurate_gpu_emulation;
float bg_red;
float bg_green;

View File

@@ -163,8 +163,8 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
Settings::values.use_frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
Settings::values.use_accurate_framebuffers);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation",
Settings::values.use_accurate_gpu_emulation);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
Settings::values.use_docked_mode);
}

View File

@@ -448,7 +448,10 @@ public:
BitField<8, 3, u32> block_depth;
BitField<12, 1, InvMemoryLayout> type;
} memory_layout;
u32 array_mode;
union {
BitField<0, 16, u32> array_mode;
BitField<16, 1, u32> volume;
};
u32 layer_stride;
u32 base_layer;
INSERT_PADDING_WORDS(7);

View File

@@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
return gpu_addr;
}
GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
for (const auto& region : mapped_regions) {
const GPUVAddr region_end{region.gpu_addr + region.size};
if (region_start >= region.gpu_addr && region_start < region_end) {
return region_end;
}
}
return {};
}
boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
GPUVAddr gpu_addr = 0;
u64 free_space = 0;

View File

@@ -26,6 +26,7 @@ public:
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;

View File

@@ -11,32 +11,77 @@
#include "common/common_types.h"
#include "core/core.h"
#include "core/settings.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
class RasterizerCacheObject {
public:
/// Gets the address of the shader in guest memory, required for cache management
virtual VAddr GetAddr() const = 0;
/// Gets the size of the shader in guest memory, required for cache management
virtual std::size_t GetSizeInBytes() const = 0;
/// Wriets any cached resources back to memory
virtual void Flush() = 0;
/// Sets whether the cached object should be considered registered
void SetIsRegistered(bool registered) {
is_registered = registered;
}
/// Returns true if the cached object is registered
bool IsRegistered() const {
return is_registered;
}
/// Returns true if the cached object is dirty
bool IsDirty() const {
return is_dirty;
}
/// Returns ticks from when this cached object was last modified
u64 GetLastModifiedTicks() const {
return last_modified_ticks;
}
/// Marks an object as recently modified, used to specify whether it is clean or dirty
template <class T>
void MarkAsModified(bool dirty, T& cache) {
is_dirty = dirty;
last_modified_ticks = cache.GetModifiedTicks();
}
private:
bool is_registered{}; ///< Whether the object is currently registered with the cache
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
};
template <class T>
class RasterizerCache : NonCopyable {
friend class RasterizerCacheObject;
public:
/// Write any cached resources overlapping the specified region back to memory
void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
FlushObject(object);
}
}
/// Mark the specified region as being invalidated
void InvalidateRegion(VAddr addr, u64 size) {
if (size == 0)
return;
const ObjectInterval interval{addr, addr + size};
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
for (auto& cached_object : pair.second) {
if (!cached_object)
continue;
remove_objects.emplace(cached_object);
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
if (!object->IsRegistered()) {
// Skip duplicates
continue;
}
Unregister(object);
}
for (auto& remove_object : remove_objects) {
Unregister(remove_object);
}
remove_objects.clear();
}
/// Invalidates everything in the cache
@@ -62,6 +107,7 @@ protected:
/// Register an object into the cache
void Register(const T& object) {
object->SetIsRegistered(true);
object_cache.add({GetInterval(object), ObjectSet{object}});
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
@@ -69,12 +115,57 @@ protected:
/// Unregisters an object from the cache
void Unregister(const T& object) {
object->SetIsRegistered(false);
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
if (Settings::values.use_accurate_gpu_emulation) {
FlushObject(object);
}
object_cache.subtract({GetInterval(object), ObjectSet{object}});
}
/// Returns a ticks counter used for tracking when cached objects were last modified
u64 GetModifiedTicks() {
return ++modified_ticks;
}
private:
/// Returns a list of cached objects from the specified memory region, ordered by access time
std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
if (size == 0) {
return {};
}
std::vector<T> objects;
const ObjectInterval interval{addr, addr + size};
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
for (auto& cached_object : pair.second) {
if (!cached_object) {
continue;
}
objects.push_back(cached_object);
}
}
std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
});
return objects;
}
/// Flushes the specified object, updating appropriate cache state as needed
void FlushObject(const T& object) {
if (!object->IsDirty()) {
return;
}
object->Flush();
object->MarkAsModified(false, *this);
}
using ObjectSet = std::set<T>;
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
using ObjectInterval = typename ObjectCache::interval_type;
@@ -84,6 +175,6 @@ private:
object->GetAddr() + object->GetSizeInBytes());
}
ObjectCache object_cache;
ObjectSet remove_objects;
ObjectCache object_cache; ///< Cache of objects
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
};

View File

@@ -15,15 +15,18 @@
namespace OpenGL {
struct CachedBufferEntry final {
VAddr GetAddr() const {
struct CachedBufferEntry final : public RasterizerCacheObject {
VAddr GetAddr() const override {
return addr;
}
std::size_t GetSizeInBytes() const {
std::size_t GetSizeInBytes() const override {
return size;
}
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
VAddr addr;
std::size_t size;
GLintptr offset;

View File

@@ -424,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
Surface color_surface =
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
if (color_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even if
// the shader doesn't actually write to it.
color_surface->MarkAsModified(true, res_cache);
}
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
@@ -434,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
if (color_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even
// if the shader doesn't actually write to it.
color_surface->MarkAsModified(true, res_cache);
}
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
@@ -453,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
}
if (depth_surface) {
// Assume that a surface will be written to if it is used as a framebuffer, even if
// the shader doesn't actually write to it.
depth_surface->MarkAsModified(true, res_cache);
if (regs.stencil_enable) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
@@ -617,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
if (Settings::values.use_accurate_gpu_emulation) {
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
res_cache.FlushRegion(addr, size);
}
}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
@@ -627,6 +652,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
FlushRegion(addr, size);
InvalidateRegion(addr, size);
}

View File

@@ -34,16 +34,53 @@ struct FormatTuple {
bool compressed;
};
static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
auto& gpu{Core::System::GetInstance().GPU()};
const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
return cpu_addr ? *cpu_addr : 0;
static bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
return true;
default:
return false;
}
}
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
return {4, 4};
case PixelFormat::ASTC_2D_5X4:
return {5, 4};
case PixelFormat::ASTC_2D_8X8:
return {8, 8};
case PixelFormat::ASTC_2D_8X5:
return {8, 5};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
}
}
void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
addr = cpu_addr ? *cpu_addr : 0;
gpu_addr = gpu_addr_;
size_in_bytes = SizeInBytesRaw();
if (IsPixelFormatASTC(pixel_format)) {
// ASTC is uncompressed in software, in emulated as RGBA8
size_in_bytes_gl = width * height * depth * 4;
} else {
size_in_bytes_gl = SizeInBytesGL();
}
}
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.tic.Address());
params.is_tiled = config.tic.IsTiled();
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
@@ -87,18 +124,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
break;
}
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = config.tic.max_mip_level + 1;
params.rt = {};
params.InitCacheParameters(config.tic.Address());
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) {
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.Address());
params.is_tiled =
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << config.memory_layout.block_width;
@@ -112,16 +149,17 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
// Render target specific parameters, not used for caching
params.rt.index = static_cast<u32>(index);
params.rt.array_mode = config.array_mode;
params.rt.layer_stride = config.layer_stride;
params.rt.volume = config.volume;
params.rt.base_layer = config.base_layer;
params.InitCacheParameters(config.Address());
return params;
}
@@ -130,7 +168,7 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(zeta_address);
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << std::min(block_width, 5U);
params.block_height = 1 << std::min(block_height, 5U);
@@ -143,18 +181,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = zeta_height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
params.InitCacheParameters(zeta_address);
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
SurfaceParams params{};
params.addr = TryGetCpuAddr(config.Address());
params.is_tiled = !config.linear;
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
@@ -167,11 +205,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
params.size_in_bytes_total = params.SizeInBytesTotal();
params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
params.InitCacheParameters(config.Address());
return params;
}
@@ -276,34 +314,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
static bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_5X4:
case PixelFormat::ASTC_2D_8X8:
case PixelFormat::ASTC_2D_8X5:
return true;
default:
return false;
}
}
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
return {4, 4};
case PixelFormat::ASTC_2D_5X4:
return {5, 4};
case PixelFormat::ASTC_2D_8X8:
return {8, 8};
case PixelFormat::ASTC_2D_8X5:
return {8, 5};
default:
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
UNREACHABLE();
}
}
MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
u32 actual_height{unaligned_height};
if (IsPixelFormatASTC(pixel_format)) {
@@ -333,23 +343,21 @@ static bool IsFormatBCn(PixelFormat format) {
template <bool morton_to_gl, PixelFormat format>
void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer,
std::size_t gl_buffer_size, VAddr addr) {
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format);
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
// pixel values.
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
if (morton_to_gl) {
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
// pixel values.
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth);
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
memcpy(gl_buffer, data.data(), size_to_copy);
} else {
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
// check the configuration for this and perform more generic un/swizzle
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
Memory::GetPointer(addr), gl_buffer, morton_to_gl);
Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth,
bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
gl_buffer, false, block_height, block_depth);
}
}
@@ -430,17 +438,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
MortonCopy<false, PixelFormat::RGBA16UI>,
MortonCopy<false, PixelFormat::R11FG11FB10F>,
MortonCopy<false, PixelFormat::RGBA32UI>,
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
// formats are not supported
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
MortonCopy<false, PixelFormat::DXT1>,
MortonCopy<false, PixelFormat::DXT23>,
MortonCopy<false, PixelFormat::DXT45>,
MortonCopy<false, PixelFormat::DXN1>,
MortonCopy<false, PixelFormat::DXN2UNORM>,
MortonCopy<false, PixelFormat::DXN2SNORM>,
MortonCopy<false, PixelFormat::BC7U>,
MortonCopy<false, PixelFormat::BC6H_UF16>,
MortonCopy<false, PixelFormat::BC6H_SF16>,
// TODO(Subv): Swizzling ASTC formats are not supported
nullptr,
MortonCopy<false, PixelFormat::G8R8U>,
MortonCopy<false, PixelFormat::G8R8S>,
@@ -626,22 +633,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type);
auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type);
std::size_t buffer_size =
std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total);
std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr);
static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
} else {
glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format,
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total),
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes),
nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) {
if (src_params.size_in_bytes < dst_params.size_in_bytes) {
// Upload the rest of the memory.
if (dst_params.is_tiled) {
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
@@ -651,12 +657,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
"reinterpretation but the texture is tiled.");
}
std::size_t remaining_size =
dst_params.size_in_bytes_total - src_params.size_in_bytes_total;
std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
std::vector<u8> data(remaining_size);
Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(),
data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size,
std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes),
data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
data.data());
}
@@ -702,7 +708,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
}
CachedSurface::CachedSurface(const SurfaceParams& params)
: params(params), gl_target(SurfaceTargetToGL(params.target)) {
: params(params), gl_target(SurfaceTargetToGL(params.target)),
cached_size_in_bytes(params.size_in_bytes) {
texture.Create();
const auto& rect{params.GetRect()};
@@ -752,9 +759,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
SurfaceParams::SurfaceTargetName(params.target));
// Clamp size to mapped GPU memory region
// TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
// R32F render buffer. We do not yet know if this is a game bug or something else, but this
// check is necessary to prevent flushing from overwriting unmapped memory.
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
if (cached_size_in_bytes > max_size) {
LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
cached_size_in_bytes = max_size;
}
}
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) {
union S8Z24 {
BitField<0, 24, u32> z24;
BitField<24, 8, u32> s8;
@@ -767,22 +786,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
};
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
S8Z24 input_pixel{};
Z24S8 output_pixel{};
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
S8Z24 s8z24_pixel{};
Z24S8 z24s8_pixel{};
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
output_pixel.s8.Assign(input_pixel.s8);
output_pixel.z24.Assign(input_pixel.z24);
std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
if (reverse) {
std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
s8z24_pixel.s8.Assign(z24s8_pixel.s8);
s8z24_pixel.z24.Assign(z24s8_pixel.z24);
std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
} else {
std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
z24s8_pixel.s8.Assign(s8z24_pixel.s8);
z24s8_pixel.z24.Assign(s8z24_pixel.z24);
std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
}
}
}
}
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
@@ -814,7 +840,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
case PixelFormat::S8Z24:
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
ConvertS8Z24ToZ24S8(data, width, height);
ConvertS8Z24ToZ24S8(data, width, height, false);
break;
case PixelFormat::G8R8U:
@@ -825,22 +851,36 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
}
/**
* Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to
* Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or
* with typical desktop GPUs.
*/
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
u32 width, u32 height) {
switch (pixel_format) {
case PixelFormat::G8R8U:
case PixelFormat::G8R8S:
case PixelFormat::ASTC_2D_4X4:
case PixelFormat::ASTC_2D_8X8: {
LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
static_cast<u32>(pixel_format));
UNREACHABLE();
break;
}
case PixelFormat::S8Z24:
// Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24.
ConvertS8Z24ToZ24S8(data, width, height, true);
break;
}
}
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
void CachedSurface::LoadGLBuffer() {
ASSERT(params.type != SurfaceType::Fill);
const u8* const texture_src_data = Memory::GetPointer(params.addr);
ASSERT(texture_src_data);
const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
const u32 copy_size = params.width * params.height * bytes_per_pixel;
const std::size_t total_size = copy_size * params.depth;
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
gl_buffer.resize(params.size_in_bytes_gl);
if (params.is_tiled) {
gl_buffer.resize(total_size);
u32 depth = params.depth;
u32 block_depth = params.block_depth;
@@ -853,13 +893,12 @@ void CachedSurface::LoadGLBuffer() {
block_depth = 1U;
}
const std::size_t size = copy_size * depth;
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
size, params.addr);
gl_buffer.size(), params.addr);
} else {
const u8* const texture_src_data_end{texture_src_data + total_size};
const auto texture_src_data{Memory::GetPointer(params.addr)};
const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
gl_buffer.assign(texture_src_data, texture_src_data_end);
}
@@ -868,7 +907,44 @@ void CachedSurface::LoadGLBuffer() {
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer() {
ASSERT_MSG(false, "Unimplemented");
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
// OpenGL temporary buffer needs to be big enough to store raw texture size
gl_buffer.resize(GetSizeInBytes());
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
ASSERT(!tuple.compressed);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(),
gl_buffer.data());
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
params.height);
ASSERT(params.type != SurfaceType::Fill);
const u8* const texture_src_data = Memory::GetPointer(params.addr);
ASSERT(texture_src_data);
if (params.is_tiled) {
u32 depth = params.depth;
u32 block_depth = params.block_depth;
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
params.block_width, static_cast<u32>(params.target));
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
depth = 1U;
}
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
gl_buffer.size(), GetAddr());
} else {
std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes());
}
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
@@ -878,9 +954,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
MICROPROFILE_SCOPE(OpenGL_TextureUL);
ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height *
GetGLBytesPerPixel(params.pixel_format) * params.depth);
const auto& rect{params.GetRect()};
// Load data from memory to the surface
@@ -889,7 +962,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
std::size_t buffer_offset =
static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width +
static_cast<std::size_t>(x0)) *
GetGLBytesPerPixel(params.pixel_format);
SurfaceParams::GetBytesPerPixel(params.pixel_format);
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
const GLuint target_tex = texture.handle;
@@ -905,7 +978,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
glActiveTexture(GL_TEXTURE0);
@@ -915,7 +988,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::Texture3D:
case SurfaceParams::SurfaceTarget::Texture2DArray:
@@ -923,16 +996,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
static_cast<GLsizei>(params.depth), 0,
static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::TextureCubemap:
for (std::size_t face = 0; face < params.depth; ++face) {
glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face),
0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d),
static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()),
&gl_buffer[buffer_offset]);
buffer_offset += params.size_in_bytes_2d;
buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -942,7 +1015,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
}
} else {
@@ -971,7 +1044,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
&gl_buffer[buffer_offset]);
buffer_offset += params.size_in_bytes_2d;
buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -1033,10 +1106,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
surface->LoadGLBuffer();
surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
}
void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
surface->FlushGLBuffer();
surface->MarkAsModified(false, *this);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
@@ -1053,8 +1123,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
} else if (preserve_contents) {
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
Unregister(surface);
Surface new_surface{RecreateSurface(surface, params)};
Unregister(surface);
Register(new_surface);
return new_surface;
} else {
@@ -1105,6 +1175,14 @@ void RasterizerCacheOpenGL::FermiCopySurface(
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
}
void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
const Surface& dst_surface) {
const auto& src_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
FlushRegion(src_params.addr, dst_params.size_in_bytes);
LoadSurface(dst_surface);
}
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
@@ -1113,6 +1191,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// Get a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{GetUncachedSurface(new_params)};
// With use_accurate_gpu_emulation enabled, do an accurate surface copy
if (Settings::values.use_accurate_gpu_emulation) {
AccurateCopySurface(old_surface, new_surface);
return new_surface;
}
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
if (old_params.target == new_params.target && old_params.type == new_params.type &&
old_params.depth == new_params.depth && old_params.depth == 1 &&
@@ -1124,11 +1208,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// If the format is the same, just do a framebuffer blit. This is significantly faster than
// using PBOs. The is also likely less accurate, as textures will be converted rather than
// reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
// reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate
// surface copy, where pixels are reinterpreted as a new format (without conversion). This
// code path uses OpenGL PBOs and is quite slow.
const bool is_blit{old_params.pixel_format == new_params.pixel_format ||
!Settings::values.use_accurate_framebuffers};
const bool is_blit{old_params.pixel_format == new_params.pixel_format};
switch (new_params.target) {
case SurfaceParams::SurfaceTarget::Texture2D:
@@ -1138,6 +1221,9 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
case SurfaceParams::SurfaceTarget::Texture3D:
AccurateCopySurface(old_surface, new_surface);
break;
case SurfaceParams::SurfaceTarget::TextureCubemap: {
if (old_params.rt.array_mode != 1) {
// TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this

View File

@@ -18,6 +18,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
@@ -131,6 +132,8 @@ struct SurfaceParams {
case Tegra::Texture::TextureType::Texture2D:
case Tegra::Texture::TextureType::Texture2DNoMipmap:
return SurfaceTarget::Texture2D;
case Tegra::Texture::TextureType::Texture3D:
return SurfaceTarget::Texture3D;
case Tegra::Texture::TextureType::TextureCubemap:
return SurfaceTarget::TextureCubemap;
case Tegra::Texture::TextureType::Texture1DArray:
@@ -701,21 +704,42 @@ struct SurfaceParams {
return SurfaceType::Invalid;
}
/// Returns the sizer in bytes of the specified pixel format
static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
if (pixel_format == SurfaceParams::PixelFormat::Invalid) {
return 0;
}
return GetFormatBpp(pixel_format) / CHAR_BIT;
}
/// Returns the rectangle corresponding to this surface
MathUtil::Rectangle<u32> GetRect() const;
/// Returns the size of this surface as a 2D texture in bytes, adjusted for compression
std::size_t SizeInBytes2D() const {
/// Returns the total size of this surface in bytes, adjusted for compression
std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
const u32 compression_factor{GetCompressionFactor(pixel_format)};
ASSERT(width % compression_factor == 0);
ASSERT(height % compression_factor == 0);
return (width / compression_factor) * (height / compression_factor) *
GetFormatBpp(pixel_format) / CHAR_BIT;
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
const size_t uncompressed_size{
Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
height, depth, block_height, block_depth)};
// Divide by compression_factor^2, as height and width are factored by this
return uncompressed_size / (compression_factor * compression_factor);
}
/// Returns the total size of this surface in bytes, adjusted for compression
std::size_t SizeInBytesTotal() const {
return SizeInBytes2D() * depth;
/// Returns the size of this surface as an OpenGL texture in bytes
std::size_t SizeInBytesGL() const {
return SizeInBytesRaw(true);
}
/// Returns the size of this surface as a cube face in bytes
std::size_t SizeInBytesCubeFace() const {
return size_in_bytes / 6;
}
/// Returns the size of this surface as an OpenGL cube face in bytes
std::size_t SizeInBytesCubeFaceGL() const {
return size_in_bytes_gl / 6;
}
/// Creates SurfaceParams from a texture configuration
@@ -742,7 +766,9 @@ struct SurfaceParams {
other.depth);
}
VAddr addr;
/// Initializes parameters for caching, should be called after everything has been initialized
void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
bool is_tiled;
u32 block_width;
u32 block_height;
@@ -754,15 +780,20 @@ struct SurfaceParams {
u32 height;
u32 depth;
u32 unaligned_height;
std::size_t size_in_bytes_total;
std::size_t size_in_bytes_2d;
SurfaceTarget target;
u32 max_mip_level;
// Parameters used for caching
VAddr addr;
Tegra::GPUVAddr gpu_addr;
std::size_t size_in_bytes;
std::size_t size_in_bytes_gl;
// Render target specific parameters, not used in caching
struct {
u32 index;
u32 array_mode;
u32 volume;
u32 layer_stride;
u32 base_layer;
} rt;
@@ -775,7 +806,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
SurfaceReserveKey res;
res.state = params;
res.state.rt = {}; // Ignore rt config in caching
res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
res.state.rt = {}; // Ignore rt config in caching
return res;
}
};
@@ -790,16 +822,20 @@ struct hash<SurfaceReserveKey> {
namespace OpenGL {
class CachedSurface final {
class CachedSurface final : public RasterizerCacheObject {
public:
CachedSurface(const SurfaceParams& params);
VAddr GetAddr() const {
VAddr GetAddr() const override {
return params.addr;
}
std::size_t GetSizeInBytes() const {
return params.size_in_bytes_total;
std::size_t GetSizeInBytes() const override {
return cached_size_in_bytes;
}
void Flush() override {
FlushGLBuffer();
}
const OGLTexture& Texture() const {
@@ -810,13 +846,6 @@ public:
return gl_target;
}
static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
if (format == SurfaceParams::PixelFormat::Invalid)
return 0;
return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
}
const SurfaceParams& GetSurfaceParams() const {
return params;
}
@@ -833,6 +862,7 @@ private:
std::vector<u8> gl_buffer;
SurfaceParams params;
GLenum gl_target;
std::size_t cached_size_in_bytes;
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -849,9 +879,6 @@ public:
/// Get the color surface based on the framebuffer configuration and the specified render target
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
/// Flushes the surface to Switch memory
void FlushSurface(const Surface& surface);
/// Tries to find a framebuffer using on the provided CPU address
Surface TryFindFramebufferSurface(VAddr addr) const;
@@ -875,6 +902,9 @@ private:
/// Tries to get a reserved surface for the specified parameters
Surface TryGetReservedSurface(const SurfaceParams& params);
/// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
/// previously been used. This is to prevent surfaces from being constantly created and
/// destroyed when used with different surface parameters.

View File

@@ -19,20 +19,21 @@ class CachedShader;
using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
class CachedShader final {
class CachedShader final : public RasterizerCacheObject {
public:
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
/// Gets the address of the shader in guest memory, required for cache management
VAddr GetAddr() const {
VAddr GetAddr() const override {
return addr;
}
/// Gets the size of the shader in guest memory, required for cache management
std::size_t GetSizeInBytes() const {
std::size_t GetSizeInBytes() const override {
return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64);
}
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
/// Gets the shader entries for the shader
const GLShader::ShaderEntries& GetShaderEntries() const {
return entries;

View File

@@ -1142,6 +1142,7 @@ private:
case Tegra::Shader::TextureType::Texture2D: {
return 2;
}
case Tegra::Shader::TextureType::Texture3D:
case Tegra::Shader::TextureType::TextureCube: {
return 3;
}
@@ -2036,9 +2037,9 @@ private:
break;
}
case OpCode::Id::TEX: {
ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
std::string coord;
const bool is_array = instr.tex.array != 0;
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
@@ -2053,21 +2054,59 @@ private:
switch (num_coordinates) {
case 1: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
coord = "float coords = " + x + ';';
if (is_array) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + index + ");";
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
coord = "float coords = " + x + ';';
}
break;
}
case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
if (is_array) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
}
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
if (depth_compare) {
if (is_array) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
");";
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
}
} else {
if (is_array) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3);
coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
");";
} else {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
}
}
break;
}
default:
@@ -2086,7 +2125,7 @@ private:
std::string op_c;
const std::string sampler =
GetSampler(instr.sampler, texture_type, false, depth_compare);
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
@@ -2106,10 +2145,13 @@ private:
}
case Tegra::Shader::TextureProcessMode::LB:
case Tegra::Shader::TextureProcessMode::LBA: {
if (num_coordinates <= 2) {
op_c = regs.GetRegisterAsFloat(instr.gpr20);
if (depth_compare) {
if (is_array)
op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2);
else
op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
} else {
op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
op_c = regs.GetRegisterAsFloat(instr.gpr20);
}
// TODO: Figure if A suffix changes the equation at all.
texture = "texture(" + sampler + ", coords, " + op_c + ')';
@@ -2252,6 +2294,8 @@ private:
ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
u32 op_c_offset = 0;
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
@@ -2266,6 +2310,7 @@ private:
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
op_c_offset = 1;
}
break;
}
@@ -2277,13 +2322,14 @@ private:
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, false);
std::string texture = "texelFetch(" + sampler + ", coords, 0)";
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1);
switch (instr.tlds.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::LZ: {
texture = "texelFetch(" + sampler + ", coords, 0)";
break;
}
case Tegra::Shader::TextureProcessMode::LL: {
const std::string op_c =
regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset);
texture = "texelFetch(" + sampler + ", coords, " + op_c + ')';
break;
}

View File

@@ -85,8 +85,8 @@ void Config::ReadValues() {
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
Settings::values.use_accurate_framebuffers =
qt_config->value("use_accurate_framebuffers", false).toBool();
Settings::values.use_accurate_gpu_emulation =
qt_config->value("use_accurate_gpu_emulation", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
@@ -233,7 +233,7 @@ void Config::SaveValues() {
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
qt_config->setValue("frame_limit", Settings::values.frame_limit);
qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers);
qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
// Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red);

View File

@@ -75,7 +75,7 @@ void ConfigureGraphics::setConfiguration() {
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setValue(Settings::values.frame_limit);
ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers);
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue);
ui->bg_button->setStyleSheet(
@@ -87,7 +87,7 @@ void ConfigureGraphics::applyConfiguration() {
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked();
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());

View File

@@ -50,9 +50,9 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_accurate_framebuffers">
<widget class="QCheckBox" name="use_accurate_gpu_emulation">
<property name="text">
<string>Use accurate framebuffers (slow)</string>
<string>Use accurate GPU emulation (slow)</string>
</property>
</widget>
</item>

View File

@@ -100,8 +100,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -825,10 +823,14 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
const auto failed = [this] {
const auto path = fmt::format("{}{:016X}/romfs",
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
const auto failed = [this, &path] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
tr("There was an error copying the RomFS files or the user "
"cancelled the operation."));
vfs->DeleteDirectory(path);
};
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
@@ -843,24 +845,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
const auto installed = Service::FileSystem::GetUnionContents();
auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id);
if (!romfs_title_id) {
failed();
return;
}
const auto path = fmt::format(
"{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
FileSys::VirtualFile romfs;
if (*romfs_title_id == program_id) {
romfs = file;
} else {
romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
}
const auto romfs =
loader->IsRomFSUpdatable()
? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
: file;
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
if (extracted == nullptr) {
@@ -872,7 +860,6 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
if (out == nullptr) {
failed();
vfs->DeleteDirectory(path);
return;
}
@@ -883,11 +870,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
"files into the new directory while <br>skeleton will only create the directory "
"structure."),
{"Full", "Skeleton"}, 0, false, &ok);
if (!ok) {
if (!ok)
failed();
vfs->DeleteDirectory(path);
return;
}
const auto full = res == "Full";
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
@@ -904,7 +888,6 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
} else {
progress.close();
failed();
vfs->DeleteDirectory(path);
}
}
@@ -1476,42 +1459,6 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
}
}
boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
const auto dlc_entries =
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::vector<FileSys::RegisteredCacheEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
std::vector<u64> romfs_tids;
romfs_tids.push_back(program_id);
for (const auto& entry : dlc_match)
romfs_tids.push_back(entry.title_id);
if (romfs_tids.size() > 1) {
QStringList list{"Base"};
for (std::size_t i = 1; i < romfs_tids.size(); ++i)
list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
bool ok;
const auto res = QInputDialog::getItem(
this, tr("Select RomFS Dump Target"),
tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
if (!ok) {
return boost::none;
}
return romfs_tids[list.indexOf(res)];
}
return program_id;
}
bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true;

View File

@@ -10,7 +10,6 @@
#include <QMainWindow>
#include <QTimer>
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "core/core.h"
#include "ui_main.h"
@@ -30,9 +29,8 @@ class WaitTreeWidget;
enum class GameListOpenTarget;
namespace FileSys {
class RegisteredCacheUnion;
class VfsFilesystem;
} // namespace FileSys
}
namespace Tegra {
class DebugContext;
@@ -177,8 +175,6 @@ private slots:
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
boost::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&,
u64 program_id);
void UpdateStatusBar();
Ui::MainWindow ui;

View File

@@ -99,8 +99,8 @@ void Config::ReadValues() {
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_accurate_framebuffers =
sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false);
Settings::values.use_accurate_gpu_emulation =
sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);

View File

@@ -110,9 +110,9 @@ use_frame_limit =
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
# Whether to use accurate framebuffers
# Whether to use accurate GPU emulation
# 0 (default): Off (fast), 1 : On (slow)
use_accurate_framebuffers =
use_accurate_gpu_emulation =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.