Compare commits

..

40 Commits

Author SHA1 Message Date
Zach Hilman
9c2d83cb9c qt: Use ProfileSelectionDialog when selecting user for save data
This allows us to present a much nicer UI to the user over a simple combo box and is made easy with the modular nature of the profile-selection applet frontend.
2018-12-25 10:52:50 -05:00
bunnei
9a22a94a51 Merge pull request #1886 from FearlessTobi/port-4164
Port citra-emu/citra#4164: "citra_qt, video_core: Screenshot functionality"
2018-12-23 14:36:51 -05:00
bunnei
52726342bd Merge pull request #1930 from lioncash/common
common/quaternion: Ensure that w is always initialized
2018-12-23 14:35:29 -05:00
bunnei
f95f6c7d86 Merge pull request #1781 from DarkLordZach/applet-profile-select
am: Implement HLE profile selector applet
2018-12-23 14:35:13 -05:00
bunnei
d08bdc861f Merge pull request #1780 from DarkLordZach/controller-profiles
configure_input: Add Controller Setup Profiles and simplify input UI
2018-12-23 14:34:29 -05:00
Lioncash
acddf16e57 common/quaternion: Ensure that w is always initialized
Previously xyz was always being zero initialized due to its constructor,
but w wasn't. Ensures that we always have a deterministic initial state.
2018-12-21 15:25:31 -05:00
bunnei
e75e8b9580 Merge pull request #1921 from ogniK5377/no-unit
Fixed uninitialized memory due to missing returns in canary
2018-12-21 14:12:54 -05:00
bunnei
42427b9c7a Merge pull request #1920 from heapo/texture_format_selection
Texture format fixes for RGBA16UI for copies and R16U when used as depth
2018-12-21 13:46:17 -05:00
bunnei
59ac3346eb Merge pull request #1925 from lioncash/pid
kernel/{process, thread}: Amend behavior related to IDs
2018-12-21 13:45:27 -05:00
bunnei
41cbd088c2 Merge pull request #1914 from lioncash/id
service/am: Unstub GetAppletResourceUserId
2018-12-21 13:43:51 -05:00
bunnei
4923df10cc Merge pull request #1923 from ogniK5377/nfp-device-list
Device handle should not be a random id, instead it's the current npad id
2018-12-19 13:14:43 -05:00
bunnei
3050f3a7ba Merge pull request #1909 from heapo/shadow_sampling_fixes
Fix arrayed texture LOD selection and depth comparison ordering
2018-12-19 13:10:37 -05:00
bunnei
80d36634e1 Merge pull request #1915 from lioncash/sm
service/sm: Improve debug log for RegisterService
2018-12-19 13:10:11 -05:00
Lioncash
b74eb88c68 kernel/svc: Handle thread handles within GetProcessId
If a thread handle is passed to svcGetProcessId, the kernel attempts to
access the process ID via the thread's instance's owning process.

Technically, this function should also be handling the kernel debug
objects as well, however we currently don't handle those kernel objects
yet, so I've left a note via a comment about it to remind myself when
implementing it in the future.
2018-12-19 12:16:15 -05:00
Lioncash
62d4377053 kernel/kernel: Use correct initial PID for userland Process instances
Starts the process ID counter off at 81, which is what the kernel itself
checks against internally when creating processes. It's actually
supposed to panic if the PID is less than 81 for a userland process.
2018-12-18 22:54:01 -05:00
Lioncash
0906302ca9 kernel/svc: Correct output parameter for svcGetThreadId
The service call uses a 64-bit value, just like svcGetProcessId. This
amends the function signature accordingly.
2018-12-18 22:38:26 -05:00
Lioncash
8435451093 kernel/thread: Make thread_id a 64-bit value
The kernel uses a 64-bit value for the thread ID, so we shouldn't be
using a 32-bit value.
2018-12-18 22:37:03 -05:00
Lioncash
43e1189688 kernel/svc: Correct output parameter for svcGetProcessId
svcGetProcessId's out parameter is a pointer to a 64-bit value, not a
32-bit one.
2018-12-18 22:30:56 -05:00
Lioncash
9b3a38e3d3 kernel/process: Make process_id a 64-bit value
In the actual kernel, this is a 64-bit value, so we shouldn't be using a
32-bit type to handle it.
2018-12-18 22:28:55 -05:00
David Marcec
807e7640aa Device handle should not be a random id, instead it's the current npad id
Found during hardware testing
2018-12-19 14:16:30 +11:00
David Marcec
20859802f0 hopefully fix clang format issue 2018-12-19 13:22:09 +11:00
David Marcec
fdd649e2ef Fixed uninitialized memory due to missing returns in canary
Functions which are suppose to crash on non canary builds usually don't return anything which lead to uninitialized memory being used.
2018-12-19 12:52:32 +11:00
Lioncash
2a533f0067 service/sm: Improve debug log for RegisterService
Now it also indicates the name and max session count. This also gives a
name to the unknown bool. This indicates if the created port is supposed
to be using light handles or regular handles internally. This is passed
to the respective svcCreatePort parameter internally.
2018-12-18 18:22:22 -05:00
zhupengfei
a2be49305d yuzu, video_core: Screenshot functionality
Allows capturing screenshot at the current internal resolution (native for software renderer), but a setting is available to capture it in other resolutions. The screenshot is saved to a single PNG in the current layout.
2018-12-18 22:54:41 +01:00
heapo
37280cf555 Texture format fixes: Flag RGBA16UI as GL_RGBA_INTEGER format, and interpret R16U as Z16 when depth_compare is enabled. 2018-12-18 11:34:51 -08:00
Lioncash
dd272298aa service/am: Unstub GetAppletResourceUserId
This is supposed to return the current process' ID. (0 indicates an
invalid ID for both process IDs and ARU IDs).
2018-12-17 21:01:14 -05:00
heapo
a6daed74f5 Fix arrayed shadow sampler array slice/depth comparison ordering, as well as invalid GLSL LOD selection. 2018-12-17 07:53:48 -08:00
Zach Hilman
c07059e7fd configure_input_simple: Properly signal docked mode change 2018-12-05 14:05:57 -05:00
Zach Hilman
233a804196 configure_input: Add ConfigureInputSimple as default input UI config
Greatly simplifies the current input UI, while still allowing power users to tweak advanced settings. Adds 'input profiles', which are easy autoconfigurations to make getting started easy and fast. Also has a custom option which brings up the current, full UI.
2018-12-05 14:02:02 -05:00
Zach Hilman
59ca8d458d configure_input: Convert into QDialog 2018-12-05 14:02:02 -05:00
Zach Hilman
20dffc22a2 configure: Use ConfigureInputSimple for Input tab 2018-12-05 14:02:02 -05:00
Zach Hilman
281b64daf4 ui_settings: Add UI setting for input profile index 2018-12-05 14:02:02 -05:00
Zach Hilman
e11e65b3d6 applets: Correct event ResetTypes from OneShot to Sticky
Fixes bugs relating to signalling in software keyboard.
2018-12-03 17:27:40 -05:00
Zach Hilman
bf90f2402d qt: Implement GUI dialog frontend for ProfileSelector
Presents profiles in a list, similar to switch.
2018-12-03 17:26:27 -05:00
Zach Hilman
60b59d554d am: Use ProfileSelect applet 2018-12-03 17:26:27 -05:00
Zach Hilman
4fb59fdfe1 applets: Implement ProfileSelect applet
Allows the player to select an emulated profile.
2018-12-03 17:26:27 -05:00
Zach Hilman
6deccc7e6b qt: Register to use Qt ProfileSelector instead of default 2018-12-03 17:26:27 -05:00
Zach Hilman
58fd0a1c50 core: Add getter/setter for ProfileSelector in System 2018-12-03 17:26:26 -05:00
Zach Hilman
d17f38494b frontend: Add frontend applet for ProfileSelect
Responsible for selecting a profile and firing callback upon completion.
2018-12-03 17:26:26 -05:00
Zach Hilman
877b31b33e software_keyboard: Signal state changed event upon construction
Previously, ILibraryAppletAccessor would signal upon creation of any applet, but this is incorrect. A flag inside of the applet code determines whether or not creation should signal state change and swkbd happens to be one of these applets.
2018-12-03 17:26:26 -05:00
71 changed files with 1367 additions and 920 deletions

View File

@@ -44,7 +44,6 @@ add_library(common STATIC
detached_tasks.cpp
detached_tasks.h
bit_field.h
bit_util.h
cityhash.cpp
cityhash.h
color.h

View File

@@ -1,61 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <climits>
#include <cstddef>
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/common_types.h"
namespace Common {
/// Gets the size of a specified type T in bits.
template <typename T>
constexpr std::size_t BitSize() {
return sizeof(T) * CHAR_BIT;
}
#ifdef _MSC_VER
inline u32 CountLeadingZeroes32(u32 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse(&leading_zero, value) != 0) {
return 31 - leading_zero;
}
return 32;
}
inline u64 CountLeadingZeroes64(u64 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse64(&leading_zero, value) != 0) {
return 63 - leading_zero;
}
return 64;
}
#else
inline u32 CountLeadingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
return __builtin_clz(value);
}
inline u64 CountLeadingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
return __builtin_clzll(value);
}
#endif
} // namespace Common

View File

@@ -12,7 +12,7 @@ template <typename T>
class Quaternion {
public:
Math::Vec3<T> xyz;
T w;
T w{};
Quaternion<decltype(-T{})> Inverse() const {
return {-xyz, w};

View File

@@ -83,6 +83,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
frontend/applets/profile_select.cpp
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
frontend/applets/software_keyboard.h
frontend/emu_window.cpp
@@ -113,8 +115,6 @@ add_library(core STATIC
hle/kernel/object.h
hle/kernel/process.cpp
hle/kernel/process.h
hle/kernel/process_capability.cpp
hle/kernel/process_capability.h
hle/kernel/readable_event.cpp
hle/kernel/readable_event.h
hle/kernel/resource_limit.cpp
@@ -164,6 +164,8 @@ add_library(core STATIC
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
hle/service/am/applets/profile_select.cpp
hle/service/am/applets/profile_select.h
hle/service/am/applets/software_keyboard.cpp
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/stub_applet.cpp

View File

@@ -99,6 +99,8 @@ struct System::Impl {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
/// Create default implementations of applets if one is not provided.
if (profile_selector == nullptr)
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
if (software_keyboard == nullptr)
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
@@ -229,6 +231,7 @@ struct System::Impl {
bool is_powered_on = false;
/// Frontend applets
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
/// Service manager
@@ -424,6 +427,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) {
impl->profile_selector = std::move(applet);
}
const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
return *impl->profile_selector;
}
void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {
impl->software_keyboard = std::move(applet);
}

View File

@@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
#include "frontend/applets/profile_select.h"
namespace Core::Frontend {
class EmuWindow;
@@ -241,6 +242,10 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet);
const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const;
void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;

View File

@@ -40,13 +40,6 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
return Loader::ResultStatus::ErrorBadFileAccessHeader;
aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
const u64 read_size = aci_header.kac_size;
const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset;
if (file->ReadBytes(aci_kernel_capabilities.data(), read_size, read_offset) != read_size) {
return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors;
}
return Loader::ResultStatus::Success;
}
@@ -78,10 +71,6 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
return aci_file_access.permissions;
}
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
return aci_kernel_capabilities;
}
void ProgramMetadata::Print() const {
LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);

View File

@@ -5,7 +5,6 @@
#pragma once
#include <array>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -39,8 +38,6 @@ enum class ProgramFilePermission : u64 {
*/
class ProgramMetadata {
public:
using KernelCapabilityDescriptors = std::vector<u32>;
ProgramMetadata();
~ProgramMetadata();
@@ -53,7 +50,6 @@ public:
u32 GetMainThreadStackSize() const;
u64 GetTitleID() const;
u64 GetFilesystemPermissions() const;
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
void Print() const;
@@ -158,8 +154,6 @@ private:
FileAccessControl acid_file_access;
FileAccessHeader aci_file_access;
KernelCapabilityDescriptors aci_kernel_capabilities;
};
} // namespace FileSys

View File

@@ -128,6 +128,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id);
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/frontend/applets/profile_select.h"
#include "core/settings.h"
namespace Core::Frontend {
ProfileSelectApplet::~ProfileSelectApplet() = default;
void DefaultProfileSelectApplet::SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
Service::Account::ProfileManager manager;
callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{}));
LOG_INFO(Service_ACC, "called, selecting current user instead of prompting...");
}
} // namespace Core::Frontend

View File

@@ -0,0 +1,27 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <optional>
#include "core/hle/service/acc/profile_manager.h"
namespace Core::Frontend {
class ProfileSelectApplet {
public:
virtual ~ProfileSelectApplet();
virtual void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0;
};
class DefaultProfileSelectApplet final : public ProfileSelectApplet {
public:
void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
};
} // namespace Core::Frontend

View File

@@ -6,6 +6,7 @@
#include "common/assert.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/settings.h"
namespace Layout {
@@ -42,4 +43,18 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
return res;
}
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
int width, height;
if (Settings::values.use_docked_mode) {
width = ScreenDocked::WidthDocked * res_scale;
height = ScreenDocked::HeightDocked * res_scale;
} else {
width = ScreenUndocked::Width * res_scale;
height = ScreenUndocked::Height * res_scale;
}
return DefaultFrameLayout(width, height);
}
} // namespace Layout

View File

@@ -9,6 +9,7 @@
namespace Layout {
enum ScreenUndocked : unsigned { Width = 1280, Height = 720 };
enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 };
/// Describes the layout of the window framebuffer
struct FramebufferLayout {
@@ -34,4 +35,10 @@ struct FramebufferLayout {
*/
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height);
/**
* Convenience method to get frame layout by resolution scale
* @param res_scale resolution scale factor
*/
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
} // namespace Layout

View File

@@ -201,11 +201,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
modules.push_back(std::move(module));
}
static Kernel::Thread* FindThreadById(int id) {
static Kernel::Thread* FindThreadById(s64 id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadID() == static_cast<u32>(id)) {
if (thread->GetThreadID() == static_cast<u64>(id)) {
current_core = core;
return thread.get();
}

View File

@@ -11,7 +11,6 @@ namespace Kernel {
// Confirmed Switch kernel error codes
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
@@ -31,7 +30,6 @@ constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121};
constexpr ResultCode ERR_ALREADY_REGISTERED{ErrorModule::Kernel, 122};
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123};
constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125};
constexpr ResultCode ERR_RESERVED_VALUE{ErrorModule::Kernel, 126};
constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132};
} // namespace Kernel

View File

@@ -43,9 +43,6 @@ enum KernelHandle : Handle {
*/
class HandleTable final : NonCopyable {
public:
/// This is the maximum limit of handles allowed per process in Horizon
static constexpr std::size_t MAX_COUNT = 1024;
HandleTable();
~HandleTable();
@@ -94,6 +91,9 @@ public:
void Clear();
private:
/// This is the maximum limit of handles allowed per process in Horizon
static constexpr std::size_t MAX_COUNT = 1024;
/// Stores the Object referenced by the handle or null if the slot is empty.
std::array<SharedPtr<Object>, MAX_COUNT> objects;

View File

@@ -112,7 +112,7 @@ struct KernelCore::Impl {
void Shutdown() {
next_object_id = 0;
next_process_id = 10;
next_process_id = Process::ProcessIDMin;
next_thread_id = 1;
process_list.clear();
@@ -153,10 +153,8 @@ struct KernelCore::Impl {
}
std::atomic<u32> next_object_id{0};
// TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
// reserved for low-level services
std::atomic<u32> next_process_id{10};
std::atomic<u32> next_thread_id{1};
std::atomic<u64> next_process_id{Process::ProcessIDMin};
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
std::vector<SharedPtr<Process>> process_list;
@@ -242,11 +240,11 @@ u32 KernelCore::CreateNewObjectID() {
return impl->next_object_id++;
}
u32 KernelCore::CreateNewThreadID() {
u64 KernelCore::CreateNewThreadID() {
return impl->next_thread_id++;
}
u32 KernelCore::CreateNewProcessID() {
u64 KernelCore::CreateNewProcessID() {
return impl->next_process_id++;
}

View File

@@ -88,10 +88,10 @@ private:
u32 CreateNewObjectID();
/// Creates a new process ID, incrementing the internal process ID counter;
u32 CreateNewProcessID();
u64 CreateNewProcessID();
/// Creates a new thread ID, incrementing the internal thread ID counter.
u32 CreateNewThreadID();
u64 CreateNewThreadID();
/// Creates a timer callback handle for the given timer.
ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer);

View File

@@ -32,6 +32,7 @@ bool Object::IsWaitable() const {
}
UNREACHABLE();
return false;
}
} // namespace Kernel

View File

@@ -28,11 +28,13 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
SharedPtr<Process> process(new Process(kernel));
process->name = std::move(name);
process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->resource_limit = kernel.GetSystemResourceLimit();
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = kernel.CreateNewProcessID();
process->capabilities.InitializeForMetadatalessProcess();
process->svc_access_mask.set();
std::mt19937 rng(Settings::values.rng_seed.value_or(0));
std::uniform_int_distribution<u64> distribution;
@@ -62,15 +64,83 @@ ResultCode Process::ClearSignalState() {
return RESULT_SUCCESS;
}
ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
ideal_processor = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
}
const auto& caps = metadata.GetKernelCapabilities();
return capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager);
void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) {
for (std::size_t i = 0; i < len; ++i) {
u32 descriptor = kernel_caps[i];
u32 type = descriptor >> 20;
if (descriptor == 0xFFFFFFFF) {
// Unused descriptor entry
continue;
} else if ((type & 0xF00) == 0xE00) { // 0x0FFF
// Allowed interrupts list
LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored");
} else if ((type & 0xF80) == 0xF00) { // 0x07FF
// Allowed syscalls mask
unsigned int index = ((descriptor >> 24) & 7) * 24;
u32 bits = descriptor & 0xFFFFFF;
while (bits && index < svc_access_mask.size()) {
svc_access_mask.set(index, bits & 1);
++index;
bits >>= 1;
}
} else if ((type & 0xFF0) == 0xFE0) { // 0x00FF
// Handle table size
handle_table_size = descriptor & 0x3FF;
} else if ((type & 0xFF8) == 0xFF0) { // 0x007F
// Misc. flags
flags.raw = descriptor & 0xFFFF;
} else if ((type & 0xFFE) == 0xFF8) { // 0x001F
// Mapped memory range
if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) {
LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored.");
continue;
}
u32 end_desc = kernel_caps[i + 1];
++i; // Skip over the second descriptor on the next iteration
AddressMapping mapping;
mapping.address = descriptor << 12;
VAddr end_address = end_desc << 12;
if (mapping.address < end_address) {
mapping.size = end_address - mapping.address;
} else {
mapping.size = 0;
}
mapping.read_only = (descriptor & (1 << 20)) != 0;
mapping.unk_flag = (end_desc & (1 << 20)) != 0;
address_mappings.push_back(mapping);
} else if ((type & 0xFFF) == 0xFFE) { // 0x000F
// Mapped memory page
AddressMapping mapping;
mapping.address = descriptor << 12;
mapping.size = Memory::PAGE_SIZE;
mapping.read_only = false;
mapping.unk_flag = false;
address_mappings.push_back(mapping);
} else if ((type & 0xFE0) == 0xFC0) { // 0x01FF
// Kernel version
kernel_version = descriptor & 0xFFFF;
int minor = kernel_version & 0xFF;
int major = (kernel_version >> 8) & 0xFF;
LOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor);
} else {
LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor);
}
}
}
void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {

View File

@@ -11,9 +11,9 @@
#include <string>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/kernel/wait_object.h"
@@ -42,6 +42,24 @@ enum class MemoryRegion : u16 {
BASE = 3,
};
union ProcessFlags {
u16 raw;
BitField<0, 1, u16>
allow_debug; ///< Allows other processes to attach to and debug this process.
BitField<1, 1, u16> force_debug; ///< Allows this process to attach to processes even if they
/// don't have allow_debug set.
BitField<2, 1, u16> allow_nonalphanum;
BitField<3, 1, u16> shared_page_writable; ///< Shared page is mapped with write permissions.
BitField<4, 1, u16> privileged_priority; ///< Can use priority levels higher than 24.
BitField<5, 1, u16> allow_main_args;
BitField<6, 1, u16> shared_device_mem;
BitField<7, 1, u16> runnable_on_sleep;
BitField<8, 4, MemoryRegion>
memory_region; ///< Default region for memory allocations for this process
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
};
/**
* Indicates the status of a Process instance.
*
@@ -102,6 +120,18 @@ struct CodeSet final {
class Process final : public WaitObject {
public:
enum : u64 {
/// Lowest allowed process ID for a kernel initial process.
InitialKIPIDMin = 1,
/// Highest allowed process ID for a kernel initial process.
InitialKIPIDMax = 80,
/// Lowest allowed process ID for a userland process.
ProcessIDMin = 81,
/// Highest allowed process ID for a userland process.
ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
};
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
@@ -144,7 +174,7 @@ public:
}
/// Gets the unique ID that identifies this particular process.
u32 GetProcessID() const {
u64 GetProcessID() const {
return process_id;
}
@@ -162,13 +192,13 @@ public:
}
/// Gets the bitmask of allowed CPUs that this process' threads can run on.
u64 GetAllowedProcessorMask() const {
return capabilities.GetCoreMask();
u32 GetAllowedProcessorMask() const {
return allowed_processor_mask;
}
/// Gets the bitmask of allowed thread priorities.
u64 GetAllowedThreadPriorityMask() const {
return capabilities.GetPriorityMask();
u32 GetAllowedThreadPriorityMask() const {
return allowed_thread_priority_mask;
}
u32 IsVirtualMemoryEnabled() const {
@@ -209,12 +239,15 @@ public:
* Loads process-specifics configuration info with metadata provided
* by an executable.
*
* @param metadata The provided metadata to load process specific info from.
*
* @returns RESULT_SUCCESS if all relevant metadata was able to be
* loaded and parsed. Otherwise, an error code is returned.
* @param metadata The provided metadata to load process specific info.
*/
ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
void LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
/**
* Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
* to this process.
*/
void ParseKernelCaps(const u32* kernel_caps, std::size_t len);
/**
* Applies address space changes and launches the process main thread.
@@ -267,16 +300,30 @@ private:
ProcessStatus status;
/// The ID of this process
u32 process_id = 0;
u64 process_id = 0;
/// Title ID corresponding to the process
u64 program_id;
u64 program_id = 0;
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
/// The process may only call SVCs which have the corresponding bit set.
std::bitset<0x80> svc_access_mask;
/// Maximum size of the handle table for the process.
u32 handle_table_size = 0x200;
/// Special memory ranges mapped into this processes address space. This is used to give
/// processes access to specific I/O regions and device memory.
boost::container::static_vector<AddressMapping, 8> address_mappings;
ProcessFlags flags;
/// Kernel compatibility version for this process
u16 kernel_version = 0;
/// The default CPU for this process, threads are scheduled on this cpu by default.
u8 ideal_processor = 0;
/// Bitmask of allowed CPUs that this process' threads can run on. TODO(Subv): Actually parse
/// this value from the process header.
u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK;
u32 allowed_thread_priority_mask = 0xFFFFFFFF;
u32 is_virtual_address_memory_enabled = 0;
/// The Thread Local Storage area is allocated as processes create threads,
@@ -286,9 +333,6 @@ private:
/// This vector will grow as more pages are allocated for new threads.
std::vector<std::bitset<8>> tls_slots;
/// Contains the parsed process capability descriptors.
ProcessCapabilities capabilities;
/// Whether or not this process is AArch64, or AArch32.
/// By default, we currently assume this is true, unless otherwise
/// specified by metadata provided to the process during loading.

View File

@@ -1,355 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/bit_util.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/vm_manager.h"
namespace Kernel {
namespace {
// clang-format off
// Shift offsets for kernel capability types.
enum : u32 {
CapabilityOffset_PriorityAndCoreNum = 3,
CapabilityOffset_Syscall = 4,
CapabilityOffset_MapPhysical = 6,
CapabilityOffset_MapIO = 7,
CapabilityOffset_Interrupt = 11,
CapabilityOffset_ProgramType = 13,
CapabilityOffset_KernelVersion = 14,
CapabilityOffset_HandleTableSize = 15,
CapabilityOffset_Debug = 16,
};
// Combined mask of all parameters that may be initialized only once.
constexpr u32 InitializeOnceMask = (1U << CapabilityOffset_PriorityAndCoreNum) |
(1U << CapabilityOffset_ProgramType) |
(1U << CapabilityOffset_KernelVersion) |
(1U << CapabilityOffset_HandleTableSize) |
(1U << CapabilityOffset_Debug);
// Packed kernel version indicating 10.4.0
constexpr u32 PackedKernelVersion = 0x520000;
// Indicates possible types of capabilities that can be specified.
enum class CapabilityType : u32 {
Unset = 0U,
PriorityAndCoreNum = (1U << CapabilityOffset_PriorityAndCoreNum) - 1,
Syscall = (1U << CapabilityOffset_Syscall) - 1,
MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1,
MapIO = (1U << CapabilityOffset_MapIO) - 1,
Interrupt = (1U << CapabilityOffset_Interrupt) - 1,
ProgramType = (1U << CapabilityOffset_ProgramType) - 1,
KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1,
HandleTableSize = (1U << CapabilityOffset_HandleTableSize) - 1,
Debug = (1U << CapabilityOffset_Debug) - 1,
Ignorable = 0xFFFFFFFFU,
};
// clang-format on
constexpr CapabilityType GetCapabilityType(u32 value) {
return static_cast<CapabilityType>((~value & (value + 1)) - 1);
}
u32 GetFlagBitOffset(CapabilityType type) {
const auto value = static_cast<u32>(type);
return static_cast<u32>(Common::BitSize<u32>() - Common::CountLeadingZeroes32(value));
}
} // Anonymous namespace
ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
std::size_t num_capabilities,
VMManager& vm_manager) {
Clear();
// Allow all cores and priorities.
core_mask = 0xF;
priority_mask = 0xFFFFFFFFFFFFFFFF;
kernel_version = PackedKernelVersion;
return ParseCapabilities(capabilities, num_capabilities, vm_manager);
}
ResultCode ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
std::size_t num_capabilities,
VMManager& vm_manager) {
Clear();
return ParseCapabilities(capabilities, num_capabilities, vm_manager);
}
void ProcessCapabilities::InitializeForMetadatalessProcess() {
// Allow all cores and priorities
core_mask = 0xF;
priority_mask = 0xFFFFFFFFFFFFFFFF;
kernel_version = PackedKernelVersion;
// Allow all system calls and interrupts.
svc_capabilities.set();
interrupt_capabilities.set();
// Allow using the maximum possible amount of handles
handle_table_size = static_cast<u32>(HandleTable::MAX_COUNT);
// Allow all debugging capabilities.
is_debuggable = true;
can_force_debug = true;
}
ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
std::size_t num_capabilities,
VMManager& vm_manager) {
u32 set_flags = 0;
u32 set_svc_bits = 0;
for (std::size_t i = 0; i < num_capabilities; ++i) {
const u32 descriptor = capabilities[i];
const auto type = GetCapabilityType(descriptor);
if (type == CapabilityType::MapPhysical) {
i++;
// The MapPhysical type uses two descriptor flags for its parameters.
// If there's only one, then there's a problem.
if (i >= num_capabilities) {
return ERR_INVALID_COMBINATION;
}
const auto size_flags = capabilities[i];
if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) {
return ERR_INVALID_COMBINATION;
}
const auto result = HandleMapPhysicalFlags(descriptor, size_flags, vm_manager);
if (result.IsError()) {
return result;
}
} else {
const auto result =
ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, vm_manager);
if (result.IsError()) {
return result;
}
}
}
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits,
u32 flag, VMManager& vm_manager) {
const auto type = GetCapabilityType(flag);
if (type == CapabilityType::Unset) {
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
// Bail early on ignorable entries, as one would expect,
// ignorable descriptors can be ignored.
if (type == CapabilityType::Ignorable) {
return RESULT_SUCCESS;
}
// Ensure that the give flag hasn't already been initialized before.
// If it has been, then bail.
const u32 flag_length = GetFlagBitOffset(type);
const u32 set_flag = 1U << flag_length;
if ((set_flag & set_flags & InitializeOnceMask) != 0) {
return ERR_INVALID_COMBINATION;
}
set_flags |= set_flag;
switch (type) {
case CapabilityType::PriorityAndCoreNum:
return HandlePriorityCoreNumFlags(flag);
case CapabilityType::Syscall:
return HandleSyscallFlags(set_svc_bits, flag);
case CapabilityType::MapIO:
return HandleMapIOFlags(flag, vm_manager);
case CapabilityType::Interrupt:
return HandleInterruptFlags(flag);
case CapabilityType::ProgramType:
return HandleProgramTypeFlags(flag);
case CapabilityType::KernelVersion:
return HandleKernelVersionFlags(flag);
case CapabilityType::HandleTableSize:
return HandleHandleTableFlags(flag);
case CapabilityType::Debug:
return HandleDebugFlags(flag);
default:
break;
}
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
void ProcessCapabilities::Clear() {
svc_capabilities.reset();
interrupt_capabilities.reset();
core_mask = 0;
priority_mask = 0;
handle_table_size = 0;
kernel_version = 0;
program_type = ProgramType::SysModule;
is_debuggable = false;
can_force_debug = false;
}
ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
if (priority_mask != 0 || core_mask != 0) {
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
const u32 core_num_min = (flags >> 16) & 0xFF;
const u32 core_num_max = (flags >> 24) & 0xFF;
if (core_num_min > core_num_max) {
return ERR_INVALID_COMBINATION;
}
const u32 priority_min = (flags >> 10) & 0x3F;
const u32 priority_max = (flags >> 4) & 0x3F;
if (priority_min > priority_max) {
return ERR_INVALID_COMBINATION;
}
// The switch only has 4 usable cores.
if (core_num_max >= 4) {
return ERR_INVALID_PROCESSOR_ID;
}
const auto make_mask = [](u64 min, u64 max) {
const u64 range = max - min + 1;
const u64 mask = (1ULL << range) - 1;
return mask << min;
};
core_mask = make_mask(core_num_min, core_num_max);
priority_mask = make_mask(priority_min, priority_max);
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) {
const u32 index = flags >> 29;
const u32 svc_bit = 1U << index;
// If we've already set this svc before, bail.
if ((set_svc_bits & svc_bit) != 0) {
return ERR_INVALID_COMBINATION;
}
set_svc_bits |= svc_bit;
const u32 svc_mask = (flags >> 5) & 0xFFFFFF;
for (u32 i = 0; i < 24; ++i) {
const u32 svc_number = index * 24 + i;
if ((svc_mask & (1U << i)) == 0) {
continue;
}
if (svc_number >= svc_capabilities.size()) {
return ERR_OUT_OF_RANGE;
}
svc_capabilities[svc_number] = true;
}
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
VMManager& vm_manager) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, VMManager& vm_manager) {
// TODO(Lioncache): Implement once the memory manager can handle this.
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
constexpr u32 interrupt_ignore_value = 0x3FF;
const u32 interrupt0 = (flags >> 12) & 0x3FF;
const u32 interrupt1 = (flags >> 22) & 0x3FF;
for (u32 interrupt : {interrupt0, interrupt1}) {
if (interrupt == interrupt_ignore_value) {
continue;
}
// NOTE:
// This should be checking a generic interrupt controller value
// as part of the calculation, however, given we don't currently
// emulate that, it's sufficient to mark every interrupt as defined.
if (interrupt >= interrupt_capabilities.size()) {
return ERR_OUT_OF_RANGE;
}
interrupt_capabilities[interrupt] = true;
}
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
const u32 reserved = flags >> 17;
if (reserved != 0) {
return ERR_RESERVED_VALUE;
}
program_type = static_cast<ProgramType>((flags >> 14) & 0b111);
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
// Yes, the internal member variable is checked in the actual kernel here.
// This might look odd for options that are only allowed to be initialized
// just once, however the kernel has a separate initialization function for
// kernel processes and userland processes. The kernel variant sets this
// member variable ahead of time.
const u32 major_version = kernel_version >> 19;
if (major_version != 0 || flags < 0x80000) {
return ERR_INVALID_CAPABILITY_DESCRIPTOR;
}
kernel_version = flags;
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
const u32 reserved = flags >> 26;
if (reserved != 0) {
return ERR_RESERVED_VALUE;
}
handle_table_size = (flags >> 16) & 0x3FF;
return RESULT_SUCCESS;
}
ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) {
const u32 reserved = flags >> 19;
if (reserved != 0) {
return ERR_RESERVED_VALUE;
}
is_debuggable = (flags & 0x20000) != 0;
can_force_debug = (flags & 0x40000) != 0;
return RESULT_SUCCESS;
}
} // namespace Kernel

View File

@@ -1,264 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <bitset>
#include "common/common_types.h"
union ResultCode;
namespace Kernel {
class VMManager;
/// The possible types of programs that may be indicated
/// by the program type capability descriptor.
enum class ProgramType {
SysModule,
Application,
Applet,
};
/// Handles kernel capability descriptors that are provided by
/// application metadata. These descriptors provide information
/// that alters certain parameters for kernel process instance
/// that will run said application (or applet).
///
/// Capabilities are a sequence of flag descriptors, that indicate various
/// configurations and constraints for a particular process.
///
/// Flag types are indicated by a sequence of set low bits. E.g. the
/// types are indicated with the low bits as follows (where x indicates "don't care"):
///
/// - Priority and core mask : 0bxxxxxxxxxxxx0111
/// - Allowed service call mask: 0bxxxxxxxxxxx01111
/// - Map physical memory : 0bxxxxxxxxx0111111
/// - Map IO memory : 0bxxxxxxxx01111111
/// - Interrupts : 0bxxxx011111111111
/// - Application type : 0bxx01111111111111
/// - Kernel version : 0bx011111111111111
/// - Handle table size : 0b0111111111111111
/// - Debugger flags : 0b1111111111111111
///
/// These are essentially a bit offset subtracted by 1 to create a mask.
/// e.g. The first entry in the above list is simply bit 3 (value 8 -> 0b1000)
/// subtracted by one (7 -> 0b0111)
///
/// An example of a bit layout (using the map physical layout):
/// <example>
/// The MapPhysical type indicates a sequence entry pair of:
///
/// [initial, memory_flags], where:
///
/// initial:
/// bits:
/// 7-24: Starting page to map memory at.
/// 25 : Indicates if the memory should be mapped as read only.
///
/// memory_flags:
/// bits:
/// 7-20 : Number of pages to map
/// 21-25: Seems to be reserved (still checked against though)
/// 26 : Whether or not the memory being mapped is IO memory, or physical memory
/// </example>
///
class ProcessCapabilities {
public:
using InterruptCapabilities = std::bitset<1024>;
using SyscallCapabilities = std::bitset<128>;
ProcessCapabilities() = default;
ProcessCapabilities(const ProcessCapabilities&) = delete;
ProcessCapabilities(ProcessCapabilities&&) = default;
ProcessCapabilities& operator=(const ProcessCapabilities&) = delete;
ProcessCapabilities& operator=(ProcessCapabilities&&) = default;
/// Initializes this process capabilities instance for a kernel process.
///
/// @param capabilities The capabilities to parse
/// @param num_capabilities The number of capabilities to parse.
/// @param vm_manager The memory manager to use for handling any mapping-related
/// operations (such as mapping IO memory, etc).
///
/// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
ResultCode InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
VMManager& vm_manager);
/// Initializes this process capabilities instance for a userland process.
///
/// @param capabilities The capabilities to parse.
/// @param num_capabilities The total number of capabilities to parse.
/// @param vm_manager The memory manager to use for handling any mapping-related
/// operations (such as mapping IO memory, etc).
///
/// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
/// otherwise, an error code upon failure.
///
ResultCode InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
VMManager& vm_manager);
/// Initializes this process capabilities instance for a process that does not
/// have any metadata to parse.
///
/// This is necessary, as we allow running raw executables, and the internal
/// kernel process capabilities also determine what CPU cores the process is
/// allowed to run on, and what priorities are allowed for threads. It also
/// determines the max handle table size, what the program type is, whether or
/// not the process can be debugged, or whether it's possible for a process to
/// forcibly debug another process.
///
/// Given the above, this essentially enables all capabilities across the board
/// for the process. It allows the process to:
///
/// - Run on any core
/// - Use any thread priority
/// - Use the maximum amount of handles a process is allowed to.
/// - Be debuggable
/// - Forcibly debug other processes.
///
/// Note that this is not a behavior that the kernel allows a process to do via
/// a single function like this. This is yuzu-specific behavior to handle
/// executables with no capability descriptors whatsoever to derive behavior from.
/// It being yuzu-specific is why this is also not the default behavior and not
/// done by default in the constructor.
///
void InitializeForMetadatalessProcess();
/// Gets the allowable core mask
u64 GetCoreMask() const {
return core_mask;
}
/// Gets the allowable priority mask
u64 GetPriorityMask() const {
return priority_mask;
}
/// Gets the SVC access permission bits
const SyscallCapabilities& GetServiceCapabilities() const {
return svc_capabilities;
}
/// Gets the valid interrupt bits.
const InterruptCapabilities& GetInterruptCapabilities() const {
return interrupt_capabilities;
}
/// Gets the program type for this process.
ProgramType GetProgramType() const {
return program_type;
}
/// Gets the number of total allowable handles for the process' handle table.
u32 GetHandleTableSize() const {
return handle_table_size;
}
/// Gets the kernel version value.
u32 GetKernelVersion() const {
return kernel_version;
}
/// Whether or not this process can be debugged.
bool IsDebuggable() const {
return is_debuggable;
}
/// Whether or not this process can forcibly debug another
/// process, even if that process is not considered debuggable.
bool CanForceDebug() const {
return can_force_debug;
}
private:
/// Attempts to parse a given sequence of capability descriptors.
///
/// @param capabilities The sequence of capability descriptors to parse.
/// @param num_capabilities The number of descriptors within the given sequence.
/// @param vm_manager The memory manager that will perform any memory
/// mapping if necessary.
///
/// @return RESULT_SUCCESS if no errors occur, otherwise an error code.
///
ResultCode ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
VMManager& vm_manager);
/// Attempts to parse a capability descriptor that is only represented by a
/// single flag set.
///
/// @param set_flags Running set of flags that are used to catch
/// flags being initialized more than once when they shouldn't be.
/// @param set_svc_bits Running set of bits representing the allowed supervisor calls mask.
/// @param flag The flag to attempt to parse.
/// @param vm_manager The memory manager that will perform any memory
/// mapping if necessary.
///
/// @return RESULT_SUCCESS if no errors occurred, otherwise an error code.
///
ResultCode ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
VMManager& vm_manager);
/// Clears the internal state of this process capability instance. Necessary,
/// to have a sane starting point due to us allowing running executables without
/// configuration metadata. We assume a process is not going to have metadata,
/// and if it turns out that the process does, in fact, have metadata, then
/// we attempt to parse it. Thus, we need this to reset data members back to
/// a good state.
///
/// DO NOT ever make this a public member function. This isn't an invariant
/// anything external should depend upon (and if anything comes to rely on it,
/// you should immediately be questioning the design of that thing, not this
/// class. If the kernel itself can run without depending on behavior like that,
/// then so can yuzu).
///
void Clear();
/// Handles flags related to the priority and core number capability flags.
ResultCode HandlePriorityCoreNumFlags(u32 flags);
/// Handles flags related to determining the allowable SVC mask.
ResultCode HandleSyscallFlags(u32& set_svc_bits, u32 flags);
/// Handles flags related to mapping physical memory pages.
ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, VMManager& vm_manager);
/// Handles flags related to mapping IO pages.
ResultCode HandleMapIOFlags(u32 flags, VMManager& vm_manager);
/// Handles flags related to the interrupt capability flags.
ResultCode HandleInterruptFlags(u32 flags);
/// Handles flags related to the program type.
ResultCode HandleProgramTypeFlags(u32 flags);
/// Handles flags related to the handle table size.
ResultCode HandleHandleTableFlags(u32 flags);
/// Handles flags related to the kernel version capability flags.
ResultCode HandleKernelVersionFlags(u32 flags);
/// Handles flags related to debug-specific capabilities.
ResultCode HandleDebugFlags(u32 flags);
SyscallCapabilities svc_capabilities;
InterruptCapabilities interrupt_capabilities;
u64 core_mask = 0;
u64 priority_mask = 0;
u32 handle_table_size = 0;
u32 kernel_version = 0;
ProgramType program_type = ProgramType::SysModule;
bool is_debuggable = false;
bool can_force_debug = false;
};
} // namespace Kernel

View File

@@ -391,7 +391,7 @@ static ResultCode SendSyncRequest(Handle handle) {
}
/// Get the ID for the specified thread.
static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
@@ -405,20 +405,33 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
return RESULT_SUCCESS;
}
/// Get the ID of the specified process
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
/// Gets the ID of the specified process or a specified thread's owning process.
static ResultCode GetProcessId(u64* process_id, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
const SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
process_handle);
return ERR_INVALID_HANDLE;
const SharedPtr<Process> process = handle_table.Get<Process>(handle);
if (process) {
*process_id = process->GetProcessID();
return RESULT_SUCCESS;
}
*process_id = process->GetProcessID();
return RESULT_SUCCESS;
const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle);
if (thread) {
const Process* const owner_process = thread->GetOwnerProcess();
if (!owner_process) {
LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered.");
return ERR_INVALID_HANDLE;
}
*process_id = owner_process->GetProcessID();
return RESULT_SUCCESS;
}
// NOTE: This should also handle debug objects before returning.
LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle);
return ERR_INVALID_HANDLE;
}
/// Default thread wakeup callback for WaitSynchronization

View File

@@ -73,7 +73,15 @@ void SvcWrap() {
template <ResultCode func(u32*, u64)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, Param(1)).raw;
const u32 retval = func(&param_1, Param(1)).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
template <ResultCode func(u64*, u32)>
void SvcWrap() {
u64 param_1 = 0;
const u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}

View File

@@ -151,7 +151,7 @@ public:
* Gets the thread's thread ID
* @return The thread's ID
*/
u32 GetThreadID() const {
u64 GetThreadID() const {
return thread_id;
}
@@ -379,7 +379,7 @@ private:
Core::ARM_Interface::ThreadContext context{};
u32 thread_id = 0;
u64 thread_id = 0;
ThreadStatus status = ThreadStatus::Dormant;

View File

@@ -19,6 +19,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/am/applets/stub_applet.h"
#include "core/hle/service/am/idle.h"
@@ -39,6 +40,7 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
};
@@ -71,10 +73,13 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
IWindowController::~IWindowController() = default;
void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID();
LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
rb.Push<u64>(process_id);
}
void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) {
@@ -772,6 +777,8 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default;
static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
switch (id) {
case AppletId::ProfileSelect:
return std::make_shared<Applets::ProfileSelect>();
case AppletId::SoftwareKeyboard:
return std::make_shared<Applets::SoftwareKeyboard>();
default:

View File

@@ -0,0 +1,77 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/profile_select.h"
namespace Service::AM::Applets {
constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
ProfileSelect::ProfileSelect() = default;
ProfileSelect::~ProfileSelect() = default;
void ProfileSelect::Initialize() {
complete = false;
status = RESULT_SUCCESS;
final_data.clear();
Applet::Initialize();
const auto user_config_storage = broker.PopNormalDataToApplet();
ASSERT(user_config_storage != nullptr);
const auto& user_config = user_config_storage->GetData();
ASSERT(user_config.size() >= sizeof(UserSelectionConfig));
std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig));
}
bool ProfileSelect::TransactionComplete() const {
return complete;
}
ResultCode ProfileSelect::GetStatus() const {
return status;
}
void ProfileSelect::ExecuteInteractive() {
UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
}
void ProfileSelect::Execute() {
if (complete) {
broker.PushNormalDataFromApplet(IStorage{final_data});
return;
}
const auto& frontend{Core::System::GetInstance().GetProfileSelector()};
frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); });
}
void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) {
UserSelectionOutput output{};
if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) {
output.result = 0;
output.uuid_selected = uuid->uuid;
} else {
status = ERR_USER_CANCELLED_SELECTION;
output.result = ERR_USER_CANCELLED_SELECTION.raw;
output.uuid_selected = Account::INVALID_UUID;
}
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
std::memcpy(final_data.data(), &output, final_data.size());
broker.PushNormalDataFromApplet(IStorage{final_data});
broker.SignalStateChanged();
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,50 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_funcs.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
struct UserSelectionConfig {
// TODO(DarkLordZach): RE this structure
// It seems to be flags and the like that determine the UI of the applet on the switch... from
// my research this is safe to ignore for now.
INSERT_PADDING_BYTES(0xA0);
};
static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size.");
struct UserSelectionOutput {
u64 result;
u128 uuid_selected;
};
static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size.");
class ProfileSelect final : public Applet {
public:
ProfileSelect();
~ProfileSelect() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void SelectionComplete(std::optional<Account::UUID> uuid);
private:
UserSelectionConfig config;
bool complete = false;
ResultCode status = RESULT_SUCCESS;
std::vector<u8> final_data;
};
} // namespace Service::AM::Applets

View File

@@ -317,8 +317,8 @@ private:
}
bool has_attached_handle{};
const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')};
const u32 npad_id{0}; // Player 1 controller
const u64 device_handle{0}; // Npad device 1
const u32 npad_id{0}; // Player 1 controller
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
Kernel::EventPair deactivate_event;

View File

@@ -145,12 +145,13 @@ void SM::RegisterService(Kernel::HLERequestContext& ctx) {
const std::string name(name_buf.begin(), end);
const auto unk_bool = static_cast<bool>(rp.PopRaw<u32>());
const auto session_count = rp.PopRaw<u32>();
const auto is_light = static_cast<bool>(rp.PopRaw<u32>());
const auto max_session_count = rp.PopRaw<u32>();
LOG_DEBUG(Service_SM, "called with unk_bool={}", unk_bool);
LOG_DEBUG(Service_SM, "called with name={}, max_session_count={}, is_light={}", name,
max_session_count, is_light);
auto handle = service_manager->RegisterService(name, session_count);
auto handle = service_manager->RegisterService(name, max_session_count);
if (handle.Failed()) {
LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}",
handle.Code().raw);

View File

@@ -129,10 +129,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
return ResultStatus::Error32BitISA;
}
if (process.LoadFromMetadata(metadata).IsError()) {
return ResultStatus::ErrorUnableToParseKernelMetadata;
}
process.LoadFromMetadata(metadata);
const FileSys::PatchManager pm(metadata.GetTitleID());
// Load NSO modules

View File

@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
constexpr std::array<const char*, 62> RESULT_MESSAGES{
constexpr std::array<const char*, 60> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -103,7 +103,6 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{
"The NPDM has a bad ACI header,",
"The NPDM file has a bad file access control.",
"The NPDM has a bad file access header.",
"The NPDM has bad kernel capability descriptors.",
"The PFS/HFS partition has a bad header.",
"The PFS/HFS partition has incorrect size as determined by the header.",
"The NCA file has a bad header.",
@@ -126,7 +125,6 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{
"The file could not be found or does not exist.",
"The game is missing a program metadata file (main.npdm).",
"The game uses the currently-unimplemented 32-bit architecture.",
"Unable to completely parse the kernel metadata when loading the emulated process",
"The RomFS could not be found.",
"The ELF file has incorrect size as determined by the header.",
"There was a general error loading the NRO into emulated memory.",

View File

@@ -67,7 +67,6 @@ enum class ResultStatus : u16 {
ErrorBadACIHeader,
ErrorBadFileAccessControl,
ErrorBadFileAccessHeader,
ErrorBadKernelCapabilityDescriptors,
ErrorBadPFSHeader,
ErrorIncorrectPFSFileSize,
ErrorBadNCAHeader,
@@ -90,7 +89,6 @@ enum class ResultStatus : u16 {
ErrorNullFile,
ErrorMissingNPDM,
Error32BitISA,
ErrorUnableToParseKernelMetadata,
ErrorNoRomFS,
ErrorIncorrectELFFileSize,
ErrorLoadingNRO,

View File

@@ -187,6 +187,7 @@ T Read(const VAddr vaddr) {
default:
UNREACHABLE();
}
return {};
}
template <typename T>

View File

@@ -164,6 +164,7 @@ public:
return 3;
default:
UNREACHABLE();
return 1;
}
}
@@ -871,6 +872,7 @@ public:
return 4;
}
UNREACHABLE();
return 1;
}
GPUVAddr StartAddress() const {

View File

@@ -1065,6 +1065,7 @@ union Instruction {
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
return TextureType::Texture1D;
}
TextureProcessMode GetTextureProcessMode() const {
@@ -1145,6 +1146,7 @@ union Instruction {
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
return TextureType::Texture1D;
}
TextureProcessMode GetTextureProcessMode() const {

View File

@@ -102,6 +102,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
return 1;
default:
UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format));
return 1;
}
}
@@ -119,6 +120,7 @@ u32 DepthFormatBytesPerPixel(DepthFormat format) {
return 2;
default:
UNIMPLEMENTED_MSG("Unimplemented Depth format {}", static_cast<u32>(format));
return 1;
}
}

View File

@@ -171,6 +171,7 @@ u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b)
default:
UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
return 0;
}
}
@@ -268,6 +269,7 @@ bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value)
return value != 0;
}
UNREACHABLE();
return true;
}
} // namespace Tegra

View File

@@ -192,6 +192,7 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
return linear_to_morton_fns[static_cast<std::size_t>(format)];
}
UNREACHABLE();
return morton_to_linear_fns[static_cast<std::size_t>(format)];
}
/// 8x8 Z-Order coordinate from 2D coordinates

View File

@@ -27,4 +27,16 @@ void RendererBase::UpdateCurrentFramebufferLayout() {
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height);
}
void RendererBase::RequestScreenshot(void* data, std::function<void()> callback,
const Layout::FramebufferLayout& layout) {
if (renderer_settings.screenshot_requested) {
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
return;
}
renderer_settings.screenshot_bits = data;
renderer_settings.screenshot_complete_callback = std::move(callback);
renderer_settings.screenshot_framebuffer_layout = layout;
renderer_settings.screenshot_requested = true;
}
} // namespace VideoCore

View File

@@ -9,6 +9,7 @@
#include <optional>
#include "common/common_types.h"
#include "core/frontend/emu_window.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
@@ -21,6 +22,12 @@ namespace VideoCore {
struct RendererSettings {
std::atomic_bool use_framelimiter{false};
std::atomic_bool set_background_color{false};
// Screenshot
std::atomic<bool> screenshot_requested{false};
void* screenshot_bits;
std::function<void()> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout;
};
class RendererBase : NonCopyable {
@@ -57,9 +64,29 @@ public:
return *rasterizer;
}
Core::Frontend::EmuWindow& GetRenderWindow() {
return render_window;
}
const Core::Frontend::EmuWindow& GetRenderWindow() const {
return render_window;
}
RendererSettings& Settings() {
return renderer_settings;
}
const RendererSettings& Settings() const {
return renderer_settings;
}
/// Refreshes the settings common to all renderers
void RefreshBaseSettings();
/// Request a screenshot of the next frame
void RequestScreenshot(void* data, std::function<void()> callback,
const Layout::FramebufferLayout& layout);
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;

View File

@@ -101,8 +101,18 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.srgb_conversion = config.tic.IsSrgbConversionEnabled();
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) {
// Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
// then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
// causes GetFormatType to properly return 'Depth' below).
params.pixel_format = PixelFormat::Z16;
}
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
params.type = GetFormatType(params.pixel_format);
UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled);
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
params.unaligned_height = config.tic.Height();
@@ -257,7 +267,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex
{GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U
{GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI
{GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI

View File

@@ -67,6 +67,7 @@ public:
6, "ShaderTrianglesAdjacency");
default:
UNREACHABLE_MSG("Unknown primitive mode.");
return LazyGeometryProgram(geometry_programs.points, "points", 1, "ShaderPoints");
}
}

View File

@@ -364,6 +364,7 @@ public:
return value;
default:
UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size));
return value;
}
}
@@ -626,6 +627,7 @@ public:
return "floatBitsToInt(" + value + ')';
} else {
UNREACHABLE();
return value;
}
}
@@ -1591,23 +1593,21 @@ private:
process_mode == Tegra::Shader::TextureProcessMode::LL ||
process_mode == Tegra::Shader::TextureProcessMode::LLA;
// LOD selection (either via bias or explicit textureLod) not supported in GL for
// sampler2DArrayShadow and samplerCubeArrayShadow.
const bool gl_lod_supported = !(
(texture_type == Tegra::Shader::TextureType::Texture2D && is_array && depth_compare) ||
(texture_type == Tegra::Shader::TextureType::TextureCube && !is_array &&
depth_compare));
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array && depth_compare));
const std::string read_method = lod_needed && gl_lod_supported ? "textureLod(" : "texture(";
std::string texture = read_method + sampler + ", coord";
if (process_mode != Tegra::Shader::TextureProcessMode::None) {
UNIMPLEMENTED_IF(process_mode != Tegra::Shader::TextureProcessMode::None &&
!gl_lod_supported);
if (process_mode != Tegra::Shader::TextureProcessMode::None && gl_lod_supported) {
if (process_mode == Tegra::Shader::TextureProcessMode::LZ) {
if (gl_lod_supported) {
texture += ", 0";
} else {
// Lod 0 is emulated by a big negative bias
// in scenarios that are not supported by glsl
texture += ", -1000";
}
texture += ", 0.0";
} else {
// If present, lod or bias are always stored in the register indexed by the
// gpr20
@@ -1645,15 +1645,15 @@ private:
if (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) {
coord += ",0.0";
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
// or in the next register if lod or bias are used
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
coord += ',' + regs.GetRegisterAsFloat(depth_register);
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
return std::make_pair(
coord, GetTextureCode(instr, texture_type, process_mode, depth_compare, is_array, 0));
@@ -1686,15 +1686,15 @@ private:
}
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
// or in the next register if lod or bias are used
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
coord += ',' + regs.GetRegisterAsFloat(depth_register);
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
return std::make_pair(coord,
@@ -2064,6 +2064,8 @@ private:
std::to_string(instr.alu.GetSignedImm20_20())};
default:
UNREACHABLE();
return {regs.GetRegisterAsInteger(instr.gpr39, 0, false),
std::to_string(instr.alu.GetSignedImm20_20())};
}
}();
const std::string offset = '(' + packed_shift + " & 0xff)";
@@ -3314,6 +3316,7 @@ private:
return std::to_string(instr.r2p.immediate_mask);
default:
UNREACHABLE();
return std::to_string(instr.r2p.immediate_mask);
}
}();
const std::string mask = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) +
@@ -3777,7 +3780,10 @@ private:
}
break;
}
default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); }
default: {
UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName());
break;
}
}
break;
@@ -3932,4 +3938,4 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u
return {};
}
} // namespace OpenGL::GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -138,7 +138,12 @@ void RendererOpenGL::SwapBuffers(
// Load the framebuffer from memory, draw it to the screen, and swap buffers
LoadFBToScreenInfo(*framebuffer);
DrawScreen();
if (renderer_settings.screenshot_requested)
CaptureScreenshot();
DrawScreen(render_window.GetFramebufferLayout());
render_window.SwapBuffers();
}
@@ -383,14 +388,13 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
/**
* Draws the emulated screens to the emulator window.
*/
void RendererOpenGL::DrawScreen() {
void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
if (renderer_settings.set_background_color) {
// Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
}
const auto& layout = render_window.GetFramebufferLayout();
const auto& screen = layout.screen;
glViewport(0, 0, layout.width, layout.height);
@@ -414,6 +418,37 @@ void RendererOpenGL::DrawScreen() {
/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
void RendererOpenGL::CaptureScreenshot() {
// Draw the current frame to the screenshot framebuffer
screenshot_framebuffer.Create();
GLuint old_read_fb = state.draw.read_framebuffer;
GLuint old_draw_fb = state.draw.draw_framebuffer;
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
state.Apply();
Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
DrawScreen(layout);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
renderer_settings.screenshot_bits);
screenshot_framebuffer.Release();
state.draw.read_framebuffer = old_read_fb;
state.draw.draw_framebuffer = old_draw_fb;
state.Apply();
glDeleteRenderbuffers(1, &renderbuffer);
renderer_settings.screenshot_complete_callback();
renderer_settings.screenshot_requested = false;
}
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \
@@ -427,6 +462,7 @@ static const char* GetSource(GLenum source) {
RET(OTHER);
default:
UNREACHABLE();
return "Unknown source";
}
#undef RET
}
@@ -445,6 +481,7 @@ static const char* GetType(GLenum type) {
RET(MARKER);
default:
UNREACHABLE();
return "Unknown type";
}
#undef RET
}

View File

@@ -16,6 +16,10 @@ namespace Core::Frontend {
class EmuWindow;
}
namespace Layout {
class FramebufferLayout;
}
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -66,10 +70,12 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture,
const Tegra::FramebufferConfig& framebuffer);
void DrawScreen();
void DrawScreen(const Layout::FramebufferLayout& layout);
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
void UpdateFramerate();
void CaptureScreenshot();
// Loads framebuffer from emulated memory into the display information structure
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
// Fills active OpenGL texture with the given RGBA color.
@@ -82,6 +88,7 @@ private:
OGLVertexArray vertex_array;
OGLBuffer vertex_buffer;
OGLProgram shader;
OGLFramebuffer screenshot_framebuffer;
/// Display information for Switch screen
ScreenInfo screen_info;

View File

@@ -65,6 +65,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return PixelFormat::S8Z24;
}
}
@@ -141,6 +142,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return PixelFormat::RGBA8_SRGB;
}
}
@@ -327,6 +329,7 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
static_cast<u32>(component_type));
UNREACHABLE();
return PixelFormat::ABGR8U;
}
}
@@ -346,6 +349,7 @@ ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));
UNREACHABLE();
return ComponentType::UNorm;
}
}
@@ -393,6 +397,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return ComponentType::UNorm;
}
}
@@ -403,6 +408,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return PixelFormat::ABGR8U;
}
}
@@ -418,6 +424,7 @@ ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return ComponentType::UNorm;
}
}

View File

@@ -226,7 +226,7 @@ u32 BytesPerPixel(TextureFormat format) {
return 8;
default:
UNIMPLEMENTED_MSG("Format not implemented");
break;
return 1;
}
}

View File

@@ -3,6 +3,8 @@
// Refer to the license.txt file included.
#include <memory>
#include "core/core.h"
#include "core/settings.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
@@ -13,4 +15,10 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
}
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
return !Settings::values.resolution_factor
? renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio()
: Settings::values.resolution_factor;
}
} // namespace VideoCore

View File

@@ -22,4 +22,6 @@ class RendererBase;
*/
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window);
u16 GetResolutionScaleFactor(const RendererBase& renderer);
} // namespace VideoCore

View File

@@ -7,6 +7,8 @@ add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
applets/profile_select.cpp
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
bootmanager.cpp
@@ -31,6 +33,8 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_input_simple.cpp
configuration/configure_input_simple.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_system.cpp
@@ -87,6 +91,7 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
configuration/configure_system.ui

View File

@@ -0,0 +1,168 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/hle/lock.h"
#include "yuzu/applets/profile_select.h"
#include "yuzu/main.h"
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
constexpr std::array<u8, 107> backup_jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
return QtProfileSelectionDialog::tr(
"%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
QString GetImagePath(Service::Account::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QPixmap GetIcon(Service::Account::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
outer_layout = new QVBoxLayout;
instruction_label = new QLabel(tr("Select a user:"));
scroll_area = new QScrollArea;
buttons = new QDialogButtonBox;
buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole);
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
outer_layout->addWidget(instruction_label);
outer_layout->addWidget(scroll_area);
outer_layout->addWidget(buttons);
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 1);
item_model->setHeaderData(0, Qt::Horizontal, "Users");
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
scroll_area->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(user, profile))
continue;
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
item_model->appendRow(item);
setLayout(outer_layout);
setWindowTitle(tr("Profile Selector"));
resize(550, 400);
}
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
void QtProfileSelectionDialog::accept() {
ok = true;
QDialog::accept();
}
void QtProfileSelectionDialog::reject() {
ok = false;
user_index = 0;
QDialog::reject();
}
bool QtProfileSelectionDialog::GetStatus() const {
return ok;
}
u32 QtProfileSelectionDialog::GetIndex() const {
return user_index;
}
void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) {
user_index = index.row();
}
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
}
QtProfileSelector::~QtProfileSelector() = default;
void QtProfileSelector::SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
this->callback = std::move(callback);
emit MainWindowSelectProfile();
}
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
callback(uuid);
}

View File

@@ -0,0 +1,73 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <QDialog>
#include <QList>
#include "core/frontend/applets/profile_select.h"
class GMainWindow;
class QDialogButtonBox;
class QGraphicsScene;
class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog {
Q_OBJECT
public:
explicit QtProfileSelectionDialog(QWidget* parent);
~QtProfileSelectionDialog() override;
void accept() override;
void reject() override;
bool GetStatus() const;
u32 GetIndex() const;
private:
bool ok = false;
u32 user_index = 0;
void SelectUser(const QModelIndex& index);
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
QVBoxLayout* outer_layout;
QLabel* instruction_label;
QScrollArea* scroll_area;
QDialogButtonBox* buttons;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
Q_OBJECT
public:
explicit QtProfileSelector(GMainWindow& parent);
~QtProfileSelector() override;
void SelectProfile(
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
signals:
void MainWindowSelectProfile() const;
private:
void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid);
mutable std::function<void(std::optional<Service::Account::UUID>)> callback;
};

View File

@@ -14,6 +14,8 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
@@ -333,6 +335,22 @@ void GRenderWindow::InitRenderTarget() {
BackupGeometry();
}
void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
auto& renderer = Core::System::GetInstance().Renderer();
if (!res_scale)
res_scale = VideoCore::GetResolutionScaleFactor(renderer);
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(screenshot_image.bits(),
[=] {
screenshot_image.mirrored(false, true).save(screenshot_path);
LOG_INFO(Frontend, "The screenshot is saved.");
},
layout);
}
void GRenderWindow::OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);

View File

@@ -8,6 +8,7 @@
#include <condition_variable>
#include <mutex>
#include <QGLWidget>
#include <QImage>
#include <QThread>
#include "common/thread.h"
#include "core/core.h"
@@ -139,6 +140,8 @@ public:
void InitRenderTarget();
void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
public slots:
void moveContext(); // overridden
@@ -165,6 +168,9 @@ private:
EmuThread* emu_thread;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
protected:
void showEvent(QShowEvent* event) override;
};

View File

@@ -4,6 +4,7 @@
#include <QSettings>
#include "common/file_util.h"
#include "configure_input_simple.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
@@ -339,6 +340,13 @@ void Config::ReadTouchscreenValues() {
qt_config->endGroup();
}
void Config::ApplyDefaultProfileIfInputInvalid() {
if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(),
[](const Settings::PlayerInput& in) { return in.connected; })) {
ApplyInputProfileConfiguration(UISettings::values.profile_index);
}
}
void Config::ReadValues() {
qt_config->beginGroup("Controls");
@@ -460,6 +468,8 @@ void Config::ReadValues() {
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
qt_config->value("enable_discord_presence", true).toBool();
UISettings::values.screenshot_resolution_factor =
static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -518,6 +528,9 @@ void Config::ReadValues() {
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt();
ApplyDefaultProfileIfInputInvalid();
qt_config->endGroup();
}
@@ -678,6 +691,8 @@ void Config::SaveValues() {
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
qt_config->setValue("screenshot_resolution_factor",
UISettings::values.screenshot_resolution_factor);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
@@ -699,6 +714,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Paths");
qt_config->setValue("romsPath", UISettings::values.roms_path);
qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
qt_config->setValue("recentFiles", UISettings::values.recent_files);
@@ -720,6 +736,7 @@ void Config::SaveValues() {
qt_config->setValue("firstStart", UISettings::values.first_start);
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
qt_config->setValue("showConsole", UISettings::values.show_console);
qt_config->setValue("profileIndex", UISettings::values.profile_index);
qt_config->endGroup();
}

View File

@@ -34,6 +34,7 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
void ApplyDefaultProfileIfInputInvalid();
void SaveValues();
void SavePlayerValues();

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>461</width>
<height>500</height>
<height>659</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,17 +24,17 @@
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureGameList" name="gameListTab">
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureGameList" name="gameListTab">
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureInput" name="inputTab">
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
</attribute>
@@ -54,11 +54,11 @@
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
</widget>
</item>
<item>
@@ -77,12 +77,12 @@
<header>configuration/configure_general.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGameList</class>
<extends>QWidget</extends>
<header>configuration/configure_gamelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGameList</class>
<extends>QWidget</extends>
<header>configuration/configure_gamelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
@@ -102,9 +102,9 @@
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureInput</class>
<class>ConfigureInputSimple</class>
<extends>QWidget</extends>
<header>configuration/configure_input.h</header>
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
<customwidget>
@@ -113,12 +113,12 @@
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureWeb</class>
<extends>QWidget</extends>
<header>configuration/configure_web.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureWeb</class>
<extends>QWidget</extends>
<header>configuration/configure_web.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>

View File

@@ -20,6 +20,33 @@
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
void OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
return;
}
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
// Message queue is shared between these services, we just need to signal an operation
// change to one and it will handle both automatically
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->OperationModeChanged();
has_signalled = true;
}
if (applet_ae != nullptr && !has_signalled) {
applet_ae->GetMessageQueue()->OperationModeChanged();
}
}
namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
@@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
} // Anonymous namespace
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
ui->setupUi(this);
players_controller = {
@@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) {
if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) {
ui->handheld_connected->setChecked(false);
}
if (last_state == new_state) {
return;
}
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
// Message queue is shared between these services, we just need to signal an operation
// change to one and it will handle both automatically
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->OperationModeChanged();
has_signalled = true;
}
if (applet_ae != nullptr && !has_signalled) {
applet_ae->GetMessageQueue()->OperationModeChanged();
}
}
void ConfigureInput::applyConfiguration() {
for (std::size_t i = 0; i < players_controller.size(); ++i) {
const auto controller_type_index = players_controller[i]->currentIndex();

View File

@@ -7,8 +7,8 @@
#include <array>
#include <memory>
#include <QDialog>
#include <QKeyEvent>
#include <QWidget>
#include "ui_configure_input.h"
@@ -20,7 +20,9 @@ namespace Ui {
class ConfigureInput;
}
class ConfigureInput : public QWidget {
void OnDockedModeChanged(bool last_state, bool new_state);
class ConfigureInput : public QDialog {
Q_OBJECT
public:
@@ -33,8 +35,6 @@ public:
private:
void updateUIEnabled();
void OnDockedModeChanged(bool last_state, bool new_state);
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
<widget class="QWidget" name="ConfigureInput">
<widget class="QDialog" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>685</height>
<width>384</width>
<height>576</height>
</rect>
</property>
<property name="windowTitle">
@@ -478,6 +478,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -485,5 +492,38 @@
</layout>
</widget>
<resources/>
<connections/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureInput</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>294</x>
<y>553</y>
</hint>
<hint type="destinationlabel">
<x>191</x>
<y>287</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureInput</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>294</x>
<y>553</y>
</hint>
<hint type="destinationlabel">
<x>191</x>
<y>287</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -6,6 +6,7 @@
#include <memory>
#include <utility>
#include <QColorDialog>
#include <QGridLayout>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>

View File

@@ -0,0 +1,142 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstring>
#include <functional>
#include <tuple>
#include <QDialog>
#include "ui_configure_input_simple.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_simple.h"
#include "yuzu/ui_settings.h"
namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
caller->applyConfiguration();
Dialog dialog(caller, std::forward<Args>(args)...);
const auto res = dialog.exec();
if (res == QDialog::Accepted) {
dialog.applyConfiguration();
}
}
// OnProfileSelect functions should (when applicable):
// - Set controller types
// - Set controller enabled
// - Set docked mode
// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch)
//
// OnProfileSelect function should NOT however:
// - Reset any button mappings
// - Open any dialogs
// - Block in any way
constexpr std::size_t HANDHELD_INDEX = 8;
void HandheldOnProfileSelect() {
Settings::values.players[HANDHELD_INDEX].connected = true;
Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon;
for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) {
Settings::values.players[player].connected = false;
}
Settings::values.use_docked_mode = false;
Settings::values.keyboard_enabled = false;
Settings::values.mouse_enabled = false;
Settings::values.debug_pad_enabled = false;
Settings::values.touchscreen.enabled = true;
}
void DualJoyconsDockedOnProfileSelect() {
Settings::values.players[0].connected = true;
Settings::values.players[0].type = Settings::ControllerType::DualJoycon;
for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) {
Settings::values.players[player].connected = false;
}
Settings::values.use_docked_mode = true;
Settings::values.keyboard_enabled = false;
Settings::values.mouse_enabled = false;
Settings::values.debug_pad_enabled = false;
Settings::values.touchscreen.enabled = false;
}
// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure
// is clicked)
using InputProfile =
std::tuple<QString, std::function<void()>, std::function<void(ConfigureInputSimple*)>>;
const std::array<InputProfile, 3> INPUT_PROFILES{{
{ConfigureInputSimple::tr("Single Player - Handheld - Undocked"), HandheldOnProfileSelect,
[](ConfigureInputSimple* caller) {
CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false);
}},
{ConfigureInputSimple::tr("Single Player - Dual Joycons - Docked"),
DualJoyconsDockedOnProfileSelect,
[](ConfigureInputSimple* caller) {
CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false);
}},
{ConfigureInputSimple::tr("Custom"), [] {}, CallConfigureDialog<ConfigureInput>},
}};
} // namespace
void ApplyInputProfileConfiguration(int profile_index) {
std::get<1>(
INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))();
}
ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) {
ui->setupUi(this);
for (const auto& profile : INPUT_PROFILES) {
ui->profile_combobox->addItem(std::get<0>(profile), std::get<0>(profile));
}
connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureInputSimple::OnSelectProfile);
connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure);
this->loadConfiguration();
}
ConfigureInputSimple::~ConfigureInputSimple() = default;
void ConfigureInputSimple::applyConfiguration() {
auto index = ui->profile_combobox->currentIndex();
// Make the stored index for "Custom" very large so that if new profiles are added it
// doesn't change.
if (index >= static_cast<int>(INPUT_PROFILES.size() - 1))
index = std::numeric_limits<int>::max();
UISettings::values.profile_index = index;
}
void ConfigureInputSimple::loadConfiguration() {
const auto index = UISettings::values.profile_index;
if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0)
ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
else
ui->profile_combobox->setCurrentIndex(index);
}
void ConfigureInputSimple::OnSelectProfile(int index) {
const auto old_docked = Settings::values.use_docked_mode;
ApplyInputProfileConfiguration(index);
OnDockedModeChanged(old_docked, Settings::values.use_docked_mode);
}
void ConfigureInputSimple::OnConfigure() {
std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this);
}

View File

@@ -0,0 +1,40 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
class QPushButton;
class QString;
class QTimer;
namespace Ui {
class ConfigureInputSimple;
}
// Used by configuration loader to apply a profile if the input is invalid.
void ApplyInputProfileConfiguration(int profile_index);
class ConfigureInputSimple : public QWidget {
Q_OBJECT
public:
explicit ConfigureInputSimple(QWidget* parent = nullptr);
~ConfigureInputSimple() override;
/// Save all button configurations to settings file
void applyConfiguration();
private:
/// Load configuration settings.
void loadConfiguration();
void OnSelectProfile(int index);
void OnConfigure();
std::unique_ptr<Ui::ConfigureInputSimple> ui;
};

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInputSimple</class>
<widget class="QWidget" name="ConfigureInputSimple">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>685</height>
</rect>
</property>
<property name="windowTitle">
<string>ConfigureInputSimple</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Profile</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QPushButton" name="profile_configure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<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>
<item row="1" column="1">
<widget class="QComboBox" name="profile_combobox">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Choose a controller configuration:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</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>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -30,6 +30,7 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat(
return Tegra::Texture::TextureFormat::A2B10G10R10;
default:
UNIMPLEMENTED_MSG("Unimplemented RT format");
return Tegra::Texture::TextureFormat::A8R8G8B8;
}
}

View File

@@ -8,6 +8,7 @@
#include <thread>
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
@@ -208,6 +209,28 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
void GMainWindow::ProfileSelectorSelectProfile() {
QtProfileSelectionDialog dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
if (!dialog.GetStatus()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
Service::Account::ProfileManager manager;
const auto uuid = manager.GetUser(dialog.GetIndex());
if (!uuid.has_value()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
emit ProfileSelectorFinishedSelection(uuid);
}
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
@@ -335,6 +358,9 @@ void GMainWindow::InitializeHotkeys() {
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
QKeySequence(QKeySequence::Print));
hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@@ -394,6 +420,12 @@ void GMainWindow::InitializeHotkeys() {
OnLoadAmiibo();
}
});
connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
&QShortcut::activated, this, [&] {
if (emu_thread->IsRunning()) {
OnCaptureScreenshot();
}
});
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -491,6 +523,10 @@ void GMainWindow::ConnectMenuEvents() {
hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Movie
connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
&GMainWindow::OnCaptureScreenshot);
// Help
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
connect(ui.action_Rederive, &QAction::triggered, this,
@@ -574,6 +610,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -727,6 +764,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
ui.action_Capture_Screenshot->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -789,31 +827,25 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
Service::Account::ProfileManager manager{};
const auto user_ids = manager.GetAllUsers();
QStringList list;
for (const auto& user_id : user_ids) {
if (user_id == Service::Account::UUID{})
continue;
Service::Account::ProfileBase base;
if (!manager.GetProfileBase(user_id, base))
continue;
const auto select_profile = [this]() -> s32 {
QtProfileSelectionDialog dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(base.username.data()), base.username.size())));
}
if (!dialog.GetStatus()) {
return -1;
}
bool ok = false;
const auto index_string =
QInputDialog::getItem(this, tr("Select User"),
tr("Please select the user's save data you would like to open."),
list, Settings::values.current_user, false, &ok);
if (!ok)
return dialog.GetIndex();
};
const auto index = select_profile();
if (index == -1)
return;
const auto index = list.indexOf(index_string);
ASSERT(index != -1 && index < 8);
Service::Account::ProfileManager manager;
const auto user_id = manager.GetUser(index);
ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
@@ -1290,6 +1322,7 @@ void GMainWindow::OnStartGame() {
discord_rpc->Update();
ui.action_Load_Amiibo->setEnabled(true);
ui.action_Capture_Screenshot->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1298,6 +1331,7 @@ void GMainWindow::OnPauseGame() {
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
ui.action_Capture_Screenshot->setEnabled(false);
}
void GMainWindow::OnStopGame() {
@@ -1460,6 +1494,18 @@ void GMainWindow::OnToggleFilterBar() {
}
}
void GMainWindow::OnCaptureScreenshot() {
OnPauseGame();
const QString path =
QFileDialog::getSaveFileName(this, tr("Capture Screenshot"),
UISettings::values.screenshot_path, tr("PNG Image (*.png)"));
if (!path.isEmpty()) {
UISettings::values.screenshot_path = QFileInfo(path).path();
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
}
OnStartGame();
}
void GMainWindow::UpdateStatusBar() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();

View File

@@ -99,10 +99,12 @@ signals:
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
public slots:
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
@@ -187,6 +189,7 @@ private slots:
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
void OnCaptureScreenshot();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);

View File

@@ -101,11 +101,13 @@
<addaction name="action_Show_Status_Bar"/>
<addaction name="menu_View_Debugging"/>
</widget>
<widget class ="QMenu" name="menu_Tools">
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="action_Rederive" />
<addaction name="action_Rederive"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
@@ -118,7 +120,7 @@
<addaction name="menu_File"/>
<addaction name="menu_Emulation"/>
<addaction name="menu_View"/>
<addaction name="menu_Tools" />
<addaction name="menu_Tools"/>
<addaction name="menu_Help"/>
</widget>
<action name="action_Install_File_NAND">
@@ -173,11 +175,11 @@
<string>&amp;Stop</string>
</property>
</action>
<action name="action_Rederive">
<property name="text">
<string>Reinitialize keys...</string>
</property>
</action>
<action name="action_Rederive">
<property name="text">
<string>Reinitialize keys...</string>
</property>
</action>
<action name="action_About">
<property name="text">
<string>About yuzu</string>
@@ -252,39 +254,47 @@
<string>Fullscreen</string>
</property>
</action>
<action name="action_Restart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restart</string>
</property>
</action>
<action name="action_Load_Amiibo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load Amiibo...</string>
</property>
<action name="action_Restart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restart</string>
</property>
</action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
<string>Open yuzu Folder</string>
</property>
</action>
</widget>
<action name="action_Load_Amiibo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load Amiibo...</string>
</property>
</action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
<string>Open yuzu Folder</string>
</property>
</action>
<action name="action_Capture_Screenshot">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Capture Screenshot</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -10,6 +10,7 @@
#include <QByteArray>
#include <QString>
#include <QStringList>
#include "common/common_types.h"
namespace UISettings {
@@ -42,8 +43,11 @@ struct Values {
// Discord RPC
bool enable_discord_presence;
u16 screenshot_resolution_factor;
QString roms_path;
QString symbols_path;
QString screenshot_path;
QString gamedir;
bool gamedir_deepscan;
QStringList recent_files;
@@ -58,6 +62,9 @@ struct Values {
// logging
bool show_console;
// Controllers
int profile_index;
// Game List
bool show_unknown;
bool show_add_ons;