Compare commits
30 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
826e0785bf | ||
|
|
7356ab1de6 | ||
|
|
daf2ef8f1c | ||
|
|
5fbd6954ef | ||
|
|
957840be91 | ||
|
|
168c9ee341 | ||
|
|
8d99aae45b | ||
|
|
f047f376d4 | ||
|
|
3027917f39 | ||
|
|
7e0f70e5a1 | ||
|
|
aab68674c0 | ||
|
|
7f1c6def1f | ||
|
|
db7bcd51ae | ||
|
|
eb0713f781 | ||
|
|
57a05b1653 | ||
|
|
69c92b8156 | ||
|
|
4acbf3a193 | ||
|
|
54d6273975 | ||
|
|
98a6cd02c8 | ||
|
|
52acdafa95 | ||
|
|
975e17aa13 | ||
|
|
327d225c3e | ||
|
|
7c50a916c7 | ||
|
|
cb971ad654 | ||
|
|
75e6ec85e1 | ||
|
|
a253d1557d | ||
|
|
9afadca5dc | ||
|
|
fb57cd26a1 | ||
|
|
b193d40d22 | ||
|
|
6c045c9beb |
10
.github/ISSUE_TEMPLATE/blank_issue_template.yml
vendored
Normal file
10
.github/ISSUE_TEMPLATE/blank_issue_template.yml
vendored
Normal 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"
|
||||
@@ -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
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
20
src/core/frontend/applets/cabinet.cpp
Normal file
20
src/core/frontend/applets/cabinet.cpp
Normal 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
|
||||
37
src/core/frontend/applets/cabinet.h
Normal file
37
src/core/frontend/applets/cabinet.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
177
src/core/hle/service/am/applets/applet_cabinet.cpp
Normal file
177
src/core/hle/service/am/applets/applet_cabinet.cpp
Normal 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
|
||||
104
src/core/hle/service/am/applets/applet_cabinet.h
Normal file
104
src/core/hle/service/am/applets/applet_cabinet.h
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"} {
|
||||
|
||||
197
src/core/hle/service/nfc/nfc_device.cpp
Normal file
197
src/core/hle/service/nfc/nfc_device.cpp
Normal 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
|
||||
70
src/core/hle/service/nfc/nfc_device.h
Normal file
70
src/core/hle/service/nfc/nfc_device.h
Normal 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
|
||||
17
src/core/hle/service/nfc/nfc_result.h
Normal file
17
src/core/hle/service/nfc/nfc_result.h
Normal 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
|
||||
365
src/core/hle/service/nfc/nfc_user.cpp
Normal file
365
src/core/hle/service/nfc/nfc_user.cpp
Normal 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
|
||||
52
src/core/hle/service/nfc/nfc_user.h
Normal file
52
src/core/hle/service/nfc/nfc_user.h
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
238
src/video_core/engines/sw_blitter/blitter.cpp
Normal file
238
src/video_core/engines/sw_blitter/blitter.cpp
Normal 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
|
||||
27
src/video_core/engines/sw_blitter/blitter.h
Normal file
27
src/video_core/engines/sw_blitter/blitter.h
Normal 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
|
||||
1234
src/video_core/engines/sw_blitter/converter.cpp
Normal file
1234
src/video_core/engines/sw_blitter/converter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
36
src/video_core/engines/sw_blitter/converter.h
Normal file
36
src/video_core/engines/sw_blitter/converter.h
Normal 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
|
||||
136
src/video_core/engines/sw_blitter/generate_converters.py
Normal file
136
src/video_core/engines/sw_blitter/generate_converters.py
Normal 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()
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -199,8 +199,6 @@ private:
|
||||
/// End a transform feedback
|
||||
void EndTransformFeedback();
|
||||
|
||||
void BindInlineIndexBuffer();
|
||||
|
||||
Tegra::GPU& gpu;
|
||||
|
||||
const Device& device;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -141,8 +141,6 @@ private:
|
||||
|
||||
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
|
||||
void BindInlineIndexBuffer();
|
||||
|
||||
Tegra::GPU& gpu;
|
||||
|
||||
ScreenInfo& screen_info;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
264
src/yuzu/applets/qt_amiibo_settings.cpp
Normal file
264
src/yuzu/applets/qt_amiibo_settings.cpp
Normal 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);
|
||||
}
|
||||
83
src/yuzu/applets/qt_amiibo_settings.h
Normal file
83
src/yuzu/applets/qt_amiibo_settings.h
Normal 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;
|
||||
};
|
||||
494
src/yuzu/applets/qt_amiibo_settings.ui
Normal file
494
src/yuzu/applets/qt_amiibo_settings.ui
Normal 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>
|
||||
@@ -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")));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user