Compare commits

..

32 Commits

Author SHA1 Message Date
Kelebek1 56b8a9ba6e Do not try to pause core timing from the audio thread when using single-core 2022-09-22 01:23:57 +01:00
bunnei 8d4458ef24 Merge pull request #8849 from Morph1984/parallel-astc
astc: Enable parallel CPU astc decoding
2022-09-19 12:17:51 -07:00
bunnei 3a5f9409c8 Merge pull request #8915 from vonchenplus/opus_multi_stream
core: implement HwOpus GetWorkBufferSizeForMultiStreamEx
2022-09-17 16:07:33 -07:00
bunnei 7172339c7a Merge pull request #8827 from german77/amiibo_release
core: nfp: Implement amiibo encryption
2022-09-17 11:11:21 -07:00
bunnei 9c32f29af1 Merge pull request #8650 from Kelebek1/vsync
[Coretiming/NVNFlinger] Improve multi-core vsync timing, and core timing accuracy
2022-09-17 11:10:54 -07:00
bunnei 4a7a771340 Merge pull request #8914 from lioncash/audio-const
audio_core: Mark several member functions as const
2022-09-16 23:51:31 -07:00
bunnei eb726677b2 Merge pull request #8916 from Docteh/muilti_build
GIT: Modify .gitignore to ignore wildcard for build directories
2022-09-16 18:19:01 -07:00
bunnei 92813f01a4 Merge pull request #8906 from Docteh/fix_icons
UI: move icons from default into colorful theme.
2022-09-16 18:12:04 -07:00
FengChen 672e61d802 core: implement HwOpus GetWorkBufferSizeForMultiStreamEx 2022-09-17 00:28:56 +08:00
Morph 809126c94a astc: Enable parallel CPU astc decoding
Given the issues with GPU accelerated ASTC decoding with NVIDIA's latest drivers, parallelize astc decoding on the CPU.
Uses half the available threads in the system for astc decoding.
2022-09-16 10:16:42 -04:00
Lioncash 7e3cdfc453 audio_renderer: Pass command buffer by const reference
This is just being copied and isn't modified at all.
2022-09-16 10:06:53 -04:00
Lioncash d5d6322640 sink_stream: Mark GetQueueSize as const 2022-09-16 10:00:52 -04:00
Lioncash 6b1cb73350 node_states: Mark relevant member functions as const 2022-09-16 09:59:56 -04:00
Lioncash e4bc7b8611 i3dl2/reverb: Mark relevant member functions as const
These two don't modify member state.
2022-09-16 09:58:49 -04:00
Lioncash b2c2138af7 behavior_info: Mark CopyErrorInfo as const
This doesn't modify member state.

We can also mark the parameter of AppendError as const as well, since it
isn't modified.
2022-09-16 09:55:17 -04:00
Lioncash b862d5d8d8 audio_device: Mark GetDeviceVolume as const
This doesn't modify instance state.
2022-09-16 09:52:34 -04:00
Lioncash 36c77761cf audio_render_manager: Mark several functions as const 2022-09-16 09:50:32 -04:00
Lioncash 7a5d235d94 audio_in: Mark several functions as const
These functions don't modify class state, so we can mark them as such
2022-09-16 09:45:54 -04:00
Lioncash d1f3c121a0 audio_out: Mark several functions as const
These don't affect class state, so we can mark them as such.
2022-09-16 09:45:51 -04:00
Lioncash e9109cb5f2 audio_buffers: Pass by const-ref in AppendBuffers
This function doesn't modify the passed in buffer, so we can make that
explicit.
2022-09-16 09:36:03 -04:00
Lioncash cb2a33babc device_session: Convert for loop into ranged for in AppendBuffers
Simplifies the indexing code a little bit.
2022-09-16 09:32:57 -04:00
Lioncash a278fa6e2a device_session: Pass arguments by const-ref in relevant functions
These functions don't modify the passed in audio buffers, so we can
signify that in the interface.
2022-09-16 09:31:33 -04:00
Kyle Kienapfel 9554c67809 UI: move icons from default into colorful theme.
colorful theme has been default theme for awhile. having colorful theme
try and grab icons from other theme doesn't work on Linux.

Also adding two additional icons, info is to hint to the user that they
should hit verify after pasting in a token, sync is to show that the
verification is occurring.
2022-09-15 23:00:49 -07:00
german77 063b23cc58 core: nfp: Remove magic numbers 2022-09-07 09:49:43 -05:00
german77 4834961736 core: nfp: Workaround for lack of multiple nfp interfaces 2022-09-07 01:04:00 -05:00
Narr the Reg caa138b33f core: nfp: Correct date and amiibo name 2022-09-07 01:04:00 -05:00
Narr the Reg 19a4e12e6e core: nfp: Implement Convert and RecreateApplicationArea, accuracy fixes 2022-09-07 01:04:00 -05:00
german77 848f69eb19 core: nfp: Implement amiibo encryption 2022-09-07 01:04:00 -05:00
Morph 606cdb17d3 core_timing: Sleep in discrete intervals, yield during spin 2022-08-02 01:01:54 -04:00
Kelebek1 658e1ee426 Add missing looping event schedule signal 2022-08-02 01:01:54 -04:00
Kelebek1 83a24ad638 Make coretiming waiting more accurate 2022-08-02 01:01:54 -04:00
Kelebek1 9d3b190465 Rework multi-core vsync 2022-08-01 23:51:53 -04:00
60 changed files with 1644 additions and 477 deletions
+4 -2
View File
@@ -52,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png
dist/qt_themes/colorful/icons/48x48/chip.png
dist/qt_themes/colorful/icons/48x48/folder.png
dist/qt_themes/colorful_dark/icons/16x16/lock.png
dist/qt_themes/colorful/icons/16x16/info.png
dist/qt_themes/colorful/icons/16x16/sync.png
Copyright: Icons8
License: MIT
Comment: https://github.com/icons8/flat-color-icons
@@ -68,8 +70,8 @@ Copyright: Ionic (http://ionic.io/)
License: MIT
Files: dist/qt_themes/colorful/icons/48x48/star.png
dist/qt_themes/default/icons/16x16/checked.png
dist/qt_themes/default/icons/16x16/failed.png
dist/qt_themes/colorful/icons/16x16/checked.png
dist/qt_themes/colorful/icons/16x16/failed.png
Copyright: SVG Repo
License: CC0-1.0

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

-1
View File
@@ -1,7 +1,6 @@
[Icon Theme]
Name=colorful
Comment=Colorful theme
Inherits=default
Directories=16x16,48x48,256x256
[16x16]
+6
View File
@@ -6,14 +6,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/colorful">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/info.png">icons/16x16/info.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/sync.png">icons/16x16/sync.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
-10
View File
@@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/colorful_dark">
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="qss_icons">
+4 -7
View File
@@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC>
<qresource prefix="icons/default">
<!-- "colorful" is now the default theme, add new icons there -->
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
</qresource>
<qresource prefix="default">
<file>style.qss</file>
+2 -1
View File
@@ -1,6 +1,7 @@
[Icon Theme]
Name=default
Comment=default theme
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]
@@ -10,4 +11,4 @@ Size=16
Size=48
[256x256]
Size=256
Size=256
+2 -2
View File
@@ -1,7 +1,7 @@
[Icon Theme]
Name=qdarkstyle
Comment=dark theme
Inherits=default
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]
@@ -11,4 +11,4 @@ Size=16
Size=48
[256x256]
Size=256
Size=256
+1 -1
View File
@@ -1,7 +1,7 @@
[Icon Theme]
Name=qdarkstyle_midnight_blue
Comment=dark theme
Inherits=default
Inherits=colorful
Directories=16x16,48x48,256x256
[16x16]
+3 -3
View File
@@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
return *system_manager;
}
auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count)
-> Result {
Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
u64& out_count) const {
if (!CheckValidRevision(params.revision)) {
return Service::Audio::ERR_INVALID_REVISION;
}
@@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
session_ids[--session_count] = session_id;
}
u32 Manager::GetSessionCount() {
u32 Manager::GetSessionCount() const {
std::scoped_lock l{session_lock};
return session_count;
}
+3 -3
View File
@@ -46,7 +46,7 @@ public:
* @param out_count - Output size of the required workbuffer.
* @return Result code.
*/
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count);
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
/**
* Get a new session id.
@@ -60,7 +60,7 @@ public:
*
* @return The number of active sessions.
*/
u32 GetSessionCount();
u32 GetSessionCount() const;
/**
* Add a renderer system to the manager.
@@ -94,7 +94,7 @@ private:
/// Number of active renderers
u32 session_count{};
/// Lock for interacting with the sessions
std::mutex session_lock{};
mutable std::mutex session_lock{};
/// Regularly generates commands from the registered systems for the AudioRenderer
std::unique_ptr<SystemManager> system_manager{};
};
+2 -2
View File
@@ -36,7 +36,7 @@ public:
*
* @param buffer - The new buffer.
*/
void AppendBuffer(AudioBuffer& buffer) {
void AppendBuffer(const AudioBuffer& buffer) {
std::scoped_lock l{lock};
buffers[appended_index] = buffer;
appended_count++;
@@ -93,7 +93,7 @@ public:
*
* @return Is the buffer was released.
*/
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
std::scoped_lock l{lock};
bool buffer_released{false};
while (registered_count > 0) {
+8 -8
View File
@@ -73,12 +73,12 @@ void DeviceSession::Stop() {
}
}
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
for (size_t i = 0; i < buffers.size(); i++) {
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
for (const auto& buffer : buffers) {
Sink::SinkBuffer new_buffer{
.frames = buffers[i].size / (channel_count * sizeof(s16)),
.frames = buffer.size / (channel_count * sizeof(s16)),
.frames_played = 0,
.tag = buffers[i].tag,
.tag = buffer.tag,
.consumed = false,
};
@@ -86,21 +86,21 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
std::vector<s16> samples{};
stream->AppendBuffer(new_buffer, samples);
} else {
std::vector<s16> samples(buffers[i].size / sizeof(s16));
system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
std::vector<s16> samples(buffer.size / sizeof(s16));
system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
stream->AppendBuffer(new_buffer, samples);
}
}
}
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) {
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
}
}
bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const {
bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
return played_sample_count >= buffer.end_timestamp;
}
+3 -3
View File
@@ -62,14 +62,14 @@ public:
*
* @param buffers - The buffers to play.
*/
void AppendBuffers(std::span<AudioBuffer> buffers) const;
void AppendBuffers(std::span<const AudioBuffer> buffers) const;
/**
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
*
* @param buffer - The buffer to write to.
*/
void ReleaseBuffer(AudioBuffer& buffer) const;
void ReleaseBuffer(const AudioBuffer& buffer) const;
/**
* Check if the buffer for the given tag has been consumed by the backend.
@@ -78,7 +78,7 @@ public:
*
* @return true if the buffer has been consumed, otherwise false.
*/
bool IsBufferConsumed(AudioBuffer& buffer) const;
bool IsBufferConsumed(const AudioBuffer& buffer) const;
/**
* Start this device session, starting the backend stream.
+4 -4
View File
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
return event->GetReadableEvent();
}
f32 In::GetVolume() {
f32 In::GetVolume() const {
std::scoped_lock l{parent_mutex};
return system.GetVolume();
}
@@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
system.SetVolume(volume);
}
bool In::ContainsAudioBuffer(u64 tag) {
bool In::ContainsAudioBuffer(u64 tag) const {
std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag);
}
u32 In::GetBufferCount() {
u32 In::GetBufferCount() const {
std::scoped_lock l{parent_mutex};
return system.GetBufferCount();
}
u64 In::GetPlayedSampleCount() {
u64 In::GetPlayedSampleCount() const {
std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount();
}
+4 -4
View File
@@ -102,7 +102,7 @@ public:
*
* @return The current volume.
*/
f32 GetVolume();
f32 GetVolume() const;
/**
* Set the system volume.
@@ -117,21 +117,21 @@ public:
* @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
bool ContainsAudioBuffer(u64 tag) const;
/**
* Get the maximum number of buffers.
*
* @return The maximum number of buffers.
*/
u32 GetBufferCount();
u32 GetBufferCount() const;
/**
* Get the total played sample count for this audio in.
*
* @return The played sample count.
*/
u64 GetPlayedSampleCount();
u64 GetPlayedSampleCount() const;
private:
/// The AudioIn::Manager this audio in is registered with
+13 -11
View File
@@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
return session_id;
}
std::string_view System::GetDefaultDeviceName() {
std::string_view System::GetDefaultDeviceName() const {
return "BuiltInHeadset";
}
std::string_view System::GetDefaultUacDeviceName() {
std::string_view System::GetDefaultUacDeviceName() const {
return "Uac";
}
Result System::IsConfigValid(const std::string_view device_name,
const AudioInParameter& in_params) {
const AudioInParameter& in_params) const {
if ((device_name.size() > 0) &&
(device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME;
@@ -114,12 +114,14 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
}
const auto timestamp{buffers.GetNextTimestamp()};
AudioBuffer new_buffer{.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size};
const AudioBuffer new_buffer{
.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size,
};
buffers.AppendBuffer(new_buffer);
RegisterBuffers();
@@ -200,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
session->SetVolume(volume_);
}
bool System::ContainsAudioBuffer(const u64 tag) {
bool System::ContainsAudioBuffer(const u64 tag) const {
return buffers.ContainsBuffer(tag);
}
u32 System::GetBufferCount() {
u32 System::GetBufferCount() const {
return buffers.GetAppendedRegisteredCount();
}
+5 -5
View File
@@ -68,7 +68,7 @@ public:
*
* @return The default audio input device name.
*/
std::string_view GetDefaultDeviceName();
std::string_view GetDefaultDeviceName() const;
/**
* Get the default USB audio input device name.
@@ -77,7 +77,7 @@ public:
*
* @return The default USB audio input device name.
*/
std::string_view GetDefaultUacDeviceName();
std::string_view GetDefaultUacDeviceName() const;
/**
* Is the given initialize config valid?
@@ -86,7 +86,7 @@ public:
* @param in_params - Input parameters, see AudioInParameter.
* @return Result code.
*/
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params);
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
/**
* Initialize this system.
@@ -218,14 +218,14 @@ public:
* @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
bool ContainsAudioBuffer(u64 tag) const;
/**
* Get the maximum number of usable buffers (default 32).
*
* @return The number of buffers.
*/
u32 GetBufferCount();
u32 GetBufferCount() const;
/**
* Get the total number of samples played by this system.
+4 -4
View File
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
return event->GetReadableEvent();
}
f32 Out::GetVolume() {
f32 Out::GetVolume() const {
std::scoped_lock l{parent_mutex};
return system.GetVolume();
}
@@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
system.SetVolume(volume);
}
bool Out::ContainsAudioBuffer(const u64 tag) {
bool Out::ContainsAudioBuffer(const u64 tag) const {
std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag);
}
u32 Out::GetBufferCount() {
u32 Out::GetBufferCount() const {
std::scoped_lock l{parent_mutex};
return system.GetBufferCount();
}
u64 Out::GetPlayedSampleCount() {
u64 Out::GetPlayedSampleCount() const {
std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount();
}
+4 -4
View File
@@ -102,7 +102,7 @@ public:
*
* @return The current volume.
*/
f32 GetVolume();
f32 GetVolume() const;
/**
* Set the system volume.
@@ -117,21 +117,21 @@ public:
* @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
bool ContainsAudioBuffer(u64 tag) const;
/**
* Get the maximum number of buffers.
*
* @return The maximum number of buffers.
*/
u32 GetBufferCount();
u32 GetBufferCount() const;
/**
* Get the total played sample count for this audio out.
*
* @return The played sample count.
*/
u64 GetPlayedSampleCount();
u64 GetPlayedSampleCount() const;
private:
/// The AudioOut::Manager this audio out is registered with
+13 -10
View File
@@ -27,11 +27,12 @@ void System::Finalize() {
buffer_event->GetWritableEvent().Signal();
}
std::string_view System::GetDefaultOutputDeviceName() {
std::string_view System::GetDefaultOutputDeviceName() const {
return "DeviceOut";
}
Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) {
Result System::IsConfigValid(std::string_view device_name,
const AudioOutParameter& in_params) const {
if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME;
}
@@ -113,12 +114,14 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
}
const auto timestamp{buffers.GetNextTimestamp()};
AudioBuffer new_buffer{.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size};
const AudioBuffer new_buffer{
.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size,
};
buffers.AppendBuffer(new_buffer);
RegisterBuffers();
@@ -198,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
session->SetVolume(volume_);
}
bool System::ContainsAudioBuffer(const u64 tag) {
bool System::ContainsAudioBuffer(const u64 tag) const {
return buffers.ContainsBuffer(tag);
}
u32 System::GetBufferCount() {
u32 System::GetBufferCount() const {
return buffers.GetAppendedRegisteredCount();
}
+4 -4
View File
@@ -68,7 +68,7 @@ public:
*
* @return The default audio output device name.
*/
std::string_view GetDefaultOutputDeviceName();
std::string_view GetDefaultOutputDeviceName() const;
/**
* Is the given initialize config valid?
@@ -77,7 +77,7 @@ public:
* @param in_params - Input parameters, see AudioOutParameter.
* @return Result code.
*/
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params);
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
/**
* Initialize this system.
@@ -209,14 +209,14 @@ public:
* @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
bool ContainsAudioBuffer(u64 tag) const;
/**
* Get the maximum number of usable buffers (default 32).
*
* @return The number of buffers.
*/
u32 GetBufferCount();
u32 GetBufferCount() const;
/**
* Get the total number of samples played by this system.
+1 -1
View File
@@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
return render_mailbox.GetRemainCommandCount(session_id);
}
void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
render_mailbox.SetCommandBuffer(session_id, command_buffer);
}
+1 -1
View File
@@ -131,7 +131,7 @@ public:
* @param session_id - The session id to check (0 or 1).
* @param command_buffer - The command buffer to process.
*/
void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
/**
* Clear the command buffers (does not clear the time taken or the remaining command count)
@@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
return command_buffers[session_id];
}
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
command_buffers[session_id] = buffer;
}
@@ -91,7 +91,7 @@ public:
* @param session_id - The session id to get (0 or 1).
* @param buffer - The command buffer to set.
*/
void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
/**
* Get the total render time taken for the last command lists sent.
@@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
error_count = 0;
}
void BehaviorInfo::AppendError(ErrorInfo& error) {
void BehaviorInfo::AppendError(const ErrorInfo& error) {
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
error.error_code.raw, error.address);
if (error_count < MaxErrors) {
@@ -42,7 +42,7 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
}
}
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
out_count = std::min(error_count, MaxErrors);
for (size_t i = 0; i < MaxErrors; i++) {
@@ -94,7 +94,7 @@ public:
*
* @param error - The new error.
*/
void AppendError(ErrorInfo& error);
void AppendError(const ErrorInfo& error);
/**
* Copy errors to the given output container.
@@ -102,7 +102,7 @@ public:
* @param out_errors - Output container to receive the errors.
* @param out_count - The number of errors written.
*/
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
/**
* Update the behaviour flags.
@@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
return ResultSuccess;
}
Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
@@ -130,7 +130,7 @@ public:
* @param behaviour - Behaviour to update.
* @return Result code.
*/
Result UpdateErrorInfo(BehaviorInfo& behaviour);
Result UpdateErrorInfo(const BehaviorInfo& behaviour);
/**
* Update splitter.
+2 -2
View File
@@ -99,7 +99,7 @@ public:
return out_sample;
}
Common::FixedPoint<50, 14> Read() {
Common::FixedPoint<50, 14> Read() const {
return *output;
}
@@ -110,7 +110,7 @@ public:
}
}
Common::FixedPoint<50, 14> TapOut(const s32 index) {
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
auto out{input - (index + 1)};
if (out < buffer.data()) {
out += max_delay + 1;
+2 -2
View File
@@ -95,7 +95,7 @@ public:
return out_sample;
}
Common::FixedPoint<50, 14> Read() {
Common::FixedPoint<50, 14> Read() const {
return *output;
}
@@ -106,7 +106,7 @@ public:
}
}
Common::FixedPoint<50, 14> TapOut(const s32 index) {
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
auto out{input - (index + 1)};
if (out < buffer.data()) {
out += sample_count;
+2 -2
View File
@@ -56,7 +56,7 @@ class NodeStates {
*
* @return The current stack position.
*/
u32 Count() {
u32 Count() const {
return pos;
}
@@ -83,7 +83,7 @@ class NodeStates {
*
* @return The node on the top of the stack.
*/
u32 top() {
u32 top() const {
return stack[pos - 1];
}
+7 -2
View File
@@ -214,8 +214,13 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// video play out without attempting to stall.
// Can hopefully remove this later with a more complete NVDEC implementation.
const auto nvdec_active{system.AudioCore().IsNVDECActive()};
if (!nvdec_active && queued_buffers > max_queue_size) {
// Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
// and never recovers to a normal state, so just skip attempting to sync things on single-core.
if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
Stall();
} else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
while (frames_written < num_frames) {
@@ -255,7 +260,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
if (stalled && queued_buffers <= max_queue_size) {
if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ public:
*
* @return The number of queued buffers.
*/
u32 GetQueueSize() {
u32 GetQueueSize() const {
return queued_buffers.load();
}
+4
View File
@@ -54,6 +54,10 @@ public:
is_set = false;
}
[[nodiscard]] bool IsSet() {
return is_set;
}
private:
std::condition_variable condvar;
std::mutex mutex;
+3
View File
@@ -519,6 +519,9 @@ add_library(core STATIC
hle/service/ncm/ncm.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h
hle/service/nfp/amiibo_types.h
hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h
hle/service/nfp/nfp_user.cpp
+38 -17
View File
@@ -134,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool absolute_time) {
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
{
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
event_queue.emplace_back(
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
event_queue.emplace_back(
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
event.Set();
}
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
@@ -229,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() {
basic_lock.lock();
if (evt.reschedule_time != 0) {
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + evt.reschedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + evt.reschedule_time;
}
const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count()
: evt.reschedule_time};
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + next_schedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + next_schedule_time;
}
event_queue.emplace_back(
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@@ -250,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() {
}
if (!event_queue.empty()) {
const s64 next_time = event_queue.front().time - global_timer;
return next_time;
return event_queue.front().time;
} else {
return std::nullopt;
}
@@ -264,11 +267,29 @@ void CoreTiming::ThreadLoop() {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
if (*next_time > 0) {
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
event.WaitFor(next_time_ns);
// There are more events left in the queue, wait until the next event.
const auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
// Assume a timer resolution of 1ms.
static constexpr s64 TimerResolutionNS = 1000000;
// Sleep in discrete intervals of the timer resolution, and spin the rest.
const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
if (sleep_time > 0) {
event.WaitFor(std::chrono::nanoseconds(sleep_time));
}
while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
// Yield to reduce thread starvation.
std::this_thread::yield();
}
if (event.IsSet()) {
event.Reset();
}
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to continue.
wait_set = true;
event.Wait();
}
@@ -32,7 +32,7 @@ enum class MiiEditResult : u32 {
};
struct MiiEditCharInfo {
Service::Mii::MiiInfo mii_info{};
Service::Mii::CharInfo mii_info{};
};
static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size.");
+27 -1
View File
@@ -255,6 +255,32 @@ void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
GetWorkBufferSize(ctx);
}
void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) {
OpusMultiStreamParametersEx param;
std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
const auto sample_rate = param.sample_rate;
const auto channel_count = param.channel_count;
const auto number_streams = param.number_streams;
const auto number_stereo_streams = param.number_stereo_streams;
LOG_DEBUG(
Audio,
"called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
sample_rate, channel_count, number_streams, number_stereo_streams);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
const u32 worker_buffer_sz =
static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(worker_buffer_sz);
}
void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
@@ -335,7 +361,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
{6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
{7, nullptr, "GetWorkBufferSizeForMultiStreamEx"},
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
};
RegisterHandlers(functions);
}
+11
View File
@@ -11,6 +11,16 @@ class System;
namespace Service::Audio {
struct OpusMultiStreamParametersEx {
u32 sample_rate;
u32 channel_count;
u32 number_streams;
u32 number_stereo_streams;
u32 use_large_frame_size;
u32 padding;
std::array<u32, 64> channel_mappings;
};
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
@@ -21,6 +31,7 @@ private:
void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);
void GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio
+22 -10
View File
@@ -43,7 +43,7 @@ public:
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, nullptr, "Convert"},
{23, &IDatabaseService::Convert, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
{26, nullptr, "Append"},
@@ -130,7 +130,7 @@ private:
return;
}
std::vector<MiiInfo> values;
std::vector<CharInfo> values;
for (const auto& element : *result) {
values.emplace_back(element.info);
}
@@ -144,7 +144,7 @@ private:
void UpdateLatest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<MiiInfo>()};
const auto info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
@@ -156,9 +156,9 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(*result);
rb.PushRaw<CharInfo>(*result);
}
void BuildRandom(Kernel::HLERequestContext& ctx) {
@@ -191,9 +191,9 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race));
rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
}
void BuildDefault(Kernel::HLERequestContext& ctx) {
@@ -210,14 +210,14 @@ private:
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<MiiInfo>(manager.BuildDefault(index));
rb.PushRaw<CharInfo>(manager.BuildDefault(index));
}
void GetIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<MiiInfo>()};
const auto info{rp.PopRaw<CharInfo>()};
LOG_DEBUG(Service_Mii, "called");
@@ -239,6 +239,18 @@ private:
rb.Push(ResultSuccess);
}
void Convert(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
LOG_INFO(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
}
constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
return current_interface_version >= interface_version;
}
+83 -6
View File
@@ -42,7 +42,7 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i
return out;
}
MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
MiiStoreBitFields bf;
std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
@@ -409,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
return static_cast<u32>(count);
}
ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info,
SourceFlag source_flag) {
ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ERROR_CANNOT_FIND_ENTRY;
}
@@ -419,14 +419,91 @@ ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info
return ERROR_CANNOT_FIND_ENTRY;
}
MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
}
MiiInfo MiiManager::BuildDefault(std::size_t index) {
CharInfo MiiManager::BuildDefault(std::size_t index) {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
}
CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
Service::Mii::MiiManager manager;
auto mii = manager.BuildDefault(0);
// Check if mii data exist
if (mii_v3.mii_name[0] == 0) {
return mii;
}
// TODO: We are ignoring a bunch of data from the mii_v3
mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
mii.height = mii_v3.height;
mii.build = mii_v3.build;
memset(mii.name.data(), 0, sizeof(mii.name));
memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
mii.font_region = mii_v3.region_information.character_set;
mii.faceline_type = mii_v3.appearance_bits1.face_shape;
mii.faceline_color = mii_v3.appearance_bits1.skin_color;
mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
mii.faceline_make = mii_v3.appearance_bits2.makeup;
mii.hair_type = mii_v3.hair_style;
mii.hair_color = mii_v3.appearance_bits3.hair_color;
mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
// TODO: Validate mii data
return mii;
}
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;
@@ -441,7 +518,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_
return result;
}
Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) {
Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
index = INVALID_INDEX;
+5 -4
View File
@@ -19,11 +19,12 @@ public:
bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
bool IsFullDatabase() const;
u32 GetCount(SourceFlag source_flag) const;
ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag);
MiiInfo BuildRandom(Age age, Gender gender, Race race);
MiiInfo BuildDefault(std::size_t index);
ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
CharInfo BuildRandom(Age age, Gender gender, Race race);
CharInfo BuildDefault(std::size_t index);
CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
Result GetIndex(const MiiInfo& info, u32& index);
Result GetIndex(const CharInfo& info, u32& index);
private:
const Common::UUID user_id{};
+132 -6
View File
@@ -86,7 +86,8 @@ enum class SourceFlag : u32 {
};
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
struct MiiInfo {
// nn::mii::CharInfo
struct CharInfo {
Common::UUID uuid;
std::array<char16_t, 11> name;
u8 font_region;
@@ -140,16 +141,16 @@ struct MiiInfo {
u8 mole_y;
u8 padding;
};
static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<MiiInfo>,
"All bits of MiiInfo must contribute to its value.");
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<CharInfo>,
"All bits of CharInfo must contribute to its value.");
#pragma pack(push, 4)
struct MiiInfoElement {
MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {}
MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
MiiInfo info{};
CharInfo info{};
Source source{};
};
static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
@@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec
static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
"MiiStoreBitFields is not trivially copyable.");
// This is nn::mii::Ver3StoreData
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
struct Ver3StoreData {
u8 version;
union {
u8 raw;
BitField<0, 1, u8> allow_copying;
BitField<1, 1, u8> profanity_flag;
BitField<2, 2, u8> region_lock;
BitField<4, 2, u8> character_set;
} region_information;
u16_be mii_id;
u64_be system_id;
u32_be specialness_and_creation_date;
std::array<u8, 0x6> creator_mac;
u16_be padding;
union {
u16 raw;
BitField<0, 1, u16> gender;
BitField<1, 4, u16> birth_month;
BitField<5, 5, u16> birth_day;
BitField<10, 4, u16> favorite_color;
BitField<14, 1, u16> favorite;
} mii_information;
std::array<char16_t, 0xA> mii_name;
u8 height;
u8 build;
union {
u8 raw;
BitField<0, 1, u8> disable_sharing;
BitField<1, 4, u8> face_shape;
BitField<5, 3, u8> skin_color;
} appearance_bits1;
union {
u8 raw;
BitField<0, 4, u8> wrinkles;
BitField<4, 4, u8> makeup;
} appearance_bits2;
u8 hair_style;
union {
u8 raw;
BitField<0, 3, u8> hair_color;
BitField<3, 1, u8> flip_hair;
} appearance_bits3;
union {
u32 raw;
BitField<0, 6, u32> eye_type;
BitField<6, 3, u32> eye_color;
BitField<9, 4, u32> eye_scale;
BitField<13, 3, u32> eye_vertical_stretch;
BitField<16, 5, u32> eye_rotation;
BitField<21, 4, u32> eye_spacing;
BitField<25, 5, u32> eye_y_position;
} appearance_bits4;
union {
u32 raw;
BitField<0, 5, u32> eyebrow_style;
BitField<5, 3, u32> eyebrow_color;
BitField<8, 4, u32> eyebrow_scale;
BitField<12, 3, u32> eyebrow_yscale;
BitField<16, 4, u32> eyebrow_rotation;
BitField<21, 4, u32> eyebrow_spacing;
BitField<25, 5, u32> eyebrow_y_position;
} appearance_bits5;
union {
u16 raw;
BitField<0, 5, u16> nose_type;
BitField<5, 4, u16> nose_scale;
BitField<9, 5, u16> nose_y_position;
} appearance_bits6;
union {
u16 raw;
BitField<0, 6, u16> mouth_type;
BitField<6, 3, u16> mouth_color;
BitField<9, 4, u16> mouth_scale;
BitField<13, 3, u16> mouth_horizontal_stretch;
} appearance_bits7;
union {
u8 raw;
BitField<0, 5, u8> mouth_y_position;
BitField<5, 3, u8> mustache_type;
} appearance_bits8;
u8 allow_copying;
union {
u16 raw;
BitField<0, 3, u16> bear_type;
BitField<3, 3, u16> facial_hair_color;
BitField<6, 4, u16> mustache_scale;
BitField<10, 5, u16> mustache_y_position;
} appearance_bits9;
union {
u16 raw;
BitField<0, 4, u16> glasses_type;
BitField<4, 3, u16> glasses_color;
BitField<7, 4, u16> glasses_scale;
BitField<11, 5, u16> glasses_y_position;
} appearance_bits10;
union {
u16 raw;
BitField<0, 1, u16> mole_enabled;
BitField<1, 4, u16> mole_scale;
BitField<5, 5, u16> mole_x_position;
BitField<10, 5, u16> mole_y_position;
} appearance_bits11;
std::array<u16_le, 0xA> author_name;
INSERT_PADDING_BYTES(0x4);
};
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
struct MiiStoreData {
using Name = std::array<char16_t, 10>;
+383
View File
@@ -0,0 +1,383 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
// SPDX-License-Identifier: MIT
#include <array>
#include <mbedtls/aes.h>
#include <mbedtls/hmac_drbg.h>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
namespace Service::NFP::AmiiboCrypto {
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
const auto& amiibo_data = ntag_file.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter);
LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
// Validate UUID
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
return false;
}
if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
ntag_file.uuid[8]) {
return false;
}
// Check against all know constants on an amiibo binary
if (ntag_file.static_lock != 0xE00F) {
return false;
}
if (ntag_file.compability_container != 0xEEFF10F1U) {
return false;
}
if (amiibo_data.constant_value != 0xA5) {
return false;
}
if (amiibo_data.model_info.constant_value != 0x02) {
return false;
}
// dynamic_lock value apparently is not constant
// ntag_file.dynamic_lock == 0x0F0001
if (ntag_file.CFG0 != 0x04000000U) {
return false;
}
if (ntag_file.CFG1 != 0x5F) {
return false;
}
return true;
}
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
NTAG215File encoded_data{};
memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
encoded_data.static_lock = nfc_data.static_lock;
encoded_data.compability_container = nfc_data.compability_container;
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
encoded_data.constant_value = nfc_data.user_memory.constant_value;
encoded_data.write_counter = nfc_data.user_memory.write_counter;
encoded_data.settings = nfc_data.user_memory.settings;
encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
encoded_data.title_id = nfc_data.user_memory.title_id;
encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
encoded_data.unknown = nfc_data.user_memory.unknown;
encoded_data.hash = nfc_data.user_memory.hash;
encoded_data.application_area = nfc_data.user_memory.application_area;
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
encoded_data.model_info = nfc_data.user_memory.model_info;
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
encoded_data.CFG0 = nfc_data.CFG0;
encoded_data.CFG1 = nfc_data.CFG1;
encoded_data.password = nfc_data.password;
return encoded_data;
}
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
EncryptedNTAG215File nfc_data{};
memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
nfc_data.static_lock = encoded_data.static_lock;
nfc_data.compability_container = encoded_data.compability_container;
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
nfc_data.user_memory.constant_value = encoded_data.constant_value;
nfc_data.user_memory.write_counter = encoded_data.write_counter;
nfc_data.user_memory.settings = encoded_data.settings;
nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
nfc_data.user_memory.title_id = encoded_data.title_id;
nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
nfc_data.user_memory.unknown = encoded_data.unknown;
nfc_data.user_memory.hash = encoded_data.hash;
nfc_data.user_memory.application_area = encoded_data.application_area;
nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
nfc_data.user_memory.model_info = encoded_data.model_info;
nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
nfc_data.dynamic_lock = encoded_data.dynamic_lock;
nfc_data.CFG0 = encoded_data.CFG0;
nfc_data.CFG1 = encoded_data.CFG1;
nfc_data.password = encoded_data.password;
return nfc_data;
}
u32 GetTagPassword(const TagUuid& uuid) {
// Verifiy that the generated password is correct
u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
return password;
}
HashSeed GetSeed(const NTAG215File& data) {
HashSeed seed{
.magic = data.write_counter,
.padding = {},
.uuid1 = {},
.uuid2 = {},
.keygen_salt = data.keygen_salt,
};
// Copy the first 8 bytes of uuid
memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
return seed;
}
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
const std::size_t string_size = key.type_string.size();
std::vector<u8> output(string_size + seedPart1Len);
// Copy whole type string
memccpy(output.data(), key.type_string.data(), '\0', string_size);
// Append (16 - magic_length) from the input seed
memcpy(output.data() + string_size, &seed, seedPart1Len);
// Append all bytes from magicBytes
output.insert(output.end(), key.magic_bytes.begin(),
key.magic_bytes.begin() + key.magic_length);
output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
}
return output;
}
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed) {
// Initialize context
ctx.used = false;
ctx.counter = 0;
ctx.buffer_size = sizeof(ctx.counter) + seed.size();
memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
// Initialize HMAC context
mbedtls_md_init(&hmac_ctx);
mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
}
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
// If used at least once, reinitialize the HMAC
if (ctx.used) {
mbedtls_md_hmac_reset(&hmac_ctx);
}
ctx.used = true;
// Store counter in big endian, and increment it
ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
ctx.counter++;
// Do HMAC magic
mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
ctx.buffer_size);
mbedtls_md_hmac_finish(&hmac_ctx, output.data());
}
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
const auto seed = GetSeed(data);
// Generate internal seed
const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
// Initialize context
CryptoCtx ctx{};
mbedtls_md_context_t hmac_ctx;
CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
// Generate derived keys
DerivedKeys derived_keys{};
std::array<DrgbOutput, 2> temp{};
CryptoStep(ctx, hmac_ctx, temp[0]);
CryptoStep(ctx, hmac_ctx, temp[1]);
memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
// Cleanup context
mbedtls_md_free(&hmac_ctx);
return derived_keys;
}
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
mbedtls_aes_context aes;
std::size_t nc_off = 0;
std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
std::array<u8, sizeof(keys.aes_iv)> stream_block{};
const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
stream_block.data(),
reinterpret_cast<const unsigned char*>(&in_data.settings),
reinterpret_cast<unsigned char*>(&out_data.settings));
// Copy the rest of the data directly
out_data.uuid2 = in_data.uuid2;
out_data.static_lock = in_data.static_lock;
out_data.compability_container = in_data.compability_container;
out_data.constant_value = in_data.constant_value;
out_data.write_counter = in_data.write_counter;
out_data.uuid = in_data.uuid;
out_data.model_info = in_data.model_info;
out_data.keygen_salt = in_data.keygen_salt;
out_data.dynamic_lock = in_data.dynamic_lock;
out_data.CFG0 = in_data.CFG0;
out_data.CFG1 = in_data.CFG1;
out_data.password = in_data.password;
}
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "No keys detected");
return false;
}
if (keys_file.Read(unfixed_info) != 1) {
LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
return false;
}
if (keys_file.Read(locked_secret) != 1) {
LOG_ERROR(Service_NFP, "Failed to read locked-secret");
return false;
}
return true;
}
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
const auto data_keys = GenerateKey(unfixed_info, encoded_data);
const auto tag_keys = GenerateKey(locked_secret, encoded_data);
// Decrypt
Cipher(data_keys, encoded_data, tag_data);
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
// Regenerate data HMAC
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
sizeof(HmacKey),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
LOG_ERROR(Service_NFP, "hmac_data doesn't match");
return false;
}
if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
LOG_ERROR(Service_NFP, "hmac_tag doesn't match");
return false;
}
return true;
}
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
const auto data_keys = GenerateKey(unfixed_info, tag_data);
const auto tag_keys = GenerateKey(locked_secret, tag_data);
NTAG215File encoded_tag_data{};
// Generate tag HMAC
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
// Init mbedtls HMAC context
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
// Generate data HMAC
mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2); // Data
mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData)); // Tag HMAC
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
input_length);
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
// HMAC cleanup
mbedtls_md_free(&ctx);
// Encrypt
Cipher(data_keys, tag_data, encoded_tag_data);
// Convert back to hardware
encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
return true;
}
} // namespace Service::NFP::AmiiboCrypto
+98
View File
@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "core/hle/service/nfp/amiibo_types.h"
struct mbedtls_md_context_t;
namespace Service::NFP::AmiiboCrypto {
// Byte locations in Service::NFP::NTAG215File
constexpr std::size_t HMAC_DATA_START = 0x8;
constexpr std::size_t SETTINGS_START = 0x2c;
constexpr std::size_t WRITE_COUNTER_START = 0x29;
constexpr std::size_t HMAC_TAG_START = 0x1B4;
constexpr std::size_t UUID_START = 0x1D4;
constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
u16 magic;
std::array<u8, 0xE> padding;
std::array<u8, 0x8> uuid1;
std::array<u8, 0x8> uuid2;
std::array<u8, 0x20> keygen_salt;
};
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
struct InternalKey {
HmacKey hmac_key;
std::array<char, 0xE> type_string;
u8 reserved;
u8 magic_length;
std::array<u8, 0x10> magic_bytes;
std::array<u8, 0x20> xor_pad;
};
static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
struct CryptoCtx {
std::array<char, 480> buffer;
bool used;
std::size_t buffer_size;
s16 counter;
};
struct DerivedKeys {
std::array<u8, 0x10> aes_key;
std::array<u8, 0x10> aes_iv;
std::array<u8, 0x10> hmac_key;
};
static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
/// Converts from encrypted file format to encoded file format
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
/// Converts from encoded file format to encrypted file format
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
/// Returns password needed to allow write access to protected memory
u32 GetTagPassword(const TagUuid& uuid);
// Generates Seed needed for key derivation
HashSeed GetSeed(const NTAG215File& data);
// Middle step on the generation of derived keys
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
// Initializes mbedtls context
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed);
// Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
// Generates the derived key from amiibo data
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
// Encodes or decodes amiibo data
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
/// Loads both amiibo keys from key_retail.bin
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
/// Decodes encripted amiibo data returns true if output is valid
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
/// Encodes plain amiibo data returns true if output is valid
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
} // namespace Service::NFP::AmiiboCrypto
+197
View File
@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "core/hle/service/mii/types.h"
namespace Service::NFP {
static constexpr std::size_t amiibo_name_length = 0xA;
enum class ServiceType : u32 {
User,
Debug,
System,
};
enum class State : u32 {
NonInitialized,
Initialized,
};
enum class DeviceState : u32 {
Initialized,
SearchingForTag,
TagFound,
TagRemoved,
TagMounted,
Unaviable,
Finalized,
};
enum class ModelType : u32 {
Amiibo,
};
enum class MountTarget : u32 {
Rom,
Ram,
All,
};
enum class AmiiboType : u8 {
Figure,
Card,
Yarn,
};
enum class AmiiboSeries : u8 {
SuperSmashBros,
SuperMario,
ChibiRobo,
YoshiWoollyWorld,
Splatoon,
AnimalCrossing,
EightBitMario,
Skylanders,
Unknown8,
TheLegendOfZelda,
ShovelKnight,
Unknown11,
Kiby,
Pokemon,
MarioSportsSuperstars,
MonsterHunter,
BoxBoy,
Pikmin,
FireEmblem,
Metroid,
Others,
MegaMan,
Diablo,
};
using TagUuid = std::array<u8, 10>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
struct AmiiboDate {
u16 raw_date{};
u16 GetYear() const {
return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
}
u8 GetMonth() const {
return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
}
u8 GetDay() const {
return static_cast<u8>(raw_date & 0x001F);
}
};
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
struct Settings {
union {
u8 raw{};
BitField<4, 1, u8> amiibo_initialized;
BitField<5, 1, u8> appdata_initialized;
};
};
static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
struct AmiiboSettings {
Settings settings;
u8 country_code_id;
u16_be crc_counter; // Incremented each time crc is changed
AmiiboDate init_date;
AmiiboDate write_date;
u32_be crc;
std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
};
static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
struct AmiiboModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x4); // Unknown
};
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
struct NTAG215Password {
u32 PWD; // Password to allow write access
u16 PACK; // Password acknowledge reply
u16 RFUI; // Reserved for future use
};
static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
#pragma pack(1)
struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
AmiiboModelInfo model_info; // Encrypted amiibo model info
HashData keygen_salt; // Salt
HashData hmac_data; // Hash
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
u64_be title_id; // Encrypted Game id
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
ApplicationArea application_area; // Encrypted Game data
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215File {
std::array<u8, 0x2> uuid2;
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
HashData hmac_data; // Hash
u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings;
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
u64_be title_id;
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id;
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
ApplicationArea application_area; // Encrypted Game data
HashData hmac_tag; // Hash
std::array<u8, 0x8> uuid;
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
#pragma pack()
struct EncryptedNTAG215File {
TagUuid uuid; // Unique serial number
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
EncryptedAmiiboFile user_memory; // Writable data
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
} // namespace Service::NFP
+427 -140
View File
@@ -4,7 +4,10 @@
#include <array>
#include <atomic>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -12,6 +15,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
@@ -19,12 +23,14 @@ namespace Service::NFP {
namespace ErrCodes {
constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
constexpr Result NfcDisabled(ErrorModule::NFP, 80);
constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result TagRemoved(ErrorModule::NFP, 97);
constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
} // namespace ErrCodes
constexpr u32 ApplicationAreaSize = 0xD8;
IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
: ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
nfp_interface{nfp_interface_} {
@@ -39,7 +45,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
{7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
{8, &IUser::GetApplicationArea, "GetApplicationArea"},
{9, &IUser::SetApplicationArea, "SetApplicationArea"},
{10, nullptr, "Flush"},
{10, &IUser::Flush, "Flush"},
{11, nullptr, "Restore"},
{12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
{13, &IUser::GetTagInfo, "GetTagInfo"},
@@ -53,7 +59,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
{21, &IUser::GetNpadId, "GetNpadId"},
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, nullptr, "RecreateApplicationArea"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
@@ -87,11 +93,23 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) {
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
std::vector<u64> devices;
// TODO(german77): Loop through all interfaces
devices.push_back(nfp_interface.GetHandle());
if (devices.size() == 0) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
return;
}
ctx.WriteBuffer(devices);
IPC::ResponseBuilder rb{ctx, 3};
@@ -105,6 +123,12 @@ void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
const auto nfp_protocol{rp.Pop<s32>()};
LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.StartDetection(nfp_protocol);
@@ -124,6 +148,12 @@ void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.StopDetection();
@@ -146,6 +176,12 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
model_type, mount_target);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Mount();
@@ -165,6 +201,12 @@ void IUser::Unmount(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Unmount();
@@ -186,6 +228,12 @@ void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
access_id);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.OpenApplicationArea(access_id);
@@ -205,9 +253,15 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
std::vector<u8> data{};
ApplicationArea data{};
const auto result = nfp_interface.GetApplicationArea(data);
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 3};
@@ -229,6 +283,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.SetApplicationArea(data);
@@ -243,6 +303,31 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
rb.Push(ErrCodes::DeviceNotFound);
}
void IUser::Flush(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.Flush();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
}
void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
@@ -251,6 +336,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
device_handle, access_id, data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.CreateApplicationArea(access_id, data);
@@ -270,6 +361,12 @@ void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
TagInfo tag_info{};
@@ -291,6 +388,12 @@ void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
RegisterInfo register_info{};
@@ -312,6 +415,12 @@ void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
CommonInfo common_info{};
@@ -333,6 +442,12 @@ void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
ModelInfo model_info{};
@@ -354,6 +469,12 @@ void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -373,6 +494,12 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -419,6 +546,12 @@ void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 3};
@@ -442,7 +575,7 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
if (device_handle == nfp_interface.GetHandle()) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(ApplicationAreaSize);
rb.Push(sizeof(ApplicationArea));
return;
}
@@ -455,11 +588,45 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "(STUBBED) called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
}
void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto access_id{rp.Pop<u32>()};
const auto data{ctx.ReadBuffer()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
device_handle, access_id, data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::NfcDisabled);
return;
}
// TODO(german77): Loop through all interfaces
if (device_handle == nfp_interface.GetHandle()) {
const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ErrCodes::DeviceNotFound);
}
Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)},
@@ -478,37 +645,43 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IUser>(*this, system);
}
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!amiibo_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Amiibo is already on use");
return false;
}
// Workaround for files with missing password data
std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
if (amiibo_file.Read(buffer) < tag_size_without_password) {
LOG_ERROR(Service_NFP, "Failed to read amiibo file");
return false;
}
memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
LOG_INFO(Service_NFP, "Invalid amiibo");
return false;
}
file_path = filename;
return true;
}
bool Module::Interface::LoadAmiibo(const std::string& filename) {
if (device_state != DeviceState::SearchingForTag) {
LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
return false;
}
constexpr auto tag_size = sizeof(NTAG215File);
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
std::vector<u8> amiibo_buffer = buffer;
if (amiibo_buffer.size() < tag_size_without_password) {
LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size());
if (!LoadAmiiboFile(filename)) {
return false;
}
// Ensure it has the correct size
if (amiibo_buffer.size() != tag_size) {
amiibo_buffer.resize(tag_size, 0);
}
LOG_INFO(Service_NFP, "Amiibo detected");
std::memcpy(&tag_data, buffer.data(), tag_size);
if (!IsAmiiboValid()) {
return false;
}
// This value can't be dumped from a tag. Generate it
tag_data.password.PWD = GetTagPassword(tag_data.uuid);
device_state = DeviceState::TagFound;
activate_event->GetWritableEvent().Signal();
return true;
@@ -517,55 +690,13 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
void Module::Interface::CloseAmiibo() {
LOG_INFO(Service_NFP, "Remove amiibo");
device_state = DeviceState::TagRemoved;
is_data_decoded = false;
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
encrypted_tag_data = {};
tag_data = {};
deactivate_event->GetWritableEvent().Signal();
}
bool Module::Interface::IsAmiiboValid() const {
const auto& amiibo_data = tag_data.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container);
LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init);
LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count);
LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock);
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0);
LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1);
// Check against all know constants on an amiibo binary
if (tag_data.lock_bytes != 0xE00F) {
return false;
}
if (tag_data.compability_container != 0xEEFF10F1U) {
return false;
}
if ((amiibo_data.crypto_init & 0xFF) != 0xA5) {
return false;
}
if (amiibo_data.model_info.fixed != 0x02) {
return false;
}
if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) {
return false;
}
if (tag_data.CFG0 != 0x04000000U) {
return false;
}
if (tag_data.CFG1 != 0x5F) {
return false;
}
return true;
}
Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
return activate_event->GetReadableEvent();
}
@@ -576,13 +707,20 @@ Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
void Module::Interface::Initialize() {
device_state = DeviceState::Initialized;
is_data_decoded = false;
is_application_area_initialized = false;
encrypted_tag_data = {};
tag_data = {};
}
void Module::Interface::Finalize() {
if (device_state == DeviceState::TagMounted) {
Unmount();
}
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
StopDetection();
}
device_state = DeviceState::Unaviable;
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
}
Result Module::Interface::StartDetection(s32 protocol_) {
@@ -618,42 +756,102 @@ Result Module::Interface::StopDetection() {
return ErrCodes::WrongDeviceState;
}
Result Module::Interface::Mount() {
if (device_state == DeviceState::TagFound) {
device_state = DeviceState::TagMounted;
Result Module::Interface::Flush() {
// Ignore write command if we can't encrypt the data
if (!is_data_decoded) {
return ResultSuccess;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
EncryptedNTAG215File tmp_encrypted_tag_data{};
const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (!amiibo_file.IsOpen()) {
LOG_ERROR(Core, "Amiibo is already on use");
return ErrCodes::WriteAmiiboFailed;
}
// Workaround for files with missing password data
std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
if (amiibo_file.Read(buffer) < tag_size_without_password) {
LOG_ERROR(Core, "Failed to read amiibo file");
return ErrCodes::WriteAmiiboFailed;
}
memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
LOG_INFO(Service_NFP, "Invalid amiibo");
return ErrCodes::WriteAmiiboFailed;
}
bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
tag_data.model_info.character_id;
if (!is_uuid_equal || !is_character_equal) {
LOG_ERROR(Service_NFP, "Not the same amiibo");
return ErrCodes::WriteAmiiboFailed;
}
if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Failed to encode data");
return ErrCodes::WriteAmiiboFailed;
}
// Return to the start of the file
if (!amiibo_file.Seek(0)) {
LOG_ERROR(Service_NFP, "Error writting to file");
return ErrCodes::WriteAmiiboFailed;
}
if (!amiibo_file.Write(encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Error writting to file");
return ErrCodes::WriteAmiiboFailed;
}
return ResultSuccess;
}
Result Module::Interface::Mount() {
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data);
LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded);
is_application_area_initialized = false;
device_state = DeviceState::TagMounted;
return ResultSuccess;
}
Result Module::Interface::Unmount() {
if (device_state == DeviceState::TagMounted) {
is_application_area_initialized = false;
application_area_id = 0;
application_area_data.clear();
device_state = DeviceState::TagFound;
return ResultSuccess;
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
is_data_decoded = false;
is_application_area_initialized = false;
device_state = DeviceState::TagFound;
return ResultSuccess;
}
Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
tag_info = {
.uuid = tag_data.uuid,
.uuid_length = static_cast<u8>(tag_data.uuid.size()),
.protocol = protocol,
.tag_type = static_cast<u32>(tag_data.user_memory.model_info.amiibo_type),
};
return ResultSuccess;
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
}
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ErrCodes::WrongDeviceState;
tag_info = {
.uuid = encrypted_tag_data.uuid,
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
.protocol = protocol,
.tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
};
return ResultSuccess;
}
Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
@@ -662,14 +860,28 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
return ErrCodes::WrongDeviceState;
}
// Read this data from the amiibo save file
if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
const auto& settings = tag_data.settings;
// TODO: Validate this data
common_info = {
.last_write_year = settings.write_date.GetYear(),
.last_write_month = settings.write_date.GetMonth(),
.last_write_day = settings.write_date.GetDay(),
.write_counter = settings.crc_counter,
.version = 1,
.application_area_size = sizeof(ApplicationArea),
};
return ResultSuccess;
}
// Generate a generic answer
common_info = {
.last_write_year = 2022,
.last_write_month = 2,
.last_write_day = 7,
.write_counter = tag_data.user_memory.write_count,
.write_counter = 0,
.version = 1,
.application_area_size = ApplicationAreaSize,
.application_area_size = sizeof(ApplicationArea),
};
return ResultSuccess;
}
@@ -680,26 +892,53 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
return ErrCodes::WrongDeviceState;
}
model_info = tag_data.user_memory.model_info;
const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
model_info = {
.character_id = model_info_data.character_id,
.character_variant = model_info_data.character_variant,
.amiibo_type = model_info_data.amiibo_type,
.model_number = model_info_data.model_number,
.series = model_info_data.series,
.constant_value = model_info_data.constant_value,
};
return ResultSuccess;
}
Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
Service::Mii::MiiManager manager;
// Read this data from the amiibo save file
if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
const auto& settings = tag_data.settings;
// TODO: Validate this data
register_info = {
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
.first_write_year = settings.init_date.GetYear(),
.first_write_month = settings.init_date.GetMonth(),
.first_write_day = settings.init_date.GetDay(),
.amiibo_name = GetAmiiboName(settings),
.font_region = {},
};
return ResultSuccess;
}
// Generate a generic answer
register_info = {
.mii_char_info = manager.BuildDefault(0),
.first_write_year = 2022,
.first_write_month = 2,
.first_write_day = 7,
.amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
.unknown = {},
.font_region = {},
};
return ResultSuccess;
}
@@ -707,31 +946,47 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
Result Module::Interface::OpenApplicationArea(u32 access_id) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (AmiiboApplicationDataExist(access_id)) {
application_area_data = LoadAmiiboApplicationData(access_id);
application_area_id = access_id;
is_application_area_initialized = true;
}
if (!is_application_area_initialized) {
// Fallback for lack of amiibo keys
if (!is_data_decoded) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
if (tag_data.settings.settings.appdata_initialized == 0) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
if (tag_data.application_area_id != access_id) {
LOG_WARNING(Service_NFP, "Wrong application area id");
return ErrCodes::WrongApplicationAreaId;
}
is_application_area_initialized = true;
return ResultSuccess;
}
Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (!is_application_area_initialized) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
data = application_area_data;
data = tag_data.application_area;
return ResultSuccess;
}
@@ -739,46 +994,69 @@ Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (!is_application_area_initialized) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
return ErrCodes::ApplicationAreaIsNotInitialized;
}
application_area_data = data;
SaveAmiiboApplicationData(application_area_id, application_area_data);
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
return ResultSuccess;
}
Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
if (AmiiboApplicationDataExist(access_id)) {
if (tag_data.settings.settings.appdata_initialized != 0) {
LOG_ERROR(Service_NFP, "Application area already exist");
return ErrCodes::ApplicationAreaExist;
}
application_area_data = data;
application_area_id = access_id;
SaveAmiiboApplicationData(application_area_id, application_area_data);
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
tag_data.application_area_id = access_id;
return ResultSuccess;
}
bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const {
// TODO(german77): Check if file exist
return false;
}
Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ErrCodes::TagRemoved;
}
return ErrCodes::WrongDeviceState;
}
std::vector<u8> Module::Interface::LoadAmiiboApplicationData(u32 access_id) const {
// TODO(german77): Read file
std::vector<u8> data(ApplicationAreaSize);
return data;
}
if (data.size() != sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
void Module::Interface::SaveAmiiboApplicationData(u32 access_id,
const std::vector<u8>& data) const {
// TODO(german77): Save file
std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
tag_data.application_area_id = access_id;
return ResultSuccess;
}
u64 Module::Interface::GetHandle() const {
@@ -791,16 +1069,25 @@ DeviceState Module::Interface::GetCurrentState() const {
}
Core::HID::NpadIdType Module::Interface::GetNpadId() const {
return npad_id;
// Return first connected npad id as a workaround for lack of a single nfc interface per
// controller
return system.HIDCore().GetFirstNpadId();
}
u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const {
// Verifiy that the generated password is correct
u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
return password;
AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
AmiiboName amiibo_name{};
// Convert from big endian to little endian
for (std::size_t i = 0; i < amiibo_name_length; i++) {
settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
}
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
return amiibo_name;
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+24 -121
View File
@@ -9,6 +9,7 @@
#include "common/common_funcs.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfp/amiibo_types.h"
#include "core/hle/service/service.h"
namespace Kernel {
@@ -21,71 +22,7 @@ enum class NpadIdType : u32;
} // namespace Core::HID
namespace Service::NFP {
enum class ServiceType : u32 {
User,
Debug,
System,
};
enum class State : u32 {
NonInitialized,
Initialized,
};
enum class DeviceState : u32 {
Initialized,
SearchingForTag,
TagFound,
TagRemoved,
TagMounted,
Unaviable,
Finalized,
};
enum class ModelType : u32 {
Amiibo,
};
enum class MountTarget : u32 {
Rom,
Ram,
All,
};
enum class AmiiboType : u8 {
Figure,
Card,
Yarn,
};
enum class AmiiboSeries : u8 {
SuperSmashBros,
SuperMario,
ChibiRobo,
YoshiWoollyWorld,
Splatoon,
AnimalCrossing,
EightBitMario,
Skylanders,
Unknown8,
TheLegendOfZelda,
ShovelKnight,
Unknown11,
Kiby,
Pokemon,
MarioSportsSuperstars,
MonsterHunter,
BoxBoy,
Pikmin,
FireEmblem,
Metroid,
Others,
MegaMan,
Diablo
};
using TagUuid = std::array<u8, 10>;
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
struct TagInfo {
TagUuid uuid;
@@ -114,21 +51,19 @@ struct ModelInfo {
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
u8 fixed; // Must be 02
INSERT_PADDING_BYTES(0x4); // Unknown
INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash
INSERT_PADDING_BYTES(0x14); // SHA256-HMAC
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x38); // Unknown
};
static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
struct RegisterInfo {
Service::Mii::MiiInfo mii_char_info;
Service::Mii::CharInfo mii_char_info;
u16 first_write_year;
u8 first_write_month;
u8 first_write_day;
std::array<u8, 11> amiibo_name;
u8 unknown;
INSERT_PADDING_BYTES(0x98);
AmiiboName amiibo_name;
u8 font_region;
INSERT_PADDING_BYTES(0x7A);
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
@@ -140,39 +75,9 @@ public:
const char* name);
~Interface() override;
struct EncryptedAmiiboFile {
u16 crypto_init; // Must be A5 XX
u16 write_count; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x20); // System crypts
INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash
ModelInfo model_info; // This struct is bigger than documentation
INSERT_PADDING_BYTES(0xC); // SHA256-HMAC
INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer
INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215Password {
u32 PWD; // Password to allow write access
u16 PACK; // Password acknowledge reply
u16 RFUI; // Reserved for future use
};
static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
struct NTAG215File {
TagUuid uuid; // Unique serial number
u16 lock_bytes; // Set defined pages as read only
u32 compability_container; // Defines available memory
EncryptedAmiiboFile user_memory; // Writable data
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
void CreateUserInterface(Kernel::HLERequestContext& ctx);
bool LoadAmiibo(const std::vector<u8>& buffer);
bool LoadAmiibo(const std::string& filename);
bool LoadAmiiboFile(const std::string& filename);
void CloseAmiibo();
void Initialize();
@@ -182,6 +87,7 @@ public:
Result StopDetection();
Result Mount();
Result Unmount();
Result Flush();
Result GetTagInfo(TagInfo& tag_info) const;
Result GetCommonInfo(CommonInfo& common_info) const;
@@ -189,9 +95,10 @@ public:
Result GetRegisterInfo(RegisterInfo& register_info) const;
Result OpenApplicationArea(u32 access_id);
Result GetApplicationArea(std::vector<u8>& data) const;
Result GetApplicationArea(ApplicationArea& data) const;
Result SetApplicationArea(const std::vector<u8>& data);
Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
u64 GetHandle() const;
DeviceState GetCurrentState() const;
@@ -204,27 +111,21 @@ public:
std::shared_ptr<Module> module;
private:
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid() const;
bool AmiiboApplicationDataExist(u32 access_id) const;
std::vector<u8> LoadAmiiboApplicationData(u32 access_id) const;
void SaveAmiiboApplicationData(u32 access_id, const std::vector<u8>& data) const;
/// return password needed to allow write access to protected memory
u32 GetTagPassword(const TagUuid& uuid) const;
AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
const Core::HID::NpadIdType npad_id;
DeviceState device_state{DeviceState::Unaviable};
KernelHelpers::ServiceContext service_context;
bool is_data_decoded{};
bool is_application_area_initialized{};
s32 protocol;
std::string file_path{};
Kernel::KEvent* activate_event;
Kernel::KEvent* deactivate_event;
DeviceState device_state{DeviceState::Unaviable};
KernelHelpers::ServiceContext service_context;
NTAG215File tag_data{};
s32 protocol;
bool is_application_area_initialized{};
u32 application_area_id;
std::vector<u8> application_area_data;
EncryptedNTAG215File encrypted_tag_data{};
};
};
@@ -243,6 +144,7 @@ private:
void OpenApplicationArea(Kernel::HLERequestContext& ctx);
void GetApplicationArea(Kernel::HLERequestContext& ctx);
void SetApplicationArea(Kernel::HLERequestContext& ctx);
void Flush(Kernel::HLERequestContext& ctx);
void CreateApplicationArea(Kernel::HLERequestContext& ctx);
void GetTagInfo(Kernel::HLERequestContext& ctx);
void GetRegisterInfo(Kernel::HLERequestContext& ctx);
@@ -255,6 +157,7 @@ private:
void GetNpadId(Kernel::HLERequestContext& ctx);
void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;
+26 -16
View File
@@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
s64 delay = 0;
while (!stop_token.stop_requested()) {
vsync_signal.wait(false);
vsync_signal.store(false);
guard->lock();
const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count();
Compose();
const auto ticks = GetNextTicks();
const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count();
const s64 time_passed = time_end - time_start;
const s64 next_time = std::max<s64>(0, ticks - time_passed - delay);
guard->unlock();
if (next_time > 0) {
std::this_thread::sleep_for(std::chrono::nanoseconds{next_time});
}
delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time;
}
}
@@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr
guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
composition_event = Core::Timing::CreateEvent(
multi_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
vsync_signal.store(true);
vsync_signal.notify_all();
return std::chrono::nanoseconds(GetNextTicks());
});
single_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
return std::max(std::chrono::nanoseconds::zero(),
std::chrono::nanoseconds(GetNextTicks()) - ns_late);
return std::chrono::nanoseconds(GetNextTicks());
});
if (system.IsMulticore()) {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event);
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
}
}
NVFlinger::~NVFlinger() {
if (!system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(composition_event, 0);
if (system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
vsync_thread.request_stop();
vsync_signal.store(true);
vsync_signal.notify_all();
} else {
system.CoreTiming().UnscheduleEvent(single_composition_event, {});
}
for (auto& display : displays) {
+4 -1
View File
@@ -126,12 +126,15 @@ private:
u32 swap_interval = 1;
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> composition_event;
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
std::shared_ptr<std::mutex> guard;
Core::System& system;
std::atomic<bool> vsync_signal;
std::jthread vsync_thread;
KernelHelpers::ServiceContext service_context;
+32 -18
View File
@@ -13,7 +13,9 @@
#include <boost/container/static_vector.hpp>
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "video_core/textures/astc.h"
class InputBitStream {
@@ -1650,29 +1652,41 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) {
u32 block_index = 0;
std::size_t depth_offset = 0;
for (u32 z = 0; z < depth; z++) {
for (u32 y = 0; y < height; y += block_height) {
for (u32 x = 0; x < width; x += block_width) {
const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
const u32 rows = Common::DivideUp(height, block_height);
const u32 cols = Common::DivideUp(width, block_width);
// Blocks can be at most 12x12
std::array<u32, 12 * 12> uncompData;
DecompressBlock(blockPtr, block_width, block_height, uncompData);
Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2,
"yuzu:ASTCDecompress"};
u32 decompWidth = std::min(block_width, width - x);
u32 decompHeight = std::min(block_height, height - y);
for (u32 z = 0; z < depth; ++z) {
const u32 depth_offset = z * height * width * 4;
for (u32 y_index = 0; y_index < rows; ++y_index) {
auto decompress_stride = [data, width, height, depth, block_width, block_height, output,
rows, cols, z, depth_offset, y_index] {
const u32 y = y_index * block_height;
for (u32 x_index = 0; x_index < cols; ++x_index) {
const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index;
const u32 x = x_index * block_width;
const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
for (u32 jj = 0; jj < decompHeight; jj++) {
std::memcpy(outRow.data() + jj * width * 4,
uncompData.data() + jj * block_width, decompWidth * 4);
const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
// Blocks can be at most 12x12
std::array<u32, 12 * 12> uncompData;
DecompressBlock(blockPtr, block_width, block_height, uncompData);
u32 decompWidth = std::min(block_width, width - x);
u32 decompHeight = std::min(block_height, height - y);
const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
for (u32 h = 0; h < decompHeight; ++h) {
std::memcpy(outRow.data() + h * width * 4,
uncompData.data() + h * block_width, decompWidth * 4);
}
}
++block_index;
}
};
workers.QueueWork(std::move(decompress_stride));
}
depth_offset += height * width * 4;
workers.WaitForRequests();
}
}
+13 -8
View File
@@ -128,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() {
void ConfigureWeb::OnLoginChanged() {
if (ui->edit_token->text().isEmpty()) {
user_verified = true;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
// Empty = no icon
ui->label_token_verified->setPixmap(QPixmap());
ui->label_token_verified->setToolTip(QString());
} else {
user_verified = false;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
// Show an info icon if it's been changed, clearer than showing failure
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setToolTip(
tr("Unverified, please click Verify before saving configuration", "Tooltip"));
}
}
void ConfigureWeb::VerifyLogin() {
ui->button_verify_login->setDisabled(true);
ui->button_verify_login->setText(tr("Verifying..."));
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verifying..."));
verify_watcher.setFuture(QtConcurrent::run(
[username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
@@ -155,13 +160,13 @@ void ConfigureWeb::OnLoginVerified() {
if (verify_watcher.result()) {
user_verified = true;
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
ui->username->setText(
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
} else {
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
ui->label_token_verified->setPixmap(pixmap);
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
ui->username->setText(tr("Unspecified"));
QMessageBox::critical(this, tr("Verification failed"),
tr("Verification failed. Check that you have entered your token "
+1 -20
View File
@@ -3259,26 +3259,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
return;
}
QFile nfc_file{filename};
if (!nfc_file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Error opening Amiibo data file"),
tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
return;
}
const u64 nfc_file_size = nfc_file.size();
std::vector<u8> buffer(nfc_file_size);
const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
if (nfc_file_size != read_size) {
QMessageBox::warning(this, tr("Error reading Amiibo data file"),
tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
"was only able to read %2 bytes.")
.arg(nfc_file_size)
.arg(read_size));
return;
}
if (!nfc->LoadAmiibo(buffer)) {
if (!nfc->LoadAmiibo(filename.toStdString())) {
QMessageBox::warning(this, tr("Error loading Amiibo data"),
tr("Unable to load Amiibo data."));
}