Compare commits

..

30 Commits

Author SHA1 Message Date
Fernando Sahmkow
826e0785bf Fermi2D: Cleanup and address feedback. 2022-11-24 21:00:48 +01:00
Fernando Sahmkow
7356ab1de6 GPU: Implement additional render target formats. 2022-11-24 20:35:44 +01:00
Fernando Sahmkow
daf2ef8f1c MaxwellDMA: Implement BlockLinear to BlockLinear copies. 2022-11-24 20:35:44 +01:00
Fernando Sahmkow
5fbd6954ef Fermi2D: Implement Bilinear software filtering and address feedback. 2022-11-24 20:35:44 +01:00
Fernando Sahmkow
957840be91 Fermi2D: Rework blit engine and add a software blitter. 2022-11-24 20:35:44 +01:00
liamwhite
168c9ee341 Merge pull request #9299 from lioncash/cast
k_handle_table: Remove cast to void* in GetObjectForIpc
2022-11-22 17:47:53 -05:00
Lioncash
8d99aae45b k_handle_table: Remove cast to void* in GetObjectForIpc
This was used to get around the KProcess class being incomplete. We can
just move this to the cpp file and eliminate the cast entirely, letting
the compiler do its work.
2022-11-22 13:58:42 -05:00
bunnei
f047f376d4 Merge pull request #9219 from german77/nfc_impl
service: nfc: Implement NFC IUser service
2022-11-22 10:28:38 -08:00
Narr the Reg
3027917f39 Merge pull request #9292 from Morph1984/amiibo-web-service
qt_amiibo_settings: Use WebClient only if ENABLE_WEB_SERVICE is enabled
2022-11-21 17:24:59 -06:00
Morph
7e0f70e5a1 qt_amiibo_settings: Use WebClient only if ENABLE_WEB_SERVICE is enabled
Resolves compilation errors when ENABLE_WEB_SERVICE is disabled in CMake configuration
2022-11-21 15:22:17 -05:00
Morph
aab68674c0 Merge pull request #9279 from liamwhite/this-would-have-never-happened-in-rust
dmnt:cht: fix copy-paste error
2022-11-20 13:30:35 -05:00
liamwhite
7f1c6def1f Merge pull request #9216 from vonchenplus/reimp_inline_index_buffer
video_core: Reimplement inline index buffer binding
2022-11-20 12:08:08 -05:00
Narr the Reg
db7bcd51ae Merge branch 'master' into nfc_impl 2022-11-20 09:31:20 -06:00
Liam
eb0713f781 dmnt:cht: fix copy-paste error 2022-11-20 10:14:22 -05:00
bunnei
57a05b1653 Merge pull request #9238 from german77/cabinet_applet
service: am: Implement cabinet applet
2022-11-20 00:48:39 -08:00
Morph
69c92b8156 Merge pull request #9249 from goldenx86/available-vram
Add available Vulkan VRAM to log files
2022-11-20 00:21:29 -05:00
Morph
4acbf3a193 Merge pull request #9274 from Morph1984/issue-forms
github: Add new issue form templates
2022-11-20 00:21:24 -05:00
Morph
54d6273975 github: Add blank issue template
This is meant to only be used by developers.
2022-11-20 00:18:53 -05:00
Morph
98a6cd02c8 github: Remove old markdown form
Replaced by the newer issue forms template
2022-11-19 17:41:26 -05:00
Morph
52acdafa95 github: Add new feature request issue form 2022-11-19 17:41:26 -05:00
Morph
975e17aa13 github: Add new bug report issue form 2022-11-19 17:41:26 -05:00
Narr the Reg
327d225c3e service: nfc: Implement nfc user 2022-11-19 08:51:59 -06:00
Matías Locatti
7c50a916c7 Update renderer_vulkan.cpp 2022-11-16 05:53:42 -03:00
Feng Chen
cb971ad654 video_core: Reimplement inline index buffer binding 2022-11-15 12:10:44 +08:00
german77
75e6ec85e1 general: Address review comments 2022-11-13 17:13:43 -06:00
german77
a253d1557d service: am: Fix cabinet applet result 2022-11-13 14:25:00 -06:00
german77
9afadca5dc yuzu: Implement cabinet applet frontend 2022-11-13 13:58:19 -06:00
german77
fb57cd26a1 service: am: Implement cabinet applet backend 2022-11-13 11:07:48 -06:00
german77
b193d40d22 input_common: Add amiibo applet functions 2022-11-13 10:56:54 -06:00
german77
6c045c9beb service: nfc: fix tagprotocol and implement GetApplicationAreaId 2022-11-13 10:52:48 -06:00
62 changed files with 4030 additions and 225 deletions

View File

@@ -0,0 +1,10 @@
name: New Issue (Developers Only)
description: A blank issue template for developers only. If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.
body:
- type: markdown
attributes:
value: |
**If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.**
- type: textarea
attributes:
label: "Issue"

View File

@@ -1,39 +0,0 @@
---
name: Bug Report / Feature Request
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
title: ''
labels: ''
assignees: ''
---
<!---
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
Please read the FAQ:
https://yuzu-emu.org/wiki/faq/
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
https://community.citra-emu.org/
If the FAQ does not answer your question, please go to:
https://community.citra-emu.org/
When submitting an issue, please check the following:
- You have read the above.
- You have provided the version (commit hash) of yuzu you are using.
- You have provided sufficient detail for the issue to be reproduced.
- You have provided system specs (if relevant).
- Please also provide:
- For any issues, a log file
- For crashes, a backtrace.
- For graphical issues, comparison screenshots with real hardware.
- For emulation inaccuracies, a test-case (if able).
-->

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Bug Report
description: File a bug report
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: input
attributes:
label: Affected Build(s)
description: List the affected build(s) that this issue applies to.
placeholder: Mainline 1234 / Early Access 1234
validations:
required: true
- type: textarea
id: issue-desc
attributes:
label: Description of Issue
description: A brief description of the issue encountered along with any images and/or videos.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: A brief description of how it is expected to work along with any images and/or videos.
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Reproduction Steps
description: A brief explanation of how to reproduce this issue. If possible, provide a save file to aid in reproducing the issue.
validations:
required: true
- type: textarea
id: log
attributes:
label: Log File
description: A log file will help our developers to better diagnose and fix the issue.
validations:
required: true
- type: textarea
id: system-config
attributes:
label: System Configuration
placeholder: |
CPU: Intel i5-10400 / AMD Ryzen 5 3600
GPU/Driver: NVIDIA GeForce GTX 1060 (Driver 512.95)
RAM: 16GB DDR4-3200
OS: Windows 11 22H2 (Build 22621.819)
value: |
CPU:
GPU/Driver:
RAM:
OS:
validations:
required: true

View File

@@ -0,0 +1,28 @@
name: Feature Request
description: File a feature request
labels: "request"
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
options:
- label: I have searched the existing issues
required: true
- type: textarea
id: what-feature
attributes:
label: What feature are you suggesting?
description: A brief description of the requested feature.
validations:
required: true
- type: textarea
id: why-feature
attributes:
label: Why would this feature be useful?
description: A brief description of why this feature would make yuzu better.
validations:
required: true

View File

@@ -132,10 +132,6 @@ Files: vcpkg.json
Copyright: 2022 yuzu Emulator Project
License: GPL-3.0-or-later
Files: .github/ISSUE_TEMPLATE/config.yml
Copyright: 2020 tgsm <doodrabbit@hotmail.com>
License: GPL-2.0-or-later
Files: .github/ISSUE_TEMPLATE/bug-report-feature-request.md
Copyright: 2016 MerryMage
Files: .github/ISSUE_TEMPLATE/*
Copyright: 2022 yuzu Emulator Project
License: GPL-2.0-or-later

View File

@@ -120,6 +120,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
frontend/applets/cabinet.cpp
frontend/applets/cabinet.h
frontend/applets/controller.cpp
frontend/applets/controller.h
frontend/applets/error.cpp
@@ -312,6 +314,8 @@ add_library(core STATIC
hle/service/am/applet_ae.h
hle/service/am/applet_oe.cpp
hle/service/am/applet_oe.h
hle/service/am/applets/applet_cabinet.cpp
hle/service/am/applets/applet_cabinet.h
hle/service/am/applets/applet_controller.cpp
hle/service/am/applets/applet_controller.h
hle/service/am/applets/applet_error.cpp
@@ -526,6 +530,11 @@ add_library(core STATIC
hle/service/ncm/ncm.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
hle/service/nfc/nfc_device.cpp
hle/service/nfc/nfc_device.h
hle/service/nfc/nfc_result.h
hle/service/nfc/nfc_user.cpp
hle/service/nfc/nfc_user.h
hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h
hle/service/nfp/nfp.cpp

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/frontend/applets/cabinet.h"
#include <thread>
namespace Core::Frontend {
CabinetApplet::~CabinetApplet() = default;
void DefaultCabinetApplet::ShowCabinetApplet(
const CabinetCallback& callback, const CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
LOG_WARNING(Service_AM, "(STUBBED) called");
callback(false, {});
}
} // namespace Core::Frontend

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
class NfpDevice;
} // namespace Service::NFP
namespace Core::Frontend {
struct CabinetParameters {
Service::NFP::TagInfo tag_info;
Service::NFP::RegisterInfo register_info;
Service::NFP::CabinetMode mode;
};
using CabinetCallback = std::function<void(bool, const std::string&)>;
class CabinetApplet {
public:
virtual ~CabinetApplet();
virtual void ShowCabinetApplet(const CabinetCallback& callback,
const CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const = 0;
};
class DefaultCabinetApplet final : public CabinetApplet {
public:
void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
};
} // namespace Core::Frontend

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
namespace Kernel {
@@ -82,6 +83,22 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
R_SUCCEED();
}
KScopedAutoObject<KAutoObject> KHandleTable::GetObjectForIpc(Handle handle,
KThread* cur_thread) const {
// Handle pseudo-handles.
ASSERT(cur_thread != nullptr);
if (handle == Svc::PseudoHandle::CurrentProcess) {
auto* const cur_process = cur_thread->GetOwnerProcess();
ASSERT(cur_process != nullptr);
return cur_process;
}
if (handle == Svc::PseudoHandle::CurrentThread) {
return cur_thread;
}
return GetObjectForIpcWithoutPseudoHandle(handle);
}
Result KHandleTable::Reserve(Handle* out_handle) {
KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);

View File

@@ -113,21 +113,7 @@ public:
return this->GetObjectImpl(handle);
}
KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const {
// Handle pseudo-handles.
ASSERT(cur_thread != nullptr);
if (handle == Svc::PseudoHandle::CurrentProcess) {
auto* const cur_process =
static_cast<KAutoObject*>(static_cast<void*>(cur_thread->GetOwnerProcess()));
ASSERT(cur_process != nullptr);
return cur_process;
}
if (handle == Svc::PseudoHandle::CurrentThread) {
return static_cast<KAutoObject*>(cur_thread);
}
return GetObjectForIpcWithoutPseudoHandle(handle);
}
KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const;
KScopedAutoObject<KAutoObject> GetObjectByIndex(Handle* out_handle, size_t index) const {
KScopedDisableDispatch dd{m_kernel};

View File

@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/applets/cabinet.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/nfp_device.h"
namespace Service::AM::Applets {
Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
const Core::Frontend::CabinetApplet& frontend_)
: Applet{system_, applet_mode_}, frontend{frontend_}, system{system_}, service_context{
system_,
"CabinetApplet"} {
availability_change_event =
service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent");
}
Cabinet::~Cabinet() = default;
void Cabinet::Initialize() {
Applet::Initialize();
LOG_INFO(Service_HID, "Initializing Cabinet Applet.");
LOG_DEBUG(Service_HID,
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
common_args.arguments_version, common_args.library_version,
common_args.play_startup_sound, common_args.size, common_args.system_tick,
common_args.theme_color);
const auto storage = broker.PopNormalDataToApplet();
ASSERT(storage != nullptr);
const auto applet_input_data = storage->GetData();
ASSERT(applet_input_data.size() >= sizeof(StartParamForAmiiboSettings));
std::memcpy(&applet_input_common, applet_input_data.data(),
sizeof(StartParamForAmiiboSettings));
}
bool Cabinet::TransactionComplete() const {
return is_complete;
}
Result Cabinet::GetStatus() const {
return ResultSuccess;
}
void Cabinet::ExecuteInteractive() {
ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet.");
}
void Cabinet::Execute() {
if (is_complete) {
return;
}
const auto callback = [this](bool apply_changes, const std::string& amiibo_name) {
DisplayCompleted(apply_changes, amiibo_name);
};
// TODO: listen on all controllers
if (nfp_device == nullptr) {
nfp_device = std::make_shared<Service::NFP::NfpDevice>(
system.HIDCore().GetFirstNpadId(), system, service_context, availability_change_event);
nfp_device->Initialize();
nfp_device->StartDetection(Service::NFP::TagProtocol::All);
}
const Core::Frontend::CabinetParameters parameters{
.tag_info = applet_input_common.tag_info,
.register_info = applet_input_common.register_info,
.mode = applet_input_common.applet_mode,
};
switch (applet_input_common.applet_mode) {
case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
case Service::NFP::CabinetMode::StartGameDataEraser:
case Service::NFP::CabinetMode::StartRestorer:
case Service::NFP::CabinetMode::StartFormatter:
frontend.ShowCabinetApplet(callback, parameters, nfp_device);
break;
default:
UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode);
DisplayCompleted(false, {});
break;
}
}
void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) {
Service::Mii::MiiManager manager;
ReturnValueForAmiiboSettings applet_output{};
if (!apply_changes) {
Cancel();
}
if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
Cancel();
}
if (nfp_device->GetCurrentState() == Service::NFP::DeviceState::TagFound) {
nfp_device->Mount(Service::NFP::MountTarget::All);
}
switch (applet_input_common.applet_mode) {
case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: {
Service::NFP::AmiiboName name{};
std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1));
nfp_device->SetNicknameAndOwner(name);
break;
}
case Service::NFP::CabinetMode::StartGameDataEraser:
nfp_device->DeleteApplicationArea();
break;
case Service::NFP::CabinetMode::StartRestorer:
nfp_device->RestoreAmiibo();
break;
case Service::NFP::CabinetMode::StartFormatter:
nfp_device->DeleteAllData();
break;
default:
UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode);
break;
}
applet_output.device_handle = applet_input_common.device_handle;
applet_output.result = CabinetResult::Cancel;
const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
nfp_device->Finalize();
if (reg_result.IsSuccess()) {
applet_output.result |= CabinetResult::RegisterInfo;
}
if (tag_result.IsSuccess()) {
applet_output.result |= CabinetResult::TagInfo;
}
std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings));
std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings));
is_complete = true;
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
broker.SignalStateChanged();
}
void Cabinet::Cancel() {
ReturnValueForAmiiboSettings applet_output{};
applet_output.device_handle = applet_input_common.device_handle;
applet_output.result = CabinetResult::Cancel;
nfp_device->Finalize();
std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings));
std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings));
is_complete = true;
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp_types.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Core {
class System;
} // namespace Core
namespace Service::NFP {
class NfpDevice;
}
namespace Service::AM::Applets {
enum class CabinetAppletVersion : u32 {
Version1 = 0x1,
};
enum class CabinetResult : u8 {
Cancel = 0,
TagInfo = 1 << 1,
RegisterInfo = 1 << 2,
All = TagInfo | RegisterInfo,
};
DECLARE_ENUM_FLAG_OPERATORS(CabinetResult)
// This is nn::nfp::AmiiboSettingsStartParam
struct AmiiboSettingsStartParam {
u64 device_handle;
std::array<u8, 0x20> param_1;
u8 param_2;
};
static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
"AmiiboSettingsStartParam is an invalid size");
#pragma pack(push, 1)
// This is nn::nfp::StartParamForAmiiboSettings
struct StartParamForAmiiboSettings {
u8 param_1;
Service::NFP::CabinetMode applet_mode;
u8 flags;
u8 amiibo_settings_1;
u64 device_handle;
Service::NFP::TagInfo tag_info;
Service::NFP::RegisterInfo register_info;
std::array<u8, 0x20> amiibo_settings_3;
INSERT_PADDING_BYTES(0x24);
};
static_assert(sizeof(StartParamForAmiiboSettings) == 0x1A8,
"StartParamForAmiiboSettings is an invalid size");
// This is nn::nfp::ReturnValueForAmiiboSettings
struct ReturnValueForAmiiboSettings {
CabinetResult result;
INSERT_PADDING_BYTES(0x3);
u64 device_handle;
Service::NFP::TagInfo tag_info;
Service::NFP::RegisterInfo register_info;
INSERT_PADDING_BYTES(0x24);
};
static_assert(sizeof(ReturnValueForAmiiboSettings) == 0x188,
"ReturnValueForAmiiboSettings is an invalid size");
#pragma pack(pop)
class Cabinet final : public Applet {
public:
explicit Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
const Core::Frontend::CabinetApplet& frontend_);
~Cabinet() override;
void Initialize() override;
bool TransactionComplete() const override;
Result GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void DisplayCompleted(bool apply_changes, std::string_view amiibo_name);
void Cancel();
private:
const Core::Frontend::CabinetApplet& frontend;
Core::System& system;
bool is_complete{false};
std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
Kernel::KEvent* availability_change_event;
KernelHelpers::ServiceContext service_context;
StartParamForAmiiboSettings applet_input_common{};
};
} // namespace Service::AM::Applets

View File

@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "core/core.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
@@ -16,6 +17,7 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/am/applets/applet_controller.h"
#include "core/hle/service/am/applets/applet_error.h"
#include "core/hle/service/am/applets/applet_general_backend.h"
@@ -171,13 +173,15 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
AppletFrontendSet::AppletFrontendSet(CabinetApplet cabinet_applet,
ControllerApplet controller_applet, ErrorApplet error_applet,
MiiEdit mii_edit_,
ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)},
: cabinet{std::move(cabinet_applet)}, controller{std::move(controller_applet)},
error{std::move(error_applet)}, mii_edit{std::move(mii_edit_)},
parental_controls{std::move(parental_controls_applet)},
photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
@@ -196,6 +200,10 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
}
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.cabinet != nullptr) {
frontend.cabinet = std::move(set.cabinet);
}
if (set.controller != nullptr) {
frontend.controller = std::move(set.controller);
}
@@ -235,6 +243,10 @@ void AppletManager::SetDefaultAppletFrontendSet() {
}
void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.cabinet == nullptr) {
frontend.cabinet = std::make_unique<Core::Frontend::DefaultCabinetApplet>();
}
if (frontend.controller == nullptr) {
frontend.controller =
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore());
@@ -279,6 +291,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(system, mode, *frontend.parental_controls);
case AppletId::Cabinet:
return std::make_shared<Cabinet>(system, mode, *frontend.cabinet);
case AppletId::Controller:
return std::make_shared<Controller>(system, mode, *frontend.controller);
case AppletId::Error:

View File

@@ -16,6 +16,7 @@ class System;
}
namespace Core::Frontend {
class CabinetApplet;
class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
@@ -176,6 +177,7 @@ protected:
};
struct AppletFrontendSet {
using CabinetApplet = std::unique_ptr<Core::Frontend::CabinetApplet>;
using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
using MiiEdit = std::unique_ptr<Core::Frontend::MiiEditApplet>;
@@ -186,10 +188,11 @@ struct AppletFrontendSet {
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
AppletFrontendSet();
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_);
AppletFrontendSet(CabinetApplet cabinet_applet, ControllerApplet controller_applet,
ErrorApplet error_applet, MiiEdit mii_edit_,
ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
WebBrowser web_browser_);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -198,6 +201,7 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
CabinetApplet cabinet;
ControllerApplet controller;
ErrorApplet error;
MiiEdit mii_edit;

View File

@@ -7,6 +7,7 @@
#include "common/settings.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfc/nfc_user.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
@@ -97,76 +98,6 @@ private:
}
};
class IUser final : public ServiceFramework<IUser> {
public:
explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUser::InitializeOld, "InitializeOld"},
{1, &IUser::FinalizeOld, "FinalizeOld"},
{2, &IUser::GetStateOld, "GetStateOld"},
{3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
{400, &IUser::InitializeOld, "Initialize"},
{401, &IUser::FinalizeOld, "Finalize"},
{402, &IUser::GetStateOld, "GetState"},
{403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},
{407, nullptr, "AttachAvailabilityChangeEvent"},
{408, nullptr, "StartDetection"},
{409, nullptr, "StopDetection"},
{410, nullptr, "GetTagInfo"},
{411, nullptr, "AttachActivateEvent"},
{412, nullptr, "AttachDeactivateEvent"},
{1000, nullptr, "ReadMifare"},
{1001, nullptr, "WriteMifare"},
{1300, nullptr, "SendCommandByPassThrough"},
{1301, nullptr, "KeepPassThroughSession"},
{1302, nullptr, "ReleasePassThroughSession"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
enum class NfcStates : u32 {
Finalized = 6,
};
void InitializeOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0};
rb.Push(ResultSuccess);
// We don't deal with hardware initialization so we can just stub this.
}
void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "IsNfcEnabledOld");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushRaw<u8>(true);
}
void GetStateOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(NfcStates::Finalized); // TODO(ogniK): Figure out if this matches nfp
}
void FinalizeOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NFC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
class NFC_U final : public ServiceFramework<NFC_U> {
public:
explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} {

View File

@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/input.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/nfc/nfc_device.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/nfc/nfc_user.h"
namespace Service::NFC {
NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
KernelHelpers::ServiceContext& service_context_,
Kernel::KEvent* availability_change_event_)
: npad_id{npad_id_}, system{system_}, service_context{service_context_},
availability_change_event{availability_change_event_} {
activate_event = service_context.CreateEvent("IUser:NFCActivateEvent");
deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent");
npad_device = system.HIDCore().GetEmulatedController(npad_id);
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
.is_npad_service = false,
};
is_controller_set = true;
callback_key = npad_device->SetCallback(engine_callback);
}
NfcDevice::~NfcDevice() {
activate_event->Close();
deactivate_event->Close();
if (!is_controller_set) {
return;
}
npad_device->DeleteCallback(callback_key);
is_controller_set = false;
};
void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
if (type == Core::HID::ControllerTriggerType::Connected ||
type == Core::HID::ControllerTriggerType::Disconnected) {
availability_change_event->Signal();
return;
}
if (type != Core::HID::ControllerTriggerType::Nfc) {
return;
}
if (!npad_device->IsConnected()) {
return;
}
const auto nfc_status = npad_device->GetNfc();
switch (nfc_status.state) {
case Common::Input::NfcState::NewAmiibo:
LoadNfcTag(nfc_status.data);
break;
case Common::Input::NfcState::AmiiboRemoved:
if (device_state != NFP::DeviceState::SearchingForTag) {
CloseNfcTag();
}
break;
default:
break;
}
}
bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
if (device_state != NFP::DeviceState::SearchingForTag) {
LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
return false;
}
if (data.size() != sizeof(NFP::EncryptedNTAG215File)) {
LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
return false;
}
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
device_state = NFP::DeviceState::TagFound;
deactivate_event->GetReadableEvent().Clear();
activate_event->Signal();
return true;
}
void NfcDevice::CloseNfcTag() {
LOG_INFO(Service_NFC, "Remove nfc tag");
device_state = NFP::DeviceState::TagRemoved;
encrypted_tag_data = {};
activate_event->GetReadableEvent().Clear();
deactivate_event->Signal();
}
Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
return activate_event->GetReadableEvent();
}
Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
return deactivate_event->GetReadableEvent();
}
void NfcDevice::Initialize() {
device_state =
npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable;
encrypted_tag_data = {};
}
void NfcDevice::Finalize() {
if (device_state == NFP::DeviceState::SearchingForTag ||
device_state == NFP::DeviceState::TagRemoved) {
StopDetection();
}
device_state = NFP::DeviceState::Unavailable;
}
Result NfcDevice::StartDetection(s32 protocol_) {
if (device_state != NFP::DeviceState::Initialized &&
device_state != NFP::DeviceState::TagRemoved) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
return WrongDeviceState;
}
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
LOG_ERROR(Service_NFC, "Nfc not supported");
return NfcDisabled;
}
device_state = NFP::DeviceState::SearchingForTag;
protocol = protocol_;
return ResultSuccess;
}
Result NfcDevice::StopDetection() {
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
if (device_state == NFP::DeviceState::Initialized) {
return ResultSuccess;
}
if (device_state == NFP::DeviceState::TagFound ||
device_state == NFP::DeviceState::TagMounted) {
CloseNfcTag();
return ResultSuccess;
}
if (device_state == NFP::DeviceState::SearchingForTag ||
device_state == NFP::DeviceState::TagRemoved) {
device_state = NFP::DeviceState::Initialized;
return ResultSuccess;
}
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
return WrongDeviceState;
}
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
if (device_state != NFP::DeviceState::TagFound &&
device_state != NFP::DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == NFP::DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
// Protocol and tag type may change here
tag_info = {
.uuid = encrypted_tag_data.uuid.uid,
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
.protocol = NFP::TagProtocol::TypeA,
.tag_type = NFP::TagType::Type2,
};
return ResultSuccess;
}
u64 NfcDevice::GetHandle() const {
// Generate a handle based of the npad id
return static_cast<u64>(npad_id);
}
NFP::DeviceState NfcDevice::GetCurrentState() const {
return device_state;
}
Core::HID::NpadIdType NfcDevice::GetNpadId() const {
return npad_id;
}
} // namespace Service::NFC

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "core/hle/service/service.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Core {
class System;
} // namespace Core
namespace Core::HID {
class EmulatedController;
enum class ControllerTriggerType;
enum class NpadIdType : u32;
} // namespace Core::HID
namespace Service::NFC {
class NfcDevice {
public:
NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
KernelHelpers::ServiceContext& service_context_,
Kernel::KEvent* availability_change_event_);
~NfcDevice();
void Initialize();
void Finalize();
Result StartDetection(s32 protocol_);
Result StopDetection();
Result GetTagInfo(NFP::TagInfo& tag_info) const;
u64 GetHandle() const;
NFP::DeviceState GetCurrentState() const;
Core::HID::NpadIdType GetNpadId() const;
Kernel::KReadableEvent& GetActivateEvent() const;
Kernel::KReadableEvent& GetDeactivateEvent() const;
private:
void NpadUpdate(Core::HID::ControllerTriggerType type);
bool LoadNfcTag(std::span<const u8> data);
void CloseNfcTag();
bool is_controller_set{};
int callback_key;
const Core::HID::NpadIdType npad_id;
Core::System& system;
Core::HID::EmulatedController* npad_device = nullptr;
KernelHelpers::ServiceContext& service_context;
Kernel::KEvent* activate_event = nullptr;
Kernel::KEvent* deactivate_event = nullptr;
Kernel::KEvent* availability_change_event = nullptr;
s32 protocol{};
NFP::DeviceState device_state{NFP::DeviceState::Unavailable};
NFP::EncryptedNTAG215File encrypted_tag_data{};
};
} // namespace Service::NFC

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
namespace Service::NFC {
constexpr Result DeviceNotFound(ErrorModule::NFC, 64);
constexpr Result InvalidArgument(ErrorModule::NFC, 65);
constexpr Result WrongDeviceState(ErrorModule::NFC, 73);
constexpr Result NfcDisabled(ErrorModule::NFC, 80);
constexpr Result TagRemoved(ErrorModule::NFC, 97);
constexpr Result CorruptedData(ErrorModule::NFC, 144);
} // namespace Service::NFC

View File

@@ -0,0 +1,365 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/nfc/nfc_device.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/nfc/nfc_user.h"
#include "core/hle/service/time/clock_types.h"
namespace Service::NFC {
IUser::IUser(Core::System& system_)
: ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "InitializeOld"},
{1, &IUser::Finalize, "FinalizeOld"},
{2, &IUser::GetState, "GetStateOld"},
{3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"},
{400, &IUser::Initialize, "Initialize"},
{401, &IUser::Finalize, "Finalize"},
{402, &IUser::GetState, "GetState"},
{403, &IUser::IsNfcEnabled, "IsNfcEnabled"},
{404, &IUser::ListDevices, "ListDevices"},
{405, &IUser::GetDeviceState, "GetDeviceState"},
{406, &IUser::GetNpadId, "GetNpadId"},
{407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{408, &IUser::StartDetection, "StartDetection"},
{409, &IUser::StopDetection, "StopDetection"},
{410, &IUser::GetTagInfo, "GetTagInfo"},
{411, &IUser::AttachActivateEvent, "AttachActivateEvent"},
{412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
{1000, nullptr, "ReadMifare"},
{1001, nullptr, "WriteMifare"},
{1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"},
{1301, nullptr, "KeepPassThroughSession"},
{1302, nullptr, "ReleasePassThroughSession"},
};
RegisterHandlers(functions);
availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
for (u32 device_index = 0; device_index < 10; device_index++) {
devices[device_index] =
std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
service_context, availability_change_event);
}
}
IUser ::~IUser() {
availability_change_event->Close();
}
void IUser::Initialize(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called");
state = State::Initialized;
for (auto& device : devices) {
device->Initialize();
}
IPC::ResponseBuilder rb{ctx, 2, 0};
rb.Push(ResultSuccess);
}
void IUser::Finalize(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called");
state = State::NonInitialized;
for (auto& device : devices) {
device->Finalize();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IUser::GetState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(state);
}
void IUser::IsNfcEnabled(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(state != State::NonInitialized);
}
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
if (!ctx.CanWriteBuffer()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(InvalidArgument);
return;
}
if (ctx.GetWriteBufferSize() == 0) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(InvalidArgument);
return;
}
std::vector<u64> nfp_devices;
const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
for (auto& device : devices) {
if (nfp_devices.size() >= max_allowed_devices) {
continue;
}
if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
nfp_devices.push_back(device->GetHandle());
}
}
if (nfp_devices.empty()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
ctx.WriteBuffer(nfp_devices);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(static_cast<s32>(nfp_devices.size()));
}
void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(device.value()->GetCurrentState());
}
void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(device.value()->GetNpadId());
}
void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
}
void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto nfp_protocol{rp.Pop<s32>()};
LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->StartDetection(nfp_protocol);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->StopDetection();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
NFP::TagInfo tag_info{};
const auto result = device.value()->GetTagInfo(tag_info);
ctx.WriteBuffer(tag_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(device.value()->GetActivateEvent());
}
void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(device.value()->GetDeactivateEvent());
}
void IUser::SendCommandByPassThrough(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
const auto command_data{ctx.ReadBuffer()};
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
device_handle, timeout.ToSeconds(), command_data.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfcDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
std::vector<u8> out_data(1);
// TODO: Request data from nfc device
ctx.WriteBuffer(out_data);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(out_data.size()));
}
std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) {
for (auto& device : devices) {
if (device->GetHandle() == handle) {
return device;
}
}
return std::nullopt;
}
} // namespace Service::NFC

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <optional>
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
namespace Service::NFC {
class NfcDevice;
class IUser final : public ServiceFramework<IUser> {
public:
explicit IUser(Core::System& system_);
~IUser();
private:
enum class State : u32 {
NonInitialized,
Initialized,
};
void Initialize(Kernel::HLERequestContext& ctx);
void Finalize(Kernel::HLERequestContext& ctx);
void GetState(Kernel::HLERequestContext& ctx);
void IsNfcEnabled(Kernel::HLERequestContext& ctx);
void ListDevices(Kernel::HLERequestContext& ctx);
void GetDeviceState(Kernel::HLERequestContext& ctx);
void GetNpadId(Kernel::HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
void StartDetection(Kernel::HLERequestContext& ctx);
void StopDetection(Kernel::HLERequestContext& ctx);
void GetTagInfo(Kernel::HLERequestContext& ctx);
void AttachActivateEvent(Kernel::HLERequestContext& ctx);
void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
void SendCommandByPassThrough(Kernel::HLERequestContext& ctx);
std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
KernelHelpers::ServiceContext service_context;
std::array<std::shared_ptr<NfcDevice>, 10> devices{};
State state{State::NonInitialized};
Kernel::KEvent* availability_change_event;
};
} // namespace Service::NFC

View File

@@ -12,7 +12,6 @@
#include "common/fs/fs.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 {

View File

@@ -2,10 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/input.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -19,7 +16,6 @@
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_device.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
@@ -49,6 +45,8 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
}
NfpDevice::~NfpDevice() {
activate_event->Close();
deactivate_event->Close();
if (!is_controller_set) {
return;
}
@@ -77,6 +75,9 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
LoadAmiibo(nfc_status.data);
break;
case Common::Input::NfcState::AmiiboRemoved:
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
break;
}
if (device_state != DeviceState::SearchingForTag) {
CloseAmiibo();
}
@@ -97,6 +98,8 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
return false;
}
// TODO: Filter by allowed_protocols here
memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
device_state = DeviceState::TagFound;
@@ -143,7 +146,7 @@ void NfpDevice::Finalize() {
device_state = DeviceState::Unavailable;
}
Result NfpDevice::StartDetection(s32 protocol_) {
Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return WrongDeviceState;
@@ -155,7 +158,7 @@ Result NfpDevice::StartDetection(s32 protocol_) {
}
device_state = DeviceState::SearchingForTag;
protocol = protocol_;
allowed_protocols = allowed_protocol;
return ResultSuccess;
}
@@ -469,6 +472,32 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {
return ResultSuccess;
}
Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const {
application_area_id = {};
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
return WrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
return ApplicationAreaIsNotInitialized;
}
application_area_id = tag_data.application_area_id;
return ResultSuccess;
}
Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);

View File

@@ -3,10 +3,10 @@
#pragma once
#include <array>
#include <span>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "core/hle/service/service.h"
@@ -37,7 +37,7 @@ public:
void Initialize();
void Finalize();
Result StartDetection(s32 protocol_);
Result StartDetection(TagProtocol allowed_protocol);
Result StopDetection();
Result Mount(MountTarget mount_target);
Result Unmount();
@@ -53,6 +53,7 @@ public:
Result DeleteAllData();
Result OpenApplicationArea(u32 access_id);
Result GetApplicationAreaId(u32& application_area_id) const;
Result GetApplicationArea(std::vector<u8>& data) const;
Result SetApplicationArea(std::span<const u8> data);
Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
@@ -88,7 +89,7 @@ private:
bool is_data_moddified{};
bool is_app_area_open{};
s32 protocol{};
TagProtocol allowed_protocols{};
s64 current_posix_time{};
MountTarget mount_target{MountTarget::None};
DeviceState device_state{DeviceState::Unavailable};

View File

@@ -88,11 +88,22 @@ enum class PackedTagType : u8 {
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
};
// Verify this enum. It might be completely wrong default protocol is 0x48
enum class TagProtocol : u32 {
None,
TypeA, // ISO14443A
TypeB, // ISO14443B
TypeF, // Sony Felica
TypeA = 1U << 0, // ISO14443A
TypeB = 1U << 1, // ISO14443B
TypeF = 1U << 2, // Sony Felica
Unknown1 = 1U << 3,
Unknown2 = 1U << 5,
All = 0xFFFFFFFFU,
};
enum class CabinetMode : u8 {
StartNicknameAndOwnerSettings,
StartGameDataEraser,
StartRestorer,
StartFormatter,
};
using UniqueSerialNumber = std::array<u8, 7>;

View File

@@ -1,9 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/hid_types.h"
@@ -55,8 +52,12 @@ IUser::IUser(Core::System& system_)
}
}
IUser ::~IUser() {
availability_change_event->Close();
}
void IUser::Initialize(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called");
LOG_INFO(Service_NFP, "called");
state = State::Initialized;
@@ -64,7 +65,7 @@ void IUser::Initialize(Kernel::HLERequestContext& ctx) {
device->Initialize();
}
IPC::ResponseBuilder rb{ctx, 2, 0};
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
@@ -130,7 +131,7 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto nfp_protocol{rp.Pop<s32>()};
const auto nfp_protocol{rp.PopEnum<TagProtocol>()};
LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
if (state == State::NonInitialized) {
@@ -551,9 +552,9 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
}
void IUser::GetState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 3, 0};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(state);
}

View File

@@ -3,6 +3,10 @@
#pragma once
#include <array>
#include <memory>
#include <optional>
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
@@ -12,6 +16,7 @@ class NfpDevice;
class IUser final : public ServiceFramework<IUser> {
public:
explicit IUser(Core::System& system_);
~IUser();
private:
enum class State : u32 {

View File

@@ -761,7 +761,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
u64 src_address =
GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address);
u64 src_value = 0;
switch (store_static->bit_width) {
switch (begin_cond->bit_width) {
case 1:
case 2:
case 4:

View File

@@ -60,6 +60,8 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData(
return Common::Input::NfcState::WriteFailed;
}
amiibo_data = data;
return Common::Input::NfcState::Success;
}
@@ -91,6 +93,15 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
return Info::Success;
}
VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
if (state == State::AmiiboIsOpen) {
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
return Info::Success;
}
return LoadAmiibo(file_path);
}
VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
: State::Initialized;
@@ -98,4 +109,8 @@ VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
return Info::Success;
}
std::string VirtualAmiibo::GetLastFilePath() const {
return file_path;
}
} // namespace InputCommon

View File

@@ -47,8 +47,11 @@ public:
State GetCurrentState() const;
Info LoadAmiibo(const std::string& amiibo_file);
Info ReloadAmiibo();
Info CloseAmiibo();
std::string GetLastFilePath() const;
private:
static constexpr std::size_t amiibo_size = 0x21C;
static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8;

View File

@@ -133,7 +133,7 @@ public:
return Common::Input::CameraError::NotSupported;
}
// Request nfc data from a controller
// Returns success if nfc is supported
virtual Common::Input::NfcState SupportsNfc(
[[maybe_unused]] const PadIdentifier& identifier) const {
return Common::Input::NfcState::NotSupported;

View File

@@ -28,6 +28,10 @@ add_library(video_core STATIC
dirty_flags.h
dma_pusher.cpp
dma_pusher.h
engines/sw_blitter/blitter.cpp
engines/sw_blitter/blitter.h
engines/sw_blitter/converter.cpp
engines/sw_blitter/converter.h
engines/const_buffer_info.h
engines/engine_interface.h
engines/engine_upload.cpp

View File

@@ -992,7 +992,20 @@ void BufferCache<P>::BindHostIndexBuffer() {
TouchBuffer(buffer, index_buffer.buffer_id);
const u32 offset = buffer.Offset(index_buffer.cpu_addr);
const u32 size = index_buffer.size;
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
if (maxwell3d->inline_index_draw_indexes.size()) {
if constexpr (USE_MEMORY_MAPS) {
auto upload_staging = runtime.UploadStagingBuffer(size);
std::array<BufferCopy, 1> copies{
{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
std::memcpy(upload_staging.mapped_span.data(),
maxwell3d->inline_index_draw_indexes.data(), size);
runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
} else {
buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes);
}
} else {
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
}
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const u32 new_offset = offset + maxwell3d->regs.index_buffer.first *
maxwell3d->regs.index_buffer.FormatSizeInBytes();
@@ -1275,7 +1288,15 @@ void BufferCache<P>::UpdateIndexBuffer() {
}
flags[Dirty::IndexBuffer] = false;
last_index_count = index_array.count;
if (maxwell3d->inline_index_draw_indexes.size()) {
auto inline_index_size = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
index_buffer = Binding{
.cpu_addr = 0,
.size = inline_index_size,
.buffer_id = CreateBuffer(0, inline_index_size),
};
return;
}
const GPUVAddr gpu_addr_begin = index_array.StartAddress();
const GPUVAddr gpu_addr_end = index_array.EndAddress();
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
@@ -1491,6 +1512,14 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
VAddr end = cpu_addr + wanted_size;
int stream_score = 0;
bool has_stream_leap = false;
if (begin == 0) {
return OverlapResult{
.ids = std::move(overlap_ids),
.begin = begin,
.end = end,
.has_stream_leap = has_stream_leap,
};
}
for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE);
cpu_addr += YUZU_PAGESIZE) {
const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS];

View File

@@ -20,7 +20,7 @@ void ChannelState::Init(Core::System& system, GPU& gpu) {
ASSERT(memory_manager);
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>();
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);

View File

@@ -3,17 +3,25 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/memory_manager.h"
#include "video_core/engines/sw_blitter/blitter.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/surface.h"
#include "video_core/textures/decoders.h"
MICROPROFILE_DECLARE(GPU_BlitEngine);
MICROPROFILE_DEFINE(GPU_BlitEngine, "GPU", "Blit Engine", MP_RGB(224, 224, 128));
using VideoCore::Surface::BytesPerBlock;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
namespace Tegra::Engines {
Fermi2D::Fermi2D() {
using namespace Texture;
Fermi2D::Fermi2D(MemoryManager& memory_manager_) {
sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager_);
// Nvidia's OpenGL driver seems to assume these values
regs.src.depth = 1;
regs.dst.depth = 1;
@@ -42,6 +50,7 @@ void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32
}
void Fermi2D::Blit() {
MICROPROFILE_SCOPE(GPU_BlitEngine);
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}",
regs.src.Address(), regs.dst.Address());
@@ -52,9 +61,16 @@ void Fermi2D::Blit() {
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
const auto& args = regs.pixels_from_memory;
constexpr s64 null_derivate = 1ULL << 32;
Surface src = regs.src;
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
src.format != regs.dst.format;
Config config{
.operation = regs.operation,
.filter = args.sample_mode.filter,
.must_accelerate =
args.du_dx != null_derivate || args.dv_dy != null_derivate || delegate_to_gpu,
.dst_x0 = args.dst_x0,
.dst_y0 = args.dst_y0,
.dst_x1 = args.dst_x0 + args.dst_width,
@@ -64,8 +80,7 @@ void Fermi2D::Blit() {
.src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32),
.src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32),
};
Surface src = regs.src;
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const auto need_align_to_pitch =
src.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch &&
static_cast<s32>(src.width) == config.src_x1 &&
@@ -78,8 +93,9 @@ void Fermi2D::Blit() {
config.src_x1 -= config.src_x0;
config.src_x0 = 0;
}
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
UNIMPLEMENTED();
sw_blitter->Blit(src, regs.dst, config);
}
}

View File

@@ -5,6 +5,7 @@
#include <array>
#include <cstddef>
#include <memory>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
@@ -21,6 +22,10 @@ class RasterizerInterface;
namespace Tegra::Engines {
namespace Blitter {
class SoftwareBlitEngine;
}
/**
* This Engine is known as G80_2D. Documentation can be found in:
* https://github.com/envytools/envytools/blob/master/rnndb/graph/g80_2d.xml
@@ -32,7 +37,7 @@ namespace Tegra::Engines {
class Fermi2D final : public EngineInterface {
public:
explicit Fermi2D();
explicit Fermi2D(MemoryManager& memory_manager_);
~Fermi2D() override;
/// Binds a rasterizer to this engine.
@@ -286,6 +291,7 @@ public:
struct Config {
Operation operation;
Filter filter;
bool must_accelerate;
s32 dst_x0;
s32 dst_y0;
s32 dst_x1;
@@ -298,6 +304,7 @@ public:
private:
VideoCore::RasterizerInterface* rasterizer = nullptr;
std::unique_ptr<Blitter::SoftwareBlitEngine> sw_blitter;
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.

View File

@@ -62,7 +62,8 @@ void MaxwellDMA::Launch() {
if (!is_src_pitch && !is_dst_pitch) {
// If both the source and the destination are in block layout, assert.
UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented");
CopyBlockLinearToBlockLinear();
ReleaseSemaphore();
return;
}
@@ -291,6 +292,70 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
void MaxwellDMA::CopyBlockLinearToBlockLinear() {
UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
const bool is_remapping = regs.launch_dma.remap_enable != 0;
// Deswizzle the input and copy it over.
const Parameters& src = regs.src_params;
const Parameters& dst = regs.dst_params;
const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
u32 src_width = src.width;
u32 dst_width = dst.width;
u32 x_elements = regs.line_length_in;
u32 src_x_offset = src.origin.x;
u32 dst_x_offset = dst.origin.x;
u32 bpp_shift = 0U;
if (!is_remapping) {
bpp_shift = Common::FoldRight(
4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
src_width, dst_width, x_elements, src_x_offset, dst_x_offset,
static_cast<u32>(regs.offset_in), static_cast<u32>(regs.offset_out));
src_width >>= bpp_shift;
dst_width >>= bpp_shift;
x_elements >>= bpp_shift;
src_x_offset >>= bpp_shift;
dst_x_offset >>= bpp_shift;
}
const u32 bytes_per_pixel = base_bpp << bpp_shift;
const size_t src_size = CalculateSize(true, bytes_per_pixel, src_width, src.height, src.depth,
src.block_size.height, src.block_size.depth);
const size_t dst_size = CalculateSize(true, bytes_per_pixel, dst_width, dst.height, dst.depth,
dst.block_size.height, dst.block_size.depth);
const u32 pitch = x_elements * bytes_per_pixel;
const size_t mid_buffer_size = pitch * regs.line_count;
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
intermediate_buffer.resize(mid_buffer_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
UnswizzleSubrect(intermediate_buffer, read_buffer, bytes_per_pixel, src_width, src.height,
src.depth, src_x_offset, src.origin.y, x_elements, regs.line_count,
src.block_size.height, src.block_size.depth, pitch);
SwizzleSubrect(write_buffer, intermediate_buffer, bytes_per_pixel, dst_width, dst.height,
dst.depth, dst_x_offset, dst.origin.y, x_elements, regs.line_count,
dst.block_size.height, dst.block_size.depth, pitch);
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
void MaxwellDMA::ReleaseSemaphore() {
const auto type = regs.launch_dma.semaphore_type;
const GPUVAddr address = regs.semaphore.address;

View File

@@ -223,6 +223,8 @@ private:
void CopyPitchToBlockLinear();
void CopyBlockLinearToBlockLinear();
void FastCopyBlockLinearToPitch();
void ReleaseSemaphore();
@@ -234,6 +236,7 @@ private:
std::vector<u8> read_buffer;
std::vector<u8> write_buffer;
std::vector<u8> intermediate_buffer;
static constexpr std::size_t NUM_REGS = 0x800;
struct Regs {

View File

@@ -0,0 +1,238 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cmath>
#include <vector>
#include "video_core/engines/sw_blitter/blitter.h"
#include "video_core/engines/sw_blitter/converter.h"
#include "video_core/memory_manager.h"
#include "video_core/surface.h"
#include "video_core/textures/decoders.h"
namespace Tegra {
class MemoryManager;
}
using VideoCore::Surface::BytesPerBlock;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
namespace Tegra::Engines::Blitter {
using namespace Texture;
namespace {
constexpr size_t ir_components = 4;
void NearestNeighbor(std::span<const u8> input, std::span<u8> output, u32 src_width, u32 src_height,
u32 dst_width, u32 dst_height, size_t bpp) {
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
size_t src_y = 0;
for (u32 y = 0; y < dst_height; y++) {
size_t src_x = 0;
for (u32 x = 0; x < dst_width; x++) {
const size_t read_from = ((src_y * src_width + src_x) >> 32) * bpp;
const size_t write_to = (y * dst_width + x) * bpp;
std::memcpy(&output[write_to], &input[read_from], bpp);
src_x += dx_du;
}
src_y += dy_dv;
}
}
void NearestNeighborFast(std::span<const f32> input, std::span<f32> output, u32 src_width,
u32 src_height, u32 dst_width, u32 dst_height) {
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
size_t src_y = 0;
for (u32 y = 0; y < dst_height; y++) {
size_t src_x = 0;
for (u32 x = 0; x < dst_width; x++) {
const size_t read_from = ((src_y * src_width + src_x) >> 32) * ir_components;
const size_t write_to = (y * dst_width + x) * ir_components;
std::memcpy(&output[write_to], &input[read_from], sizeof(f32) * ir_components);
src_x += dx_du;
}
src_y += dy_dv;
}
}
void Bilinear(std::span<const f32> input, std::span<f32> output, size_t src_width,
size_t src_height, size_t dst_width, size_t dst_height) {
const auto bilinear_sample = [](std::span<const f32> x0_y0, std::span<const f32> x1_y0,
std::span<const f32> x0_y1, std::span<const f32> x1_y1,
f32 weight_x, f32 weight_y) {
std::array<f32, ir_components> result{};
for (size_t i = 0; i < ir_components; i++) {
const f32 a = std::lerp(x0_y0[i], x1_y0[i], weight_x);
const f32 b = std::lerp(x0_y1[i], x1_y1[i], weight_x);
result[i] = std::lerp(a, b, weight_y);
}
return result;
};
const f32 dx_du =
dst_width > 1 ? static_cast<f32>(src_width - 1) / static_cast<f32>(dst_width - 1) : 0.f;
const f32 dy_dv =
dst_height > 1 ? static_cast<f32>(src_height - 1) / static_cast<f32>(dst_height - 1) : 0.f;
for (u32 y = 0; y < dst_height; y++) {
for (u32 x = 0; x < dst_width; x++) {
const f32 x_low = std::floor(static_cast<f32>(x) * dx_du);
const f32 y_low = std::floor(static_cast<f32>(y) * dy_dv);
const f32 x_high = std::ceil(static_cast<f32>(x) * dx_du);
const f32 y_high = std::ceil(static_cast<f32>(y) * dy_dv);
const f32 weight_x = (static_cast<f32>(x) * dx_du) - x_low;
const f32 weight_y = (static_cast<f32>(y) * dy_dv) - y_low;
const auto read_src = [&](f32 in_x, f32 in_y) {
const size_t read_from =
((static_cast<size_t>(in_x) * src_width + static_cast<size_t>(in_y)) >> 32) *
ir_components;
return std::span<const f32>(&input[read_from], ir_components);
};
auto x0_y0 = read_src(x_low, y_low);
auto x1_y0 = read_src(x_high, y_low);
auto x0_y1 = read_src(x_low, y_high);
auto x1_y1 = read_src(x_high, y_high);
const auto result = bilinear_sample(x0_y0, x1_y0, x0_y1, x1_y1, weight_x, weight_y);
const size_t write_to = (y * dst_width + x) * ir_components;
std::memcpy(&output[write_to], &result, sizeof(f32) * ir_components);
}
}
}
} // namespace
struct SoftwareBlitEngine::BlitEngineImpl {
std::vector<u8> tmp_buffer;
std::vector<u8> src_buffer;
std::vector<u8> dst_buffer;
std::vector<f32> intermediate_src;
std::vector<f32> intermediate_dst;
ConverterFactory converter_factory;
};
SoftwareBlitEngine::SoftwareBlitEngine(MemoryManager& memory_manager_)
: memory_manager{memory_manager_} {
impl = std::make_unique<BlitEngineImpl>();
}
SoftwareBlitEngine::~SoftwareBlitEngine() = default;
bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
Fermi2D::Config& config) {
const auto get_surface_size = [](Fermi2D::Surface& surface, u32 bytes_per_pixel) {
if (surface.linear == Fermi2D::MemoryLayout::BlockLinear) {
return CalculateSize(true, bytes_per_pixel, surface.width, surface.height,
surface.depth, surface.block_height, surface.block_depth);
}
return static_cast<size_t>(surface.pitch * surface.height);
};
const auto process_pitch_linear = [](bool unpack, std::span<const u8> input,
std::span<u8> output, u32 extent_x, u32 extent_y,
u32 pitch, u32 x0, u32 y0, size_t bpp) {
const size_t base_offset = x0 * bpp;
const size_t copy_size = extent_x * bpp;
for (u32 y = y0; y < extent_y; y++) {
const size_t first_offset = y * pitch + base_offset;
const size_t second_offset = y * extent_x * bpp;
u8* write_to = unpack ? &output[first_offset] : &output[second_offset];
const u8* read_from = unpack ? &input[second_offset] : &input[first_offset];
std::memcpy(write_to, read_from, copy_size);
}
};
const u32 src_extent_x = config.src_x1 - config.src_x0;
const u32 src_extent_y = config.src_y1 - config.src_y0;
const u32 dst_extent_x = config.dst_x1 - config.dst_x0;
const u32 dst_extent_y = config.dst_y1 - config.dst_y0;
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
impl->tmp_buffer.resize(src_size);
memory_manager.ReadBlock(src.Address(), impl->tmp_buffer.data(), src_size);
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
impl->src_buffer.resize(src_copy_size);
const bool no_passthrough =
src.format != dst.format || src_extent_x != dst_extent_x || src_extent_y != dst_extent_y;
const auto convertion_phase_same_format = [&]() {
NearestNeighbor(impl->src_buffer, impl->dst_buffer, src_extent_x, src_extent_y,
dst_extent_x, dst_extent_y, dst_bytes_per_pixel);
};
const auto convertion_phase_ir = [&]() {
auto* input_converter = impl->converter_factory.GetFormatConverter(src.format);
impl->intermediate_src.resize((src_copy_size / src_bytes_per_pixel) * ir_components);
impl->intermediate_dst.resize((dst_copy_size / dst_bytes_per_pixel) * ir_components);
input_converter->ConvertTo(impl->src_buffer, impl->intermediate_src);
if (config.filter != Fermi2D::Filter::Bilinear) {
NearestNeighborFast(impl->intermediate_src, impl->intermediate_dst, src_extent_x,
src_extent_y, dst_extent_x, dst_extent_y);
} else {
Bilinear(impl->intermediate_src, impl->intermediate_dst, src_extent_x, src_extent_y,
dst_extent_x, dst_extent_y);
}
auto* output_converter = impl->converter_factory.GetFormatConverter(dst.format);
output_converter->ConvertFrom(impl->intermediate_dst, impl->dst_buffer);
};
// Do actuall Blit
impl->dst_buffer.resize(dst_copy_size);
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
UnswizzleSubrect(impl->src_buffer, impl->tmp_buffer, src_bytes_per_pixel, src.width,
src.height, src.depth, config.src_x0, config.src_y0, src_extent_x,
src_extent_y, src.block_height, src.block_depth,
src_extent_x * src_bytes_per_pixel);
} else {
process_pitch_linear(false, impl->tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
src.pitch, config.src_x0, config.src_y0, src_bytes_per_pixel);
}
// Conversion Phase
if (no_passthrough) {
if (src.format != dst.format || config.filter == Fermi2D::Filter::Bilinear) {
convertion_phase_ir();
} else {
convertion_phase_same_format();
}
} else {
impl->dst_buffer.swap(impl->src_buffer);
}
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
impl->tmp_buffer.resize(dst_size);
memory_manager.ReadBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
SwizzleSubrect(impl->tmp_buffer, impl->dst_buffer, dst_bytes_per_pixel, dst.width,
dst.height, dst.depth, config.dst_x0, config.dst_y0, dst_extent_x,
dst_extent_y, dst.block_height, dst.block_depth,
dst_extent_x * dst_bytes_per_pixel);
} else {
process_pitch_linear(true, impl->dst_buffer, impl->tmp_buffer, dst_extent_x, dst_extent_y,
dst.pitch, config.dst_x0, config.dst_y0,
static_cast<size_t>(dst_bytes_per_pixel));
}
memory_manager.WriteBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
return true;
}
} // namespace Tegra::Engines::Blitter

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "video_core/engines/fermi_2d.h"
namespace Tegra {
class MemoryManager;
}
namespace Tegra::Engines::Blitter {
class SoftwareBlitEngine {
public:
explicit SoftwareBlitEngine(MemoryManager& memory_manager_);
~SoftwareBlitEngine();
bool Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst, Fermi2D::Config& copy_config);
private:
MemoryManager& memory_manager;
struct BlitEngineImpl;
std::unique_ptr<BlitEngineImpl> impl;
};
} // namespace Tegra::Engines::Blitter

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <memory>
#include <span>
#include "common/common_types.h"
#include "video_core/gpu.h"
namespace Tegra::Engines::Blitter {
class Converter {
public:
virtual void ConvertTo(std::span<const u8> input, std::span<f32> output) = 0;
virtual void ConvertFrom(std::span<const f32> input, std::span<u8> output) = 0;
virtual ~Converter() = default;
};
class ConverterFactory {
public:
ConverterFactory();
~ConverterFactory();
Converter* GetFormatConverter(RenderTargetFormat format);
private:
Converter* BuildConverter(RenderTargetFormat format);
struct ConverterFactoryImpl;
std::unique_ptr<ConverterFactoryImpl> impl;
};
} // namespace Tegra::Engines::Blitter

View File

@@ -0,0 +1,136 @@
# SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
import re
class Format:
def __init__(self, string_value):
self.name = string_value
tmp = string_value.split('_')
self.component_type = tmp[1]
component_data = re.findall(r"\w\d+", tmp[0])
self.num_components = len(component_data)
sizes = []
swizzle = []
for data in component_data:
swizzle.append(data[0])
sizes.append(int(data[1:]))
self.sizes = sizes
self.swizzle = swizzle
def build_component_type_array(self):
result = "{ "
b = False
for i in range(0, self.num_components):
if b:
result += ", "
b = True
result += "ComponentType::" + self.component_type
result += " }"
return result
def build_component_sizes_array(self):
result = "{ "
b = False
for i in range(0, self.num_components):
if b:
result += ", "
b = True
result += str(self.sizes[i])
result += " }"
return result
def build_component_swizzle_array(self):
result = "{ "
b = False
for i in range(0, self.num_components):
if b:
result += ", "
b = True
swizzle = self.swizzle[i]
if swizzle == "X":
swizzle = "None"
result += "Swizzle::" + swizzle
result += " }"
return result
def print_declaration(self):
print("struct " + self.name + "Traits {")
print(" static constexpr size_t num_components = " + str(self.num_components) + ";")
print(" static constexpr std::array<ComponentType, num_components> component_types = " + self.build_component_type_array() + ";")
print(" static constexpr std::array<size_t, num_components> component_sizes = " + self.build_component_sizes_array() + ";")
print(" static constexpr std::array<Swizzle, num_components> component_swizzle = " + self.build_component_swizzle_array() + ";")
print("};\n")
def print_case(self):
print("case RenderTargetFormat::" + self.name + ":")
print(" return impl->converters_cache")
print(" .emplace(format, std::make_unique<ConverterImpl<" + self.name + "Traits>>())")
print(" .first->second.get();")
print(" break;")
txt = """
R32G32B32A32_FLOAT
R32G32B32A32_SINT
R32G32B32A32_UINT
R32G32B32X32_FLOAT
R32G32B32X32_SINT
R32G32B32X32_UINT
R16G16B16A16_UNORM
R16G16B16A16_SNORM
R16G16B16A16_SINT
R16G16B16A16_UINT
R16G16B16A16_FLOAT
R32G32_FLOAT
R32G32_SINT
R32G32_UINT
R16G16B16X16_FLOAT
A8R8G8B8_UNORM
A8R8G8B8_SRGB
A2B10G10R10_UNORM
A2B10G10R10_UINT
A2R10G10B10_UNORM
A8B8G8R8_UNORM
A8B8G8R8_SRGB
A8B8G8R8_SNORM
A8B8G8R8_SINT
A8B8G8R8_UINT
R16G16_UNORM
R16G16_SNORM
R16G16_SINT
R16G16_UINT
R16G16_FLOAT
B10G11R11_FLOAT
R32_SINT
R32_UINT
R32_FLOAT
X8R8G8B8_UNORM
X8R8G8B8_SRGB
R5G6B5_UNORM
A1R5G5B5_UNORM
R8G8_UNORM
R8G8_SNORM
R8G8_SINT
R8G8_UINT
R16_UNORM
R16_SNORM
R16_SINT
R16_UINT
R16_FLOAT
R8_UNORM
R8_SNORM
R8_SINT
R8_UINT
X1R5G5B5_UNORM
X8B8G8R8_UNORM
X8B8G8R8_SRGB
"""
x = txt.split()
y = list(map(lambda a: Format(a), x))
formats = list(y)
for format in formats:
format.print_declaration()
for format in formats:
format.print_case()

View File

@@ -27,12 +27,12 @@ struct CommandList;
// TODO: Implement the commented ones
enum class RenderTargetFormat : u32 {
NONE = 0x0,
R32B32G32A32_FLOAT = 0xC0,
R32G32B32A32_FLOAT = 0xC0,
R32G32B32A32_SINT = 0xC1,
R32G32B32A32_UINT = 0xC2,
// R32G32B32X32_FLOAT = 0xC3,
// R32G32B32X32_SINT = 0xC4,
// R32G32B32X32_UINT = 0xC5,
R32G32B32X32_FLOAT = 0xC3,
R32G32B32X32_SINT = 0xC4,
R32G32B32X32_UINT = 0xC5,
R16G16B16A16_UNORM = 0xC6,
R16G16B16A16_SNORM = 0xC7,
R16G16B16A16_SINT = 0xC8,
@@ -56,13 +56,13 @@ enum class RenderTargetFormat : u32 {
R16G16_SINT = 0xDC,
R16G16_UINT = 0xDD,
R16G16_FLOAT = 0xDE,
// A2R10G10B10_UNORM = 0xDF,
A2R10G10B10_UNORM = 0xDF,
B10G11R11_FLOAT = 0xE0,
R32_SINT = 0xE3,
R32_UINT = 0xE4,
R32_FLOAT = 0xE5,
// X8R8G8B8_UNORM = 0xE6,
// X8R8G8B8_SRGB = 0xE7,
X8R8G8B8_UNORM = 0xE6,
X8R8G8B8_SRGB = 0xE7,
R5G6B5_UNORM = 0xE8,
A1R5G5B5_UNORM = 0xE9,
R8G8_UNORM = 0xEA,
@@ -79,11 +79,11 @@ enum class RenderTargetFormat : u32 {
R8_SINT = 0xF5,
R8_UINT = 0xF6,
/*
A8_UNORM = 0xF7,
// A8_UNORM = 0xF7,
X1R5G5B5_UNORM = 0xF8,
X8B8G8R8_UNORM = 0xF9,
X8B8G8R8_SRGB = 0xFA,
/*
Z1R5G5B5_UNORM = 0xFB,
O1R5G5B5_UNORM = 0xFC,
Z8R8G8B8_UNORM = 0xFD,

View File

@@ -222,8 +222,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
BindInlineIndexBuffer();
SyncState();
const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d->regs.draw.topology);
@@ -468,8 +466,7 @@ bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surf
const Tegra::Engines::Fermi2D::Config& copy_config) {
MICROPROFILE_SCOPE(OpenGL_Blits);
std::scoped_lock lock{texture_cache.mutex};
texture_cache.BlitImage(dst, src, copy_config);
return true;
return texture_cache.BlitImage(dst, src, copy_config);
}
Tegra::Engines::AccelerateDMAInterface& RasterizerOpenGL::AccessAccelerateDMA() {
@@ -1140,16 +1137,6 @@ void RasterizerOpenGL::ReleaseChannel(s32 channel_id) {
query_cache.EraseChannel(channel_id);
}
void RasterizerOpenGL::BindInlineIndexBuffer() {
if (maxwell3d->inline_index_draw_indexes.empty()) {
return;
}
const auto data_count = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
auto buffer = Buffer(buffer_cache_runtime, *this, 0, data_count);
buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes);
buffer_cache_runtime.BindIndexBuffer(buffer, 0, data_count);
}
AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_) : buffer_cache{buffer_cache_} {}
bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {

View File

@@ -199,8 +199,6 @@ private:
/// End a transform feedback
void EndTransformFeedback();
void BindInlineIndexBuffer();
Tegra::GPU& gpu;
const Device& device;

View File

@@ -28,6 +28,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1R5G5B5_UNORM
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM
{GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT
{GL_RGB10_A2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2R10G10B10_UNORM
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A5B5G5R1_UNORM
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM

View File

@@ -125,6 +125,7 @@ struct FormatTuple {
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM
{VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
{VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
{VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle)
{VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled)
{VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM

View File

@@ -172,6 +172,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
}
void RendererVulkan::Report() const {
using namespace Common::Literals;
const std::string vendor_name{device.GetVendorName()};
const std::string model_name{device.GetModelName()};
const std::string driver_version = GetDriverVersion(device);
@@ -181,9 +182,12 @@ void RendererVulkan::Report() const {
const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions());
const auto available_vram = static_cast<f64>(device.GetDeviceLocalMemory()) / f64{1_GiB};
LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);

View File

@@ -191,8 +191,6 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
BindInlineIndexBuffer();
BeginTransformFeedback();
UpdateDynamicStates();
@@ -544,8 +542,7 @@ bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surf
const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Config& copy_config) {
std::scoped_lock lock{texture_cache.mutex};
texture_cache.BlitImage(dst, src, copy_config);
return true;
return texture_cache.BlitImage(dst, src, copy_config);
}
Tegra::Engines::AccelerateDMAInterface& RasterizerVulkan::AccessAccelerateDMA() {
@@ -1029,17 +1026,4 @@ void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
query_cache.EraseChannel(channel_id);
}
void RasterizerVulkan::BindInlineIndexBuffer() {
if (maxwell3d->inline_index_draw_indexes.empty()) {
return;
}
const auto data_count = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
auto buffer = buffer_cache_runtime.UploadStagingBuffer(data_count);
std::memcpy(buffer.mapped_span.data(), maxwell3d->inline_index_draw_indexes.data(), data_count);
buffer_cache_runtime.BindIndexBuffer(
maxwell3d->regs.draw.topology, maxwell3d->regs.index_buffer.format,
maxwell3d->regs.index_buffer.first, maxwell3d->regs.index_buffer.count, buffer.buffer,
static_cast<u32>(buffer.offset), data_count);
}
} // namespace Vulkan

View File

@@ -141,8 +141,6 @@ private:
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
void BindInlineIndexBuffer();
Tegra::GPU& gpu;
ScreenInfo& screen_info;

View File

@@ -93,11 +93,14 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
switch (format) {
case Tegra::RenderTargetFormat::R32B32G32A32_FLOAT:
case Tegra::RenderTargetFormat::R32G32B32A32_FLOAT:
case Tegra::RenderTargetFormat::R32G32B32X32_FLOAT:
return PixelFormat::R32G32B32A32_FLOAT;
case Tegra::RenderTargetFormat::R32G32B32A32_SINT:
case Tegra::RenderTargetFormat::R32G32B32X32_SINT:
return PixelFormat::R32G32B32A32_SINT;
case Tegra::RenderTargetFormat::R32G32B32A32_UINT:
case Tegra::RenderTargetFormat::R32G32B32X32_UINT:
return PixelFormat::R32G32B32A32_UINT;
case Tegra::RenderTargetFormat::R16G16B16A16_UNORM:
return PixelFormat::R16G16B16A16_UNORM;
@@ -118,16 +121,22 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
case Tegra::RenderTargetFormat::R16G16B16X16_FLOAT:
return PixelFormat::R16G16B16X16_FLOAT;
case Tegra::RenderTargetFormat::A8R8G8B8_UNORM:
case Tegra::RenderTargetFormat::X8R8G8B8_UNORM:
return PixelFormat::B8G8R8A8_UNORM;
case Tegra::RenderTargetFormat::A8R8G8B8_SRGB:
case Tegra::RenderTargetFormat::X8R8G8B8_SRGB:
return PixelFormat::B8G8R8A8_SRGB;
case Tegra::RenderTargetFormat::A2B10G10R10_UNORM:
return PixelFormat::A2B10G10R10_UNORM;
case Tegra::RenderTargetFormat::A2B10G10R10_UINT:
return PixelFormat::A2B10G10R10_UINT;
case Tegra::RenderTargetFormat::A2R10G10B10_UNORM:
return PixelFormat::A2R10G10B10_UNORM;
case Tegra::RenderTargetFormat::A8B8G8R8_UNORM:
case Tegra::RenderTargetFormat::X8B8G8R8_UNORM:
return PixelFormat::A8B8G8R8_UNORM;
case Tegra::RenderTargetFormat::A8B8G8R8_SRGB:
case Tegra::RenderTargetFormat::X8B8G8R8_SRGB:
return PixelFormat::A8B8G8R8_SRGB;
case Tegra::RenderTargetFormat::A8B8G8R8_SNORM:
return PixelFormat::A8B8G8R8_SNORM;
@@ -156,6 +165,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
case Tegra::RenderTargetFormat::R5G6B5_UNORM:
return PixelFormat::R5G6B5_UNORM;
case Tegra::RenderTargetFormat::A1R5G5B5_UNORM:
case Tegra::RenderTargetFormat::X1R5G5B5_UNORM:
return PixelFormat::A1R5G5B5_UNORM;
case Tegra::RenderTargetFormat::R8G8_UNORM:
return PixelFormat::R8G8_UNORM;

View File

@@ -23,6 +23,7 @@ enum class PixelFormat {
A1R5G5B5_UNORM,
A2B10G10R10_UNORM,
A2B10G10R10_UINT,
A2R10G10B10_UNORM,
A1B5G5R5_UNORM,
A5B5G5R1_UNORM,
R8_UNORM,
@@ -159,6 +160,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // A1R5G5B5_UNORM
1, // A2B10G10R10_UNORM
1, // A2B10G10R10_UINT
1, // A2R10G10B10_UNORM
1, // A1B5G5R5_UNORM
1, // A5B5G5R1_UNORM
1, // R8_UNORM
@@ -264,6 +266,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // A1R5G5B5_UNORM
1, // A2B10G10R10_UNORM
1, // A2B10G10R10_UINT
1, // A2R10G10B10_UNORM
1, // A1B5G5R5_UNORM
1, // A5B5G5R1_UNORM
1, // R8_UNORM
@@ -369,6 +372,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
16, // A1R5G5B5_UNORM
32, // A2B10G10R10_UNORM
32, // A2B10G10R10_UINT
32, // A2R10G10B10_UNORM
16, // A1B5G5R5_UNORM
16, // A5B5G5R1_UNORM
8, // R8_UNORM

View File

@@ -35,6 +35,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "A2B10G10R10_UNORM";
case PixelFormat::A2B10G10R10_UINT:
return "A2B10G10R10_UINT";
case PixelFormat::A2R10G10B10_UNORM:
return "A2R10G10B10_UNORM";
case PixelFormat::A1B5G5R5_UNORM:
return "A1B5G5R5_UNORM";
case PixelFormat::A5B5G5R1_UNORM:

View File

@@ -506,10 +506,14 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
}
template <class P>
void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
bool TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy) {
const BlitImages images = GetBlitImages(dst, src, copy);
const auto result = GetBlitImages(dst, src, copy);
if (!result) {
return false;
}
const BlitImages images = *result;
const ImageId dst_id = images.dst_id;
const ImageId src_id = images.src_id;
@@ -596,6 +600,7 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
runtime.BlitImage(dst_framebuffer, dst_view, src_view, dst_region, src_region, copy.filter,
copy.operation);
}
return true;
}
template <class P>
@@ -1133,7 +1138,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
}
template <class P>
typename TextureCache<P>::BlitImages TextureCache<P>::GetBlitImages(
std::optional<typename TextureCache<P>::BlitImages> TextureCache<P>::GetBlitImages(
const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy) {
@@ -1154,6 +1159,20 @@ typename TextureCache<P>::BlitImages TextureCache<P>::GetBlitImages(
has_deleted_images = false;
src_id = FindImage(src_info, src_addr, try_options);
dst_id = FindImage(dst_info, dst_addr, try_options);
if (!copy.must_accelerate) {
do {
if (!src_id && !dst_id) {
return std::nullopt;
}
if (src_id && True(slot_images[src_id].flags & ImageFlagBits::GpuModified)) {
break;
}
if (dst_id && True(slot_images[dst_id].flags & ImageFlagBits::GpuModified)) {
break;
}
return std::nullopt;
} while (false);
}
const ImageBase* const src_image = src_id ? &slot_images[src_id] : nullptr;
if (src_image && src_image->info.num_samples > 1) {
RelaxedOptions find_options{FIND_OPTIONS | RelaxedOptions::ForceBrokenViews};
@@ -1194,12 +1213,12 @@ typename TextureCache<P>::BlitImages TextureCache<P>::GetBlitImages(
dst_id = FindOrInsertImage(dst_info, dst_addr, RelaxedOptions{});
} while (has_deleted_images);
}
return BlitImages{
return {BlitImages{
.dst_id = dst_id,
.src_id = src_id,
.dst_format = dst_info.format,
.src_format = src_info.format,
};
}};
}
template <class P>

View File

@@ -174,7 +174,7 @@ public:
void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size);
/// Blit an image with the given parameters
void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
bool BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy);
@@ -285,9 +285,9 @@ private:
[[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
/// Return a blit image pair from the given guest blit parameters
[[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy);
[[nodiscard]] std::optional<BlitImages> GetBlitImages(
const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy);
/// Find or create a sampler from a guest descriptor sampler
[[nodiscard]] SamplerId FindSampler(const TSCEntry& config);

View File

@@ -18,6 +18,9 @@ add_executable(yuzu
about_dialog.cpp
about_dialog.h
aboutdialog.ui
applets/qt_amiibo_settings.cpp
applets/qt_amiibo_settings.h
applets/qt_amiibo_settings.ui
applets/qt_controller.cpp
applets/qt_controller.h
applets/qt_controller.ui

View File

@@ -0,0 +1,264 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <thread>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/hle/service/nfp/nfp_device.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
#include "ui_qt_amiibo_settings.h"
#ifdef ENABLE_WEB_SERVICE
#include "web_service/web_backend.h"
#endif
#include "yuzu/applets/qt_amiibo_settings.h"
#include "yuzu/main.h"
QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
Core::Frontend::CabinetParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device_)
: QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()),
input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)},
parameters(std::move(parameters_)) {
ui->setupUi(this);
LoadInfo();
resize(0, 0);
}
QtAmiiboSettingsDialog::~QtAmiiboSettingsDialog() = default;
int QtAmiiboSettingsDialog::exec() {
if (!is_initalized) {
return QDialog::Rejected;
}
return QDialog::exec();
}
std::string QtAmiiboSettingsDialog::GetName() const {
return ui->amiiboCustomNameValue->text().toStdString();
}
void QtAmiiboSettingsDialog::LoadInfo() {
if (input_subsystem->GetVirtualAmiibo()->ReloadAmiibo() !=
InputCommon::VirtualAmiibo::Info::Success) {
return;
}
if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
return;
}
nfp_device->Mount(Service::NFP::MountTarget::All);
LoadAmiiboInfo();
LoadAmiiboData();
LoadAmiiboGameInfo();
ui->amiiboDirectoryValue->setText(
QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath()));
SetSettingsDescription();
is_initalized = true;
}
void QtAmiiboSettingsDialog::LoadAmiiboInfo() {
Service::NFP::ModelInfo model_info{};
const auto model_result = nfp_device->GetModelInfo(model_info);
if (model_result.IsFailure()) {
ui->amiiboImageLabel->setVisible(false);
ui->amiiboInfoGroup->setVisible(false);
return;
}
const auto amiibo_id =
fmt::format("{:04x}{:02x}{:02x}{:04x}{:02x}02", Common::swap16(model_info.character_id),
model_info.character_variant, model_info.amiibo_type, model_info.model_number,
model_info.series);
LOG_DEBUG(Frontend, "Loading amiibo id {}", amiibo_id);
// Note: This function is not being used until we host the images on our server
// LoadAmiiboApiInfo(amiibo_id);
ui->amiiboImageLabel->setVisible(false);
ui->amiiboInfoGroup->setVisible(false);
}
void QtAmiiboSettingsDialog::LoadAmiiboApiInfo(std::string_view amiibo_id) {
#ifdef ENABLE_WEB_SERVICE
// TODO: Host this data on our website
WebService::Client client{"https://amiiboapi.com", {}, {}};
WebService::Client image_client{"https://raw.githubusercontent.com", {}, {}};
const auto url_path = fmt::format("/api/amiibo/?id={}", amiibo_id);
const auto amiibo_json = client.GetJson(url_path, true).returned_data;
if (amiibo_json.empty()) {
ui->amiiboImageLabel->setVisible(false);
ui->amiiboInfoGroup->setVisible(false);
return;
}
std::string amiibo_series{};
std::string amiibo_name{};
std::string amiibo_image_url{};
std::string amiibo_type{};
const auto parsed_amiibo_json_json = nlohmann::json::parse(amiibo_json).at("amiibo");
parsed_amiibo_json_json.at("amiiboSeries").get_to(amiibo_series);
parsed_amiibo_json_json.at("name").get_to(amiibo_name);
parsed_amiibo_json_json.at("image").get_to(amiibo_image_url);
parsed_amiibo_json_json.at("type").get_to(amiibo_type);
ui->amiiboSeriesValue->setText(QString::fromStdString(amiibo_series));
ui->amiiboNameValue->setText(QString::fromStdString(amiibo_name));
ui->amiiboTypeValue->setText(QString::fromStdString(amiibo_type));
if (amiibo_image_url.size() < 34) {
ui->amiiboImageLabel->setVisible(false);
}
const auto image_url_path = amiibo_image_url.substr(34, amiibo_image_url.size() - 34);
const auto image_data = image_client.GetImage(image_url_path, true).returned_data;
if (image_data.empty()) {
ui->amiiboImageLabel->setVisible(false);
}
QPixmap pixmap;
pixmap.loadFromData(reinterpret_cast<const u8*>(image_data.data()),
static_cast<uint>(image_data.size()));
pixmap = pixmap.scaled(250, 350, Qt::AspectRatioMode::KeepAspectRatio,
Qt::TransformationMode::SmoothTransformation);
ui->amiiboImageLabel->setPixmap(pixmap);
#endif
}
void QtAmiiboSettingsDialog::LoadAmiiboData() {
Service::NFP::RegisterInfo register_info{};
Service::NFP::CommonInfo common_info{};
const auto register_result = nfp_device->GetRegisterInfo(register_info);
const auto common_result = nfp_device->GetCommonInfo(common_info);
if (register_result.IsFailure()) {
ui->creationDateValue->setDisabled(true);
ui->modificationDateValue->setDisabled(true);
ui->amiiboCustomNameValue->setReadOnly(false);
ui->amiiboOwnerValue->setReadOnly(false);
return;
}
if (parameters.mode == Service::NFP::CabinetMode::StartNicknameAndOwnerSettings) {
ui->creationDateValue->setDisabled(true);
ui->modificationDateValue->setDisabled(true);
}
const auto amiibo_name = std::string(register_info.amiibo_name.data());
const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data());
const auto creation_date =
QDate(register_info.creation_date.year, register_info.creation_date.month,
register_info.creation_date.day);
ui->amiiboCustomNameValue->setText(QString::fromStdString(amiibo_name));
ui->amiiboOwnerValue->setText(QString::fromStdString(owner_name));
ui->amiiboCustomNameValue->setReadOnly(true);
ui->amiiboOwnerValue->setReadOnly(true);
ui->creationDateValue->setDate(creation_date);
if (common_result.IsFailure()) {
ui->modificationDateValue->setDisabled(true);
return;
}
const auto modification_date =
QDate(common_info.last_write_date.year, common_info.last_write_date.month,
common_info.last_write_date.day);
ui->modificationDateValue->setDate(modification_date);
}
void QtAmiiboSettingsDialog::LoadAmiiboGameInfo() {
u32 application_area_id{};
const auto application_result = nfp_device->GetApplicationAreaId(application_area_id);
if (application_result.IsFailure()) {
ui->gameIdValue->setVisible(false);
ui->gameIdLabel->setText(tr("No game data present"));
return;
}
SetGameDataName(application_area_id);
}
void QtAmiiboSettingsDialog::SetGameDataName(u32 application_area_id) {
static constexpr std::array<std::pair<u32, const char*>, 12> game_name_list = {
// 3ds, wii u
std::pair<u32, const char*>{0x10110E00, "Super Smash Bros (3DS/WiiU)"},
{0x00132600, "Mario & Luigi: Paper Jam"},
{0x0014F000, "Animal Crossing: Happy Home Designer"},
{0x00152600, "Chibi-Robo!: Zip Lash"},
{0x10161f00, "Mario Party 10"},
{0x1019C800, "The Legend of Zelda: Twilight Princess HD"},
// switch
{0x10162B00, "Splatoon 2"},
{0x1016e100, "Shovel Knight: Treasure Trove"},
{0x1019C800, "The Legend of Zelda: Breath of the Wild"},
{0x34F80200, "Super Smash Bros. Ultimate"},
{0x38600500, "Splatoon 3"},
{0x3B440400, "The Legend of Zelda: Link's Awakening"},
};
for (const auto& [game_id, game_name] : game_name_list) {
if (application_area_id == game_id) {
ui->gameIdValue->setText(QString::fromStdString(game_name));
return;
}
}
const auto application_area_string = fmt::format("{:016x}", application_area_id);
ui->gameIdValue->setText(QString::fromStdString(application_area_string));
}
void QtAmiiboSettingsDialog::SetSettingsDescription() {
switch (parameters.mode) {
case Service::NFP::CabinetMode::StartFormatter:
ui->cabinetActionDescriptionLabel->setText(
tr("The following amiibo data will be formatted:"));
break;
case Service::NFP::CabinetMode::StartGameDataEraser:
ui->cabinetActionDescriptionLabel->setText(tr("The following game data will removed:"));
break;
case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
ui->cabinetActionDescriptionLabel->setText(tr("Set nickname and owner:"));
break;
case Service::NFP::CabinetMode::StartRestorer:
ui->cabinetActionDescriptionLabel->setText(tr("Do you wish to restore this amiibo?"));
break;
}
}
QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) {
connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent,
&GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
connect(&parent, &GMainWindow::AmiiboSettingsFinished, this,
&QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection);
}
QtAmiiboSettings::~QtAmiiboSettings() = default;
void QtAmiiboSettings::ShowCabinetApplet(
const Core::Frontend::CabinetCallback& callback_,
const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
callback = std::move(callback_);
emit MainWindowShowAmiiboSettings(parameters, nfp_device);
}
void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) {
callback(is_success, name);
}

View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <QDialog>
#include "core/frontend/applets/cabinet.h"
class GMainWindow;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
class QGroupBox;
class QLabel;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class QtAmiiboSettingsDialog;
}
namespace Service::NFP {
class NfpDevice;
} // namespace Service::NFP
class QtAmiiboSettingsDialog final : public QDialog {
Q_OBJECT
public:
explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device_);
~QtAmiiboSettingsDialog() override;
int exec() override;
std::string GetName() const;
private:
void LoadInfo();
void LoadAmiiboInfo();
void LoadAmiiboApiInfo(std::string_view amiibo_id);
void LoadAmiiboData();
void LoadAmiiboGameInfo();
void SetGameDataName(u32 application_area_id);
void SetSettingsDescription();
std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui;
InputCommon::InputSubsystem* input_subsystem;
std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
// Parameters sent in from the backend HLE applet.
Core::Frontend::CabinetParameters parameters;
// If false amiibo settings failed to load
bool is_initalized{};
};
class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApplet {
Q_OBJECT
public:
explicit QtAmiiboSettings(GMainWindow& parent);
~QtAmiiboSettings() override;
void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_,
const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
signals:
void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const;
private:
void MainWindowFinished(bool is_success, const std::string& name);
mutable Core::Frontend::CabinetCallback callback;
};

View File

@@ -0,0 +1,494 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QtAmiiboSettingsDialog</class>
<widget class="QDialog" name="QtAmiiboSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>839</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Amiibo Settings</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="mainControllerApplet" native="true">
<layout class="QVBoxLayout" name="verticalLayout_1" stretch="0,3,0">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="topControllerApplet" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="cabinetActionDescriptionLabel">
<property name="font">
<font>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="middleControllerApplet" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>20</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="amiiboImageLabel">
<property name="minimumSize">
<size>
<width>250</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>236</width>
<height>350</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QGroupBox" name="amiiboInfoGroup">
<property name="title">
<string>Amiibo Info</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QGridLayout" name="gridLayout_1">
<item row="0" column="0">
<widget class="QLabel" name="amiiboSeriesLabel">
<property name="text">
<string>Series</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="amiiboSeriesValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="amiiboTypeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="amiiboTypeValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="amiiboNameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="amiiboNameValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="amiiboDataGroup">
<property name="title">
<string>Amiibo Data</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="amiiboCustomNameLabel">
<property name="text">
<string>Custom Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="amiiboCustomNameValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maxLength">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="amiiboOwnerLabel">
<property name="text">
<string>Owner</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="amiiboOwnerValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maxLength">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="creationDateLabel">
<property name="text">
<string>Creation Date</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDateTimeEdit" name="creationDateValue">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="minimumDate">
<date>
<year>1970</year>
<month>1</month>
<day>1</day>
</date>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="modificationDateLabel">
<property name="text">
<string>Modification Date</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDateTimeEdit" name="modificationDateValue">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="minimumDate">
<date>
<year>1970</year>
<month>1</month>
<day>1</day>
</date>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy </string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gameDataGroup">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Game Data</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="gameIdLabel">
<property name="text">
<string>Game Id</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="gameIdValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="MountAmiiboGroup">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Mount Amiibo</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="3">
<widget class="QToolButton" name="amiiboDirectoryButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>60</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="amiiboDirectoryLabel">
<property name="text">
<string>File Path</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="amiiboDirectoryValue"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="bottomControllerApplet" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>15</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>8</number>
</property>
<item alignment="Qt::AlignBottom">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QtAmiiboSettingsDialog</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QtAmiiboSettingsDialog</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>

View File

@@ -736,6 +736,9 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
emulated_controller->SetNpadStyleIndex(type);
});
connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults);
ui->comboDevices->setCurrentIndex(-1);
ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));

View File

@@ -15,6 +15,7 @@
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_amiibo_settings.h"
#include "applets/qt_controller.h"
#include "applets/qt_error.h"
#include "applets/qt_profile_select.h"
@@ -26,6 +27,7 @@
#include "configuration/configure_tas.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/mii_edit.h"
@@ -548,6 +550,11 @@ void GMainWindow::RegisterMetaTypes() {
// Register applet types
// Cabinet Applet
qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters");
qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>(
"std::shared_ptr<Service::NFP::NfpDevice>");
// Controller Applet
qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
@@ -569,6 +576,21 @@ void GMainWindow::RegisterMetaTypes() {
qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");
}
void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device) {
QtAmiiboSettingsDialog dialog(this, parameters, input_subsystem.get(), nfp_device);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
emit AmiiboSettingsFinished(false, {});
return;
}
emit AmiiboSettingsFinished(true, dialog.GetName());
}
void GMainWindow::ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters) {
QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system);
@@ -1546,6 +1568,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetFilesystem(vfs);
system->SetAppletFrontendSet({
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
std::make_unique<QtControllerSelector>(*this), // Controller Selector
std::make_unique<QtErrorDisplay>(*this), // Error Display
nullptr, // Mii Editor

View File

@@ -55,6 +55,7 @@ class System;
} // namespace Core
namespace Core::Frontend {
struct CabinetParameters;
struct ControllerParameters;
struct InlineAppearParameters;
struct InlineTextParameters;
@@ -82,6 +83,10 @@ enum class SwkbdReplyType : u32;
enum class WebExitReason : u32;
} // namespace Service::AM::Applets
namespace Service::NFP {
class NfpDevice;
} // namespace Service::NFP
namespace Ui {
class MainWindow;
}
@@ -149,6 +154,8 @@ signals:
void UpdateInstallProgress();
void AmiiboSettingsFinished(bool is_success, const std::string& name);
void ControllerSelectorReconfigureFinished();
void ErrorDisplayFinished();
@@ -170,6 +177,8 @@ public slots:
void OnExecuteProgram(std::size_t program_index);
void OnExit();
void OnSaveConfig();
void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFP::NfpDevice> nfp_device);
void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters);
void SoftwareKeyboardInitialize(