Compare commits
87 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c09ff382a4 | ||
|
|
8d7a012297 | ||
|
|
0bcc305797 | ||
|
|
799302bc9d | ||
|
|
81e09bb121 | ||
|
|
6aff1005ef | ||
|
|
e1d755bdda | ||
|
|
2beaaa35c5 | ||
|
|
1eb979221f | ||
|
|
dd4fe0dab1 | ||
|
|
433ca686a8 | ||
|
|
6ce5f3e1bf | ||
|
|
dfc40a3f9e | ||
|
|
4f7a1f6c8c | ||
|
|
2ba4aa8a3b | ||
|
|
2548661c08 | ||
|
|
42f5fd0ab3 | ||
|
|
79189c7e3e | ||
|
|
55f8111543 | ||
|
|
b35953e4fd | ||
|
|
a32c52b1d8 | ||
|
|
5ccf2a7b82 | ||
|
|
cfac942e63 | ||
|
|
b9b23c98ff | ||
|
|
50048d9f5a | ||
|
|
ca7ca2919c | ||
|
|
b73ea457cc | ||
|
|
2c679cda51 | ||
|
|
b46e615551 | ||
|
|
7ea07c6063 | ||
|
|
cf0d01a5d7 | ||
|
|
00f0827a26 | ||
|
|
77ce85f51d | ||
|
|
e32bf646cf | ||
|
|
536c9cf006 | ||
|
|
0a650ec99e | ||
|
|
2575403acf | ||
|
|
cfb59aad3f | ||
|
|
49e3a6e924 | ||
|
|
e70f16fff7 | ||
|
|
1edf018319 | ||
|
|
d0d97de1e4 | ||
|
|
de2533d389 | ||
|
|
ed74a3cb8b | ||
|
|
75561d190a | ||
|
|
e72b9044a0 | ||
|
|
ad321564ed | ||
|
|
838b6d2ff8 | ||
|
|
9b2d38582f | ||
|
|
e9772a2539 | ||
|
|
3f11d1c821 | ||
|
|
e3608578e4 | ||
|
|
665b7e8e18 | ||
|
|
cfc9d92b38 | ||
|
|
802dd3cc95 | ||
|
|
139301c5a1 | ||
|
|
8bbe930fac | ||
|
|
c6f05b586f | ||
|
|
84a8fb9264 | ||
|
|
b1a4ab2ccc | ||
|
|
215fd82738 | ||
|
|
05af9d915c | ||
|
|
2fb3b9b951 | ||
|
|
46e2ca5475 | ||
|
|
944c07ac7d | ||
|
|
f95bdb5088 | ||
|
|
180f22f17e | ||
|
|
37eaf39b44 | ||
|
|
4b80dd23a4 | ||
|
|
d623e38d18 | ||
|
|
d8827b07b5 | ||
|
|
46177901b8 | ||
|
|
016f2eab73 | ||
|
|
2ed896075e | ||
|
|
d81b58f320 | ||
|
|
7fba9c7224 | ||
|
|
7dbf4c1ae5 | ||
|
|
9c3461604c | ||
|
|
ada79fa8ad | ||
|
|
4e462d1fd7 | ||
|
|
851c01c45e | ||
|
|
1aa2b99a98 | ||
|
|
c40cff454d | ||
|
|
f0db2e3ef3 | ||
|
|
e25a7891e9 | ||
|
|
daf5b8c61b | ||
|
|
ca5638a142 |
@@ -7,7 +7,7 @@ yuzu is an experimental open-source emulator for the Nintendo Switch from the cr
|
||||
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows, Linux and macOS. The emulator is currently only useful for homebrew development and research purposes.
|
||||
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. At this time, yuzu cannot play any commercial games without major problems. yuzu can boot some games, to varying degrees of success.
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. yuzu can boot some games, to varying degrees of success.
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ Stream::State Stream::GetState() const {
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
|
||||
return Core::Timing::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
const auto us =
|
||||
std::chrono::microseconds((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
return Core::Timing::usToCycles(us);
|
||||
}
|
||||
|
||||
static void VolumeAdjustSamples(std::vector<s16>& samples) {
|
||||
|
||||
@@ -123,6 +123,8 @@ add_library(common STATIC
|
||||
timer.h
|
||||
uint128.cpp
|
||||
uint128.h
|
||||
uuid.cpp
|
||||
uuid.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
zstd_compression.cpp
|
||||
|
||||
@@ -41,4 +41,7 @@ struct Rectangle {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Rectangle(T, T, T, T)->Rectangle<T>;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
33
src/common/uuid.cpp
Normal file
33
src/common/uuid.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
UUID UUID::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
return UUID{distribution(gen), distribution(gen)};
|
||||
}
|
||||
|
||||
std::string UUID::Format() const {
|
||||
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
|
||||
}
|
||||
|
||||
std::string UUID::FormatSwitch() const {
|
||||
std::array<u8, 16> s{};
|
||||
std::memcpy(s.data(), uuid.data(), sizeof(u128));
|
||||
return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
|
||||
":02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
|
||||
s[12], s[13], s[14], s[15]);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
48
src/common/uuid.h
Normal file
48
src/common/uuid.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
constexpr u128 INVALID_UUID{{0, 0}};
|
||||
|
||||
struct UUID {
|
||||
// UUIDs which are 0 are considered invalid!
|
||||
u128 uuid = INVALID_UUID;
|
||||
constexpr UUID() = default;
|
||||
constexpr explicit UUID(const u128& id) : uuid{id} {}
|
||||
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
|
||||
|
||||
constexpr explicit operator bool() const {
|
||||
return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1];
|
||||
}
|
||||
|
||||
constexpr bool operator==(const UUID& rhs) const {
|
||||
// TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20
|
||||
return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1];
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const UUID& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate uuids based on RFC-4122
|
||||
static UUID Generate();
|
||||
|
||||
// Set the UUID to {0,0} to be considered an invalid user
|
||||
constexpr void Invalidate() {
|
||||
uuid = INVALID_UUID;
|
||||
}
|
||||
|
||||
std::string Format() const;
|
||||
std::string FormatSwitch() const;
|
||||
};
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
} // namespace Common
|
||||
@@ -310,6 +310,8 @@ add_library(core STATIC
|
||||
hle/service/mig/mig.h
|
||||
hle/service/mii/mii.cpp
|
||||
hle/service/mii/mii.h
|
||||
hle/service/mii/mii_manager.cpp
|
||||
hle/service/mii/mii_manager.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/ncm/ncm.cpp
|
||||
@@ -326,6 +328,9 @@ add_library(core STATIC
|
||||
hle/service/nim/nim.h
|
||||
hle/service/npns/npns.cpp
|
||||
hle/service/npns/npns.h
|
||||
hle/service/ns/errors.h
|
||||
hle/service/ns/language.cpp
|
||||
hle/service/ns/language.h
|
||||
hle/service/ns/ns.cpp
|
||||
hle/service/ns/ns.h
|
||||
hle/service/ns/pl_u.cpp
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/frontend/applets/error.h"
|
||||
#include "core/frontend/applets/general_frontend.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -37,9 +32,6 @@
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
@@ -144,20 +136,10 @@ struct System::Impl {
|
||||
ResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
return ResultStatus::ErrorGetLoader;
|
||||
}
|
||||
std::pair<std::optional<u32>, Loader::ResultStatus> system_mode =
|
||||
app_loader->LoadKernelSystemMode();
|
||||
|
||||
if (system_mode.second != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(system_mode.second));
|
||||
|
||||
return ResultStatus::ErrorSystemMode;
|
||||
}
|
||||
|
||||
ResultStatus init_result{Init(system, emu_window)};
|
||||
if (init_result != ResultStatus::Success) {
|
||||
@@ -167,6 +149,7 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
telemetry_session->AddInitialInfo(*app_loader);
|
||||
auto main_process = Kernel::Process::Create(system, "main");
|
||||
const auto [load_result, load_parameters] = app_loader->Load(*main_process);
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
ErrorSharedFont, ///< Error in finding shared font
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
|
||||
@@ -13,52 +13,40 @@ namespace Core::Timing {
|
||||
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
s64 usToCycles(s64 us) {
|
||||
if (static_cast<u64>(us / 1000000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
s64 msToCycles(std::chrono::milliseconds ms) {
|
||||
if (static_cast<u64>(ms.count() / 1000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (static_cast<u64>(us) > MAX_VALUE_TO_MULTIPLY) {
|
||||
if (static_cast<u64>(ms.count()) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (us / 1000000);
|
||||
return BASE_CLOCK_RATE * (ms.count() / 1000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * us) / 1000000;
|
||||
return (BASE_CLOCK_RATE * ms.count()) / 1000;
|
||||
}
|
||||
|
||||
s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
s64 usToCycles(std::chrono::microseconds us) {
|
||||
if (static_cast<u64>(us.count() / 1000000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
if (static_cast<u64>(us.count()) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
|
||||
return BASE_CLOCK_RATE * (us.count() / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
|
||||
return (BASE_CLOCK_RATE * us.count()) / 1000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(s64 ns) {
|
||||
if (static_cast<u64>(ns / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
s64 nsToCycles(std::chrono::nanoseconds ns) {
|
||||
if (static_cast<u64>(ns.count() / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (static_cast<u64>(ns) > MAX_VALUE_TO_MULTIPLY) {
|
||||
if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (ns / 1000000000);
|
||||
return BASE_CLOCK_RATE * (ns.count() / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * ns) / 1000000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
return (BASE_CLOCK_RATE * ns.count()) / 1000000000;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
@@ -13,53 +14,20 @@ namespace Core::Timing {
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 CNTFREQ = 19200000; // Value from fusee.
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ms) / 1000;
|
||||
s64 msToCycles(std::chrono::milliseconds ms);
|
||||
s64 usToCycles(std::chrono::microseconds us);
|
||||
s64 nsToCycles(std::chrono::nanoseconds ns);
|
||||
|
||||
inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
|
||||
return std::chrono::milliseconds(cycles * 1000 / BASE_CLOCK_RATE);
|
||||
}
|
||||
|
||||
inline s64 msToCycles(float ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001f) * ms);
|
||||
inline std::chrono::nanoseconds CyclesToNs(s64 cycles) {
|
||||
return std::chrono::nanoseconds(cycles * 1000000000 / BASE_CLOCK_RATE);
|
||||
}
|
||||
|
||||
inline s64 msToCycles(double ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.001) * ms);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(float us) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000001f) * us);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(int us) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
|
||||
}
|
||||
|
||||
s64 usToCycles(s64 us);
|
||||
|
||||
s64 usToCycles(u64 us);
|
||||
|
||||
inline s64 nsToCycles(float ns) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(int ns) {
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(s64 ns);
|
||||
|
||||
s64 nsToCycles(u64 ns);
|
||||
|
||||
inline u64 cyclesToNs(s64 cycles) {
|
||||
return cycles * 1000000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline s64 cyclesToUs(s64 cycles) {
|
||||
return cycles * 1000000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
inline std::chrono::microseconds CyclesToUs(s64 cycles) {
|
||||
return std::chrono::microseconds(cycles * 1000000 / BASE_CLOCK_RATE);
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
@@ -87,6 +87,10 @@ u64 NACP::GetDefaultJournalSaveSize() const {
|
||||
return raw.user_account_save_data_journal_size;
|
||||
}
|
||||
|
||||
u32 NACP::GetSupportedLanguages() const {
|
||||
return raw.supported_languages;
|
||||
}
|
||||
|
||||
std::vector<u8> NACP::GetRawBytes() const {
|
||||
std::vector<u8> out(sizeof(RawNACP));
|
||||
std::memcpy(out.data(), &raw, sizeof(RawNACP));
|
||||
|
||||
@@ -109,6 +109,7 @@ public:
|
||||
std::string GetVersionString() const;
|
||||
u64 GetDefaultNormalSaveSize() const;
|
||||
u64 GetDefaultJournalSaveSize() const;
|
||||
u32 GetSupportedLanguages() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
@@ -10,9 +11,9 @@ namespace Core::Frontend {
|
||||
ProfileSelectApplet::~ProfileSelectApplet() = default;
|
||||
|
||||
void DefaultProfileSelectApplet::SelectProfile(
|
||||
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
|
||||
std::function<void(std::optional<Common::UUID>)> callback) const {
|
||||
Service::Account::ProfileManager manager;
|
||||
callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{}));
|
||||
callback(manager.GetUser(Settings::values.current_user).value_or(Common::UUID{}));
|
||||
LOG_INFO(Service_ACC, "called, selecting current user instead of prompting...");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
@@ -14,14 +14,12 @@ class ProfileSelectApplet {
|
||||
public:
|
||||
virtual ~ProfileSelectApplet();
|
||||
|
||||
virtual void SelectProfile(
|
||||
std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0;
|
||||
virtual void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const = 0;
|
||||
};
|
||||
|
||||
class DefaultProfileSelectApplet final : public ProfileSelectApplet {
|
||||
public:
|
||||
void SelectProfile(
|
||||
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
|
||||
void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -169,8 +169,7 @@ private:
|
||||
* For the request to be honored, EmuWindow implementations will usually reimplement this
|
||||
* function.
|
||||
*/
|
||||
virtual void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
virtual void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned>) {
|
||||
// By default, ignore this request and do nothing.
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
}
|
||||
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
// The drawing code needs at least somewhat valid values for both screens
|
||||
@@ -29,22 +29,23 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
|
||||
const float emulation_aspect_ratio{static_cast<float>(ScreenUndocked::Height) /
|
||||
ScreenUndocked::Width};
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<unsigned> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
const auto window_aspect_ratio = static_cast<float>(height) / width;
|
||||
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
const Common::Rectangle<u32> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
|
||||
if (window_aspect_ratio < emulation_aspect_ratio) {
|
||||
screen = screen.TranslateX((screen_window_area.GetWidth() - screen.GetWidth()) / 2);
|
||||
} else {
|
||||
screen = screen.TranslateY((height - screen.GetHeight()) / 2);
|
||||
}
|
||||
|
||||
res.screen = screen;
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
|
||||
int width, height;
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
||||
u32 width, height;
|
||||
|
||||
if (Settings::values.use_docked_mode) {
|
||||
width = ScreenDocked::WidthDocked * res_scale;
|
||||
|
||||
@@ -8,15 +8,22 @@
|
||||
|
||||
namespace Layout {
|
||||
|
||||
enum ScreenUndocked : unsigned { Width = 1280, Height = 720 };
|
||||
enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 };
|
||||
enum ScreenUndocked : u32 {
|
||||
Width = 1280,
|
||||
Height = 720,
|
||||
};
|
||||
|
||||
enum ScreenDocked : u32 {
|
||||
WidthDocked = 1920,
|
||||
HeightDocked = 1080,
|
||||
};
|
||||
|
||||
/// Describes the layout of the window framebuffer
|
||||
struct FramebufferLayout {
|
||||
unsigned width{ScreenUndocked::Width};
|
||||
unsigned height{ScreenUndocked::Height};
|
||||
u32 width{ScreenUndocked::Width};
|
||||
u32 height{ScreenUndocked::Height};
|
||||
|
||||
Common::Rectangle<unsigned> screen;
|
||||
Common::Rectangle<u32> screen;
|
||||
|
||||
/**
|
||||
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
|
||||
@@ -33,12 +40,12 @@ struct FramebufferLayout {
|
||||
* @param height Window framebuffer height in pixels
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height);
|
||||
FramebufferLayout DefaultFrameLayout(u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* Convenience method to get frame layout by resolution scale
|
||||
* @param res_scale resolution scale factor
|
||||
*/
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale);
|
||||
|
||||
} // namespace Layout
|
||||
|
||||
@@ -75,9 +75,9 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
|
||||
// This function might be called from any thread so we have to be cautious and use the
|
||||
// thread-safe version of ScheduleEvent.
|
||||
const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
|
||||
Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe(
|
||||
Core::Timing::nsToCycles(nanoseconds), kernel.ThreadWakeupCallbackEventType(),
|
||||
callback_handle);
|
||||
cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
|
||||
@@ -34,7 +34,7 @@ constexpr std::array<u8, backup_jpeg_size> backup_jpeg{{
|
||||
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
}};
|
||||
|
||||
static std::string GetImagePath(UUID uuid) {
|
||||
static std::string GetImagePath(Common::UUID uuid) {
|
||||
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
|
||||
}
|
||||
@@ -46,7 +46,7 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
|
||||
|
||||
class IProfile final : public ServiceFramework<IProfile> {
|
||||
public:
|
||||
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
|
||||
explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager)
|
||||
: ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IProfile::Get, "Get"},
|
||||
@@ -131,7 +131,7 @@ private:
|
||||
}
|
||||
|
||||
const ProfileManager& profile_manager;
|
||||
UUID user_id; ///< The user id this profile refers to.
|
||||
Common::UUID user_id; ///< The user id this profile refers to.
|
||||
};
|
||||
|
||||
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
|
||||
@@ -179,7 +179,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
UUID user_id = rp.PopRaw<UUID>();
|
||||
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
||||
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
@@ -205,12 +205,12 @@ void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_ACC, "called");
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser());
|
||||
rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser());
|
||||
}
|
||||
|
||||
void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
UUID user_id = rp.PopRaw<UUID>();
|
||||
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
||||
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
@@ -245,15 +245,15 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
if (profile_manager->GetUserCount() != 1) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<u128>(INVALID_UUID);
|
||||
rb.PushRaw<u128>(Common::INVALID_UUID);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto user_list = profile_manager->GetAllUsers();
|
||||
if (std::all_of(user_list.begin(), user_list.end(),
|
||||
[](const auto& user) { return user.uuid == INVALID_UUID; })) {
|
||||
[](const auto& user) { return user.uuid == Common::INVALID_UUID; })) {
|
||||
rb.Push(ResultCode(-1)); // TODO(ogniK): Find the correct error code
|
||||
rb.PushRaw<u128>(INVALID_UUID);
|
||||
rb.PushRaw<u128>(Common::INVALID_UUID);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
using Common::UUID;
|
||||
|
||||
struct UserRaw {
|
||||
UUID uuid;
|
||||
UUID uuid2;
|
||||
@@ -35,26 +37,6 @@ constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
|
||||
|
||||
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
|
||||
|
||||
UUID UUID::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
return UUID{distribution(gen), distribution(gen)};
|
||||
}
|
||||
|
||||
std::string UUID::Format() const {
|
||||
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
|
||||
}
|
||||
|
||||
std::string UUID::FormatSwitch() const {
|
||||
std::array<u8, 16> s{};
|
||||
std::memcpy(s.data(), uuid.data(), sizeof(u128));
|
||||
return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
|
||||
":02x}{:02x}{:02x}{:02x}{:02x}",
|
||||
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
|
||||
s[12], s[13], s[14], s[15]);
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager() {
|
||||
ParseUserSaveFile();
|
||||
|
||||
@@ -217,7 +199,7 @@ bool ProfileManager::UserExists(UUID uuid) const {
|
||||
bool ProfileManager::UserExistsIndex(std::size_t index) const {
|
||||
if (index >= MAX_USERS)
|
||||
return false;
|
||||
return profiles[index].user_uuid.uuid != INVALID_UUID;
|
||||
return profiles[index].user_uuid.uuid != Common::INVALID_UUID;
|
||||
}
|
||||
|
||||
/// Opens a specific user
|
||||
@@ -311,7 +293,7 @@ bool ProfileManager::RemoveUser(UUID uuid) {
|
||||
|
||||
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
|
||||
const auto index = GetUserIndex(uuid);
|
||||
if (!index || profile_new.user_uuid == UUID(INVALID_UUID)) {
|
||||
if (!index || profile_new.user_uuid == UUID(Common::INVALID_UUID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -342,7 +324,7 @@ void ProfileManager::ParseUserSaveFile() {
|
||||
}
|
||||
|
||||
for (const auto& user : data.users) {
|
||||
if (user.uuid == UUID(INVALID_UUID)) {
|
||||
if (user.uuid == UUID(Common::INVALID_UUID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,47 +9,15 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Account {
|
||||
constexpr std::size_t MAX_USERS = 8;
|
||||
constexpr u128 INVALID_UUID{{0, 0}};
|
||||
|
||||
struct UUID {
|
||||
// UUIDs which are 0 are considered invalid!
|
||||
u128 uuid = INVALID_UUID;
|
||||
UUID() = default;
|
||||
explicit UUID(const u128& id) : uuid{id} {}
|
||||
explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
|
||||
|
||||
explicit operator bool() const {
|
||||
return uuid != INVALID_UUID;
|
||||
}
|
||||
|
||||
bool operator==(const UUID& rhs) const {
|
||||
return uuid == rhs.uuid;
|
||||
}
|
||||
|
||||
bool operator!=(const UUID& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate uuids based on RFC-4122
|
||||
static UUID Generate();
|
||||
|
||||
// Set the UUID to {0,0} to be considered an invalid user
|
||||
void Invalidate() {
|
||||
uuid = INVALID_UUID;
|
||||
}
|
||||
|
||||
std::string Format() const;
|
||||
std::string FormatSwitch() const;
|
||||
};
|
||||
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
|
||||
|
||||
constexpr std::size_t profile_username_size = 32;
|
||||
using ProfileUsername = std::array<u8, profile_username_size>;
|
||||
using UserIDArray = std::array<UUID, MAX_USERS>;
|
||||
using UserIDArray = std::array<Common::UUID, MAX_USERS>;
|
||||
|
||||
/// Contains extra data related to a user.
|
||||
/// TODO: RE this structure
|
||||
@@ -66,7 +34,7 @@ static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect
|
||||
/// This holds general information about a users profile. This is where we store all the information
|
||||
/// based on a specific user
|
||||
struct ProfileInfo {
|
||||
UUID user_uuid;
|
||||
Common::UUID user_uuid;
|
||||
ProfileUsername username;
|
||||
u64 creation_time;
|
||||
ProfileData data; // TODO(ognik): Work out what this is
|
||||
@@ -74,7 +42,7 @@ struct ProfileInfo {
|
||||
};
|
||||
|
||||
struct ProfileBase {
|
||||
UUID user_uuid;
|
||||
Common::UUID user_uuid;
|
||||
u64_le timestamp;
|
||||
ProfileUsername username;
|
||||
|
||||
@@ -96,33 +64,33 @@ public:
|
||||
~ProfileManager();
|
||||
|
||||
ResultCode AddUser(const ProfileInfo& user);
|
||||
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(UUID uuid, const std::string& username);
|
||||
std::optional<UUID> GetUser(std::size_t index) const;
|
||||
std::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
|
||||
ResultCode CreateNewUser(Common::UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(Common::UUID uuid, const std::string& username);
|
||||
std::optional<Common::UUID> GetUser(std::size_t index) const;
|
||||
std::optional<std::size_t> GetUserIndex(const Common::UUID& uuid) const;
|
||||
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
|
||||
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
|
||||
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
|
||||
bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const;
|
||||
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
|
||||
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
|
||||
ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, ProfileData& data) const;
|
||||
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
|
||||
ProfileData& data) const;
|
||||
std::size_t GetUserCount() const;
|
||||
std::size_t GetOpenUserCount() const;
|
||||
bool UserExists(UUID uuid) const;
|
||||
bool UserExists(Common::UUID uuid) const;
|
||||
bool UserExistsIndex(std::size_t index) const;
|
||||
void OpenUser(UUID uuid);
|
||||
void CloseUser(UUID uuid);
|
||||
void OpenUser(Common::UUID uuid);
|
||||
void CloseUser(Common::UUID uuid);
|
||||
UserIDArray GetOpenUsers() const;
|
||||
UserIDArray GetAllUsers() const;
|
||||
UUID GetLastOpenedUser() const;
|
||||
Common::UUID GetLastOpenedUser() const;
|
||||
|
||||
bool CanSystemRegisterUser() const;
|
||||
|
||||
bool RemoveUser(UUID uuid);
|
||||
bool SetProfileBase(UUID uuid, const ProfileBase& profile_new);
|
||||
bool RemoveUser(Common::UUID uuid);
|
||||
bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
|
||||
|
||||
private:
|
||||
void ParseUserSaveFile();
|
||||
@@ -132,7 +100,7 @@ private:
|
||||
|
||||
std::array<ProfileInfo, MAX_USERS> profiles{};
|
||||
std::size_t user_count = 0;
|
||||
UUID last_opened_user{INVALID_UUID};
|
||||
Common::UUID last_opened_user{Common::INVALID_UUID};
|
||||
};
|
||||
|
||||
}; // namespace Service::Account
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <cstring>
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -29,9 +31,11 @@
|
||||
#include "core/hle/service/am/tcap.h"
|
||||
#include "core/hle/service/apm/apm.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/ns.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/hle/service/pm/pm.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
@@ -1100,10 +1104,42 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
|
||||
// TODO(bunnei): This should be configurable
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
// Get supported languages from NACP, if possible
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()};
|
||||
|
||||
const auto res = pm.GetControlMetadata();
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
}
|
||||
|
||||
// Call IApplicationManagerInterface implementation.
|
||||
auto& service_manager = Core::System::GetInstance().ServiceManager();
|
||||
auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2");
|
||||
auto app_man = ns_am2->GetApplicationManagerInterface();
|
||||
|
||||
// Get desired application language
|
||||
const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages);
|
||||
if (res_lang.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res_lang.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to settings language code.
|
||||
const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang);
|
||||
if (res_code.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res_code.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(
|
||||
static_cast<u64>(Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index)));
|
||||
rb.Push(*res_code);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -121,6 +121,21 @@ void Applet::Initialize() {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet() = default;
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
|
||||
ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
|
||||
: error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move(
|
||||
profile_select)},
|
||||
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {}
|
||||
|
||||
AppletFrontendSet::~AppletFrontendSet() = default;
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
|
||||
|
||||
AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
|
||||
|
||||
AppletManager::AppletManager() = default;
|
||||
|
||||
AppletManager::~AppletManager() = default;
|
||||
|
||||
@@ -137,11 +137,28 @@ protected:
|
||||
};
|
||||
|
||||
struct AppletFrontendSet {
|
||||
std::unique_ptr<Core::Frontend::ErrorApplet> error;
|
||||
std::unique_ptr<Core::Frontend::PhotoViewerApplet> photo_viewer;
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_select;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
|
||||
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
|
||||
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
|
||||
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
|
||||
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
|
||||
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
||||
|
||||
AppletFrontendSet();
|
||||
AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser);
|
||||
~AppletFrontendSet();
|
||||
|
||||
AppletFrontendSet(const AppletFrontendSet&) = delete;
|
||||
AppletFrontendSet& operator=(const AppletFrontendSet&) = delete;
|
||||
|
||||
AppletFrontendSet(AppletFrontendSet&&) noexcept;
|
||||
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
|
||||
|
||||
ErrorApplet error;
|
||||
PhotoViewer photo_viewer;
|
||||
ProfileSelect profile_select;
|
||||
SoftwareKeyboard software_keyboard;
|
||||
WebBrowser web_browser;
|
||||
};
|
||||
|
||||
class AppletManager {
|
||||
|
||||
@@ -53,19 +53,19 @@ void ProfileSelect::Execute() {
|
||||
return;
|
||||
}
|
||||
|
||||
frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); });
|
||||
frontend.SelectProfile([this](std::optional<Common::UUID> uuid) { SelectionComplete(uuid); });
|
||||
}
|
||||
|
||||
void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) {
|
||||
void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) {
|
||||
UserSelectionOutput output{};
|
||||
|
||||
if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) {
|
||||
if (uuid.has_value() && uuid->uuid != Common::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;
|
||||
output.uuid_selected = Common::INVALID_UUID;
|
||||
}
|
||||
|
||||
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
@@ -38,7 +39,7 @@ public:
|
||||
void ExecuteInteractive() override;
|
||||
void Execute() override;
|
||||
|
||||
void SelectionComplete(std::optional<Account::UUID> uuid);
|
||||
void SelectionComplete(std::optional<Common::UUID> uuid);
|
||||
|
||||
private:
|
||||
const Core::Frontend::ProfileSelectApplet& frontend;
|
||||
|
||||
@@ -4,42 +4,50 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/mii/mii.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
|
||||
constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
|
||||
constexpr ResultCode ERROR_NOT_IN_TEST_MODE{ErrorModule::Mii, 99};
|
||||
|
||||
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
|
||||
public:
|
||||
explicit IDatabaseService() : ServiceFramework{"IDatabaseService"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "IsUpdated"},
|
||||
{1, nullptr, "IsFullDatabase"},
|
||||
{2, nullptr, "GetCount"},
|
||||
{3, nullptr, "Get"},
|
||||
{4, nullptr, "Get1"},
|
||||
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
|
||||
{1, &IDatabaseService::IsFullDatabase, "IsFullDatabase"},
|
||||
{2, &IDatabaseService::GetCount, "GetCount"},
|
||||
{3, &IDatabaseService::Get, "Get"},
|
||||
{4, &IDatabaseService::Get1, "Get1"},
|
||||
{5, nullptr, "UpdateLatest"},
|
||||
{6, nullptr, "BuildRandom"},
|
||||
{7, nullptr, "BuildDefault"},
|
||||
{8, nullptr, "Get2"},
|
||||
{9, nullptr, "Get3"},
|
||||
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
|
||||
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
|
||||
{8, &IDatabaseService::Get2, "Get2"},
|
||||
{9, &IDatabaseService::Get3, "Get3"},
|
||||
{10, nullptr, "UpdateLatest1"},
|
||||
{11, nullptr, "FindIndex"},
|
||||
{12, nullptr, "Move"},
|
||||
{13, nullptr, "AddOrReplace"},
|
||||
{14, nullptr, "Delete"},
|
||||
{15, nullptr, "DestroyFile"},
|
||||
{16, nullptr, "DeleteFile"},
|
||||
{17, nullptr, "Format"},
|
||||
{11, &IDatabaseService::FindIndex, "FindIndex"},
|
||||
{12, &IDatabaseService::Move, "Move"},
|
||||
{13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
|
||||
{14, &IDatabaseService::Delete, "Delete"},
|
||||
{15, &IDatabaseService::DestroyFile, "DestroyFile"},
|
||||
{16, &IDatabaseService::DeleteFile, "DeleteFile"},
|
||||
{17, &IDatabaseService::Format, "Format"},
|
||||
{18, nullptr, "Import"},
|
||||
{19, nullptr, "Export"},
|
||||
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
|
||||
{21, nullptr, "GetIndex"},
|
||||
{21, &IDatabaseService::GetIndex, "GetIndex"},
|
||||
{22, nullptr, "SetInterfaceVersion"},
|
||||
{23, nullptr, "Convert"},
|
||||
};
|
||||
@@ -47,6 +55,305 @@ public:
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename OutType>
|
||||
std::vector<u8> SerializeArray(OutType (MiiManager::*getter)(u32) const, u32 offset,
|
||||
u32 requested_size, u32& read_size) {
|
||||
read_size = std::min(requested_size, db.Size() - offset);
|
||||
|
||||
std::vector<u8> out(read_size * sizeof(OutType));
|
||||
|
||||
for (u32 i = 0; i < read_size; ++i) {
|
||||
const auto obj = (db.*getter)(offset + i);
|
||||
std::memcpy(out.data() + i * sizeof(OutType), &obj, sizeof(OutType));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void IsUpdated(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source={}", source);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(db.CheckUpdatedFlag());
|
||||
db.ResetUpdatedFlag();
|
||||
}
|
||||
|
||||
void IsFullDatabase(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(db.Full());
|
||||
}
|
||||
|
||||
void GetCount(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source={}", source);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(db.Size());
|
||||
}
|
||||
|
||||
// Gets Miis from database at offset and index in format MiiInfoElement
|
||||
void Get(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto size{rp.PopRaw<u32>()};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
|
||||
offsets[0], source);
|
||||
|
||||
u32 read_size{};
|
||||
ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfoElement, offsets[0], size, read_size));
|
||||
offsets[0] += read_size;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(read_size);
|
||||
}
|
||||
|
||||
// Gets Miis from database at offset and index in format MiiInfo
|
||||
void Get1(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto size{rp.PopRaw<u32>()};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
|
||||
offsets[1], source);
|
||||
|
||||
u32 read_size{};
|
||||
ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfo, offsets[1], size, read_size));
|
||||
offsets[1] += read_size;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(read_size);
|
||||
}
|
||||
|
||||
void BuildRandom(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto [unknown1, unknown2, unknown3] = rp.PopRaw<RandomParameters>();
|
||||
|
||||
if (unknown1 > 3) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "Invalid unknown1 value: {}", unknown1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unknown2 > 2) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "Invalid unknown2 value: {}", unknown2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unknown3 > 3) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "Invalid unknown3 value: {}", unknown3);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with param_1={:08X}, param_2={:08X}, param_3={:08X}",
|
||||
unknown1, unknown2, unknown3);
|
||||
|
||||
const auto info = db.CreateRandom({unknown1, unknown2, unknown3});
|
||||
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<MiiInfo>(info);
|
||||
}
|
||||
|
||||
void BuildDefault(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto index{rp.PopRaw<u32>()};
|
||||
|
||||
if (index > 5) {
|
||||
LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
|
||||
index);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with index={:08X}", index);
|
||||
|
||||
const auto info = db.CreateDefault(index);
|
||||
IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<MiiInfo>(info);
|
||||
}
|
||||
|
||||
// Gets Miis from database at offset and index in format MiiStoreDataElement
|
||||
void Get2(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto size{rp.PopRaw<u32>()};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
|
||||
offsets[2], source);
|
||||
|
||||
u32 read_size{};
|
||||
ctx.WriteBuffer(
|
||||
SerializeArray(&MiiManager::GetStoreDataElement, offsets[2], size, read_size));
|
||||
offsets[2] += read_size;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(read_size);
|
||||
}
|
||||
|
||||
// Gets Miis from database at offset and index in format MiiStoreData
|
||||
void Get3(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto size{rp.PopRaw<u32>()};
|
||||
const auto source{rp.PopRaw<Source>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
|
||||
offsets[3], source);
|
||||
|
||||
u32 read_size{};
|
||||
ctx.WriteBuffer(SerializeArray(&MiiManager::GetStoreData, offsets[3], size, read_size));
|
||||
offsets[3] += read_size;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(read_size);
|
||||
}
|
||||
|
||||
void FindIndex(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||
const auto unknown{rp.PopRaw<bool>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with uuid={}, unknown={}", uuid.FormatSwitch(), unknown);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
|
||||
const auto index = db.IndexOf(uuid);
|
||||
if (index > MAX_MIIS) {
|
||||
// TODO(DarkLordZach): Find a better error code
|
||||
rb.Push(ResultCode(-1));
|
||||
rb.Push(index);
|
||||
} else {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(index);
|
||||
}
|
||||
}
|
||||
|
||||
void Move(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||
const auto index{rp.PopRaw<s32>()};
|
||||
|
||||
if (index < 0) {
|
||||
LOG_ERROR(Service_Mii, "Index cannot be negative but is {:08X}!", index);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with uuid={}, index={:08X}", uuid.FormatSwitch(), index);
|
||||
|
||||
const auto success = db.Move(uuid, index);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code
|
||||
rb.Push(success ? RESULT_SUCCESS : ResultCode(-1));
|
||||
}
|
||||
|
||||
void AddOrReplace(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto data{rp.PopRaw<MiiStoreData>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with Mii data uuid={}, name={}", data.uuid.FormatSwitch(),
|
||||
Common::UTF16ToUTF8(data.Name()));
|
||||
|
||||
const auto success = db.AddOrReplace(data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code
|
||||
rb.Push(success ? RESULT_SUCCESS : ResultCode(-1));
|
||||
}
|
||||
|
||||
void Delete(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with uuid={}", uuid.FormatSwitch());
|
||||
|
||||
const auto success = db.Remove(uuid);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(success ? RESULT_SUCCESS : ERROR_CANNOT_FIND_ENTRY);
|
||||
}
|
||||
|
||||
void DestroyFile(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
if (!db.IsTestModeEnabled()) {
|
||||
LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot destory database file.");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NOT_IN_TEST_MODE);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(db.DestroyFile());
|
||||
}
|
||||
|
||||
void DeleteFile(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
if (!db.IsTestModeEnabled()) {
|
||||
LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot delete database file.");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_NOT_IN_TEST_MODE);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(db.DeleteFile());
|
||||
}
|
||||
|
||||
void Format(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
db.Clear();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetIndex(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto info{rp.PopRaw<MiiInfo>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with Mii info uuid={}, name={}", info.uuid.FormatSwitch(),
|
||||
Common::UTF16ToUTF8(info.Name()));
|
||||
|
||||
const auto index = db.IndexOf(info);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(index);
|
||||
}
|
||||
|
||||
MiiManager db;
|
||||
|
||||
// Last read offsets of Get functions
|
||||
std::array<u32, 4> offsets{};
|
||||
};
|
||||
|
||||
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
|
||||
|
||||
416
src/core/hle/service/mii/mii_manager.cpp
Normal file
416
src/core/hle/service/mii/mii_manager.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char MII_SAVE_DATABASE_PATH[] = "/system/save/8000000000000030/MiiDatabase.dat";
|
||||
constexpr std::array<char16_t, 11> DEFAULT_MII_NAME = {u'y', u'u', u'z', u'u', u'\0'};
|
||||
|
||||
// This value was retrieved from HW test
|
||||
constexpr MiiStoreData DEFAULT_MII = {
|
||||
{
|
||||
0x21, 0x40, 0x40, 0x01, 0x08, 0x01, 0x13, 0x08, 0x08, 0x02, 0x17, 0x8C, 0x06, 0x01,
|
||||
0x69, 0x6D, 0x8A, 0x6A, 0x82, 0x14, 0x00, 0x00, 0x00, 0x20, 0x64, 0x72, 0x44, 0x44,
|
||||
},
|
||||
{'y', 'u', 'z', 'u', '\0'},
|
||||
Common::UUID{1, 0},
|
||||
0,
|
||||
0,
|
||||
};
|
||||
|
||||
// Default values taken from multiple real databases
|
||||
const MiiDatabase DEFAULT_MII_DATABASE{Common::MakeMagic('N', 'F', 'D', 'B'), {}, {1}, 0, 0};
|
||||
|
||||
constexpr std::array<const char*, 4> SOURCE_NAMES{
|
||||
"Database",
|
||||
"Default",
|
||||
"Account",
|
||||
"Friend",
|
||||
};
|
||||
|
||||
template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
|
||||
std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
|
||||
std::array<T, DestArraySize> out{};
|
||||
std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
|
||||
return out;
|
||||
}
|
||||
|
||||
MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
|
||||
MiiStoreBitFields bf{};
|
||||
std::memcpy(&bf, data.data.data(), sizeof(MiiStoreBitFields));
|
||||
return {
|
||||
data.uuid,
|
||||
ResizeArray<char16_t, 10, 11>(data.name),
|
||||
static_cast<u8>(bf.font_region.Value()),
|
||||
static_cast<u8>(bf.favorite_color.Value()),
|
||||
static_cast<u8>(bf.gender.Value()),
|
||||
static_cast<u8>(bf.height.Value()),
|
||||
static_cast<u8>(bf.weight.Value()),
|
||||
static_cast<u8>(bf.mii_type.Value()),
|
||||
static_cast<u8>(bf.mii_region.Value()),
|
||||
static_cast<u8>(bf.face_type.Value()),
|
||||
static_cast<u8>(bf.face_color.Value()),
|
||||
static_cast<u8>(bf.face_wrinkle.Value()),
|
||||
static_cast<u8>(bf.face_makeup.Value()),
|
||||
static_cast<u8>(bf.hair_type.Value()),
|
||||
static_cast<u8>(bf.hair_color.Value()),
|
||||
static_cast<bool>(bf.hair_flip.Value()),
|
||||
static_cast<u8>(bf.eye_type.Value()),
|
||||
static_cast<u8>(bf.eye_color.Value()),
|
||||
static_cast<u8>(bf.eye_scale.Value()),
|
||||
static_cast<u8>(bf.eye_aspect.Value()),
|
||||
static_cast<u8>(bf.eye_rotate.Value()),
|
||||
static_cast<u8>(bf.eye_x.Value()),
|
||||
static_cast<u8>(bf.eye_y.Value()),
|
||||
static_cast<u8>(bf.eyebrow_type.Value()),
|
||||
static_cast<u8>(bf.eyebrow_color.Value()),
|
||||
static_cast<u8>(bf.eyebrow_scale.Value()),
|
||||
static_cast<u8>(bf.eyebrow_aspect.Value()),
|
||||
static_cast<u8>(bf.eyebrow_rotate.Value()),
|
||||
static_cast<u8>(bf.eyebrow_x.Value()),
|
||||
static_cast<u8>(bf.eyebrow_y.Value()),
|
||||
static_cast<u8>(bf.nose_type.Value()),
|
||||
static_cast<u8>(bf.nose_scale.Value()),
|
||||
static_cast<u8>(bf.nose_y.Value()),
|
||||
static_cast<u8>(bf.mouth_type.Value()),
|
||||
static_cast<u8>(bf.mouth_color.Value()),
|
||||
static_cast<u8>(bf.mouth_scale.Value()),
|
||||
static_cast<u8>(bf.mouth_aspect.Value()),
|
||||
static_cast<u8>(bf.mouth_y.Value()),
|
||||
static_cast<u8>(bf.facial_hair_color.Value()),
|
||||
static_cast<u8>(bf.beard_type.Value()),
|
||||
static_cast<u8>(bf.mustache_type.Value()),
|
||||
static_cast<u8>(bf.mustache_scale.Value()),
|
||||
static_cast<u8>(bf.mustache_y.Value()),
|
||||
static_cast<u8>(bf.glasses_type.Value()),
|
||||
static_cast<u8>(bf.glasses_color.Value()),
|
||||
static_cast<u8>(bf.glasses_scale.Value()),
|
||||
static_cast<u8>(bf.glasses_y.Value()),
|
||||
static_cast<u8>(bf.mole_type.Value()),
|
||||
static_cast<u8>(bf.mole_scale.Value()),
|
||||
static_cast<u8>(bf.mole_x.Value()),
|
||||
static_cast<u8>(bf.mole_y.Value()),
|
||||
0x00,
|
||||
};
|
||||
}
|
||||
MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) {
|
||||
MiiStoreData out{};
|
||||
out.name = ResizeArray<char16_t, 11, 10>(info.name);
|
||||
out.uuid = info.uuid;
|
||||
|
||||
MiiStoreBitFields bf{};
|
||||
|
||||
bf.hair_type.Assign(info.hair_type);
|
||||
bf.mole_type.Assign(info.mole_type);
|
||||
bf.height.Assign(info.height);
|
||||
bf.hair_flip.Assign(info.hair_flip);
|
||||
bf.weight.Assign(info.weight);
|
||||
bf.hair_color.Assign(info.hair_color);
|
||||
|
||||
bf.gender.Assign(info.gender);
|
||||
bf.eye_color.Assign(info.eye_color);
|
||||
bf.eyebrow_color.Assign(info.eyebrow_color);
|
||||
bf.mouth_color.Assign(info.mouth_color);
|
||||
bf.facial_hair_color.Assign(info.facial_hair_color);
|
||||
|
||||
bf.mii_type.Assign(info.mii_type);
|
||||
bf.glasses_color.Assign(info.glasses_color);
|
||||
bf.font_region.Assign(info.font_region);
|
||||
bf.eye_type.Assign(info.eye_type);
|
||||
bf.mii_region.Assign(info.mii_region);
|
||||
bf.mouth_type.Assign(info.mouth_type);
|
||||
bf.glasses_scale.Assign(info.glasses_scale);
|
||||
bf.eye_y.Assign(info.eye_y);
|
||||
|
||||
bf.mustache_type.Assign(info.mustache_type);
|
||||
bf.eyebrow_type.Assign(info.eyebrow_type);
|
||||
bf.beard_type.Assign(info.beard_type);
|
||||
bf.nose_type.Assign(info.nose_type);
|
||||
bf.mouth_aspect.Assign(info.mouth_aspect_ratio);
|
||||
bf.nose_y.Assign(info.nose_y);
|
||||
bf.eyebrow_aspect.Assign(info.eyebrow_aspect_ratio);
|
||||
bf.mouth_y.Assign(info.mouth_y);
|
||||
|
||||
bf.eye_rotate.Assign(info.eye_rotate);
|
||||
bf.mustache_y.Assign(info.mustache_y);
|
||||
bf.eye_aspect.Assign(info.eye_aspect_ratio);
|
||||
bf.glasses_y.Assign(info.glasses_y);
|
||||
bf.eye_scale.Assign(info.eye_scale);
|
||||
bf.mole_x.Assign(info.mole_x);
|
||||
bf.mole_y.Assign(info.mole_y);
|
||||
|
||||
bf.glasses_type.Assign(info.glasses_type);
|
||||
bf.face_type.Assign(info.face_type);
|
||||
bf.favorite_color.Assign(info.favorite_color);
|
||||
bf.face_wrinkle.Assign(info.face_wrinkle);
|
||||
bf.face_color.Assign(info.face_color);
|
||||
bf.eye_x.Assign(info.eye_x);
|
||||
bf.face_makeup.Assign(info.face_makeup);
|
||||
|
||||
bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
|
||||
bf.eyebrow_scale.Assign(info.eyebrow_scale);
|
||||
bf.eyebrow_y.Assign(info.eyebrow_y);
|
||||
bf.eyebrow_x.Assign(info.eyebrow_x);
|
||||
bf.mouth_scale.Assign(info.mouth_scale);
|
||||
bf.nose_scale.Assign(info.nose_scale);
|
||||
bf.mole_scale.Assign(info.mole_scale);
|
||||
bf.mustache_scale.Assign(info.mustache_scale);
|
||||
|
||||
std::memcpy(out.data.data(), &bf, sizeof(MiiStoreBitFields));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Source source) {
|
||||
os << SOURCE_NAMES.at(static_cast<std::size_t>(source));
|
||||
return os;
|
||||
}
|
||||
|
||||
std::u16string MiiInfo::Name() const {
|
||||
return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
|
||||
}
|
||||
|
||||
bool operator==(const MiiInfo& lhs, const MiiInfo& rhs) {
|
||||
return std::memcmp(&lhs, &rhs, sizeof(MiiInfo)) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
std::u16string MiiStoreData::Name() const {
|
||||
return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
|
||||
}
|
||||
|
||||
MiiManager::MiiManager() = default;
|
||||
|
||||
MiiManager::~MiiManager() = default;
|
||||
|
||||
MiiInfo MiiManager::CreateRandom(RandomParameters params) {
|
||||
LOG_WARNING(Service_Mii,
|
||||
"(STUBBED) called with params={:08X}{:08X}{:08X}, returning default Mii",
|
||||
params.unknown_1, params.unknown_2, params.unknown_3);
|
||||
|
||||
return ConvertStoreDataToInfo(CreateMiiWithUniqueUUID());
|
||||
}
|
||||
|
||||
MiiInfo MiiManager::CreateDefault(u32 index) {
|
||||
const auto new_mii = CreateMiiWithUniqueUUID();
|
||||
|
||||
database.miis.at(index) = new_mii;
|
||||
|
||||
EnsureDatabasePartition();
|
||||
return ConvertStoreDataToInfo(new_mii);
|
||||
}
|
||||
|
||||
bool MiiManager::CheckUpdatedFlag() const {
|
||||
return updated_flag;
|
||||
}
|
||||
|
||||
void MiiManager::ResetUpdatedFlag() {
|
||||
updated_flag = false;
|
||||
}
|
||||
|
||||
bool MiiManager::IsTestModeEnabled() const {
|
||||
return is_test_mode_enabled;
|
||||
}
|
||||
|
||||
bool MiiManager::Empty() const {
|
||||
return Size() == 0;
|
||||
}
|
||||
|
||||
bool MiiManager::Full() const {
|
||||
return Size() == MAX_MIIS;
|
||||
}
|
||||
|
||||
void MiiManager::Clear() {
|
||||
updated_flag = true;
|
||||
std::fill(database.miis.begin(), database.miis.end(), MiiStoreData{});
|
||||
}
|
||||
|
||||
u32 MiiManager::Size() const {
|
||||
return static_cast<u32>(std::count_if(database.miis.begin(), database.miis.end(),
|
||||
[](const MiiStoreData& elem) { return elem.uuid; }));
|
||||
}
|
||||
|
||||
MiiInfo MiiManager::GetInfo(u32 index) const {
|
||||
return ConvertStoreDataToInfo(GetStoreData(index));
|
||||
}
|
||||
|
||||
MiiInfoElement MiiManager::GetInfoElement(u32 index) const {
|
||||
return {GetInfo(index), Source::Database};
|
||||
}
|
||||
|
||||
MiiStoreData MiiManager::GetStoreData(u32 index) const {
|
||||
return database.miis.at(index);
|
||||
}
|
||||
|
||||
MiiStoreDataElement MiiManager::GetStoreDataElement(u32 index) const {
|
||||
return {GetStoreData(index), Source::Database};
|
||||
}
|
||||
|
||||
bool MiiManager::Remove(Common::UUID uuid) {
|
||||
const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
|
||||
[uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
|
||||
|
||||
if (iter == database.miis.end())
|
||||
return false;
|
||||
|
||||
updated_flag = true;
|
||||
*iter = MiiStoreData{};
|
||||
EnsureDatabasePartition();
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 MiiManager::IndexOf(Common::UUID uuid) const {
|
||||
const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
|
||||
[uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
|
||||
|
||||
if (iter == database.miis.end())
|
||||
return INVALID_INDEX;
|
||||
|
||||
return static_cast<u32>(std::distance(database.miis.begin(), iter));
|
||||
}
|
||||
|
||||
u32 MiiManager::IndexOf(const MiiInfo& info) const {
|
||||
const auto iter =
|
||||
std::find_if(database.miis.begin(), database.miis.end(), [&info](const MiiStoreData& elem) {
|
||||
return ConvertStoreDataToInfo(elem) == info;
|
||||
});
|
||||
|
||||
if (iter == database.miis.end())
|
||||
return INVALID_INDEX;
|
||||
|
||||
return static_cast<u32>(std::distance(database.miis.begin(), iter));
|
||||
}
|
||||
|
||||
bool MiiManager::Move(Common::UUID uuid, u32 new_index) {
|
||||
const auto index = IndexOf(uuid);
|
||||
|
||||
if (index == INVALID_INDEX || new_index >= MAX_MIIS)
|
||||
return false;
|
||||
|
||||
updated_flag = true;
|
||||
const auto moving = database.miis[index];
|
||||
const auto replacing = database.miis[new_index];
|
||||
if (replacing.uuid) {
|
||||
database.miis[index] = replacing;
|
||||
database.miis[new_index] = moving;
|
||||
} else {
|
||||
database.miis[index] = MiiStoreData{};
|
||||
database.miis[new_index] = moving;
|
||||
}
|
||||
|
||||
EnsureDatabasePartition();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MiiManager::AddOrReplace(const MiiStoreData& data) {
|
||||
const auto index = IndexOf(data.uuid);
|
||||
|
||||
updated_flag = true;
|
||||
if (index == INVALID_INDEX) {
|
||||
const auto size = Size();
|
||||
if (size == MAX_MIIS)
|
||||
return false;
|
||||
database.miis[size] = data;
|
||||
} else {
|
||||
database.miis[index] = data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MiiManager::DestroyFile() {
|
||||
database = DEFAULT_MII_DATABASE;
|
||||
updated_flag = false;
|
||||
return DeleteFile();
|
||||
}
|
||||
|
||||
bool MiiManager::DeleteFile() {
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
|
||||
return FileUtil::Exists(path) && FileUtil::Delete(path);
|
||||
}
|
||||
|
||||
void MiiManager::WriteToFile() {
|
||||
const auto raw_path =
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000030";
|
||||
if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
|
||||
FileUtil::Delete(raw_path);
|
||||
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
|
||||
|
||||
if (!FileUtil::CreateFullPath(path)) {
|
||||
LOG_WARNING(Service_Mii,
|
||||
"Failed to create full path of MiiDatabase.dat. Create the directory "
|
||||
"nand/system/save/8000000000000030 to mitigate this "
|
||||
"issue.");
|
||||
return;
|
||||
}
|
||||
|
||||
FileUtil::IOFile save(path, "wb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_Mii, "Failed to write save data to file... No changes to user data "
|
||||
"made in current session will be saved.");
|
||||
return;
|
||||
}
|
||||
|
||||
save.Resize(sizeof(MiiDatabase));
|
||||
if (save.WriteBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
|
||||
LOG_WARNING(Service_Mii, "Failed to write all data to save file... Data may be malformed "
|
||||
"and/or regenerated on next run.");
|
||||
save.Resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
void MiiManager::ReadFromFile() {
|
||||
FileUtil::IOFile save(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH, "rb");
|
||||
|
||||
if (!save.IsOpen()) {
|
||||
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
|
||||
"blank Mii database with no Miis.");
|
||||
std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
|
||||
return;
|
||||
}
|
||||
|
||||
if (save.ReadBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
|
||||
LOG_WARNING(Service_ACC, "MiiDatabase.dat is smaller than expected... Generating new blank "
|
||||
"Mii database with no Miis.");
|
||||
std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureDatabasePartition();
|
||||
}
|
||||
|
||||
MiiStoreData MiiManager::CreateMiiWithUniqueUUID() const {
|
||||
auto new_mii = DEFAULT_MII;
|
||||
|
||||
do {
|
||||
new_mii.uuid = Common::UUID::Generate();
|
||||
} while (IndexOf(new_mii.uuid) != INVALID_INDEX);
|
||||
|
||||
return new_mii;
|
||||
}
|
||||
|
||||
void MiiManager::EnsureDatabasePartition() {
|
||||
std::stable_partition(database.miis.begin(), database.miis.end(),
|
||||
[](const MiiStoreData& elem) { return elem.uuid; });
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
||||
273
src/core/hle/service/mii/mii_manager.h
Normal file
273
src/core/hle/service/mii/mii_manager.h
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
constexpr std::size_t MAX_MIIS = 100;
|
||||
constexpr u32 INVALID_INDEX = 0xFFFFFFFF;
|
||||
|
||||
struct RandomParameters {
|
||||
u32 unknown_1;
|
||||
u32 unknown_2;
|
||||
u32 unknown_3;
|
||||
};
|
||||
static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size.");
|
||||
|
||||
enum class Source : u32 {
|
||||
Database = 0,
|
||||
Default = 1,
|
||||
Account = 2,
|
||||
Friend = 3,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Source source);
|
||||
|
||||
struct MiiInfo {
|
||||
Common::UUID uuid;
|
||||
std::array<char16_t, 11> name;
|
||||
u8 font_region;
|
||||
u8 favorite_color;
|
||||
u8 gender;
|
||||
u8 height;
|
||||
u8 weight;
|
||||
u8 mii_type;
|
||||
u8 mii_region;
|
||||
u8 face_type;
|
||||
u8 face_color;
|
||||
u8 face_wrinkle;
|
||||
u8 face_makeup;
|
||||
u8 hair_type;
|
||||
u8 hair_color;
|
||||
bool hair_flip;
|
||||
u8 eye_type;
|
||||
u8 eye_color;
|
||||
u8 eye_scale;
|
||||
u8 eye_aspect_ratio;
|
||||
u8 eye_rotate;
|
||||
u8 eye_x;
|
||||
u8 eye_y;
|
||||
u8 eyebrow_type;
|
||||
u8 eyebrow_color;
|
||||
u8 eyebrow_scale;
|
||||
u8 eyebrow_aspect_ratio;
|
||||
u8 eyebrow_rotate;
|
||||
u8 eyebrow_x;
|
||||
u8 eyebrow_y;
|
||||
u8 nose_type;
|
||||
u8 nose_scale;
|
||||
u8 nose_y;
|
||||
u8 mouth_type;
|
||||
u8 mouth_color;
|
||||
u8 mouth_scale;
|
||||
u8 mouth_aspect_ratio;
|
||||
u8 mouth_y;
|
||||
u8 facial_hair_color;
|
||||
u8 beard_type;
|
||||
u8 mustache_type;
|
||||
u8 mustache_scale;
|
||||
u8 mustache_y;
|
||||
u8 glasses_type;
|
||||
u8 glasses_color;
|
||||
u8 glasses_scale;
|
||||
u8 glasses_y;
|
||||
u8 mole_type;
|
||||
u8 mole_scale;
|
||||
u8 mole_x;
|
||||
u8 mole_y;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
|
||||
std::u16string Name() const;
|
||||
};
|
||||
static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
|
||||
static_assert(std::has_unique_object_representations_v<MiiInfo>,
|
||||
"All bits of MiiInfo must contribute to its value.");
|
||||
|
||||
bool operator==(const MiiInfo& lhs, const MiiInfo& rhs);
|
||||
bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs);
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct MiiInfoElement {
|
||||
MiiInfo info;
|
||||
Source source;
|
||||
};
|
||||
static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size.");
|
||||
|
||||
struct MiiStoreBitFields {
|
||||
union {
|
||||
u32 word_0;
|
||||
|
||||
BitField<24, 8, u32> hair_type;
|
||||
BitField<23, 1, u32> mole_type;
|
||||
BitField<16, 7, u32> height;
|
||||
BitField<15, 1, u32> hair_flip;
|
||||
BitField<8, 7, u32> weight;
|
||||
BitField<0, 7, u32> hair_color;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_1;
|
||||
|
||||
BitField<31, 1, u32> gender;
|
||||
BitField<24, 7, u32> eye_color;
|
||||
BitField<16, 7, u32> eyebrow_color;
|
||||
BitField<8, 7, u32> mouth_color;
|
||||
BitField<0, 7, u32> facial_hair_color;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_2;
|
||||
|
||||
BitField<31, 1, u32> mii_type;
|
||||
BitField<24, 7, u32> glasses_color;
|
||||
BitField<22, 2, u32> font_region;
|
||||
BitField<16, 6, u32> eye_type;
|
||||
BitField<14, 2, u32> mii_region;
|
||||
BitField<8, 6, u32> mouth_type;
|
||||
BitField<5, 3, u32> glasses_scale;
|
||||
BitField<0, 5, u32> eye_y;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_3;
|
||||
|
||||
BitField<29, 3, u32> mustache_type;
|
||||
BitField<24, 5, u32> eyebrow_type;
|
||||
BitField<21, 3, u32> beard_type;
|
||||
BitField<16, 5, u32> nose_type;
|
||||
BitField<13, 3, u32> mouth_aspect;
|
||||
BitField<8, 5, u32> nose_y;
|
||||
BitField<5, 3, u32> eyebrow_aspect;
|
||||
BitField<0, 5, u32> mouth_y;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_4;
|
||||
|
||||
BitField<29, 3, u32> eye_rotate;
|
||||
BitField<24, 5, u32> mustache_y;
|
||||
BitField<21, 3, u32> eye_aspect;
|
||||
BitField<16, 5, u32> glasses_y;
|
||||
BitField<13, 3, u32> eye_scale;
|
||||
BitField<8, 5, u32> mole_x;
|
||||
BitField<0, 5, u32> mole_y;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_5;
|
||||
|
||||
BitField<24, 5, u32> glasses_type;
|
||||
BitField<20, 4, u32> face_type;
|
||||
BitField<16, 4, u32> favorite_color;
|
||||
BitField<12, 4, u32> face_wrinkle;
|
||||
BitField<8, 4, u32> face_color;
|
||||
BitField<4, 4, u32> eye_x;
|
||||
BitField<0, 4, u32> face_makeup;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_6;
|
||||
|
||||
BitField<28, 4, u32> eyebrow_rotate;
|
||||
BitField<24, 4, u32> eyebrow_scale;
|
||||
BitField<20, 4, u32> eyebrow_y;
|
||||
BitField<16, 4, u32> eyebrow_x;
|
||||
BitField<12, 4, u32> mouth_scale;
|
||||
BitField<8, 4, u32> nose_scale;
|
||||
BitField<4, 4, u32> mole_scale;
|
||||
BitField<0, 4, u32> mustache_scale;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(MiiStoreBitFields) == 0x1C, "MiiStoreBitFields has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
|
||||
"MiiStoreBitFields is not trivially copyable.");
|
||||
|
||||
struct MiiStoreData {
|
||||
// This corresponds to the above structure MiiStoreBitFields. I did it like this because the
|
||||
// BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
|
||||
// not suitable for our uses.
|
||||
std::array<u8, 0x1C> data;
|
||||
static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
|
||||
|
||||
std::array<char16_t, 10> name;
|
||||
Common::UUID uuid;
|
||||
u16 crc_1;
|
||||
u16 crc_2;
|
||||
|
||||
std::u16string Name() const;
|
||||
};
|
||||
static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
|
||||
|
||||
struct MiiStoreDataElement {
|
||||
MiiStoreData data;
|
||||
Source source;
|
||||
};
|
||||
static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
|
||||
|
||||
struct MiiDatabase {
|
||||
u32 magic; // 'NFDB'
|
||||
std::array<MiiStoreData, MAX_MIIS> miis;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u8 count;
|
||||
u16 crc;
|
||||
};
|
||||
static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
|
||||
#pragma pack(pop)
|
||||
|
||||
// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
|
||||
// with providing an easy interface for HLE emulation of the mii service.
|
||||
class MiiManager {
|
||||
public:
|
||||
MiiManager();
|
||||
~MiiManager();
|
||||
|
||||
MiiInfo CreateRandom(RandomParameters params);
|
||||
MiiInfo CreateDefault(u32 index);
|
||||
|
||||
bool CheckUpdatedFlag() const;
|
||||
void ResetUpdatedFlag();
|
||||
|
||||
bool IsTestModeEnabled() const;
|
||||
|
||||
bool Empty() const;
|
||||
bool Full() const;
|
||||
|
||||
void Clear();
|
||||
|
||||
u32 Size() const;
|
||||
|
||||
MiiInfo GetInfo(u32 index) const;
|
||||
MiiInfoElement GetInfoElement(u32 index) const;
|
||||
MiiStoreData GetStoreData(u32 index) const;
|
||||
MiiStoreDataElement GetStoreDataElement(u32 index) const;
|
||||
|
||||
bool Remove(Common::UUID uuid);
|
||||
u32 IndexOf(Common::UUID uuid) const;
|
||||
u32 IndexOf(const MiiInfo& info) const;
|
||||
|
||||
bool Move(Common::UUID uuid, u32 new_index);
|
||||
bool AddOrReplace(const MiiStoreData& data);
|
||||
|
||||
bool DestroyFile();
|
||||
bool DeleteFile();
|
||||
|
||||
private:
|
||||
void WriteToFile();
|
||||
void ReadFromFile();
|
||||
|
||||
MiiStoreData CreateMiiWithUniqueUUID() const;
|
||||
|
||||
void EnsureDatabasePartition();
|
||||
|
||||
MiiDatabase database;
|
||||
bool updated_flag = false;
|
||||
bool is_test_mode_enabled = false;
|
||||
};
|
||||
|
||||
}; // namespace Service::Mii
|
||||
12
src/core/hle/service/ns/errors.h
Normal file
12
src/core/hle/service/ns/errors.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
constexpr ResultCode ERR_APPLICATION_LANGUAGE_NOT_FOUND{ErrorModule::NS, 300};
|
||||
}
|
||||
392
src/core/hle/service/ns/language.cpp
Normal file
392
src/core/hle/service/ns/language.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_american_english = {{
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_british_english = {{
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_japanese = {{
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_french = {{
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_german = {{
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_latin_american_spanish = {{
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_spanish = {{
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_italian = {{
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_dutch = {{
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_canadian_french = {{
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_portuguese = {{
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_russian = {{
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_korean = {{
|
||||
ApplicationLanguage::Korean,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_traditional_chinese = {{
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
constexpr ApplicationLanguagePriorityList priority_list_simplified_chinese = {{
|
||||
ApplicationLanguage::SimplifiedChinese,
|
||||
ApplicationLanguage::TraditionalChinese,
|
||||
ApplicationLanguage::AmericanEnglish,
|
||||
ApplicationLanguage::BritishEnglish,
|
||||
ApplicationLanguage::Japanese,
|
||||
ApplicationLanguage::LatinAmericanSpanish,
|
||||
ApplicationLanguage::CanadianFrench,
|
||||
ApplicationLanguage::French,
|
||||
ApplicationLanguage::German,
|
||||
ApplicationLanguage::Spanish,
|
||||
ApplicationLanguage::Italian,
|
||||
ApplicationLanguage::Dutch,
|
||||
ApplicationLanguage::Portuguese,
|
||||
ApplicationLanguage::Russian,
|
||||
ApplicationLanguage::Korean,
|
||||
}};
|
||||
|
||||
const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(
|
||||
const ApplicationLanguage lang) {
|
||||
switch (lang) {
|
||||
case ApplicationLanguage::AmericanEnglish:
|
||||
return &priority_list_american_english;
|
||||
case ApplicationLanguage::BritishEnglish:
|
||||
return &priority_list_british_english;
|
||||
case ApplicationLanguage::Japanese:
|
||||
return &priority_list_japanese;
|
||||
case ApplicationLanguage::French:
|
||||
return &priority_list_french;
|
||||
case ApplicationLanguage::German:
|
||||
return &priority_list_german;
|
||||
case ApplicationLanguage::LatinAmericanSpanish:
|
||||
return &priority_list_latin_american_spanish;
|
||||
case ApplicationLanguage::Spanish:
|
||||
return &priority_list_spanish;
|
||||
case ApplicationLanguage::Italian:
|
||||
return &priority_list_italian;
|
||||
case ApplicationLanguage::Dutch:
|
||||
return &priority_list_dutch;
|
||||
case ApplicationLanguage::CanadianFrench:
|
||||
return &priority_list_canadian_french;
|
||||
case ApplicationLanguage::Portuguese:
|
||||
return &priority_list_portuguese;
|
||||
case ApplicationLanguage::Russian:
|
||||
return &priority_list_russian;
|
||||
case ApplicationLanguage::Korean:
|
||||
return &priority_list_korean;
|
||||
case ApplicationLanguage::TraditionalChinese:
|
||||
return &priority_list_traditional_chinese;
|
||||
case ApplicationLanguage::SimplifiedChinese:
|
||||
return &priority_list_simplified_chinese;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ApplicationLanguage> ConvertToApplicationLanguage(
|
||||
const Set::LanguageCode language_code) {
|
||||
switch (language_code) {
|
||||
case Set::LanguageCode::EN_US:
|
||||
return ApplicationLanguage::AmericanEnglish;
|
||||
case Set::LanguageCode::EN_GB:
|
||||
return ApplicationLanguage::BritishEnglish;
|
||||
case Set::LanguageCode::JA:
|
||||
return ApplicationLanguage::Japanese;
|
||||
case Set::LanguageCode::FR:
|
||||
return ApplicationLanguage::French;
|
||||
case Set::LanguageCode::DE:
|
||||
return ApplicationLanguage::German;
|
||||
case Set::LanguageCode::ES_419:
|
||||
return ApplicationLanguage::LatinAmericanSpanish;
|
||||
case Set::LanguageCode::ES:
|
||||
return ApplicationLanguage::Spanish;
|
||||
case Set::LanguageCode::IT:
|
||||
return ApplicationLanguage::Italian;
|
||||
case Set::LanguageCode::NL:
|
||||
return ApplicationLanguage::Dutch;
|
||||
case Set::LanguageCode::FR_CA:
|
||||
return ApplicationLanguage::CanadianFrench;
|
||||
case Set::LanguageCode::PT:
|
||||
return ApplicationLanguage::Portuguese;
|
||||
case Set::LanguageCode::RU:
|
||||
return ApplicationLanguage::Russian;
|
||||
case Set::LanguageCode::KO:
|
||||
return ApplicationLanguage::Korean;
|
||||
case Set::LanguageCode::ZH_HANT:
|
||||
return ApplicationLanguage::TraditionalChinese;
|
||||
case Set::LanguageCode::ZH_HANS:
|
||||
return ApplicationLanguage::SimplifiedChinese;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Set::LanguageCode> ConvertToLanguageCode(const ApplicationLanguage lang) {
|
||||
switch (lang) {
|
||||
case ApplicationLanguage::AmericanEnglish:
|
||||
return Set::LanguageCode::EN_US;
|
||||
case ApplicationLanguage::BritishEnglish:
|
||||
return Set::LanguageCode::EN_GB;
|
||||
case ApplicationLanguage::Japanese:
|
||||
return Set::LanguageCode::JA;
|
||||
case ApplicationLanguage::French:
|
||||
return Set::LanguageCode::FR;
|
||||
case ApplicationLanguage::German:
|
||||
return Set::LanguageCode::DE;
|
||||
case ApplicationLanguage::LatinAmericanSpanish:
|
||||
return Set::LanguageCode::ES_419;
|
||||
case ApplicationLanguage::Spanish:
|
||||
return Set::LanguageCode::ES;
|
||||
case ApplicationLanguage::Italian:
|
||||
return Set::LanguageCode::IT;
|
||||
case ApplicationLanguage::Dutch:
|
||||
return Set::LanguageCode::NL;
|
||||
case ApplicationLanguage::CanadianFrench:
|
||||
return Set::LanguageCode::FR_CA;
|
||||
case ApplicationLanguage::Portuguese:
|
||||
return Set::LanguageCode::PT;
|
||||
case ApplicationLanguage::Russian:
|
||||
return Set::LanguageCode::RU;
|
||||
case ApplicationLanguage::Korean:
|
||||
return Set::LanguageCode::KO;
|
||||
case ApplicationLanguage::TraditionalChinese:
|
||||
return Set::LanguageCode::ZH_HANT;
|
||||
case ApplicationLanguage::SimplifiedChinese:
|
||||
return Set::LanguageCode::ZH_HANS;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
} // namespace Service::NS
|
||||
45
src/core/hle/service/ns/language.h
Normal file
45
src/core/hle/service/ns/language.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::Set {
|
||||
enum class LanguageCode : u64;
|
||||
}
|
||||
|
||||
namespace Service::NS {
|
||||
/// This is nn::ns::detail::ApplicationLanguage
|
||||
enum class ApplicationLanguage : u8 {
|
||||
AmericanEnglish = 0,
|
||||
BritishEnglish,
|
||||
Japanese,
|
||||
French,
|
||||
German,
|
||||
LatinAmericanSpanish,
|
||||
Spanish,
|
||||
Italian,
|
||||
Dutch,
|
||||
CanadianFrench,
|
||||
Portuguese,
|
||||
Russian,
|
||||
Korean,
|
||||
TraditionalChinese,
|
||||
SimplifiedChinese,
|
||||
Count
|
||||
};
|
||||
using ApplicationLanguagePriorityList =
|
||||
const std::array<ApplicationLanguage, static_cast<std::size_t>(ApplicationLanguage::Count)>;
|
||||
|
||||
constexpr u32 GetSupportedLanguageFlag(const ApplicationLanguage lang) {
|
||||
return 1U << static_cast<u32>(lang);
|
||||
}
|
||||
|
||||
const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(ApplicationLanguage lang);
|
||||
std::optional<ApplicationLanguage> ConvertToApplicationLanguage(Set::LanguageCode language_code);
|
||||
std::optional<Set::LanguageCode> ConvertToLanguageCode(ApplicationLanguage lang);
|
||||
} // namespace Service::NS
|
||||
@@ -7,445 +7,507 @@
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/ns/errors.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/ns/ns.h"
|
||||
#include "core/hle/service/ns/pl_u.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
|
||||
public:
|
||||
explicit IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateUserAccount"},
|
||||
};
|
||||
// clang-format on
|
||||
IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateUserAccount"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
|
||||
public:
|
||||
explicit IApplicationManagerInterface() : ServiceFramework{"IApplicationManagerInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "ListApplicationRecord"},
|
||||
{1, nullptr, "GenerateApplicationRecordCount"},
|
||||
{2, nullptr, "GetApplicationRecordUpdateSystemEvent"},
|
||||
{3, nullptr, "GetApplicationViewDeprecated"},
|
||||
{4, nullptr, "DeleteApplicationEntity"},
|
||||
{5, nullptr, "DeleteApplicationCompletely"},
|
||||
{6, nullptr, "IsAnyApplicationEntityRedundant"},
|
||||
{7, nullptr, "DeleteRedundantApplicationEntity"},
|
||||
{8, nullptr, "IsApplicationEntityMovable"},
|
||||
{9, nullptr, "MoveApplicationEntity"},
|
||||
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
||||
{16, nullptr, "PushApplicationRecord"},
|
||||
{17, nullptr, "ListApplicationRecordContentMeta"},
|
||||
{19, nullptr, "LaunchApplicationOld"},
|
||||
{21, nullptr, "GetApplicationContentPath"},
|
||||
{22, nullptr, "TerminateApplication"},
|
||||
{23, nullptr, "ResolveApplicationContentPath"},
|
||||
{26, nullptr, "BeginInstallApplication"},
|
||||
{27, nullptr, "DeleteApplicationRecord"},
|
||||
{30, nullptr, "RequestApplicationUpdateInfo"},
|
||||
{32, nullptr, "CancelApplicationDownload"},
|
||||
{33, nullptr, "ResumeApplicationDownload"},
|
||||
{35, nullptr, "UpdateVersionList"},
|
||||
{36, nullptr, "PushLaunchVersion"},
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{38, nullptr, "CheckApplicationLaunchVersion"},
|
||||
{39, nullptr, "CheckApplicationLaunchRights"},
|
||||
{40, nullptr, "GetApplicationLogoData"},
|
||||
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
|
||||
{42, nullptr, "CleanupSdCard"},
|
||||
{43, nullptr, "CheckSdCardMountStatus"},
|
||||
{44, nullptr, "GetSdCardMountStatusChangedEvent"},
|
||||
{45, nullptr, "GetGameCardAttachmentEvent"},
|
||||
{46, nullptr, "GetGameCardAttachmentInfo"},
|
||||
{47, nullptr, "GetTotalSpaceSize"},
|
||||
{48, nullptr, "GetFreeSpaceSize"},
|
||||
{49, nullptr, "GetSdCardRemovedEvent"},
|
||||
{52, nullptr, "GetGameCardUpdateDetectionEvent"},
|
||||
{53, nullptr, "DisableApplicationAutoDelete"},
|
||||
{54, nullptr, "EnableApplicationAutoDelete"},
|
||||
{55, nullptr, "GetApplicationDesiredLanguage"},
|
||||
{56, nullptr, "SetApplicationTerminateResult"},
|
||||
{57, nullptr, "ClearApplicationTerminateResult"},
|
||||
{58, nullptr, "GetLastSdCardMountUnexpectedResult"},
|
||||
{59, nullptr, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
{61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
|
||||
{62, nullptr, "GetGameCardStopper"},
|
||||
{63, nullptr, "IsSystemProgramInstalled"},
|
||||
{64, nullptr, "StartApplyDeltaTask"},
|
||||
{65, nullptr, "GetRequestServerStopper"},
|
||||
{66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
|
||||
{67, nullptr, "CancelApplicationApplyDelta"},
|
||||
{68, nullptr, "ResumeApplicationApplyDelta"},
|
||||
{69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
|
||||
{70, nullptr, "ResumeAll"},
|
||||
{71, nullptr, "GetStorageSize"},
|
||||
{80, nullptr, "RequestDownloadApplication"},
|
||||
{81, nullptr, "RequestDownloadAddOnContent"},
|
||||
{82, nullptr, "DownloadApplication"},
|
||||
{83, nullptr, "CheckApplicationResumeRights"},
|
||||
{84, nullptr, "GetDynamicCommitEvent"},
|
||||
{85, nullptr, "RequestUpdateApplication2"},
|
||||
{86, nullptr, "EnableApplicationCrashReport"},
|
||||
{87, nullptr, "IsApplicationCrashReportEnabled"},
|
||||
{90, nullptr, "BoostSystemMemoryResourceLimit"},
|
||||
{91, nullptr, "DeprecatedLaunchApplication"},
|
||||
{92, nullptr, "GetRunningApplicationProgramId"},
|
||||
{93, nullptr, "GetMainApplicationProgramIndex"},
|
||||
{94, nullptr, "LaunchApplication"},
|
||||
{95, nullptr, "GetApplicationLaunchInfo"},
|
||||
{96, nullptr, "AcquireApplicationLaunchInfo"},
|
||||
{97, nullptr, "GetMainApplicationProgramIndex2"},
|
||||
{98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
|
||||
{100, nullptr, "ResetToFactorySettings"},
|
||||
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
|
||||
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
|
||||
{200, nullptr, "CalculateUserSaveDataStatistics"},
|
||||
{201, nullptr, "DeleteUserSaveDataAll"},
|
||||
{210, nullptr, "DeleteUserSystemSaveData"},
|
||||
{211, nullptr, "DeleteSaveData"},
|
||||
{220, nullptr, "UnregisterNetworkServiceAccount"},
|
||||
{221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
|
||||
{300, nullptr, "GetApplicationShellEvent"},
|
||||
{301, nullptr, "PopApplicationShellEventInfo"},
|
||||
{302, nullptr, "LaunchLibraryApplet"},
|
||||
{303, nullptr, "TerminateLibraryApplet"},
|
||||
{304, nullptr, "LaunchSystemApplet"},
|
||||
{305, nullptr, "TerminateSystemApplet"},
|
||||
{306, nullptr, "LaunchOverlayApplet"},
|
||||
{307, nullptr, "TerminateOverlayApplet"},
|
||||
{400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
|
||||
{401, nullptr, "InvalidateAllApplicationControlCache"},
|
||||
{402, nullptr, "RequestDownloadApplicationControlData"},
|
||||
{403, nullptr, "GetMaxApplicationControlCacheCount"},
|
||||
{404, nullptr, "InvalidateApplicationControlCache"},
|
||||
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
|
||||
{406, nullptr, "GetApplicationControlProperty"},
|
||||
{502, nullptr, "RequestCheckGameCardRegistration"},
|
||||
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
|
||||
{504, nullptr, "RequestRegisterGameCard"},
|
||||
{505, nullptr, "GetGameCardMountFailureEvent"},
|
||||
{506, nullptr, "IsGameCardInserted"},
|
||||
{507, nullptr, "EnsureGameCardAccess"},
|
||||
{508, nullptr, "GetLastGameCardMountFailureResult"},
|
||||
{509, nullptr, "ListApplicationIdOnGameCard"},
|
||||
{600, nullptr, "CountApplicationContentMeta"},
|
||||
{601, nullptr, "ListApplicationContentMetaStatus"},
|
||||
{602, nullptr, "ListAvailableAddOnContent"},
|
||||
{603, nullptr, "GetOwnedApplicationContentMetaStatus"},
|
||||
{604, nullptr, "RegisterContentsExternalKey"},
|
||||
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
|
||||
{606, nullptr, "GetContentMetaStorage"},
|
||||
{607, nullptr, "ListAvailableAddOnContent"},
|
||||
{700, nullptr, "PushDownloadTaskList"},
|
||||
{701, nullptr, "ClearTaskStatusList"},
|
||||
{702, nullptr, "RequestDownloadTaskList"},
|
||||
{703, nullptr, "RequestEnsureDownloadTask"},
|
||||
{704, nullptr, "ListDownloadTaskStatus"},
|
||||
{705, nullptr, "RequestDownloadTaskListData"},
|
||||
{800, nullptr, "RequestVersionList"},
|
||||
{801, nullptr, "ListVersionList"},
|
||||
{802, nullptr, "RequestVersionListData"},
|
||||
{900, nullptr, "GetApplicationRecord"},
|
||||
{901, nullptr, "GetApplicationRecordProperty"},
|
||||
{902, nullptr, "EnableApplicationAutoUpdate"},
|
||||
{903, nullptr, "DisableApplicationAutoUpdate"},
|
||||
{904, nullptr, "TouchApplication"},
|
||||
{905, nullptr, "RequestApplicationUpdate"},
|
||||
{906, nullptr, "IsApplicationUpdateRequested"},
|
||||
{907, nullptr, "WithdrawApplicationUpdateRequest"},
|
||||
{908, nullptr, "ListApplicationRecordInstalledContentMeta"},
|
||||
{909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
|
||||
{910, nullptr, "HasApplicationRecord"},
|
||||
{911, nullptr, "SetPreInstalledApplication"},
|
||||
{912, nullptr, "ClearPreInstalledApplicationFlag"},
|
||||
{1000, nullptr, "RequestVerifyApplicationDeprecated"},
|
||||
{1001, nullptr, "CorruptApplicationForDebug"},
|
||||
{1002, nullptr, "RequestVerifyAddOnContentsRights"},
|
||||
{1003, nullptr, "RequestVerifyApplication"},
|
||||
{1004, nullptr, "CorruptContentForDebug"},
|
||||
{1200, nullptr, "NeedsUpdateVulnerability"},
|
||||
{1300, nullptr, "IsAnyApplicationEntityInstalled"},
|
||||
{1301, nullptr, "DeleteApplicationContentEntities"},
|
||||
{1302, nullptr, "CleanupUnrecordedApplicationEntity"},
|
||||
{1303, nullptr, "CleanupAddOnContentsWithNoRights"},
|
||||
{1304, nullptr, "DeleteApplicationContentEntity"},
|
||||
{1305, nullptr, "TryDeleteRunningApplicationEntity"},
|
||||
{1306, nullptr, "TryDeleteRunningApplicationCompletely"},
|
||||
{1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
|
||||
{1308, nullptr, "DeleteApplicationCompletelyForDebug"},
|
||||
{1309, nullptr, "CleanupUnavailableAddOnContents"},
|
||||
{1400, nullptr, "PrepareShutdown"},
|
||||
{1500, nullptr, "FormatSdCard"},
|
||||
{1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
|
||||
{1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
|
||||
{1504, nullptr, "InsertSdCard"},
|
||||
{1505, nullptr, "RemoveSdCard"},
|
||||
{1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
|
||||
{1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
|
||||
{1700, nullptr, "ListApplicationDownloadingContentMeta"},
|
||||
{1701, nullptr, "GetApplicationView"},
|
||||
{1702, nullptr, "GetApplicationDownloadTaskStatus"},
|
||||
{1703, nullptr, "GetApplicationViewDownloadErrorContext"},
|
||||
{1800, nullptr, "IsNotificationSetupCompleted"},
|
||||
{1801, nullptr, "GetLastNotificationInfoCount"},
|
||||
{1802, nullptr, "ListLastNotificationInfo"},
|
||||
{1803, nullptr, "ListNotificationTask"},
|
||||
{1900, nullptr, "IsActiveAccount"},
|
||||
{1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
|
||||
{1902, nullptr, "GetApplicationTicketInfo"},
|
||||
{2000, nullptr, "GetSystemDeliveryInfo"},
|
||||
{2001, nullptr, "SelectLatestSystemDeliveryInfo"},
|
||||
{2002, nullptr, "VerifyDeliveryProtocolVersion"},
|
||||
{2003, nullptr, "GetApplicationDeliveryInfo"},
|
||||
{2004, nullptr, "HasAllContentsToDeliver"},
|
||||
{2005, nullptr, "CompareApplicationDeliveryInfo"},
|
||||
{2006, nullptr, "CanDeliverApplication"},
|
||||
{2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
|
||||
{2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
|
||||
{2009, nullptr, "EstimateRequiredSize"},
|
||||
{2010, nullptr, "RequestReceiveApplication"},
|
||||
{2011, nullptr, "CommitReceiveApplication"},
|
||||
{2012, nullptr, "GetReceiveApplicationProgress"},
|
||||
{2013, nullptr, "RequestSendApplication"},
|
||||
{2014, nullptr, "GetSendApplicationProgress"},
|
||||
{2015, nullptr, "CompareSystemDeliveryInfo"},
|
||||
{2016, nullptr, "ListNotCommittedContentMeta"},
|
||||
{2017, nullptr, "CreateDownloadTask"},
|
||||
{2018, nullptr, "GetApplicationDeliveryInfoHash"},
|
||||
{2050, nullptr, "GetApplicationRightsOnClient"},
|
||||
{2100, nullptr, "GetApplicationTerminateResult"},
|
||||
{2101, nullptr, "GetRawApplicationTerminateResult"},
|
||||
{2150, nullptr, "CreateRightsEnvironment"},
|
||||
{2151, nullptr, "DestroyRightsEnvironment"},
|
||||
{2152, nullptr, "ActivateRightsEnvironment"},
|
||||
{2153, nullptr, "DeactivateRightsEnvironment"},
|
||||
{2154, nullptr, "ForceActivateRightsContextForExit"},
|
||||
{2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
|
||||
{2161, nullptr, "SetUsersToRightsEnvironment"},
|
||||
{2170, nullptr, "GetRightsEnvironmentStatus"},
|
||||
{2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
|
||||
{2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
|
||||
{2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
|
||||
{2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
|
||||
{2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
|
||||
{2199, nullptr, "GetRightsEnvironmentCountForDebug"},
|
||||
{2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
|
||||
{2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
|
||||
{2250, nullptr, "RequestReportActiveELicence"},
|
||||
{2300, nullptr, "ListEventLog"},
|
||||
};
|
||||
// clang-format on
|
||||
IAccountProxyInterface::~IAccountProxyInterface() = default;
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
IApplicationManagerInterface::IApplicationManagerInterface()
|
||||
: ServiceFramework{"IApplicationManagerInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "ListApplicationRecord"},
|
||||
{1, nullptr, "GenerateApplicationRecordCount"},
|
||||
{2, nullptr, "GetApplicationRecordUpdateSystemEvent"},
|
||||
{3, nullptr, "GetApplicationViewDeprecated"},
|
||||
{4, nullptr, "DeleteApplicationEntity"},
|
||||
{5, nullptr, "DeleteApplicationCompletely"},
|
||||
{6, nullptr, "IsAnyApplicationEntityRedundant"},
|
||||
{7, nullptr, "DeleteRedundantApplicationEntity"},
|
||||
{8, nullptr, "IsApplicationEntityMovable"},
|
||||
{9, nullptr, "MoveApplicationEntity"},
|
||||
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
||||
{16, nullptr, "PushApplicationRecord"},
|
||||
{17, nullptr, "ListApplicationRecordContentMeta"},
|
||||
{19, nullptr, "LaunchApplicationOld"},
|
||||
{21, nullptr, "GetApplicationContentPath"},
|
||||
{22, nullptr, "TerminateApplication"},
|
||||
{23, nullptr, "ResolveApplicationContentPath"},
|
||||
{26, nullptr, "BeginInstallApplication"},
|
||||
{27, nullptr, "DeleteApplicationRecord"},
|
||||
{30, nullptr, "RequestApplicationUpdateInfo"},
|
||||
{32, nullptr, "CancelApplicationDownload"},
|
||||
{33, nullptr, "ResumeApplicationDownload"},
|
||||
{35, nullptr, "UpdateVersionList"},
|
||||
{36, nullptr, "PushLaunchVersion"},
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{38, nullptr, "CheckApplicationLaunchVersion"},
|
||||
{39, nullptr, "CheckApplicationLaunchRights"},
|
||||
{40, nullptr, "GetApplicationLogoData"},
|
||||
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
|
||||
{42, nullptr, "CleanupSdCard"},
|
||||
{43, nullptr, "CheckSdCardMountStatus"},
|
||||
{44, nullptr, "GetSdCardMountStatusChangedEvent"},
|
||||
{45, nullptr, "GetGameCardAttachmentEvent"},
|
||||
{46, nullptr, "GetGameCardAttachmentInfo"},
|
||||
{47, nullptr, "GetTotalSpaceSize"},
|
||||
{48, nullptr, "GetFreeSpaceSize"},
|
||||
{49, nullptr, "GetSdCardRemovedEvent"},
|
||||
{52, nullptr, "GetGameCardUpdateDetectionEvent"},
|
||||
{53, nullptr, "DisableApplicationAutoDelete"},
|
||||
{54, nullptr, "EnableApplicationAutoDelete"},
|
||||
{55, &IApplicationManagerInterface::GetApplicationDesiredLanguage, "GetApplicationDesiredLanguage"},
|
||||
{56, nullptr, "SetApplicationTerminateResult"},
|
||||
{57, nullptr, "ClearApplicationTerminateResult"},
|
||||
{58, nullptr, "GetLastSdCardMountUnexpectedResult"},
|
||||
{59, &IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
{61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
|
||||
{62, nullptr, "GetGameCardStopper"},
|
||||
{63, nullptr, "IsSystemProgramInstalled"},
|
||||
{64, nullptr, "StartApplyDeltaTask"},
|
||||
{65, nullptr, "GetRequestServerStopper"},
|
||||
{66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
|
||||
{67, nullptr, "CancelApplicationApplyDelta"},
|
||||
{68, nullptr, "ResumeApplicationApplyDelta"},
|
||||
{69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
|
||||
{70, nullptr, "ResumeAll"},
|
||||
{71, nullptr, "GetStorageSize"},
|
||||
{80, nullptr, "RequestDownloadApplication"},
|
||||
{81, nullptr, "RequestDownloadAddOnContent"},
|
||||
{82, nullptr, "DownloadApplication"},
|
||||
{83, nullptr, "CheckApplicationResumeRights"},
|
||||
{84, nullptr, "GetDynamicCommitEvent"},
|
||||
{85, nullptr, "RequestUpdateApplication2"},
|
||||
{86, nullptr, "EnableApplicationCrashReport"},
|
||||
{87, nullptr, "IsApplicationCrashReportEnabled"},
|
||||
{90, nullptr, "BoostSystemMemoryResourceLimit"},
|
||||
{91, nullptr, "DeprecatedLaunchApplication"},
|
||||
{92, nullptr, "GetRunningApplicationProgramId"},
|
||||
{93, nullptr, "GetMainApplicationProgramIndex"},
|
||||
{94, nullptr, "LaunchApplication"},
|
||||
{95, nullptr, "GetApplicationLaunchInfo"},
|
||||
{96, nullptr, "AcquireApplicationLaunchInfo"},
|
||||
{97, nullptr, "GetMainApplicationProgramIndex2"},
|
||||
{98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
|
||||
{100, nullptr, "ResetToFactorySettings"},
|
||||
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
|
||||
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
|
||||
{200, nullptr, "CalculateUserSaveDataStatistics"},
|
||||
{201, nullptr, "DeleteUserSaveDataAll"},
|
||||
{210, nullptr, "DeleteUserSystemSaveData"},
|
||||
{211, nullptr, "DeleteSaveData"},
|
||||
{220, nullptr, "UnregisterNetworkServiceAccount"},
|
||||
{221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
|
||||
{300, nullptr, "GetApplicationShellEvent"},
|
||||
{301, nullptr, "PopApplicationShellEventInfo"},
|
||||
{302, nullptr, "LaunchLibraryApplet"},
|
||||
{303, nullptr, "TerminateLibraryApplet"},
|
||||
{304, nullptr, "LaunchSystemApplet"},
|
||||
{305, nullptr, "TerminateSystemApplet"},
|
||||
{306, nullptr, "LaunchOverlayApplet"},
|
||||
{307, nullptr, "TerminateOverlayApplet"},
|
||||
{400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
|
||||
{401, nullptr, "InvalidateAllApplicationControlCache"},
|
||||
{402, nullptr, "RequestDownloadApplicationControlData"},
|
||||
{403, nullptr, "GetMaxApplicationControlCacheCount"},
|
||||
{404, nullptr, "InvalidateApplicationControlCache"},
|
||||
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
|
||||
{406, nullptr, "GetApplicationControlProperty"},
|
||||
{502, nullptr, "RequestCheckGameCardRegistration"},
|
||||
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
|
||||
{504, nullptr, "RequestRegisterGameCard"},
|
||||
{505, nullptr, "GetGameCardMountFailureEvent"},
|
||||
{506, nullptr, "IsGameCardInserted"},
|
||||
{507, nullptr, "EnsureGameCardAccess"},
|
||||
{508, nullptr, "GetLastGameCardMountFailureResult"},
|
||||
{509, nullptr, "ListApplicationIdOnGameCard"},
|
||||
{600, nullptr, "CountApplicationContentMeta"},
|
||||
{601, nullptr, "ListApplicationContentMetaStatus"},
|
||||
{602, nullptr, "ListAvailableAddOnContent"},
|
||||
{603, nullptr, "GetOwnedApplicationContentMetaStatus"},
|
||||
{604, nullptr, "RegisterContentsExternalKey"},
|
||||
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
|
||||
{606, nullptr, "GetContentMetaStorage"},
|
||||
{607, nullptr, "ListAvailableAddOnContent"},
|
||||
{700, nullptr, "PushDownloadTaskList"},
|
||||
{701, nullptr, "ClearTaskStatusList"},
|
||||
{702, nullptr, "RequestDownloadTaskList"},
|
||||
{703, nullptr, "RequestEnsureDownloadTask"},
|
||||
{704, nullptr, "ListDownloadTaskStatus"},
|
||||
{705, nullptr, "RequestDownloadTaskListData"},
|
||||
{800, nullptr, "RequestVersionList"},
|
||||
{801, nullptr, "ListVersionList"},
|
||||
{802, nullptr, "RequestVersionListData"},
|
||||
{900, nullptr, "GetApplicationRecord"},
|
||||
{901, nullptr, "GetApplicationRecordProperty"},
|
||||
{902, nullptr, "EnableApplicationAutoUpdate"},
|
||||
{903, nullptr, "DisableApplicationAutoUpdate"},
|
||||
{904, nullptr, "TouchApplication"},
|
||||
{905, nullptr, "RequestApplicationUpdate"},
|
||||
{906, nullptr, "IsApplicationUpdateRequested"},
|
||||
{907, nullptr, "WithdrawApplicationUpdateRequest"},
|
||||
{908, nullptr, "ListApplicationRecordInstalledContentMeta"},
|
||||
{909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
|
||||
{910, nullptr, "HasApplicationRecord"},
|
||||
{911, nullptr, "SetPreInstalledApplication"},
|
||||
{912, nullptr, "ClearPreInstalledApplicationFlag"},
|
||||
{1000, nullptr, "RequestVerifyApplicationDeprecated"},
|
||||
{1001, nullptr, "CorruptApplicationForDebug"},
|
||||
{1002, nullptr, "RequestVerifyAddOnContentsRights"},
|
||||
{1003, nullptr, "RequestVerifyApplication"},
|
||||
{1004, nullptr, "CorruptContentForDebug"},
|
||||
{1200, nullptr, "NeedsUpdateVulnerability"},
|
||||
{1300, nullptr, "IsAnyApplicationEntityInstalled"},
|
||||
{1301, nullptr, "DeleteApplicationContentEntities"},
|
||||
{1302, nullptr, "CleanupUnrecordedApplicationEntity"},
|
||||
{1303, nullptr, "CleanupAddOnContentsWithNoRights"},
|
||||
{1304, nullptr, "DeleteApplicationContentEntity"},
|
||||
{1305, nullptr, "TryDeleteRunningApplicationEntity"},
|
||||
{1306, nullptr, "TryDeleteRunningApplicationCompletely"},
|
||||
{1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
|
||||
{1308, nullptr, "DeleteApplicationCompletelyForDebug"},
|
||||
{1309, nullptr, "CleanupUnavailableAddOnContents"},
|
||||
{1400, nullptr, "PrepareShutdown"},
|
||||
{1500, nullptr, "FormatSdCard"},
|
||||
{1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
|
||||
{1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
|
||||
{1504, nullptr, "InsertSdCard"},
|
||||
{1505, nullptr, "RemoveSdCard"},
|
||||
{1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
|
||||
{1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
|
||||
{1700, nullptr, "ListApplicationDownloadingContentMeta"},
|
||||
{1701, nullptr, "GetApplicationView"},
|
||||
{1702, nullptr, "GetApplicationDownloadTaskStatus"},
|
||||
{1703, nullptr, "GetApplicationViewDownloadErrorContext"},
|
||||
{1800, nullptr, "IsNotificationSetupCompleted"},
|
||||
{1801, nullptr, "GetLastNotificationInfoCount"},
|
||||
{1802, nullptr, "ListLastNotificationInfo"},
|
||||
{1803, nullptr, "ListNotificationTask"},
|
||||
{1900, nullptr, "IsActiveAccount"},
|
||||
{1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
|
||||
{1902, nullptr, "GetApplicationTicketInfo"},
|
||||
{2000, nullptr, "GetSystemDeliveryInfo"},
|
||||
{2001, nullptr, "SelectLatestSystemDeliveryInfo"},
|
||||
{2002, nullptr, "VerifyDeliveryProtocolVersion"},
|
||||
{2003, nullptr, "GetApplicationDeliveryInfo"},
|
||||
{2004, nullptr, "HasAllContentsToDeliver"},
|
||||
{2005, nullptr, "CompareApplicationDeliveryInfo"},
|
||||
{2006, nullptr, "CanDeliverApplication"},
|
||||
{2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
|
||||
{2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
|
||||
{2009, nullptr, "EstimateRequiredSize"},
|
||||
{2010, nullptr, "RequestReceiveApplication"},
|
||||
{2011, nullptr, "CommitReceiveApplication"},
|
||||
{2012, nullptr, "GetReceiveApplicationProgress"},
|
||||
{2013, nullptr, "RequestSendApplication"},
|
||||
{2014, nullptr, "GetSendApplicationProgress"},
|
||||
{2015, nullptr, "CompareSystemDeliveryInfo"},
|
||||
{2016, nullptr, "ListNotCommittedContentMeta"},
|
||||
{2017, nullptr, "CreateDownloadTask"},
|
||||
{2018, nullptr, "GetApplicationDeliveryInfoHash"},
|
||||
{2050, nullptr, "GetApplicationRightsOnClient"},
|
||||
{2100, nullptr, "GetApplicationTerminateResult"},
|
||||
{2101, nullptr, "GetRawApplicationTerminateResult"},
|
||||
{2150, nullptr, "CreateRightsEnvironment"},
|
||||
{2151, nullptr, "DestroyRightsEnvironment"},
|
||||
{2152, nullptr, "ActivateRightsEnvironment"},
|
||||
{2153, nullptr, "DeactivateRightsEnvironment"},
|
||||
{2154, nullptr, "ForceActivateRightsContextForExit"},
|
||||
{2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
|
||||
{2161, nullptr, "SetUsersToRightsEnvironment"},
|
||||
{2170, nullptr, "GetRightsEnvironmentStatus"},
|
||||
{2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
|
||||
{2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
|
||||
{2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
|
||||
{2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
|
||||
{2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
|
||||
{2199, nullptr, "GetRightsEnvironmentCountForDebug"},
|
||||
{2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
|
||||
{2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
|
||||
{2250, nullptr, "RequestReportActiveELicence"},
|
||||
{2300, nullptr, "ListEventLog"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
void GetApplicationControlData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto flag = rp.PopRaw<u64>();
|
||||
LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
IApplicationManagerInterface::~IApplicationManagerInterface() = default;
|
||||
|
||||
const auto size = ctx.GetWriteBufferSize();
|
||||
void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto flag = rp.PopRaw<u64>();
|
||||
LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
|
||||
|
||||
const FileSys::PatchManager pm{title_id};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
std::vector<u8> out;
|
||||
const auto size = ctx.GetWriteBufferSize();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
if (size < 0x4000) {
|
||||
LOG_ERROR(Service_NS,
|
||||
"output buffer is too small! (actual={:016X}, expected_min=0x4000)",
|
||||
size);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code for this.
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
const FileSys::PatchManager pm{title_id};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
out.resize(0x4000);
|
||||
const auto bytes = control.first->GetRawBytes();
|
||||
std::memcpy(out.data(), bytes.data(), bytes.size());
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
|
||||
title_id);
|
||||
out.resize(std::min<u64>(0x4000, size));
|
||||
std::vector<u8> out;
|
||||
|
||||
if (control.first != nullptr) {
|
||||
if (size < 0x4000) {
|
||||
LOG_ERROR(Service_NS,
|
||||
"output buffer is too small! (actual={:016X}, expected_min=0x4000)", size);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code for this.
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (control.second != nullptr) {
|
||||
if (size < 0x4000 + control.second->GetSize()) {
|
||||
LOG_ERROR(Service_NS,
|
||||
"output buffer is too small! (actual={:016X}, expected_min={:016X})",
|
||||
size, 0x4000 + control.second->GetSize());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code for this.
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
out.resize(0x4000);
|
||||
const auto bytes = control.first->GetRawBytes();
|
||||
std::memcpy(out.data(), bytes.data(), bytes.size());
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
|
||||
title_id);
|
||||
out.resize(std::min<u64>(0x4000, size));
|
||||
}
|
||||
|
||||
out.resize(0x4000 + control.second->GetSize());
|
||||
control.second->Read(out.data() + 0x4000, control.second->GetSize());
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
|
||||
title_id);
|
||||
if (control.second != nullptr) {
|
||||
if (size < 0x4000 + control.second->GetSize()) {
|
||||
LOG_ERROR(Service_NS,
|
||||
"output buffer is too small! (actual={:016X}, expected_min={:016X})", size,
|
||||
0x4000 + control.second->GetSize());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find a better error code for this.
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(out);
|
||||
out.resize(0x4000 + control.second->GetSize());
|
||||
control.second->Read(out.data() + 0x4000, control.second->GetSize());
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
|
||||
title_id);
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(out);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(static_cast<u32>(out.size()));
|
||||
}
|
||||
|
||||
void IApplicationManagerInterface::GetApplicationDesiredLanguage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto supported_languages = rp.Pop<u32>();
|
||||
|
||||
const auto res = GetApplicationDesiredLanguage(supported_languages);
|
||||
if (res.Succeeded()) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(static_cast<u32>(out.size()));
|
||||
rb.Push<u32>(*res);
|
||||
} else {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res.Code());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
|
||||
public:
|
||||
explicit IApplicationVersionInterface() : ServiceFramework{"IApplicationVersionInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetLaunchRequiredVersion"},
|
||||
{1, nullptr, "UpgradeLaunchRequiredVersion"},
|
||||
{35, nullptr, "UpdateVersionList"},
|
||||
{36, nullptr, "PushLaunchVersion"},
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{800, nullptr, "RequestVersionList"},
|
||||
{801, nullptr, "ListVersionList"},
|
||||
{802, nullptr, "RequestVersionListData"},
|
||||
{1000, nullptr, "PerformAutoUpdate"},
|
||||
};
|
||||
// clang-format on
|
||||
ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
|
||||
const u32 supported_languages) {
|
||||
LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
|
||||
|
||||
RegisterHandlers(functions);
|
||||
// Get language code from settings
|
||||
const auto language_code = Set::GetLanguageCodeFromIndex(Settings::values.language_index);
|
||||
|
||||
// Convert to application language, get priority list
|
||||
const auto application_language = ConvertToApplicationLanguage(language_code);
|
||||
if (application_language == std::nullopt) {
|
||||
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
|
||||
}
|
||||
};
|
||||
|
||||
class IContentManagerInterface final : public ServiceFramework<IContentManagerInterface> {
|
||||
public:
|
||||
explicit IContentManagerInterface() : ServiceFramework{"IContentManagerInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
||||
{43, nullptr, "CheckSdCardMountStatus"},
|
||||
{47, nullptr, "GetTotalSpaceSize"},
|
||||
{48, nullptr, "GetFreeSpaceSize"},
|
||||
{600, nullptr, "CountApplicationContentMeta"},
|
||||
{601, nullptr, "ListApplicationContentMetaStatus"},
|
||||
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
|
||||
{607, nullptr, "IsAnyApplicationRunning"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
|
||||
if (!priority_list) {
|
||||
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
|
||||
}
|
||||
};
|
||||
|
||||
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
|
||||
public:
|
||||
explicit IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{21, nullptr, "GetApplicationContentPath"},
|
||||
{23, nullptr, "ResolveApplicationContentPath"},
|
||||
{93, nullptr, "GetRunningApplicationProgramId"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
// Try to find a valid language.
|
||||
for (const auto lang : *priority_list) {
|
||||
const auto supported_flag = GetSupportedLanguageFlag(lang);
|
||||
if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
|
||||
return MakeResult(static_cast<u8>(lang));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
|
||||
public:
|
||||
explicit IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{701, nullptr, "ClearTaskStatusList"},
|
||||
{702, nullptr, "RequestDownloadTaskList"},
|
||||
{703, nullptr, "RequestEnsureDownloadTask"},
|
||||
{704, nullptr, "ListDownloadTaskStatus"},
|
||||
{705, nullptr, "RequestDownloadTaskListData"},
|
||||
{706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
|
||||
{707, nullptr, "EnableAutoCommit"},
|
||||
{708, nullptr, "DisableAutoCommit"},
|
||||
{709, nullptr, "TriggerDynamicCommitEvent"},
|
||||
};
|
||||
// clang-format on
|
||||
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
|
||||
}
|
||||
|
||||
RegisterHandlers(functions);
|
||||
void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto application_language = rp.Pop<u8>();
|
||||
|
||||
const auto res = ConvertApplicationLanguageToLanguageCode(application_language);
|
||||
if (res.Succeeded()) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(*res);
|
||||
} else {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res.Code());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
|
||||
public:
|
||||
explicit IECommerceInterface() : ServiceFramework{"IECommerceInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestLinkDevice"},
|
||||
{1, nullptr, "RequestCleanupAllPreInstalledApplications"},
|
||||
{2, nullptr, "RequestCleanupPreInstalledApplication"},
|
||||
{3, nullptr, "RequestSyncRights"},
|
||||
{4, nullptr, "RequestUnlinkDevice"},
|
||||
{5, nullptr, "RequestRevokeAllELicense"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
|
||||
u8 application_language) {
|
||||
const auto language_code =
|
||||
ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
|
||||
if (language_code == std::nullopt) {
|
||||
return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
|
||||
}
|
||||
};
|
||||
|
||||
class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
|
||||
public:
|
||||
explicit IFactoryResetInterface() : ServiceFramework{"IFactoryResetInterface"} {
|
||||
// clang-format off
|
||||
return MakeResult(static_cast<u64>(*language_code));
|
||||
}
|
||||
|
||||
IApplicationVersionInterface::IApplicationVersionInterface()
|
||||
: ServiceFramework{"IApplicationVersionInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetLaunchRequiredVersion"},
|
||||
{1, nullptr, "UpgradeLaunchRequiredVersion"},
|
||||
{35, nullptr, "UpdateVersionList"},
|
||||
{36, nullptr, "PushLaunchVersion"},
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{800, nullptr, "RequestVersionList"},
|
||||
{801, nullptr, "ListVersionList"},
|
||||
{802, nullptr, "RequestVersionListData"},
|
||||
{1000, nullptr, "PerformAutoUpdate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IApplicationVersionInterface::~IApplicationVersionInterface() = default;
|
||||
|
||||
IContentManagerInterface::IContentManagerInterface()
|
||||
: ServiceFramework{"IContentManagerInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
||||
{43, nullptr, "CheckSdCardMountStatus"},
|
||||
{47, nullptr, "GetTotalSpaceSize"},
|
||||
{48, nullptr, "GetFreeSpaceSize"},
|
||||
{600, nullptr, "CountApplicationContentMeta"},
|
||||
{601, nullptr, "ListApplicationContentMetaStatus"},
|
||||
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
|
||||
{607, nullptr, "IsAnyApplicationRunning"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IContentManagerInterface::~IContentManagerInterface() = default;
|
||||
|
||||
IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{21, nullptr, "GetApplicationContentPath"},
|
||||
{23, nullptr, "ResolveApplicationContentPath"},
|
||||
{93, nullptr, "GetRunningApplicationProgramId"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDocumentInterface::~IDocumentInterface() = default;
|
||||
|
||||
IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{701, nullptr, "ClearTaskStatusList"},
|
||||
{702, nullptr, "RequestDownloadTaskList"},
|
||||
{703, nullptr, "RequestEnsureDownloadTask"},
|
||||
{704, nullptr, "ListDownloadTaskStatus"},
|
||||
{705, nullptr, "RequestDownloadTaskListData"},
|
||||
{706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
|
||||
{707, nullptr, "EnableAutoCommit"},
|
||||
{708, nullptr, "DisableAutoCommit"},
|
||||
{709, nullptr, "TriggerDynamicCommitEvent"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDownloadTaskInterface::~IDownloadTaskInterface() = default;
|
||||
|
||||
IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestLinkDevice"},
|
||||
{1, nullptr, "RequestCleanupAllPreInstalledApplications"},
|
||||
{2, nullptr, "RequestCleanupPreInstalledApplication"},
|
||||
{3, nullptr, "RequestSyncRights"},
|
||||
{4, nullptr, "RequestUnlinkDevice"},
|
||||
{5, nullptr, "RequestRevokeAllELicense"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IECommerceInterface::~IECommerceInterface() = default;
|
||||
|
||||
IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
|
||||
: ServiceFramework{"IFactoryResetInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{100, nullptr, "ResetToFactorySettings"},
|
||||
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
|
||||
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
|
||||
};
|
||||
// clang-format on
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
class NS final : public ServiceFramework<NS> {
|
||||
public:
|
||||
explicit NS(const char* name) : ServiceFramework{name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"},
|
||||
{7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"},
|
||||
{7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"},
|
||||
{7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"},
|
||||
{7996, &NS::PushInterface<IApplicationManagerInterface>, "GetApplicationManagerInterface"},
|
||||
{7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
|
||||
{7998, &NS::PushInterface<IContentManagerInterface>, "GetContentManagementInterface"},
|
||||
{7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
|
||||
};
|
||||
// clang-format on
|
||||
IFactoryResetInterface::~IFactoryResetInterface() = default;
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
NS::NS(const char* name) : ServiceFramework{name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"},
|
||||
{7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"},
|
||||
{7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"},
|
||||
{7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"},
|
||||
{7996, &NS::PushInterface<IApplicationManagerInterface>, "GetApplicationManagerInterface"},
|
||||
{7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
|
||||
{7998, &NS::PushInterface<IContentManagerInterface>, "GetContentManagementInterface"},
|
||||
{7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void PushInterface(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NS, "called");
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<T>();
|
||||
}
|
||||
};
|
||||
NS::~NS() = default;
|
||||
|
||||
std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const {
|
||||
return GetInterface<IApplicationManagerInterface>();
|
||||
}
|
||||
|
||||
class NS_DEV final : public ServiceFramework<NS_DEV> {
|
||||
public:
|
||||
|
||||
@@ -8,6 +8,88 @@
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
|
||||
public:
|
||||
explicit IAccountProxyInterface();
|
||||
~IAccountProxyInterface();
|
||||
};
|
||||
|
||||
class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
|
||||
public:
|
||||
explicit IApplicationManagerInterface();
|
||||
~IApplicationManagerInterface();
|
||||
|
||||
ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
|
||||
ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language);
|
||||
|
||||
private:
|
||||
void GetApplicationControlData(Kernel::HLERequestContext& ctx);
|
||||
void GetApplicationDesiredLanguage(Kernel::HLERequestContext& ctx);
|
||||
void ConvertApplicationLanguageToLanguageCode(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
|
||||
public:
|
||||
explicit IApplicationVersionInterface();
|
||||
~IApplicationVersionInterface();
|
||||
};
|
||||
|
||||
class IContentManagerInterface final : public ServiceFramework<IContentManagerInterface> {
|
||||
public:
|
||||
explicit IContentManagerInterface();
|
||||
~IContentManagerInterface();
|
||||
};
|
||||
|
||||
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
|
||||
public:
|
||||
explicit IDocumentInterface();
|
||||
~IDocumentInterface();
|
||||
};
|
||||
|
||||
class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
|
||||
public:
|
||||
explicit IDownloadTaskInterface();
|
||||
~IDownloadTaskInterface();
|
||||
};
|
||||
|
||||
class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
|
||||
public:
|
||||
explicit IECommerceInterface();
|
||||
~IECommerceInterface();
|
||||
};
|
||||
|
||||
class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
|
||||
public:
|
||||
explicit IFactoryResetInterface();
|
||||
~IFactoryResetInterface();
|
||||
};
|
||||
|
||||
class NS final : public ServiceFramework<NS> {
|
||||
public:
|
||||
explicit NS(const char* name);
|
||||
~NS();
|
||||
|
||||
std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void PushInterface(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> GetInterface() const {
|
||||
static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
|
||||
"Not a base of ServiceFrameworkBase");
|
||||
|
||||
return std::make_shared<T>();
|
||||
}
|
||||
};
|
||||
|
||||
/// Registers all NS services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
|
||||
42
src/core/hle/service/ns/ns_language.h
Normal file
42
src/core/hle/service/ns/ns_language.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
|
||||
namespace Service::NS {
|
||||
/// This is nn::ns::detail::ApplicationLanguage
|
||||
enum class ApplicationLanguage : u8 {
|
||||
AmericanEnglish = 0,
|
||||
BritishEnglish,
|
||||
Japanese,
|
||||
French,
|
||||
German,
|
||||
LatinAmericanSpanish,
|
||||
Spanish,
|
||||
Italian,
|
||||
Dutch,
|
||||
CanadianFrench,
|
||||
Portuguese,
|
||||
Russian,
|
||||
Korean,
|
||||
TraditionalChinese,
|
||||
SimplifiedChinese,
|
||||
Count
|
||||
};
|
||||
using ApplicationLanguagePriorityList =
|
||||
const std::array<ApplicationLanguage, static_cast<std::size_t>(ApplicationLanguage::Count)>;
|
||||
|
||||
constexpr u32 GetSupportedLanguageFlag(const ApplicationLanguage lang) {
|
||||
return 1U << static_cast<u32>(lang);
|
||||
}
|
||||
|
||||
const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(ApplicationLanguage lang);
|
||||
std::optional<ApplicationLanguage> ConvertToApplicationLanguage(
|
||||
Service::Set::LanguageCode language_code);
|
||||
std::optional<Service::Set::LanguageCode> ConvertToLanguageCode(ApplicationLanguage lang);
|
||||
} // namespace Service::NS
|
||||
@@ -185,7 +185,8 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o
|
||||
|
||||
IoctlGetGpuTime params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
params.gpu_time = Core::Timing::cyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
|
||||
const auto ns = Core::Timing::CyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
|
||||
params.gpu_time = static_cast<u64_le>(ns.count());
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -108,8 +108,9 @@ private:
|
||||
LOG_DEBUG(Service_Time, "called");
|
||||
|
||||
const auto& core_timing = Core::System::GetInstance().CoreTiming();
|
||||
const SteadyClockTimePoint steady_clock_time_point{
|
||||
Core::Timing::cyclesToMs(core_timing.GetTicks()) / 1000};
|
||||
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
|
||||
const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000),
|
||||
{}};
|
||||
IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(steady_clock_time_point);
|
||||
@@ -284,8 +285,8 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
const auto& core_timing = Core::System::GetInstance().CoreTiming();
|
||||
const SteadyClockTimePoint steady_clock_time_point{
|
||||
Core::Timing::cyclesToMs(core_timing.GetTicks()) / 1000, {}};
|
||||
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
|
||||
const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}};
|
||||
|
||||
CalendarTime calendar_time{};
|
||||
calendar_time.year = tm->tm_year + 1900;
|
||||
|
||||
@@ -153,17 +153,6 @@ public:
|
||||
*/
|
||||
virtual LoadResult Load(Kernel::Process& process) = 0;
|
||||
|
||||
/**
|
||||
* Loads the system mode that this application needs.
|
||||
* This function defaults to 2 (96MB allocated to the application) if it can't read the
|
||||
* information.
|
||||
* @returns A pair with the optional system mode, and and the status.
|
||||
*/
|
||||
virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
|
||||
// 96MB allocated to the application.
|
||||
return std::make_pair(2, ResultStatus::Success);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code (typically .code section) of the application
|
||||
* @param buffer Reference to buffer to store data
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
@@ -101,7 +100,30 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
|
||||
#endif
|
||||
}
|
||||
|
||||
TelemetrySession::TelemetrySession() {
|
||||
TelemetrySession::TelemetrySession() = default;
|
||||
|
||||
TelemetrySession::~TelemetrySession() {
|
||||
// Log one-time session end information
|
||||
const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()};
|
||||
AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>(
|
||||
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
|
||||
#else
|
||||
auto backend = std::make_unique<Telemetry::NullVisitor>();
|
||||
#endif
|
||||
|
||||
// Complete the session, submitting to the web service backend if necessary
|
||||
field_collection.Accept(*backend);
|
||||
if (Settings::values.enable_telemetry) {
|
||||
backend->Complete();
|
||||
}
|
||||
}
|
||||
|
||||
void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
|
||||
// Log one-time top-level information
|
||||
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
|
||||
|
||||
@@ -112,26 +134,28 @@ TelemetrySession::TelemetrySession() {
|
||||
AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
|
||||
|
||||
u64 program_id{};
|
||||
const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
|
||||
const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)};
|
||||
if (res == Loader::ResultStatus::Success) {
|
||||
const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
|
||||
AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
|
||||
|
||||
std::string name;
|
||||
System::GetInstance().GetAppLoader().ReadTitle(name);
|
||||
app_loader.ReadTitle(name);
|
||||
|
||||
if (name.empty()) {
|
||||
auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
|
||||
if (nacp != nullptr)
|
||||
if (nacp != nullptr) {
|
||||
name = nacp->GetApplicationName();
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
if (!name.empty()) {
|
||||
AddField(Telemetry::FieldType::Session, "ProgramName", name);
|
||||
}
|
||||
}
|
||||
|
||||
AddField(Telemetry::FieldType::Session, "ProgramFormat",
|
||||
static_cast<u8>(System::GetInstance().GetAppLoader().GetFileType()));
|
||||
static_cast<u8>(app_loader.GetFileType()));
|
||||
|
||||
// Log application information
|
||||
Telemetry::AppendBuildInfo(field_collection);
|
||||
@@ -162,27 +186,6 @@ TelemetrySession::TelemetrySession() {
|
||||
Settings::values.use_docked_mode);
|
||||
}
|
||||
|
||||
TelemetrySession::~TelemetrySession() {
|
||||
// Log one-time session end information
|
||||
const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()};
|
||||
AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>(
|
||||
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
|
||||
#else
|
||||
auto backend = std::make_unique<Telemetry::NullVisitor>();
|
||||
#endif
|
||||
|
||||
// Complete the session, submitting to web service if necessary
|
||||
field_collection.Accept(*backend);
|
||||
if (Settings::values.enable_telemetry)
|
||||
backend->Complete();
|
||||
backend = nullptr;
|
||||
}
|
||||
|
||||
bool TelemetrySession::SubmitTestcase() {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>(
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/telemetry.h"
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
@@ -15,11 +18,33 @@ namespace Core {
|
||||
* session, logging any one-time fields. Interfaces with the telemetry backend used for submitting
|
||||
* data to the web service. Submits session data on close.
|
||||
*/
|
||||
class TelemetrySession : NonCopyable {
|
||||
class TelemetrySession {
|
||||
public:
|
||||
TelemetrySession();
|
||||
explicit TelemetrySession();
|
||||
~TelemetrySession();
|
||||
|
||||
TelemetrySession(const TelemetrySession&) = delete;
|
||||
TelemetrySession& operator=(const TelemetrySession&) = delete;
|
||||
|
||||
TelemetrySession(TelemetrySession&&) = delete;
|
||||
TelemetrySession& operator=(TelemetrySession&&) = delete;
|
||||
|
||||
/**
|
||||
* Adds the initial telemetry info necessary when starting up a title.
|
||||
*
|
||||
* This includes information such as:
|
||||
* - Telemetry ID
|
||||
* - Initialization time
|
||||
* - Title ID
|
||||
* - Title name
|
||||
* - Title file format
|
||||
* - Miscellaneous settings values.
|
||||
*
|
||||
* @param app_loader The application loader to use to retrieve
|
||||
* title-specific information.
|
||||
*/
|
||||
void AddInitialInfo(Loader::AppLoader& app_loader);
|
||||
|
||||
/**
|
||||
* Wrapper around the Telemetry::FieldCollection::AddField method.
|
||||
* @param type Type of the field to add.
|
||||
|
||||
@@ -6,15 +6,8 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Common {
|
||||
class ParamPackage;
|
||||
} // namespace Common
|
||||
|
||||
namespace InputCommon::Polling {
|
||||
class DevicePoller;
|
||||
enum class DeviceType;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -15,7 +14,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <SDL.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/param_package.h"
|
||||
@@ -23,12 +21,10 @@
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/sdl/sdl_impl.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
namespace SDL {
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
static std::string GetGUID(SDL_Joystick* joystick) {
|
||||
SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||
char guid_str[33];
|
||||
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
|
||||
return guid_str;
|
||||
@@ -37,26 +33,27 @@ static std::string GetGUID(SDL_Joystick* joystick) {
|
||||
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
|
||||
static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
|
||||
|
||||
static int SDLEventWatcher(void* userdata, SDL_Event* event) {
|
||||
SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata);
|
||||
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
|
||||
auto* const sdl_state = static_cast<SDLState*>(user_data);
|
||||
|
||||
// Don't handle the event if we are configuring
|
||||
if (sdl_state->polling) {
|
||||
sdl_state->event_queue.Push(*event);
|
||||
} else {
|
||||
sdl_state->HandleGameControllerEvent(*event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class SDLJoystick {
|
||||
public:
|
||||
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
|
||||
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
|
||||
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
|
||||
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {}
|
||||
|
||||
void SetButton(int button, bool value) {
|
||||
std::lock_guard lock{mutex};
|
||||
state.buttons[button] = value;
|
||||
state.buttons.insert_or_assign(button, value);
|
||||
}
|
||||
|
||||
bool GetButton(int button) const {
|
||||
@@ -66,7 +63,7 @@ public:
|
||||
|
||||
void SetAxis(int axis, Sint16 value) {
|
||||
std::lock_guard lock{mutex};
|
||||
state.axes[axis] = value;
|
||||
state.axes.insert_or_assign(axis, value);
|
||||
}
|
||||
|
||||
float GetAxis(int axis) const {
|
||||
@@ -93,7 +90,7 @@ public:
|
||||
|
||||
void SetHat(int hat, Uint8 direction) {
|
||||
std::lock_guard lock{mutex};
|
||||
state.hats[hat] = direction;
|
||||
state.hats.insert_or_assign(hat, direction);
|
||||
}
|
||||
|
||||
bool GetHatDirection(int hat, Uint8 direction) const {
|
||||
@@ -118,10 +115,8 @@ public:
|
||||
return sdl_joystick.get();
|
||||
}
|
||||
|
||||
void SetSDLJoystick(SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
|
||||
sdl_joystick =
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
|
||||
void SetSDLJoystick(SDL_Joystick* joystick) {
|
||||
sdl_joystick.reset(joystick);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -136,59 +131,57 @@ private:
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the nth joystick with the corresponding GUID
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
const auto it = joystick_map.find(guid);
|
||||
if (it != joystick_map.end()) {
|
||||
while (it->second.size() <= port) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
|
||||
[](SDL_Joystick*) {});
|
||||
while (it->second.size() <= static_cast<std::size_t>(port)) {
|
||||
auto joystick =
|
||||
std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr);
|
||||
it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
return it->second[port];
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr);
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
|
||||
* it to a SDLJoystick with the same guid and that port
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
|
||||
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
auto map_it = joystick_map.find(guid);
|
||||
const auto map_it = joystick_map.find(guid);
|
||||
if (map_it != joystick_map.end()) {
|
||||
auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return sdl_joystick == joystick->GetSDLJoystick();
|
||||
});
|
||||
const auto vec_it =
|
||||
std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return sdl_joystick == joystick->GetSDLJoystick();
|
||||
});
|
||||
if (vec_it != map_it->second.end()) {
|
||||
// This is the common case: There is already an existing SDL_Joystick maped to a
|
||||
// SDLJoystick. return the SDLJoystick
|
||||
return *vec_it;
|
||||
}
|
||||
|
||||
// Search for a SDLJoystick without a mapped SDL_Joystick...
|
||||
auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return !joystick->GetSDLJoystick();
|
||||
});
|
||||
const auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return !joystick->GetSDLJoystick();
|
||||
});
|
||||
if (nullptr_it != map_it->second.end()) {
|
||||
// ... and map it
|
||||
(*nullptr_it)->SetSDLJoystick(sdl_joystick);
|
||||
return *nullptr_it;
|
||||
}
|
||||
|
||||
// There is no SDLJoystick without a mapped SDL_Joystick
|
||||
// Create a new SDLJoystick
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
|
||||
const int port = static_cast<int>(map_it->second.size());
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
|
||||
return map_it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
@@ -215,17 +208,19 @@ void SDLState::InitJoystick(int joystick_index) {
|
||||
(*it)->SetSDLJoystick(sdl_joystick);
|
||||
return;
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
|
||||
const int port = static_cast<int>(joystick_guid_list.size());
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
|
||||
joystick_guid_list.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||
std::string guid = GetGUID(sdl_joystick);
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
{
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
// This call to guid is safe since the joystick is guaranteed to be in the map
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto joystick_it =
|
||||
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
@@ -233,9 +228,10 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||
});
|
||||
joystick = *joystick_it;
|
||||
}
|
||||
// Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback
|
||||
// which locks the mutex again
|
||||
joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
|
||||
|
||||
// Destruct SDL_Joystick outside the lock guard because SDL can internally call the
|
||||
// event callback which locks the mutex again.
|
||||
joystick->SetSDLJoystick(nullptr);
|
||||
}
|
||||
|
||||
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
|
||||
@@ -317,9 +313,10 @@ public:
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
float axis_value = joystick->GetAxis(axis);
|
||||
if (trigger_if_greater)
|
||||
const float axis_value = joystick->GetAxis(axis);
|
||||
if (trigger_if_greater) {
|
||||
return axis_value > threshold;
|
||||
}
|
||||
return axis_value < threshold;
|
||||
}
|
||||
|
||||
@@ -444,7 +441,7 @@ public:
|
||||
const int port = params.Get("port", 0);
|
||||
const int axis_x = params.Get("axis_x", 0);
|
||||
const int axis_y = params.Get("axis_y", 1);
|
||||
float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
|
||||
const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
|
||||
|
||||
auto joystick = state.GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
@@ -470,7 +467,7 @@ SDLState::SDLState() {
|
||||
return;
|
||||
}
|
||||
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
|
||||
LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError());
|
||||
LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_AddEventWatch(&SDLEventWatcher, this);
|
||||
@@ -507,12 +504,12 @@ SDLState::~SDLState() {
|
||||
}
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
|
||||
static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
|
||||
Common::ParamPackage params({{"engine", "sdl"}});
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis", event.jaxis.axis);
|
||||
@@ -526,14 +523,14 @@ Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Eve
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("button", event.jbutton.button);
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
|
||||
const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("hat", event.jhat.hat);
|
||||
@@ -607,8 +604,8 @@ public:
|
||||
SDLPoller::Start();
|
||||
|
||||
// Reset stored axes
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_x_axis = -1;
|
||||
analog_y_axis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
}
|
||||
|
||||
@@ -620,25 +617,25 @@ public:
|
||||
}
|
||||
// An analog device needs two axes, so we need to store the axis for later and wait for
|
||||
// a second SDL event. The axes also must be from the same joystick.
|
||||
int axis = event.jaxis.axis;
|
||||
if (analog_xaxis == -1) {
|
||||
analog_xaxis = axis;
|
||||
const int axis = event.jaxis.axis;
|
||||
if (analog_x_axis == -1) {
|
||||
analog_x_axis = axis;
|
||||
analog_axes_joystick = event.jaxis.which;
|
||||
} else if (analog_yaxis == -1 && analog_xaxis != axis &&
|
||||
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
|
||||
analog_axes_joystick == event.jaxis.which) {
|
||||
analog_yaxis = axis;
|
||||
analog_y_axis = axis;
|
||||
}
|
||||
}
|
||||
Common::ParamPackage params;
|
||||
if (analog_xaxis != -1 && analog_yaxis != -1) {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
if (analog_x_axis != -1 && analog_y_axis != -1) {
|
||||
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("engine", "sdl");
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis_x", analog_xaxis);
|
||||
params.Set("axis_y", analog_yaxis);
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
params.Set("axis_x", analog_x_axis);
|
||||
params.Set("axis_y", analog_y_axis);
|
||||
analog_x_axis = -1;
|
||||
analog_y_axis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
return params;
|
||||
}
|
||||
@@ -646,8 +643,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
int analog_xaxis = -1;
|
||||
int analog_yaxis = -1;
|
||||
int analog_x_axis = -1;
|
||||
int analog_y_axis = -1;
|
||||
SDL_JoystickID analog_axes_joystick = -1;
|
||||
};
|
||||
} // namespace Polling
|
||||
@@ -667,5 +664,4 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
|
||||
return pollers;
|
||||
}
|
||||
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
} // namespace InputCommon::SDL
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "common/common_types.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
|
||||
@@ -16,9 +19,9 @@ using SDL_JoystickID = s32;
|
||||
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
class SDLJoystick;
|
||||
class SDLButtonFactory;
|
||||
class SDLAnalogFactory;
|
||||
class SDLButtonFactory;
|
||||
class SDLJoystick;
|
||||
|
||||
class SDLState : public State {
|
||||
public:
|
||||
@@ -31,7 +34,13 @@ public:
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
void HandleGameControllerEvent(const SDL_Event& event);
|
||||
|
||||
/// Get the nth joystick with the corresponding GUID
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
|
||||
* tie it to a SDLJoystick with the same guid and that port
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
||||
|
||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
||||
|
||||
@@ -1663,6 +1663,7 @@ private:
|
||||
INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
|
||||
INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"),
|
||||
INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
|
||||
INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"),
|
||||
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
|
||||
@@ -1686,7 +1687,6 @@ private:
|
||||
INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
|
||||
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
|
||||
|
||||
@@ -75,7 +75,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPus
|
||||
|
||||
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
|
||||
const u64 fence{PushCommand(SubmitListCommand(std::move(entries)))};
|
||||
const s64 synchronization_ticks{Core::Timing::usToCycles(9000)};
|
||||
const s64 synchronization_ticks{Core::Timing::usToCycles(std::chrono::microseconds{9000})};
|
||||
system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
@@ -24,6 +27,7 @@ Device::Device() {
|
||||
max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
|
||||
max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
|
||||
has_variable_aoffi = TestVariableAoffi();
|
||||
has_component_indexing_bug = TestComponentIndexingBug();
|
||||
}
|
||||
|
||||
Device::Device(std::nullptr_t) {
|
||||
@@ -31,6 +35,7 @@ Device::Device(std::nullptr_t) {
|
||||
max_vertex_attributes = 16;
|
||||
max_varyings = 15;
|
||||
has_variable_aoffi = true;
|
||||
has_component_indexing_bug = false;
|
||||
}
|
||||
|
||||
bool Device::TestVariableAoffi() {
|
||||
@@ -52,4 +57,53 @@ void main() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
bool Device::TestComponentIndexingBug() {
|
||||
constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}";
|
||||
const GLchar* COMPONENT_TEST = R"(#version 430 core
|
||||
layout (std430, binding = 0) buffer OutputBuffer {
|
||||
uint output_value;
|
||||
};
|
||||
layout (std140, binding = 0) uniform InputBuffer {
|
||||
uvec4 input_value[4096];
|
||||
};
|
||||
layout (location = 0) uniform uint idx;
|
||||
void main() {
|
||||
output_value = input_value[idx >> 2][idx & 3];
|
||||
})";
|
||||
const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &COMPONENT_TEST)};
|
||||
SCOPE_EXIT({ glDeleteProgram(shader); });
|
||||
glUseProgram(shader);
|
||||
|
||||
OGLVertexArray vao;
|
||||
vao.Create();
|
||||
glBindVertexArray(vao.handle);
|
||||
|
||||
constexpr std::array<GLuint, 8> values{0, 0, 0, 0, 0x1236327, 0x985482, 0x872753, 0x2378432};
|
||||
OGLBuffer ubo;
|
||||
ubo.Create();
|
||||
glNamedBufferData(ubo.handle, sizeof(values), values.data(), GL_STATIC_DRAW);
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo.handle);
|
||||
|
||||
OGLBuffer ssbo;
|
||||
ssbo.Create();
|
||||
glNamedBufferStorage(ssbo.handle, sizeof(GLuint), nullptr, GL_CLIENT_STORAGE_BIT);
|
||||
|
||||
for (GLuint index = 4; index < 8; ++index) {
|
||||
glInvalidateBufferData(ssbo.handle);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo.handle);
|
||||
|
||||
glProgramUniform1ui(shader, 0, index);
|
||||
glDrawArrays(GL_POINTS, 0, 1);
|
||||
|
||||
GLuint result;
|
||||
glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result);
|
||||
if (result != values.at(index)) {
|
||||
LOG_INFO(Render_OpenGL, log_message, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG_INFO(Render_OpenGL, log_message, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -30,13 +30,19 @@ public:
|
||||
return has_variable_aoffi;
|
||||
}
|
||||
|
||||
bool HasComponentIndexingBug() const {
|
||||
return has_component_indexing_bug;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool TestVariableAoffi();
|
||||
static bool TestComponentIndexingBug();
|
||||
|
||||
std::size_t uniform_buffer_alignment{};
|
||||
u32 max_vertex_attributes{};
|
||||
u32 max_varyings{};
|
||||
bool has_variable_aoffi{};
|
||||
bool has_component_indexing_bug{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -35,8 +35,8 @@ struct UnspecializedShader {
|
||||
namespace {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()};
|
||||
GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program) {
|
||||
const auto& gpu{system.GPU().Maxwell3D()};
|
||||
const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
@@ -350,7 +350,8 @@ ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
|
||||
|
||||
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
Core::Frontend::EmuWindow& emu_window, const Device& device)
|
||||
: RasterizerCache{rasterizer}, emu_window{emu_window}, device{device}, disk_cache{system} {}
|
||||
: RasterizerCache{rasterizer}, system{system}, emu_window{emu_window}, device{device},
|
||||
disk_cache{system} {}
|
||||
|
||||
void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
@@ -546,42 +547,45 @@ std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecia
|
||||
}
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
|
||||
return last_shaders[static_cast<u32>(program)];
|
||||
if (!system.GPU().Maxwell3D().dirty_flags.shaders) {
|
||||
return last_shaders[static_cast<std::size_t>(program)];
|
||||
}
|
||||
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const GPUVAddr program_addr{GetShaderAddress(program)};
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const GPUVAddr program_addr{GetShaderAddress(system, program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
const auto& host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
const auto host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
Shader shader{TryGet(host_ptr)};
|
||||
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)};
|
||||
ProgramCode program_code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
const GPUVAddr program_addr_b{GetShaderAddress(Maxwell::ShaderProgram::VertexB)};
|
||||
program_code_b = GetShaderCode(memory_manager, program_addr_b,
|
||||
memory_manager.GetPointer(program_addr_b));
|
||||
}
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
shader =
|
||||
std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second, host_ptr);
|
||||
} else {
|
||||
shader = std::make_shared<CachedShader>(
|
||||
device, cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b), host_ptr);
|
||||
}
|
||||
Register(shader);
|
||||
if (shader) {
|
||||
return last_shaders[static_cast<std::size_t>(program)] = shader;
|
||||
}
|
||||
|
||||
return last_shaders[static_cast<u32>(program)] = shader;
|
||||
// No shader found - create a new one
|
||||
ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)};
|
||||
ProgramCode program_code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
const GPUVAddr program_addr_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
|
||||
program_code_b = GetShaderCode(memory_manager, program_addr_b,
|
||||
memory_manager.GetPointer(program_addr_b));
|
||||
}
|
||||
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
// Create a shader from the cache
|
||||
shader = std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second, host_ptr);
|
||||
} else {
|
||||
// Create a shader from guest memory
|
||||
shader = std::make_shared<CachedShader>(
|
||||
device, cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b), host_ptr);
|
||||
}
|
||||
Register(shader);
|
||||
|
||||
return last_shaders[static_cast<std::size_t>(program)] = shader;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -137,6 +137,7 @@ private:
|
||||
CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
|
||||
const std::set<GLenum>& supported_formats);
|
||||
|
||||
Core::System& system;
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
const Device& device;
|
||||
ShaderDiskCacheOpenGL disk_cache;
|
||||
|
||||
@@ -31,6 +31,8 @@ using Tegra::Shader::IpaInterpMode;
|
||||
using Tegra::Shader::IpaMode;
|
||||
using Tegra::Shader::IpaSampleMode;
|
||||
using Tegra::Shader::Register;
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace VideoCommon::Shader;
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
@@ -93,11 +95,9 @@ private:
|
||||
};
|
||||
|
||||
/// Generates code to use for a swizzle operation.
|
||||
std::string GetSwizzle(u32 elem) {
|
||||
ASSERT(elem <= 3);
|
||||
std::string swizzle = ".";
|
||||
swizzle += "xyzw"[elem];
|
||||
return swizzle;
|
||||
constexpr const char* GetSwizzle(u32 element) {
|
||||
constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"};
|
||||
return swizzle.at(element);
|
||||
}
|
||||
|
||||
/// Translate topology
|
||||
@@ -577,9 +577,26 @@ private:
|
||||
if (std::holds_alternative<OperationNode>(*offset)) {
|
||||
// Indirect access
|
||||
const std::string final_offset = code.GenerateTemporary();
|
||||
code.AddLine("uint {} = (ftou({}) / 4);", final_offset, Visit(offset));
|
||||
return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()),
|
||||
final_offset, final_offset);
|
||||
code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset));
|
||||
|
||||
if (!device.HasComponentIndexingBug()) {
|
||||
return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
|
||||
final_offset, final_offset);
|
||||
}
|
||||
|
||||
// AMD's proprietary GLSL compiler emits ill code for variable component access.
|
||||
// To bypass this driver bug generate 4 ifs, one per each component.
|
||||
const std::string pack = code.GenerateTemporary();
|
||||
code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
|
||||
final_offset);
|
||||
|
||||
const std::string result = code.GenerateTemporary();
|
||||
code.AddLine("float {};", result);
|
||||
for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
|
||||
code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
|
||||
pack, GetSwizzle(swizzle));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("Unmanaged offset node type");
|
||||
@@ -636,7 +653,7 @@ private:
|
||||
if (stage != ShaderStage::Fragment) {
|
||||
return GeometryPass("position") + GetSwizzle(element);
|
||||
} else {
|
||||
return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element);
|
||||
return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element));
|
||||
}
|
||||
case Attribute::Index::PointCoord:
|
||||
switch (element) {
|
||||
@@ -921,7 +938,7 @@ private:
|
||||
target = [&]() -> std::string {
|
||||
switch (const auto attribute = abuf->GetIndex(); abuf->GetIndex()) {
|
||||
case Attribute::Index::Position:
|
||||
return "position" + GetSwizzle(abuf->GetElement());
|
||||
return "position"s + GetSwizzle(abuf->GetElement());
|
||||
case Attribute::Index::PointSize:
|
||||
return "gl_PointSize";
|
||||
case Attribute::Index::ClipDistances0123:
|
||||
@@ -1526,6 +1543,16 @@ private:
|
||||
return "uintBitsToFloat(config_pack[2])";
|
||||
}
|
||||
|
||||
template <u32 element>
|
||||
std::string LocalInvocationId(Operation) {
|
||||
return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')';
|
||||
}
|
||||
|
||||
template <u32 element>
|
||||
std::string WorkGroupId(Operation) {
|
||||
return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')';
|
||||
}
|
||||
|
||||
static constexpr OperationDecompilersArray operation_decompilers = {
|
||||
&GLSLDecompiler::Assign,
|
||||
|
||||
@@ -1665,6 +1692,12 @@ private:
|
||||
&GLSLDecompiler::EndPrimitive,
|
||||
|
||||
&GLSLDecompiler::YNegate,
|
||||
&GLSLDecompiler::LocalInvocationId<0>,
|
||||
&GLSLDecompiler::LocalInvocationId<1>,
|
||||
&GLSLDecompiler::LocalInvocationId<2>,
|
||||
&GLSLDecompiler::WorkGroupId<0>,
|
||||
&GLSLDecompiler::WorkGroupId<1>,
|
||||
&GLSLDecompiler::WorkGroupId<2>,
|
||||
};
|
||||
|
||||
std::string GetRegister(u32 index) const {
|
||||
|
||||
@@ -1035,6 +1035,18 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
template <u32 element>
|
||||
Id LocalInvocationId(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
template <u32 element>
|
||||
Id WorkGroupId(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
|
||||
const std::string& name) {
|
||||
const Id id = OpVariable(type, storage);
|
||||
@@ -1291,6 +1303,12 @@ private:
|
||||
&SPIRVDecompiler::EndPrimitive,
|
||||
|
||||
&SPIRVDecompiler::YNegate,
|
||||
&SPIRVDecompiler::LocalInvocationId<0>,
|
||||
&SPIRVDecompiler::LocalInvocationId<1>,
|
||||
&SPIRVDecompiler::LocalInvocationId<2>,
|
||||
&SPIRVDecompiler::WorkGroupId<0>,
|
||||
&SPIRVDecompiler::WorkGroupId<1>,
|
||||
&SPIRVDecompiler::WorkGroupId<2>,
|
||||
};
|
||||
|
||||
const ShaderIR& ir;
|
||||
|
||||
@@ -14,6 +14,7 @@ using Tegra::Shader::ConditionCode;
|
||||
using Tegra::Shader::Instruction;
|
||||
using Tegra::Shader::OpCode;
|
||||
using Tegra::Shader::Register;
|
||||
using Tegra::Shader::SystemVariable;
|
||||
|
||||
u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
@@ -59,20 +60,33 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::MOV_SYS: {
|
||||
switch (instr.sys20) {
|
||||
case Tegra::Shader::SystemVariable::InvocationInfo: {
|
||||
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
|
||||
SetRegister(bb, instr.gpr0, Immediate(0u));
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::SystemVariable::Ydirection: {
|
||||
// Config pack's third value is Y_NEGATE's state.
|
||||
SetRegister(bb, instr.gpr0, Operation(OperationCode::YNegate));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled system move: {}", static_cast<u32>(instr.sys20.Value()));
|
||||
}
|
||||
const Node value = [&]() {
|
||||
switch (instr.sys20) {
|
||||
case SystemVariable::Ydirection:
|
||||
return Operation(OperationCode::YNegate);
|
||||
case SystemVariable::InvocationInfo:
|
||||
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
|
||||
return Immediate(0u);
|
||||
case SystemVariable::TidX:
|
||||
return Operation(OperationCode::LocalInvocationIdX);
|
||||
case SystemVariable::TidY:
|
||||
return Operation(OperationCode::LocalInvocationIdY);
|
||||
case SystemVariable::TidZ:
|
||||
return Operation(OperationCode::LocalInvocationIdZ);
|
||||
case SystemVariable::CtaIdX:
|
||||
return Operation(OperationCode::WorkGroupIdX);
|
||||
case SystemVariable::CtaIdY:
|
||||
return Operation(OperationCode::WorkGroupIdY);
|
||||
case SystemVariable::CtaIdZ:
|
||||
return Operation(OperationCode::WorkGroupIdZ);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled system move: {}",
|
||||
static_cast<u32>(instr.sys20.Value()));
|
||||
return Immediate(0u);
|
||||
}
|
||||
}();
|
||||
SetRegister(bb, instr.gpr0, value);
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::BRA: {
|
||||
|
||||
@@ -181,7 +181,13 @@ enum class OperationCode {
|
||||
EmitVertex, /// () -> void
|
||||
EndPrimitive, /// () -> void
|
||||
|
||||
YNegate, /// () -> float
|
||||
YNegate, /// () -> float
|
||||
LocalInvocationIdX, /// () -> uint
|
||||
LocalInvocationIdY, /// () -> uint
|
||||
LocalInvocationIdZ, /// () -> uint
|
||||
WorkGroupIdX, /// () -> uint
|
||||
WorkGroupIdY, /// () -> uint
|
||||
WorkGroupIdZ, /// () -> uint
|
||||
|
||||
Amount,
|
||||
};
|
||||
|
||||
@@ -27,20 +27,20 @@ constexpr std::array<u8, 107> backup_jpeg{
|
||||
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
};
|
||||
|
||||
QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
|
||||
QString FormatUserEntryText(const QString& username, Common::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) {
|
||||
QString GetImagePath(Common::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 GetIcon(Common::UUID uuid) {
|
||||
QPixmap icon{GetImagePath(uuid)};
|
||||
|
||||
if (!icon) {
|
||||
@@ -122,21 +122,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
||||
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 {
|
||||
int QtProfileSelectionDialog::GetIndex() const {
|
||||
return user_index;
|
||||
}
|
||||
|
||||
@@ -154,12 +148,12 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
|
||||
QtProfileSelector::~QtProfileSelector() = default;
|
||||
|
||||
void QtProfileSelector::SelectProfile(
|
||||
std::function<void(std::optional<Service::Account::UUID>)> callback) const {
|
||||
std::function<void(std::optional<Common::UUID>)> callback) const {
|
||||
this->callback = std::move(callback);
|
||||
emit MainWindowSelectProfile();
|
||||
}
|
||||
|
||||
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) {
|
||||
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
|
||||
// Acquire the HLE mutex
|
||||
std::lock_guard lock{HLE::g_hle_lock};
|
||||
callback(uuid);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QList>
|
||||
#include <QTreeView>
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
class GMainWindow;
|
||||
class QDialogButtonBox;
|
||||
@@ -29,15 +30,13 @@ public:
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
|
||||
bool GetStatus() const;
|
||||
u32 GetIndex() const;
|
||||
int GetIndex() const;
|
||||
|
||||
private:
|
||||
bool ok = false;
|
||||
u32 user_index = 0;
|
||||
|
||||
void SelectUser(const QModelIndex& index);
|
||||
|
||||
int user_index = 0;
|
||||
|
||||
QVBoxLayout* layout;
|
||||
QTreeView* tree_view;
|
||||
QStandardItemModel* item_model;
|
||||
@@ -60,14 +59,13 @@ public:
|
||||
explicit QtProfileSelector(GMainWindow& parent);
|
||||
~QtProfileSelector() override;
|
||||
|
||||
void SelectProfile(
|
||||
std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
|
||||
void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowSelectProfile() const;
|
||||
|
||||
private:
|
||||
void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid);
|
||||
void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
|
||||
|
||||
mutable std::function<void(std::optional<Service::Account::UUID>)> callback;
|
||||
mutable std::function<void(std::optional<Common::UUID>)> callback;
|
||||
};
|
||||
|
||||
@@ -104,13 +104,11 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
|
||||
QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default;
|
||||
|
||||
void QtSoftwareKeyboardDialog::accept() {
|
||||
ok = true;
|
||||
text = line_edit->text().toStdU16String();
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void QtSoftwareKeyboardDialog::reject() {
|
||||
ok = false;
|
||||
text.clear();
|
||||
QDialog::reject();
|
||||
}
|
||||
@@ -119,10 +117,6 @@ std::u16string QtSoftwareKeyboardDialog::GetText() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
bool QtSoftwareKeyboardDialog::GetStatus() const {
|
||||
return ok;
|
||||
}
|
||||
|
||||
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
|
||||
connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window,
|
||||
&GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection);
|
||||
|
||||
@@ -36,10 +36,8 @@ public:
|
||||
void reject() override;
|
||||
|
||||
std::u16string GetText() const;
|
||||
bool GetStatus() const;
|
||||
|
||||
private:
|
||||
bool ok = false;
|
||||
std::u16string text;
|
||||
|
||||
QDialogButtonBox* buttons;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
||||
|
||||
EmuThread::~EmuThread() = default;
|
||||
|
||||
void EmuThread::run() {
|
||||
render_window->MakeCurrent();
|
||||
|
||||
@@ -185,7 +187,7 @@ private:
|
||||
bool do_painting;
|
||||
};
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
|
||||
: QWidget(parent), emu_thread(emu_thread) {
|
||||
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
|
||||
.arg(QString::fromUtf8(Common::g_build_name),
|
||||
@@ -194,8 +196,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
|
||||
InputCommon::Init();
|
||||
connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent),
|
||||
&GMainWindow::OnLoadComplete);
|
||||
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
|
||||
}
|
||||
|
||||
GRenderWindow::~GRenderWindow() {
|
||||
@@ -247,9 +248,9 @@ void GRenderWindow::PollEvents() {}
|
||||
void GRenderWindow::OnFramebufferSizeChanged() {
|
||||
// Screen changes potentially incur a change in screen DPI, hence we should update the
|
||||
// framebuffer size
|
||||
qreal pixelRatio = GetWindowPixelRatio();
|
||||
unsigned width = child->QPaintDevice::width() * pixelRatio;
|
||||
unsigned height = child->QPaintDevice::height() * pixelRatio;
|
||||
const qreal pixel_ratio = GetWindowPixelRatio();
|
||||
const u32 width = child->QPaintDevice::width() * pixel_ratio;
|
||||
const u32 height = child->QPaintDevice::height() * pixel_ratio;
|
||||
UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
@@ -266,7 +267,7 @@ void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
|
||||
}
|
||||
|
||||
void GRenderWindow::BackupGeometry() {
|
||||
geometry = ((QWidget*)this)->saveGeometry();
|
||||
geometry = QWidget::saveGeometry();
|
||||
}
|
||||
|
||||
void GRenderWindow::RestoreGeometry() {
|
||||
@@ -283,10 +284,11 @@ void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
|
||||
QByteArray GRenderWindow::saveGeometry() {
|
||||
// If we are a top-level widget, store the current geometry
|
||||
// otherwise, store the last backup
|
||||
if (parent() == nullptr)
|
||||
return ((QWidget*)this)->saveGeometry();
|
||||
else
|
||||
return geometry;
|
||||
if (parent() == nullptr) {
|
||||
return QWidget::saveGeometry();
|
||||
}
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
qreal GRenderWindow::GetWindowPixelRatio() const {
|
||||
@@ -294,10 +296,10 @@ qreal GRenderWindow::GetWindowPixelRatio() const {
|
||||
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
const qreal pixel_ratio = GetWindowPixelRatio();
|
||||
return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
}
|
||||
|
||||
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||
@@ -353,7 +355,7 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
||||
InputCommon::GetKeyboard()->ReleaseAllKeys();
|
||||
}
|
||||
|
||||
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
|
||||
void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {
|
||||
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
||||
}
|
||||
|
||||
@@ -394,7 +396,7 @@ void GRenderWindow::InitRenderTarget() {
|
||||
context->setShareContext(shared_context.get());
|
||||
context->setFormat(fmt);
|
||||
context->create();
|
||||
fmt.setSwapInterval(false);
|
||||
fmt.setSwapInterval(0);
|
||||
|
||||
child = new GGLWidgetInternal(this, shared_context.get());
|
||||
container = QWidget::createWindowContainer(child, this);
|
||||
@@ -424,24 +426,29 @@ void GRenderWindow::InitRenderTarget() {
|
||||
BackupGeometry();
|
||||
}
|
||||
|
||||
void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
|
||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||
auto& renderer = Core::System::GetInstance().Renderer();
|
||||
|
||||
if (!res_scale)
|
||||
if (res_scale == 0) {
|
||||
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);
|
||||
renderer.RequestScreenshot(
|
||||
screenshot_image.bits(),
|
||||
[=] {
|
||||
const std::string std_screenshot_path = screenshot_path.toStdString();
|
||||
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
|
||||
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
|
||||
}
|
||||
},
|
||||
layout);
|
||||
}
|
||||
|
||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,12 @@ namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
}
|
||||
|
||||
class EmuThread : public QThread {
|
||||
class EmuThread final : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmuThread(GRenderWindow* render_window);
|
||||
~EmuThread() override;
|
||||
|
||||
/**
|
||||
* Start emulation (on new thread)
|
||||
@@ -114,7 +115,7 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
|
||||
GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
|
||||
~GRenderWindow() override;
|
||||
|
||||
// EmuWindow implementation
|
||||
@@ -133,17 +134,17 @@ public:
|
||||
QByteArray saveGeometry(); // overridden
|
||||
|
||||
qreal GetWindowPixelRatio() const;
|
||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
||||
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
bool event(QEvent* event) override;
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
void OnClientAreaResized(unsigned width, unsigned height);
|
||||
void OnClientAreaResized(u32 width, u32 height);
|
||||
|
||||
void InitRenderTarget();
|
||||
|
||||
void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
|
||||
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
||||
|
||||
public slots:
|
||||
void moveContext(); // overridden
|
||||
@@ -162,8 +163,7 @@ private:
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
QWidget* container = nullptr;
|
||||
GGLWidgetInternal* child = nullptr;
|
||||
|
||||
@@ -645,6 +645,8 @@ void Config::ReadUIGamelistValues() {
|
||||
UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
|
||||
UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
|
||||
UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
|
||||
UISettings::values.cache_game_list =
|
||||
ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
@@ -1009,6 +1011,7 @@ void Config::SaveUIGamelistValues() {
|
||||
WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
|
||||
WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
|
||||
WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
|
||||
WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "audio_core/sink.h"
|
||||
#include "audio_core/sink_details.h"
|
||||
#include "core/core.h"
|
||||
@@ -15,18 +17,14 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->output_sink_combo_box->clear();
|
||||
ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
|
||||
for (const char* id : AudioCore::GetSinkIDs()) {
|
||||
ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
|
||||
}
|
||||
InitializeAudioOutputSinkComboBox();
|
||||
|
||||
connect(ui->volume_slider, &QSlider::valueChanged, this,
|
||||
&ConfigureAudio::setVolumeIndicatorText);
|
||||
|
||||
this->setConfiguration();
|
||||
&ConfigureAudio::SetVolumeIndicatorText);
|
||||
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureAudio::updateAudioDevices);
|
||||
&ConfigureAudio::UpdateAudioDevices);
|
||||
|
||||
SetConfiguration();
|
||||
|
||||
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
|
||||
ui->output_sink_combo_box->setEnabled(!is_powered_on);
|
||||
@@ -35,22 +33,23 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||
|
||||
ConfigureAudio::~ConfigureAudio() = default;
|
||||
|
||||
void ConfigureAudio::setConfiguration() {
|
||||
setOutputSinkFromSinkID();
|
||||
void ConfigureAudio::SetConfiguration() {
|
||||
SetOutputSinkFromSinkID();
|
||||
|
||||
// The device list cannot be pre-populated (nor listed) until the output sink is known.
|
||||
updateAudioDevices(ui->output_sink_combo_box->currentIndex());
|
||||
UpdateAudioDevices(ui->output_sink_combo_box->currentIndex());
|
||||
|
||||
setAudioDeviceFromDeviceID();
|
||||
SetAudioDeviceFromDeviceID();
|
||||
|
||||
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
||||
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
|
||||
setVolumeIndicatorText(ui->volume_slider->sliderPosition());
|
||||
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
|
||||
}
|
||||
|
||||
void ConfigureAudio::setOutputSinkFromSinkID() {
|
||||
int new_sink_index = 0;
|
||||
void ConfigureAudio::SetOutputSinkFromSinkID() {
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box);
|
||||
|
||||
int new_sink_index = 0;
|
||||
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
|
||||
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
|
||||
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
|
||||
@@ -62,7 +61,7 @@ void ConfigureAudio::setOutputSinkFromSinkID() {
|
||||
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
|
||||
}
|
||||
|
||||
void ConfigureAudio::setAudioDeviceFromDeviceID() {
|
||||
void ConfigureAudio::SetAudioDeviceFromDeviceID() {
|
||||
int new_device_index = -1;
|
||||
|
||||
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
|
||||
@@ -76,11 +75,11 @@ void ConfigureAudio::setAudioDeviceFromDeviceID() {
|
||||
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
|
||||
}
|
||||
|
||||
void ConfigureAudio::setVolumeIndicatorText(int percentage) {
|
||||
void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
|
||||
ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
|
||||
}
|
||||
|
||||
void ConfigureAudio::applyConfiguration() {
|
||||
void ConfigureAudio::ApplyConfiguration() {
|
||||
Settings::values.sink_id =
|
||||
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
|
||||
.toStdString();
|
||||
@@ -92,7 +91,15 @@ void ConfigureAudio::applyConfiguration() {
|
||||
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
|
||||
}
|
||||
|
||||
void ConfigureAudio::updateAudioDevices(int sink_index) {
|
||||
void ConfigureAudio::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureAudio::UpdateAudioDevices(int sink_index) {
|
||||
ui->audio_device_combo_box->clear();
|
||||
ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
|
||||
|
||||
@@ -102,6 +109,16 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureAudio::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
void ConfigureAudio::InitializeAudioOutputSinkComboBox() {
|
||||
ui->output_sink_combo_box->clear();
|
||||
ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
|
||||
|
||||
for (const char* id : AudioCore::GetSinkIDs()) {
|
||||
ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureAudio::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
|
||||
}
|
||||
|
||||
@@ -18,16 +18,21 @@ public:
|
||||
explicit ConfigureAudio(QWidget* parent = nullptr);
|
||||
~ConfigureAudio() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void updateAudioDevices(int sink_index);
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
void setConfiguration();
|
||||
void setOutputSinkFromSinkID();
|
||||
void setAudioDeviceFromDeviceID();
|
||||
void setVolumeIndicatorText(int percentage);
|
||||
void InitializeAudioOutputSinkComboBox();
|
||||
|
||||
void RetranslateUI();
|
||||
|
||||
void UpdateAudioDevices(int sink_index);
|
||||
|
||||
void SetConfiguration();
|
||||
void SetOutputSinkFromSinkID();
|
||||
void SetAudioDeviceFromDeviceID();
|
||||
void SetVolumeIndicatorText(int percentage);
|
||||
|
||||
std::unique_ptr<Ui::ConfigureAudio> ui;
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_1">
|
||||
<property name="text">
|
||||
<string>Output Engine:</string>
|
||||
</property>
|
||||
@@ -44,7 +44,7 @@
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Audio Device:</string>
|
||||
</property>
|
||||
@@ -61,7 +61,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Volume:</string>
|
||||
</property>
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
SetConfiguration();
|
||||
|
||||
connect(ui->open_log_button, &QPushButton::pressed, []() {
|
||||
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
@@ -25,7 +26,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
|
||||
|
||||
ConfigureDebug::~ConfigureDebug() = default;
|
||||
|
||||
void ConfigureDebug::setConfiguration() {
|
||||
void ConfigureDebug::SetConfiguration() {
|
||||
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
|
||||
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
|
||||
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
|
||||
@@ -37,7 +38,7 @@ void ConfigureDebug::setConfiguration() {
|
||||
ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
|
||||
}
|
||||
|
||||
void ConfigureDebug::applyConfiguration() {
|
||||
void ConfigureDebug::ApplyConfiguration() {
|
||||
Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
|
||||
Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
|
||||
UISettings::values.show_console = ui->toggle_console->isChecked();
|
||||
@@ -50,3 +51,15 @@ void ConfigureDebug::applyConfiguration() {
|
||||
filter.ParseFilterString(Settings::values.log_filter);
|
||||
Log::SetGlobalFilter(filter);
|
||||
}
|
||||
|
||||
void ConfigureDebug::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureDebug::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,13 @@ public:
|
||||
explicit ConfigureDebug(QWidget* parent = nullptr);
|
||||
~ConfigureDebug() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
void RetranslateUI();
|
||||
void SetConfiguration();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="label_1">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
@@ -70,11 +70,11 @@
|
||||
<property name="title">
|
||||
<string>Logging</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Global Log Filter</string>
|
||||
</property>
|
||||
@@ -86,7 +86,7 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_console">
|
||||
<property name="text">
|
||||
@@ -111,11 +111,11 @@
|
||||
<property name="title">
|
||||
<string>Homebrew</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Arguments String</string>
|
||||
</property>
|
||||
@@ -134,7 +134,7 @@
|
||||
<property name="title">
|
||||
<string>Dump</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dump_decompressed_nso">
|
||||
<property name="whatsThis">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <QHash>
|
||||
#include <QListWidgetItem>
|
||||
#include <QSignalBlocker>
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
@@ -15,11 +16,11 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
||||
: QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
|
||||
ui->setupUi(this);
|
||||
ui->hotkeysTab->Populate(registry);
|
||||
this->setConfiguration();
|
||||
this->PopulateSelectionList();
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
SetConfiguration();
|
||||
PopulateSelectionList();
|
||||
|
||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureDialog::UpdateVisibleTabs);
|
||||
|
||||
@@ -29,30 +30,55 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
||||
|
||||
ConfigureDialog::~ConfigureDialog() = default;
|
||||
|
||||
void ConfigureDialog::setConfiguration() {}
|
||||
void ConfigureDialog::SetConfiguration() {}
|
||||
|
||||
void ConfigureDialog::applyConfiguration() {
|
||||
ui->generalTab->applyConfiguration();
|
||||
ui->gameListTab->applyConfiguration();
|
||||
ui->systemTab->applyConfiguration();
|
||||
ui->profileManagerTab->applyConfiguration();
|
||||
ui->inputTab->applyConfiguration();
|
||||
ui->hotkeysTab->applyConfiguration(registry);
|
||||
ui->graphicsTab->applyConfiguration();
|
||||
ui->audioTab->applyConfiguration();
|
||||
ui->debugTab->applyConfiguration();
|
||||
ui->webTab->applyConfiguration();
|
||||
void ConfigureDialog::ApplyConfiguration() {
|
||||
ui->generalTab->ApplyConfiguration();
|
||||
ui->gameListTab->ApplyConfiguration();
|
||||
ui->systemTab->ApplyConfiguration();
|
||||
ui->profileManagerTab->ApplyConfiguration();
|
||||
ui->inputTab->ApplyConfiguration();
|
||||
ui->hotkeysTab->ApplyConfiguration(registry);
|
||||
ui->graphicsTab->ApplyConfiguration();
|
||||
ui->audioTab->ApplyConfiguration();
|
||||
ui->debugTab->ApplyConfiguration();
|
||||
ui->webTab->ApplyConfiguration();
|
||||
Settings::Apply();
|
||||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void ConfigureDialog::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureDialog::RetranslateUI() {
|
||||
const int old_row = ui->selectorList->currentRow();
|
||||
const int old_index = ui->tabWidget->currentIndex();
|
||||
|
||||
ui->retranslateUi(this);
|
||||
|
||||
PopulateSelectionList();
|
||||
ui->selectorList->setCurrentRow(old_row);
|
||||
|
||||
UpdateVisibleTabs();
|
||||
ui->tabWidget->setCurrentIndex(old_index);
|
||||
}
|
||||
|
||||
void ConfigureDialog::PopulateSelectionList() {
|
||||
const std::array<std::pair<QString, QStringList>, 4> items{
|
||||
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
|
||||
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
|
||||
{tr("Graphics"), {tr("Graphics")}},
|
||||
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
|
||||
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}},
|
||||
};
|
||||
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
|
||||
|
||||
ui->selectorList->clear();
|
||||
for (const auto& entry : items) {
|
||||
auto* const item = new QListWidgetItem(entry.first);
|
||||
item->setData(Qt::UserRole, entry.second);
|
||||
@@ -63,24 +89,28 @@ void ConfigureDialog::PopulateSelectionList() {
|
||||
|
||||
void ConfigureDialog::UpdateVisibleTabs() {
|
||||
const auto items = ui->selectorList->selectedItems();
|
||||
if (items.isEmpty())
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab},
|
||||
{tr("System"), ui->systemTab},
|
||||
{tr("Profiles"), ui->profileManagerTab},
|
||||
{tr("Input"), ui->inputTab},
|
||||
{tr("Hotkeys"), ui->hotkeysTab},
|
||||
{tr("Graphics"), ui->graphicsTab},
|
||||
{tr("Audio"), ui->audioTab},
|
||||
{tr("Debug"), ui->debugTab},
|
||||
{tr("Web"), ui->webTab},
|
||||
{tr("Game List"), ui->gameListTab}};
|
||||
const std::map<QString, QWidget*> widgets = {
|
||||
{tr("General"), ui->generalTab},
|
||||
{tr("System"), ui->systemTab},
|
||||
{tr("Profiles"), ui->profileManagerTab},
|
||||
{tr("Input"), ui->inputTab},
|
||||
{tr("Hotkeys"), ui->hotkeysTab},
|
||||
{tr("Graphics"), ui->graphicsTab},
|
||||
{tr("Audio"), ui->audioTab},
|
||||
{tr("Debug"), ui->debugTab},
|
||||
{tr("Web"), ui->webTab},
|
||||
{tr("Game List"), ui->gameListTab},
|
||||
};
|
||||
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
|
||||
|
||||
ui->tabWidget->clear();
|
||||
|
||||
const QStringList tabs = items[0]->data(Qt::UserRole).toStringList();
|
||||
|
||||
for (const auto& tab : tabs)
|
||||
for (const auto& tab : tabs) {
|
||||
ui->tabWidget->addTab(widgets.find(tab)->second, tab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,14 @@ public:
|
||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
|
||||
~ConfigureDialog() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
void RetranslateUI();
|
||||
|
||||
void SetConfiguration();
|
||||
void UpdateVisibleTabs();
|
||||
void PopulateSelectionList();
|
||||
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{
|
||||
constexpr std::array default_icon_sizes{
|
||||
std::make_pair(0, QT_TR_NOOP("None")),
|
||||
std::make_pair(32, QT_TR_NOOP("Small (32x32)")),
|
||||
std::make_pair(64, QT_TR_NOOP("Standard (64x64)")),
|
||||
std::make_pair(128, QT_TR_NOOP("Large (128x128)")),
|
||||
std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")),
|
||||
}};
|
||||
};
|
||||
|
||||
constexpr std::array<const char*, 4> row_text_names{{
|
||||
constexpr std::array row_text_names{
|
||||
QT_TR_NOOP("Filename"),
|
||||
QT_TR_NOOP("Filetype"),
|
||||
QT_TR_NOOP("Title ID"),
|
||||
QT_TR_NOOP("Title Name"),
|
||||
}};
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureGameList::ConfigureGameList(QWidget* parent)
|
||||
@@ -35,7 +35,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
|
||||
InitializeIconSizeComboBox();
|
||||
InitializeRowComboBoxes();
|
||||
|
||||
this->setConfiguration();
|
||||
SetConfiguration();
|
||||
|
||||
// Force game list reload if any of the relevant settings are changed.
|
||||
connect(ui->show_unknown, &QCheckBox::stateChanged, this,
|
||||
@@ -50,7 +50,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
|
||||
|
||||
ConfigureGameList::~ConfigureGameList() = default;
|
||||
|
||||
void ConfigureGameList::applyConfiguration() {
|
||||
void ConfigureGameList::ApplyConfiguration() {
|
||||
UISettings::values.show_unknown = ui->show_unknown->isChecked();
|
||||
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
|
||||
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
|
||||
@@ -63,7 +63,7 @@ void ConfigureGameList::RequestGameListUpdate() {
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
}
|
||||
|
||||
void ConfigureGameList::setConfiguration() {
|
||||
void ConfigureGameList::SetConfiguration() {
|
||||
ui->show_unknown->setChecked(UISettings::values.show_unknown);
|
||||
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
|
||||
ui->icon_size_combobox->setCurrentIndex(
|
||||
@@ -77,7 +77,6 @@ void ConfigureGameList::setConfiguration() {
|
||||
void ConfigureGameList::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
|
||||
@@ -18,12 +18,12 @@ public:
|
||||
explicit ConfigureGameList(QWidget* parent = nullptr);
|
||||
~ConfigureGameList() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void RequestGameListUpdate();
|
||||
|
||||
void setConfiguration();
|
||||
void SetConfiguration();
|
||||
|
||||
void changeEvent(QEvent*) override;
|
||||
void RetranslateUI();
|
||||
|
||||
@@ -18,7 +18,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
QString::fromUtf8(theme.second));
|
||||
}
|
||||
|
||||
this->setConfiguration();
|
||||
SetConfiguration();
|
||||
|
||||
connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this,
|
||||
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
|
||||
@@ -28,7 +28,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
|
||||
ConfigureGeneral::~ConfigureGeneral() = default;
|
||||
|
||||
void ConfigureGeneral::setConfiguration() {
|
||||
void ConfigureGeneral::SetConfiguration() {
|
||||
ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
|
||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
|
||||
@@ -36,7 +36,7 @@ void ConfigureGeneral::setConfiguration() {
|
||||
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
void ConfigureGeneral::ApplyConfiguration() {
|
||||
UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
|
||||
@@ -45,3 +45,15 @@ void ConfigureGeneral::applyConfiguration() {
|
||||
|
||||
Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureGeneral::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
@@ -20,10 +20,13 @@ public:
|
||||
explicit ConfigureGeneral(QWidget* parent = nullptr);
|
||||
~ConfigureGeneral() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void SetConfiguration();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureGeneral> ui;
|
||||
};
|
||||
|
||||
@@ -51,29 +51,29 @@ Resolution FromResolutionFactor(float factor) {
|
||||
|
||||
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
|
||||
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
|
||||
ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
|
||||
connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
|
||||
&QSpinBox::setEnabled);
|
||||
SetConfiguration();
|
||||
|
||||
connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
|
||||
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
||||
const QColor new_bg_color = QColorDialog::getColor(bg_color);
|
||||
if (!new_bg_color.isValid())
|
||||
if (!new_bg_color.isValid()) {
|
||||
return;
|
||||
}
|
||||
UpdateBackgroundColorButton(new_bg_color);
|
||||
});
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
||||
void ConfigureGraphics::setConfiguration() {
|
||||
void ConfigureGraphics::SetConfiguration() {
|
||||
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
|
||||
|
||||
ui->resolution_factor_combobox->setCurrentIndex(
|
||||
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
|
||||
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
|
||||
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
|
||||
ui->frame_limit->setValue(Settings::values.frame_limit);
|
||||
ui->use_compatibility_profile->setEnabled(runtime_lock);
|
||||
ui->use_compatibility_profile->setChecked(Settings::values.use_compatibility_profile);
|
||||
@@ -88,7 +88,7 @@ void ConfigureGraphics::setConfiguration() {
|
||||
Settings::values.bg_blue));
|
||||
}
|
||||
|
||||
void ConfigureGraphics::applyConfiguration() {
|
||||
void ConfigureGraphics::ApplyConfiguration() {
|
||||
Settings::values.resolution_factor =
|
||||
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
|
||||
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
|
||||
@@ -104,6 +104,18 @@ void ConfigureGraphics::applyConfiguration() {
|
||||
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
|
||||
}
|
||||
|
||||
void ConfigureGraphics::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
|
||||
bg_color = color;
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ public:
|
||||
explicit ConfigureGraphics(QWidget* parent = nullptr);
|
||||
~ConfigureGraphics() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void SetConfiguration();
|
||||
|
||||
void UpdateBackgroundColorButton(QColor color);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
|
||||
|
||||
model = new QStandardItemModel(this);
|
||||
model->setColumnCount(3);
|
||||
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
|
||||
|
||||
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
|
||||
ui->hotkey_list->setModel(model);
|
||||
@@ -27,6 +26,8 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
|
||||
|
||||
ui->hotkey_list->setColumnWidth(0, 200);
|
||||
ui->hotkey_list->resizeColumnToContents(1);
|
||||
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
ConfigureHotkeys::~ConfigureHotkeys() = default;
|
||||
@@ -49,6 +50,20 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
|
||||
ui->hotkey_list->expandAll();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
|
||||
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Configure(QModelIndex index) {
|
||||
if (!index.parent().isValid()) {
|
||||
return;
|
||||
@@ -92,7 +107,7 @@ bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
|
||||
void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
|
||||
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
|
||||
const QStandardItem* parent = model->item(key_id, 0);
|
||||
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
|
||||
@@ -112,7 +127,3 @@ void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
|
||||
|
||||
registry.SaveHotkeys();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ public:
|
||||
explicit ConfigureHotkeys(QWidget* parent = nullptr);
|
||||
~ConfigureHotkeys() override;
|
||||
|
||||
void applyConfiguration(HotkeyRegistry& registry);
|
||||
void retranslateUi();
|
||||
void ApplyConfiguration(HotkeyRegistry& registry);
|
||||
|
||||
/**
|
||||
* Populates the hotkey list widget using data from the provided registry.
|
||||
@@ -32,6 +31,9 @@ public:
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void Configure(QModelIndex index);
|
||||
bool IsUsedKey(QKeySequence key_sequence) const;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <QSignalBlocker>
|
||||
#include <QTimer>
|
||||
|
||||
#include "configuration/configure_touchscreen_advanced.h"
|
||||
@@ -50,12 +51,12 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
|
||||
namespace {
|
||||
template <typename Dialog, typename... Args>
|
||||
void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
|
||||
parent.applyConfiguration();
|
||||
parent.ApplyConfiguration();
|
||||
Dialog dialog(&parent, std::forward<Args>(args)...);
|
||||
|
||||
const auto res = dialog.exec();
|
||||
if (res == QDialog::Accepted) {
|
||||
dialog.applyConfiguration();
|
||||
dialog.ApplyConfiguration();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -74,29 +75,25 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
||||
ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,
|
||||
};
|
||||
|
||||
for (auto* controller_box : players_controller) {
|
||||
controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
|
||||
tr("Single Right Joycon"), tr("Single Left Joycon")});
|
||||
}
|
||||
|
||||
this->loadConfiguration();
|
||||
updateUIEnabled();
|
||||
RetranslateUI();
|
||||
LoadConfiguration();
|
||||
UpdateUIEnabled();
|
||||
|
||||
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
|
||||
&ConfigureInput::restoreDefaults);
|
||||
&ConfigureInput::RestoreDefaults);
|
||||
|
||||
for (auto* enabled : players_controller) {
|
||||
connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureInput::updateUIEnabled);
|
||||
&ConfigureInput::UpdateUIEnabled);
|
||||
}
|
||||
connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
|
||||
connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
|
||||
connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
|
||||
&ConfigureInput::updateUIEnabled);
|
||||
connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
|
||||
connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
|
||||
connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
|
||||
&ConfigureInput::UpdateUIEnabled);
|
||||
connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
|
||||
connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
|
||||
connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
|
||||
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
|
||||
&ConfigureInput::updateUIEnabled);
|
||||
&ConfigureInput::UpdateUIEnabled);
|
||||
|
||||
for (std::size_t i = 0; i < players_configure.size(); ++i) {
|
||||
connect(players_configure[i], &QPushButton::pressed, this,
|
||||
@@ -118,7 +115,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
||||
|
||||
ConfigureInput::~ConfigureInput() = default;
|
||||
|
||||
void ConfigureInput::applyConfiguration() {
|
||||
void ConfigureInput::ApplyConfiguration() {
|
||||
for (std::size_t i = 0; i < players_controller.size(); ++i) {
|
||||
const auto controller_type_index = players_controller[i]->currentIndex();
|
||||
|
||||
@@ -144,7 +141,32 @@ void ConfigureInput::applyConfiguration() {
|
||||
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureInput::updateUIEnabled() {
|
||||
void ConfigureInput::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureInput::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
RetranslateControllerComboBoxes();
|
||||
}
|
||||
|
||||
void ConfigureInput::RetranslateControllerComboBoxes() {
|
||||
for (auto* controller_box : players_controller) {
|
||||
[[maybe_unused]] const QSignalBlocker blocker(controller_box);
|
||||
|
||||
controller_box->clear();
|
||||
controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
|
||||
tr("Single Right Joycon"), tr("Single Left Joycon")});
|
||||
}
|
||||
|
||||
LoadPlayerControllerIndices();
|
||||
}
|
||||
|
||||
void ConfigureInput::UpdateUIEnabled() {
|
||||
bool hit_disabled = false;
|
||||
for (auto* player : players_controller) {
|
||||
player->setDisabled(hit_disabled);
|
||||
@@ -168,18 +190,14 @@ void ConfigureInput::updateUIEnabled() {
|
||||
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
|
||||
}
|
||||
|
||||
void ConfigureInput::loadConfiguration() {
|
||||
void ConfigureInput::LoadConfiguration() {
|
||||
std::stable_partition(
|
||||
Settings::values.players.begin(),
|
||||
Settings::values.players.begin() +
|
||||
Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
|
||||
[](const auto& player) { return player.connected; });
|
||||
|
||||
for (std::size_t i = 0; i < players_controller.size(); ++i) {
|
||||
const auto connected = Settings::values.players[i].connected;
|
||||
players_controller[i]->setCurrentIndex(
|
||||
connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
|
||||
}
|
||||
LoadPlayerControllerIndices();
|
||||
|
||||
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
|
||||
ui->handheld_connected->setChecked(
|
||||
@@ -191,10 +209,18 @@ void ConfigureInput::loadConfiguration() {
|
||||
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
|
||||
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
|
||||
|
||||
updateUIEnabled();
|
||||
UpdateUIEnabled();
|
||||
}
|
||||
|
||||
void ConfigureInput::restoreDefaults() {
|
||||
void ConfigureInput::LoadPlayerControllerIndices() {
|
||||
for (std::size_t i = 0; i < players_controller.size(); ++i) {
|
||||
const auto connected = Settings::values.players[i].connected;
|
||||
players_controller[i]->setCurrentIndex(
|
||||
connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureInput::RestoreDefaults() {
|
||||
players_controller[0]->setCurrentIndex(2);
|
||||
|
||||
for (std::size_t i = 1; i < players_controller.size(); ++i) {
|
||||
@@ -207,5 +233,5 @@ void ConfigureInput::restoreDefaults() {
|
||||
ui->keyboard_enabled->setCheckState(Qt::Unchecked);
|
||||
ui->debug_enabled->setCheckState(Qt::Unchecked);
|
||||
ui->touchscreen_enabled->setCheckState(Qt::Checked);
|
||||
updateUIEnabled();
|
||||
UpdateUIEnabled();
|
||||
}
|
||||
|
||||
@@ -30,15 +30,21 @@ public:
|
||||
~ConfigureInput() override;
|
||||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void updateUIEnabled();
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
void RetranslateControllerComboBoxes();
|
||||
|
||||
void UpdateUIEnabled();
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
void LoadConfiguration();
|
||||
void LoadPlayerControllerIndices();
|
||||
|
||||
/// Restore all buttons to their default values.
|
||||
void restoreDefaults();
|
||||
void RestoreDefaults();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureInput> ui;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gridGroupBox">
|
||||
<widget class="QGroupBox" name="gridGroupBox_1">
|
||||
<property name="title">
|
||||
<string>Players</string>
|
||||
</property>
|
||||
@@ -260,7 +260,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gridGroupBox">
|
||||
<widget class="QGroupBox" name="gridGroupBox_2">
|
||||
<property name="title">
|
||||
<string>Handheld</string>
|
||||
</property>
|
||||
@@ -332,7 +332,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gridGroupBox">
|
||||
<widget class="QGroupBox" name="gridGroupBox_3">
|
||||
<property name="title">
|
||||
<string>Other</string>
|
||||
</property>
|
||||
|
||||
@@ -245,7 +245,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
|
||||
button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(button, &QPushButton::released, [=] {
|
||||
handleClick(
|
||||
HandleClick(
|
||||
button_map[button_id],
|
||||
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
@@ -274,7 +274,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
|
||||
analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(analog_button, &QPushButton::released, [=]() {
|
||||
handleClick(analog_map_buttons[analog_id][sub_button_id],
|
||||
HandleClick(analog_map_buttons[analog_id][sub_button_id],
|
||||
[=](const Common::ParamPackage& params) {
|
||||
SetAnalogButton(params, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
@@ -304,7 +304,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
QMessageBox::information(this, tr("Information"),
|
||||
tr("After pressing OK, first move your joystick horizontally, "
|
||||
"and then vertically."));
|
||||
handleClick(
|
||||
HandleClick(
|
||||
analog_map_stick[analog_id],
|
||||
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
|
||||
InputCommon::Polling::DeviceType::Analog);
|
||||
@@ -312,17 +312,17 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
}
|
||||
|
||||
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
|
||||
|
||||
timeout_timer->setSingleShot(true);
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this]() {
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this] {
|
||||
Common::ParamPackage params;
|
||||
for (auto& poller : device_pollers) {
|
||||
params = poller->GetNextInput();
|
||||
if (params.Has("engine")) {
|
||||
setPollingResult(params, false);
|
||||
SetPollingResult(params, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -340,8 +340,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
[this, i] { OnControllerButtonClick(static_cast<int>(i)); });
|
||||
}
|
||||
|
||||
this->loadConfiguration();
|
||||
this->resize(0, 0);
|
||||
LoadConfiguration();
|
||||
resize(0, 0);
|
||||
|
||||
// TODO(wwylele): enable this when we actually emulate it
|
||||
ui->buttonHome->setEnabled(false);
|
||||
@@ -349,7 +349,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
|
||||
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
|
||||
|
||||
void ConfigureInputPlayer::applyConfiguration() {
|
||||
void ConfigureInputPlayer::ApplyConfiguration() {
|
||||
auto& buttons =
|
||||
debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
|
||||
auto& analogs =
|
||||
@@ -373,6 +373,19 @@ void ConfigureInputPlayer::applyConfiguration() {
|
||||
Settings::values.players[player_index].button_color_right = colors[3];
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::OnControllerButtonClick(int i) {
|
||||
const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);
|
||||
if (!new_bg_color.isValid())
|
||||
@@ -382,7 +395,7 @@ void ConfigureInputPlayer::OnControllerButtonClick(int i) {
|
||||
QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::loadConfiguration() {
|
||||
void ConfigureInputPlayer::LoadConfiguration() {
|
||||
if (debug) {
|
||||
std::transform(Settings::values.debug_pad_buttons.begin(),
|
||||
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
|
||||
@@ -399,7 +412,7 @@ void ConfigureInputPlayer::loadConfiguration() {
|
||||
[](const std::string& str) { return Common::ParamPackage(str); });
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
|
||||
if (debug)
|
||||
return;
|
||||
@@ -421,7 +434,7 @@ void ConfigureInputPlayer::loadConfiguration() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::restoreDefaults() {
|
||||
void ConfigureInputPlayer::RestoreDefaults() {
|
||||
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
|
||||
buttons_param[button_id] = Common::ParamPackage{
|
||||
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
|
||||
@@ -434,7 +447,7 @@ void ConfigureInputPlayer::restoreDefaults() {
|
||||
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
|
||||
}
|
||||
}
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::ClearAll() {
|
||||
@@ -458,10 +471,10 @@ void ConfigureInputPlayer::ClearAll() {
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::updateButtonLabels() {
|
||||
void ConfigureInputPlayer::UpdateButtonLabels() {
|
||||
for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
|
||||
button_map[button]->setText(ButtonToText(buttons_param[button]));
|
||||
}
|
||||
@@ -481,7 +494,7 @@ void ConfigureInputPlayer::updateButtonLabels() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::handleClick(
|
||||
void ConfigureInputPlayer::HandleClick(
|
||||
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
button->setText(tr("[press key]"));
|
||||
@@ -509,7 +522,7 @@ void ConfigureInputPlayer::handleClick(
|
||||
poll_timer->start(200); // Check for new inputs every 200ms
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
timeout_timer->stop();
|
||||
@@ -522,22 +535,23 @@ void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params,
|
||||
(*input_setter)(params);
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
input_setter = std::nullopt;
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
|
||||
if (!input_setter || !event)
|
||||
if (!input_setter || !event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->key() != Qt::Key_Escape) {
|
||||
if (want_keyboard_keys) {
|
||||
setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
|
||||
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
|
||||
false);
|
||||
} else {
|
||||
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
|
||||
return;
|
||||
}
|
||||
}
|
||||
setPollingResult({}, true);
|
||||
SetPollingResult({}, true);
|
||||
}
|
||||
|
||||
@@ -38,28 +38,31 @@ public:
|
||||
~ConfigureInputPlayer() override;
|
||||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void OnControllerButtonClick(int i);
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
void LoadConfiguration();
|
||||
/// Restore all buttons to their default values.
|
||||
void restoreDefaults();
|
||||
void RestoreDefaults();
|
||||
/// Clear all input configuration
|
||||
void ClearAll();
|
||||
|
||||
/// Update UI to reflect current configuration.
|
||||
void updateButtonLabels();
|
||||
void UpdateButtonLabels();
|
||||
|
||||
/// Called when the button was pressed.
|
||||
void handleClick(QPushButton* button,
|
||||
void HandleClick(QPushButton* button,
|
||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type);
|
||||
|
||||
/// Finish polling and configure input using the input_setter
|
||||
void setPollingResult(const Common::ParamPackage& params, bool abort);
|
||||
void SetPollingResult(const Common::ParamPackage& params, bool abort);
|
||||
|
||||
/// Handle key press events.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
@@ -15,12 +15,12 @@ namespace {
|
||||
|
||||
template <typename Dialog, typename... Args>
|
||||
void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
|
||||
caller->applyConfiguration();
|
||||
caller->ApplyConfiguration();
|
||||
Dialog dialog(caller, std::forward<Args>(args)...);
|
||||
|
||||
const auto res = dialog.exec();
|
||||
if (res == QDialog::Accepted) {
|
||||
dialog.applyConfiguration();
|
||||
dialog.ApplyConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,27 +103,41 @@ ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
|
||||
&ConfigureInputSimple::OnSelectProfile);
|
||||
connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure);
|
||||
|
||||
this->loadConfiguration();
|
||||
LoadConfiguration();
|
||||
}
|
||||
|
||||
ConfigureInputSimple::~ConfigureInputSimple() = default;
|
||||
|
||||
void ConfigureInputSimple::applyConfiguration() {
|
||||
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))
|
||||
if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) {
|
||||
index = std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
UISettings::values.profile_index = index;
|
||||
}
|
||||
|
||||
void ConfigureInputSimple::loadConfiguration() {
|
||||
void ConfigureInputSimple::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureInputSimple::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureInputSimple::LoadConfiguration() {
|
||||
const auto index = UISettings::values.profile_index;
|
||||
if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0)
|
||||
if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) {
|
||||
ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
|
||||
else
|
||||
} else {
|
||||
ui->profile_combobox->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureInputSimple::OnSelectProfile(int index) {
|
||||
|
||||
@@ -27,11 +27,14 @@ public:
|
||||
~ConfigureInputSimple() override;
|
||||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
void LoadConfiguration();
|
||||
|
||||
void OnSelectProfile(int index);
|
||||
void OnConfigure();
|
||||
|
||||
@@ -84,7 +84,7 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
|
||||
|
||||
button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(button, &QPushButton::released, [=] {
|
||||
handleClick(
|
||||
HandleClick(
|
||||
button_map[button_id],
|
||||
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
@@ -105,48 +105,60 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
|
||||
}
|
||||
|
||||
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
|
||||
|
||||
timeout_timer->setSingleShot(true);
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this]() {
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this] {
|
||||
Common::ParamPackage params;
|
||||
for (auto& poller : device_pollers) {
|
||||
params = poller->GetNextInput();
|
||||
if (params.Has("engine")) {
|
||||
setPollingResult(params, false);
|
||||
SetPollingResult(params, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loadConfiguration();
|
||||
LoadConfiguration();
|
||||
resize(0, 0);
|
||||
}
|
||||
|
||||
ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default;
|
||||
|
||||
void ConfigureMouseAdvanced::applyConfiguration() {
|
||||
void ConfigureMouseAdvanced::ApplyConfiguration() {
|
||||
std::transform(buttons_param.begin(), buttons_param.end(),
|
||||
Settings::values.mouse_buttons.begin(),
|
||||
[](const Common::ParamPackage& param) { return param.Serialize(); });
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::loadConfiguration() {
|
||||
void ConfigureMouseAdvanced::LoadConfiguration() {
|
||||
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
|
||||
buttons_param.begin(),
|
||||
[](const std::string& str) { return Common::ParamPackage(str); });
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::restoreDefaults() {
|
||||
void ConfigureMouseAdvanced::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::RestoreDefaults() {
|
||||
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
|
||||
buttons_param[button_id] = Common::ParamPackage{
|
||||
InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::ClearAll() {
|
||||
@@ -157,16 +169,16 @@ void ConfigureMouseAdvanced::ClearAll() {
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::updateButtonLabels() {
|
||||
void ConfigureMouseAdvanced::UpdateButtonLabels() {
|
||||
for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {
|
||||
button_map[button]->setText(ButtonToText(buttons_param[button]));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::handleClick(
|
||||
void ConfigureMouseAdvanced::HandleClick(
|
||||
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
button->setText(tr("[press key]"));
|
||||
@@ -194,7 +206,7 @@ void ConfigureMouseAdvanced::handleClick(
|
||||
poll_timer->start(200); // Check for new inputs every 200ms
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||
void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
timeout_timer->stop();
|
||||
@@ -207,22 +219,23 @@ void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params
|
||||
(*input_setter)(params);
|
||||
}
|
||||
|
||||
updateButtonLabels();
|
||||
UpdateButtonLabels();
|
||||
input_setter = std::nullopt;
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
|
||||
if (!input_setter || !event)
|
||||
if (!input_setter || !event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->key() != Qt::Key_Escape) {
|
||||
if (want_keyboard_keys) {
|
||||
setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
|
||||
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
|
||||
false);
|
||||
} else {
|
||||
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
|
||||
return;
|
||||
}
|
||||
}
|
||||
setPollingResult({}, true);
|
||||
SetPollingResult({}, true);
|
||||
}
|
||||
|
||||
@@ -25,26 +25,29 @@ public:
|
||||
explicit ConfigureMouseAdvanced(QWidget* parent);
|
||||
~ConfigureMouseAdvanced() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
void LoadConfiguration();
|
||||
/// Restore all buttons to their default values.
|
||||
void restoreDefaults();
|
||||
void RestoreDefaults();
|
||||
/// Clear all input configuration
|
||||
void ClearAll();
|
||||
|
||||
/// Update UI to reflect current configuration.
|
||||
void updateButtonLabels();
|
||||
void UpdateButtonLabels();
|
||||
|
||||
/// Called when the button was pressed.
|
||||
void handleClick(QPushButton* button,
|
||||
void HandleClick(QPushButton* button,
|
||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type);
|
||||
|
||||
/// Finish polling and configure input using the input_setter
|
||||
void setPollingResult(const Common::ParamPackage& params, bool abort);
|
||||
void SetPollingResult(const Common::ParamPackage& params, bool abort);
|
||||
|
||||
/// Handle key press events.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <QTimer>
|
||||
#include <QTreeView>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
@@ -65,12 +67,12 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
|
||||
connect(item_model, &QStandardItemModel::itemChanged,
|
||||
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
|
||||
|
||||
this->loadConfiguration();
|
||||
LoadConfiguration();
|
||||
}
|
||||
|
||||
ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
|
||||
|
||||
void ConfigurePerGameGeneral::applyConfiguration() {
|
||||
void ConfigurePerGameGeneral::ApplyConfiguration() {
|
||||
std::vector<std::string> disabled_addons;
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
@@ -79,15 +81,35 @@ void ConfigurePerGameGeneral::applyConfiguration() {
|
||||
disabled_addons.push_back(item.front()->text().toStdString());
|
||||
}
|
||||
|
||||
auto current = Settings::values.disabled_addons[title_id];
|
||||
std::sort(disabled_addons.begin(), disabled_addons.end());
|
||||
std::sort(current.begin(), current.end());
|
||||
if (disabled_addons != current) {
|
||||
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
|
||||
"game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
|
||||
}
|
||||
|
||||
Settings::values.disabled_addons[title_id] = disabled_addons;
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
|
||||
this->file = std::move(file);
|
||||
this->loadConfiguration();
|
||||
void ConfigurePerGameGeneral::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::loadConfiguration() {
|
||||
void ConfigurePerGameGeneral::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) {
|
||||
this->file = std::move(file);
|
||||
LoadConfiguration();
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::LoadConfiguration() {
|
||||
if (file == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,16 @@ public:
|
||||
~ConfigurePerGameGeneral() override;
|
||||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
void loadFromFile(FileSys::VirtualFile file);
|
||||
void LoadFromFile(FileSys::VirtualFile file);
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void LoadConfiguration();
|
||||
|
||||
std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
|
||||
FileSys::VirtualFile file;
|
||||
u64 title_id;
|
||||
@@ -45,6 +50,4 @@ private:
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
void loadConfiguration();
|
||||
};
|
||||
|
||||
@@ -33,14 +33,13 @@ constexpr std::array<u8, 107> backup_jpeg{
|
||||
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
|
||||
};
|
||||
|
||||
QString GetImagePath(Service::Account::UUID uuid) {
|
||||
QString GetImagePath(Common::UUID uuid) {
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
|
||||
return QString::fromStdString(path);
|
||||
}
|
||||
|
||||
QString GetAccountUsername(const Service::Account::ProfileManager& manager,
|
||||
Service::Account::UUID uuid) {
|
||||
QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {
|
||||
Service::Account::ProfileBase profile;
|
||||
if (!manager.GetProfileBase(uuid, profile)) {
|
||||
return {};
|
||||
@@ -51,14 +50,14 @@ QString GetAccountUsername(const Service::Account::ProfileManager& manager,
|
||||
return QString::fromStdString(text);
|
||||
}
|
||||
|
||||
QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
|
||||
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
|
||||
return ConfigureProfileManager::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()));
|
||||
}
|
||||
|
||||
QPixmap GetIcon(Service::Account::UUID uuid) {
|
||||
QPixmap GetIcon(Common::UUID uuid) {
|
||||
QPixmap icon{GetImagePath(uuid)};
|
||||
|
||||
if (!icon) {
|
||||
@@ -81,11 +80,10 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
|
||||
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
item_model->insertColumns(0, 1);
|
||||
tree_view->setModel(item_model);
|
||||
|
||||
tree_view->setAlternatingRowColors(true);
|
||||
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
||||
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
|
||||
@@ -97,13 +95,11 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
|
||||
tree_view->setIconSize({64, 64});
|
||||
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
item_model->insertColumns(0, 1);
|
||||
item_model->setHeaderData(0, Qt::Horizontal, tr("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 = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(tree_view);
|
||||
@@ -120,12 +116,26 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
|
||||
scene = new QGraphicsScene;
|
||||
ui->current_user_icon->setScene(scene);
|
||||
|
||||
this->setConfiguration();
|
||||
SetConfiguration();
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
ConfigureProfileManager::~ConfigureProfileManager() = default;
|
||||
|
||||
void ConfigureProfileManager::setConfiguration() {
|
||||
void ConfigureProfileManager::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureProfileManager::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
|
||||
}
|
||||
|
||||
void ConfigureProfileManager::SetConfiguration() {
|
||||
enabled = !Core::System::GetInstance().IsPoweredOn();
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
list_items.clear();
|
||||
@@ -165,9 +175,10 @@ void ConfigureProfileManager::UpdateCurrentUser() {
|
||||
ui->current_user_username->setText(username);
|
||||
}
|
||||
|
||||
void ConfigureProfileManager::applyConfiguration() {
|
||||
if (!enabled)
|
||||
void ConfigureProfileManager::ApplyConfiguration() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::Apply();
|
||||
}
|
||||
@@ -190,7 +201,7 @@ void ConfigureProfileManager::AddUser() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto uuid = Service::Account::UUID::Generate();
|
||||
const auto uuid = Common::UUID::Generate();
|
||||
profile_manager->CreateNewUser(uuid, username.toStdString());
|
||||
|
||||
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
|
||||
|
||||
@@ -30,10 +30,14 @@ public:
|
||||
explicit ConfigureProfileManager(QWidget* parent = nullptr);
|
||||
~ConfigureProfileManager() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void setConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void SetConfiguration();
|
||||
|
||||
void PopulateUserList();
|
||||
void UpdateCurrentUser();
|
||||
|
||||
|
||||
@@ -23,22 +23,36 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
|
||||
|
||||
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
|
||||
ui->rng_seed_edit->setEnabled(checked);
|
||||
if (!checked)
|
||||
if (!checked) {
|
||||
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
|
||||
ui->custom_rtc_edit->setEnabled(checked);
|
||||
if (!checked)
|
||||
if (!checked) {
|
||||
ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
|
||||
}
|
||||
});
|
||||
|
||||
this->setConfiguration();
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
ConfigureSystem::~ConfigureSystem() = default;
|
||||
|
||||
void ConfigureSystem::setConfiguration() {
|
||||
void ConfigureSystem::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureSystem::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureSystem::SetConfiguration() {
|
||||
enabled = !Core::System::GetInstance().IsPoweredOn();
|
||||
|
||||
ui->combo_language->setCurrentIndex(Settings::values.language_index);
|
||||
@@ -61,22 +75,25 @@ void ConfigureSystem::setConfiguration() {
|
||||
|
||||
void ConfigureSystem::ReadSystemSettings() {}
|
||||
|
||||
void ConfigureSystem::applyConfiguration() {
|
||||
if (!enabled)
|
||||
void ConfigureSystem::ApplyConfiguration() {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::values.language_index = ui->combo_language->currentIndex();
|
||||
|
||||
if (ui->rng_seed_checkbox->isChecked())
|
||||
if (ui->rng_seed_checkbox->isChecked()) {
|
||||
Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16);
|
||||
else
|
||||
} else {
|
||||
Settings::values.rng_seed = std::nullopt;
|
||||
}
|
||||
|
||||
if (ui->custom_rtc_checkbox->isChecked())
|
||||
if (ui->custom_rtc_checkbox->isChecked()) {
|
||||
Settings::values.custom_rtc =
|
||||
std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
|
||||
else
|
||||
} else {
|
||||
Settings::values.custom_rtc = std::nullopt;
|
||||
}
|
||||
|
||||
Settings::Apply();
|
||||
}
|
||||
@@ -89,8 +106,10 @@ void ConfigureSystem::RefreshConsoleID() {
|
||||
"if you use an outdated config savegame. Continue?");
|
||||
reply = QMessageBox::critical(this, tr("Warning"), warning_text,
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
if (reply == QMessageBox::No)
|
||||
if (reply == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 console_id{};
|
||||
ui->label_console_id->setText(
|
||||
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
|
||||
|
||||
@@ -20,10 +20,14 @@ public:
|
||||
explicit ConfigureSystem(QWidget* parent = nullptr);
|
||||
~ConfigureSystem() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void setConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void SetConfiguration();
|
||||
|
||||
void ReadSystemSettings();
|
||||
|
||||
void RefreshConsoleID();
|
||||
|
||||
@@ -12,29 +12,41 @@ ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
|
||||
&ConfigureTouchscreenAdvanced::restoreDefaults);
|
||||
&ConfigureTouchscreenAdvanced::RestoreDefaults);
|
||||
|
||||
loadConfiguration();
|
||||
LoadConfiguration();
|
||||
resize(0, 0);
|
||||
}
|
||||
|
||||
ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default;
|
||||
|
||||
void ConfigureTouchscreenAdvanced::applyConfiguration() {
|
||||
void ConfigureTouchscreenAdvanced::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureTouchscreenAdvanced::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureTouchscreenAdvanced::ApplyConfiguration() {
|
||||
Settings::values.touchscreen.finger = ui->finger_box->value();
|
||||
Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();
|
||||
Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();
|
||||
Settings::values.touchscreen.rotation_angle = ui->angle_box->value();
|
||||
}
|
||||
|
||||
void ConfigureTouchscreenAdvanced::loadConfiguration() {
|
||||
void ConfigureTouchscreenAdvanced::LoadConfiguration() {
|
||||
ui->finger_box->setValue(Settings::values.touchscreen.finger);
|
||||
ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);
|
||||
ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);
|
||||
ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);
|
||||
}
|
||||
|
||||
void ConfigureTouchscreenAdvanced::restoreDefaults() {
|
||||
void ConfigureTouchscreenAdvanced::RestoreDefaults() {
|
||||
ui->finger_box->setValue(0);
|
||||
ui->diameter_x_box->setValue(15);
|
||||
ui->diameter_y_box->setValue(15);
|
||||
|
||||
@@ -18,13 +18,16 @@ public:
|
||||
explicit ConfigureTouchscreenAdvanced(QWidget* parent);
|
||||
~ConfigureTouchscreenAdvanced() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
void LoadConfiguration();
|
||||
/// Restore all buttons to their default values.
|
||||
void restoreDefaults();
|
||||
void RestoreDefaults();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;
|
||||
};
|
||||
|
||||
@@ -22,41 +22,61 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||
#ifndef USE_DISCORD_PRESENCE
|
||||
ui->discord_group->setVisible(false);
|
||||
#endif
|
||||
this->setConfiguration();
|
||||
|
||||
SetConfiguration();
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
ConfigureWeb::~ConfigureWeb() = default;
|
||||
|
||||
void ConfigureWeb::setConfiguration() {
|
||||
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||
void ConfigureWeb::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureWeb::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
|
||||
ui->telemetry_learn_more->setText(
|
||||
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
|
||||
"underline; color:#039be5;\">Learn more</span></a>"));
|
||||
|
||||
ui->web_signup_link->setOpenExternalLinks(true);
|
||||
ui->web_signup_link->setText(
|
||||
tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
|
||||
"color:#039be5;\">Sign up</span></a>"));
|
||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||
|
||||
ui->web_token_info_link->setText(
|
||||
tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
|
||||
"underline; color:#039be5;\">What is my token?</span></a>"));
|
||||
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureWeb::SetConfiguration() {
|
||||
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||
|
||||
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||
ui->web_signup_link->setOpenExternalLinks(true);
|
||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||
|
||||
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
|
||||
ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
|
||||
ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
|
||||
|
||||
// Connect after setting the values, to avoid calling OnLoginChanged now
|
||||
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||
connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||
|
||||
user_verified = true;
|
||||
|
||||
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
|
||||
}
|
||||
|
||||
void ConfigureWeb::applyConfiguration() {
|
||||
void ConfigureWeb::ApplyConfiguration() {
|
||||
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||
if (user_verified) {
|
||||
@@ -120,7 +140,3 @@ void ConfigureWeb::OnLoginVerified() {
|
||||
"correctly, and that your internet connection is working."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
@@ -19,16 +19,18 @@ public:
|
||||
explicit ConfigureWeb(QWidget* parent = nullptr);
|
||||
~ConfigureWeb() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void RefreshTelemetryID();
|
||||
void OnLoginChanged();
|
||||
void VerifyLogin();
|
||||
void OnLoginVerified();
|
||||
|
||||
void setConfiguration();
|
||||
void SetConfiguration();
|
||||
|
||||
bool user_verified = true;
|
||||
QFutureWatcher<bool> verify_watcher;
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
@@ -30,13 +32,108 @@
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
|
||||
const std::function<QString()>& generator) {
|
||||
if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
|
||||
return generator();
|
||||
}
|
||||
|
||||
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
|
||||
DIR_SEP + filename + '.' + ext;
|
||||
|
||||
FileUtil::CreateFullPath(path);
|
||||
|
||||
if (!FileUtil::Exists(path)) {
|
||||
const auto str = generator();
|
||||
|
||||
QFile file{QString::fromStdString(path)};
|
||||
if (file.open(QFile::WriteOnly)) {
|
||||
file.write(str.toUtf8());
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
QFile file{QString::fromStdString(path)};
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
return QString::fromUtf8(file.readAll());
|
||||
}
|
||||
|
||||
return generator();
|
||||
}
|
||||
|
||||
std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
|
||||
const std::string& filename, const std::string& ext,
|
||||
const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
|
||||
if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
|
||||
return generator();
|
||||
}
|
||||
|
||||
const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
|
||||
DIR_SEP + filename + ".jpeg";
|
||||
const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
|
||||
DIR_SEP + filename + ".appname.txt";
|
||||
|
||||
FileUtil::CreateFullPath(path1);
|
||||
|
||||
if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
|
||||
const auto [icon, nacp] = generator();
|
||||
|
||||
QFile file1{QString::fromStdString(path1)};
|
||||
if (!file1.open(QFile::WriteOnly)) {
|
||||
LOG_ERROR(Frontend, "Failed to open cache file.");
|
||||
return generator();
|
||||
}
|
||||
|
||||
if (!file1.resize(icon.size())) {
|
||||
LOG_ERROR(Frontend, "Failed to resize cache file to necessary size.");
|
||||
return generator();
|
||||
}
|
||||
|
||||
if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) != icon.size()) {
|
||||
LOG_ERROR(Frontend, "Failed to write data to cache file.");
|
||||
return generator();
|
||||
}
|
||||
|
||||
QFile file2{QString::fromStdString(path2)};
|
||||
if (file2.open(QFile::WriteOnly)) {
|
||||
file2.write(nacp.data(), nacp.size());
|
||||
}
|
||||
|
||||
return std::make_pair(icon, nacp);
|
||||
}
|
||||
|
||||
QFile file1(QString::fromStdString(path1));
|
||||
QFile file2(QString::fromStdString(path2));
|
||||
|
||||
if (!file1.open(QFile::ReadOnly)) {
|
||||
LOG_ERROR(Frontend, "Failed to open cache file for reading.");
|
||||
return generator();
|
||||
}
|
||||
|
||||
if (!file2.open(QFile::ReadOnly)) {
|
||||
LOG_ERROR(Frontend, "Failed to open cache file for reading.");
|
||||
return generator();
|
||||
}
|
||||
|
||||
std::vector<u8> vec(file1.size());
|
||||
if (file1.read(reinterpret_cast<char*>(vec.data()), vec.size()) !=
|
||||
static_cast<s64>(vec.size())) {
|
||||
return generator();
|
||||
}
|
||||
|
||||
const auto data = file2.readAll();
|
||||
return std::make_pair(vec, data.toStdString());
|
||||
}
|
||||
|
||||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
|
||||
std::vector<u8>& icon, std::string& name) {
|
||||
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
|
||||
if (icon_file != nullptr)
|
||||
icon = icon_file->ReadAllBytes();
|
||||
if (nacp != nullptr)
|
||||
name = nacp->GetApplicationName();
|
||||
std::tie(icon, name) = GetGameListCachedObject(
|
||||
fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
|
||||
const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
|
||||
return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
|
||||
});
|
||||
}
|
||||
|
||||
bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
@@ -114,8 +211,11 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
||||
};
|
||||
|
||||
if (UISettings::values.show_add_ons) {
|
||||
list.insert(
|
||||
2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
|
||||
const auto patch_versions = GetGameListCachedObject(
|
||||
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
|
||||
return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
|
||||
});
|
||||
list.insert(2, new GameListItem(patch_versions));
|
||||
}
|
||||
|
||||
return list;
|
||||
|
||||
@@ -238,15 +238,13 @@ void GMainWindow::ProfileSelectorSelectProfile() {
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
dialog.exec();
|
||||
|
||||
if (!dialog.GetStatus()) {
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
emit ProfileSelectorFinishedSelection(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
Service::Account::ProfileManager manager;
|
||||
const auto uuid = manager.GetUser(dialog.GetIndex());
|
||||
const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex()));
|
||||
if (!uuid.has_value()) {
|
||||
emit ProfileSelectorFinishedSelection(std::nullopt);
|
||||
return;
|
||||
@@ -261,9 +259,8 @@ void GMainWindow::SoftwareKeyboardGetText(
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
dialog.exec();
|
||||
|
||||
if (!dialog.GetStatus()) {
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
emit SoftwareKeyboardFinishedText(std::nullopt);
|
||||
return;
|
||||
}
|
||||
@@ -850,11 +847,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
QMessageBox::critical(this, tr("Error while loading ROM!"),
|
||||
tr("The ROM format is not supported."));
|
||||
break;
|
||||
case Core::System::ResultStatus::ErrorSystemMode:
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
QMessageBox::critical(this, tr("Error while loading ROM!"),
|
||||
tr("Could not determine the system mode."));
|
||||
break;
|
||||
case Core::System::ResultStatus::ErrorVideoCore:
|
||||
QMessageBox::critical(
|
||||
this, tr("An error occurred initializing the video core."),
|
||||
@@ -901,11 +893,12 @@ void GMainWindow::SelectAndSetCurrentUser() {
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
dialog.exec();
|
||||
|
||||
if (dialog.GetStatus()) {
|
||||
Settings::values.current_user = static_cast<s32>(dialog.GetIndex());
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::values.current_user = dialog.GetIndex();
|
||||
}
|
||||
|
||||
void GMainWindow::BootGame(const QString& filename) {
|
||||
@@ -1055,14 +1048,13 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||
ASSERT(program_id != 0);
|
||||
|
||||
const auto select_profile = [this]() -> s32 {
|
||||
const auto select_profile = [this] {
|
||||
QtProfileSelectionDialog dialog(this);
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
dialog.exec();
|
||||
|
||||
if (!dialog.GetStatus()) {
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1070,11 +1062,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
};
|
||||
|
||||
const auto index = select_profile();
|
||||
if (index == -1)
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Service::Account::ProfileManager manager;
|
||||
const auto user_id = manager.GetUser(index);
|
||||
const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
|
||||
ASSERT(user_id);
|
||||
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
|
||||
FileSys::SaveDataType::SaveData,
|
||||
@@ -1296,10 +1289,10 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
|
||||
}
|
||||
|
||||
ConfigurePerGameGeneral dialog(this, title_id);
|
||||
dialog.loadFromFile(v_file);
|
||||
dialog.LoadFromFile(v_file);
|
||||
auto result = dialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
dialog.applyConfiguration();
|
||||
dialog.ApplyConfiguration();
|
||||
|
||||
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
|
||||
if (reload) {
|
||||
@@ -1396,6 +1389,8 @@ void GMainWindow::OnMenuInstallToNAND() {
|
||||
tr("The file was successfully installed."));
|
||||
game_list->PopulateAsync(UISettings::values.game_directory_path,
|
||||
UISettings::values.game_directory_deepscan);
|
||||
FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
||||
DIR_SEP + "game_list");
|
||||
};
|
||||
|
||||
const auto failed = [this]() {
|
||||
@@ -1688,26 +1683,31 @@ void GMainWindow::ToggleWindowMode() {
|
||||
}
|
||||
|
||||
void GMainWindow::OnConfigure() {
|
||||
ConfigureDialog configureDialog(this, hotkey_registry);
|
||||
auto old_theme = UISettings::values.theme;
|
||||
const auto old_theme = UISettings::values.theme;
|
||||
const bool old_discord_presence = UISettings::values.enable_discord_presence;
|
||||
auto result = configureDialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
configureDialog.applyConfiguration();
|
||||
InitializeHotkeys();
|
||||
if (UISettings::values.theme != old_theme)
|
||||
UpdateUITheme();
|
||||
if (UISettings::values.enable_discord_presence != old_discord_presence)
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence);
|
||||
|
||||
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
|
||||
if (reload) {
|
||||
game_list->PopulateAsync(UISettings::values.game_directory_path,
|
||||
UISettings::values.game_directory_deepscan);
|
||||
}
|
||||
|
||||
config->Save();
|
||||
ConfigureDialog configure_dialog(this, hotkey_registry);
|
||||
const auto result = configure_dialog.exec();
|
||||
if (result != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
configure_dialog.ApplyConfiguration();
|
||||
InitializeHotkeys();
|
||||
if (UISettings::values.theme != old_theme) {
|
||||
UpdateUITheme();
|
||||
}
|
||||
if (UISettings::values.enable_discord_presence != old_discord_presence) {
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence);
|
||||
}
|
||||
|
||||
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
|
||||
if (reload) {
|
||||
game_list->PopulateAsync(UISettings::values.game_directory_path,
|
||||
UISettings::values.game_directory_deepscan);
|
||||
}
|
||||
|
||||
config->Save();
|
||||
}
|
||||
|
||||
void GMainWindow::OnLoadAmiibo() {
|
||||
|
||||
@@ -104,7 +104,7 @@ signals:
|
||||
|
||||
void ErrorDisplayFinished();
|
||||
|
||||
void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);
|
||||
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
|
||||
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
|
||||
void SoftwareKeyboardFinishedCheckDialog();
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ struct Values {
|
||||
uint8_t row_1_text_id;
|
||||
uint8_t row_2_text_id;
|
||||
std::atomic_bool is_game_list_reload_pending{false};
|
||||
bool cache_game_list;
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user