Compare commits

...

98 Commits

Author SHA1 Message Date
Lioncash
96644385ca file_sys/directory: Remove unused DirectoryBackend class
This isn't used at all, so we can just get rid of it.
2019-01-18 14:33:50 -05:00
Hexagon12
7c6bb8c17f Merge pull request #2020 from otaviopace/remove-spaces
audio_core: remove unnecessary spaces on comments
2019-01-14 16:49:37 +02:00
Otávio Pace
215ca770df audio_core: remove unnecessary spaces on comments 2019-01-14 00:22:05 -02:00
bunnei
bc879ae880 Merge pull request #1848 from FreddyFunk/QJsonArray
game_list: Remove a reference of a reference
2019-01-12 02:01:13 -05:00
bunnei
c9ef8b0af1 Merge pull request #1959 from DarkLordZach/custom-rtc
settings: Add support for setting the RTC manually
2019-01-10 17:05:21 -05:00
bunnei
83e8ad2331 Merge pull request #1939 from DarkLordZach/web-applet
applets: Implement HLE web browser applet (LibAppletOff)
2019-01-10 17:04:38 -05:00
bunnei
912f2a520a Merge pull request #2010 from ReinUsesLisp/gmem
gl_global_cache: Add dummy global cache manager
2019-01-08 16:44:53 -05:00
ReinUsesLisp
3121408a90 gl_global_cache: Add dummy global cache manager 2019-01-08 17:47:45 -03:00
Zach Hilman
ac7d8983eb settings: Fix comment structure 2019-01-07 19:40:28 -05:00
Zach Hilman
05dbb47af5 settings: Use std::chrono::seconds instead of s64 for RTC 2019-01-07 19:19:40 -05:00
Zach Hilman
dbb1eb9c29 time: Use custom RTC settings if applicable for game 2019-01-07 19:19:40 -05:00
Zach Hilman
21f1b2889d core: Set custom RTC differential on game boot 2019-01-07 19:19:40 -05:00
Zach Hilman
26c9f12271 qt: Provide UI to edit custom RTC settings 2019-01-07 19:19:40 -05:00
Zach Hilman
c6016856d8 settings: Add custom RTC settings
Stored as signed seconds since epoch.
2019-01-07 19:18:45 -05:00
bunnei
23ebd4920e Merge pull request #1999 from ReinUsesLisp/dirty-shader
gl_shader_cache: Use dirty flags for shaders
2019-01-07 14:22:30 -05:00
ReinUsesLisp
0ab17ab406 gl_shader_cache: Use dirty flags for shaders 2019-01-07 16:13:12 -03:00
bunnei
17a68e5ebe Merge pull request #1989 from lioncash/set
service/vi: Unstub IApplicationDisplayService's SetLayerScalingMode
2019-01-07 12:59:33 -05:00
bunnei
e04d75f44c Merge pull request #1992 from DarkLordZach/move-profile-manager-ui
qt: Move profile manager to own UI tab
2019-01-07 11:41:37 -05:00
bunnei
37ac1bb576 Merge pull request #1990 from ReinUsesLisp/copy-surface-stream-copy
gl_rasterizer_cache: Use GL_STREAM_COPY for PBOs
2019-01-07 11:34:05 -05:00
Zach Hilman
2378ecd0e8 build: Copy web engine resources to correct location 2019-01-05 15:27:15 -05:00
ReinUsesLisp
fc8a8789da gl_rasterizer_cache: Use GL_STREAM_COPY for PBOs
Since the data is doing the path CPU -> GPU -> GPU copy is the most
approximate hint. Using GL_STREAM_DRAW generated a performance warning
on Nvidia's stack. Changing this hint removed the warning.
2019-01-05 02:27:55 -03:00
bunnei
59c665b28e Merge pull request #1988 from lioncash/res
service/vi: Correct reported dimensions from IApplicationDisplayService's GetDisplayResolution()
2019-01-05 00:26:29 -05:00
Lioncash
9e8737b535 service/vi: Correct scaling mode conversions
These values are not equivalent, based off RE. The internal value is put
into a lookup table with the following values:

[3, 0, 1, 2, 4]

So the values absolutely do not map 1:1 like the comment was indicating.
2019-01-04 21:45:18 -05:00
Lioncash
56e51da1d9 service/vi: Factor out scaling mode conversions from the IPC function itself
Avoids entangling the IPC buffer appending with the actual operation of
converting the scaling values over. This also inserts the proper error
handling for invalid scaling values.
2019-01-04 21:45:18 -05:00
Lioncash
40aa1ea9f9 service/vi: Unstub IApplicationDisplayService' SetLayerScalingMode()
This appears to only check if the scaling mode can actually be
handled, rather than actually setting the scaling mode for the layer.

This implements the same error handling performed on the passed in
values.
2019-01-04 21:45:14 -05:00
bunnei
a01832b2e0 Merge pull request #1981 from ogniK5377/open-app-area-create
Return no application area when games try to open an application area
2019-01-04 21:42:23 -05:00
bunnei
1c7696e0d9 Merge pull request #1980 from ogniK5377/applet-msg-update
Proper no message handling for AM::PopMessage
2019-01-04 21:42:12 -05:00
Lioncash
bf036b46fc service/vi: Correct reported dimensions from IApplicationDisplayService's GetDisplayResolution()
Within the actual service, it makes no distinguishing between docked and
undocked modes. This will always return the constants values reporting
1280x720 as the dimensions.
2019-01-04 19:13:32 -05:00
Zach Hilman
54d7b664da qt: Move profile manager to own UI tab 2019-01-04 17:32:13 -05:00
Zach Hilman
0f887daa72 build: Copy QtWebEngineProcess[d].exe to release dir on windows 2019-01-04 10:34:29 -05:00
Mat M
c5262b1890 Merge pull request #1984 from ogniK5377/remove-pulse
Removed pulse event type
2019-01-04 09:18:41 -05:00
David Marcec
4f41fd84ff Removed pulse event type
Pulse is considered a hack and nothing should be using it. We should completely remove it
2019-01-04 20:47:20 +11:00
bunnei
de4f931d13 Merge pull request #1975 from lioncash/vi
service/vi: Minor updates and corrections to the DisplayInfo struct
2019-01-03 22:45:27 -05:00
bunnei
548fe0fc62 Merge pull request #1979 from ogniK5377/30-fps
Fixed botw deadlock due to incorrect event signal type
2019-01-03 22:42:22 -05:00
David Marcec
48a526dba1 Return no application area when games try to open an application area
This will prompt CreateApplicationArea
2019-01-04 13:22:25 +11:00
David Marcec
f2536cafe5 Proper no message handling for AM::PopMessage
When we have no messages, we should be returning an error code.
2019-01-04 13:11:17 +11:00
Hexagon12
9ec9709f68 Merge pull request #1724 from FearlessTobi/port-4412
Port citra-emu/citra#4412: "CONTRIBUTING.md: migrate to the wiki"
2019-01-03 18:12:33 +02:00
David Marcec
5af0340066 Fixed botw deadlock(and possibly 30 fps games rendering too fast? needs testing to confirm)
Upon investigating the issue with #1878, I found that games are the ones who handle the vsync event resetting and not us.
2019-01-03 20:32:47 +11:00
bunnei
2c0f831468 Merge pull request #1976 from lioncash/display
service/vi: Implement OpenDefaultDisplay in terms of OpenDisplay
2019-01-02 21:51:20 -05:00
bunnei
56a97647a3 Merge pull request #1978 from lioncash/enabled
service/vi: Implement SetDisplayEnabled()
2019-01-02 21:34:12 -05:00
bunnei
a6aa710b84 Merge pull request #1942 from DarkLordZach/profile-select-game-boot
qt: Add setting to prompt for user on game boot
2019-01-02 21:05:27 -05:00
bunnei
11f990975c Merge pull request #1941 from DarkLordZach/profile-select-save-data
qt: Use ProfileSelectionDialog when selecting user for save data
2019-01-02 21:05:02 -05:00
Lioncash
ceeff8ebfd service/vi: Implement SetDisplayEnabled()
This IPC command is simply a stub inside the actual service itself, and
just returns a successful error code regardless of input. This is likely
only retained in the service interface to not break older code that relied
upon it succeeding in some way.
2019-01-02 20:48:04 -05:00
bunnei
7538fa4a67 Merge pull request #1977 from lioncash/vi-log
service/vi: Log more information where applicable
2019-01-02 20:36:40 -05:00
Lioncash
5bfd810ab5 service/vi: Log more information where applicable
In many cases, we didn't bother to log out any of the popped data
members. This logs them out to the console within the logging call to
provide more contextual information.
2019-01-02 19:42:10 -05:00
Lioncash
dea1db0414 service/vi: Implement OpenDefaultDisplay in terms of OpenDisplay
Internally within the vi services, this is essentially all that
OpenDefaultDisplay does, so it's trivial to just do the same, and
forward the default display string into the function.
2019-01-02 18:16:36 -05:00
bunnei
c91d2bac45 Merge pull request #1961 from ReinUsesLisp/tex-view-2d
gl_rasterizer_cache: Texture view if shader samples array but OGL is not
2019-01-02 17:51:32 -05:00
Lioncash
351f010cfc service/vi: Correct initial width and height values
Based off RE, it appears that almost all display types seem to use
1920x1080 except for a few (null display, edid display).
2019-01-02 17:36:03 -05:00
Lioncash
fe3bf4f075 service/vi: Document unknown DisplayInfo struct members
It appears that the two members indicate whether a display has a bounded
number of layers (and if set, the second member indicates the total
number of layers).
2019-01-02 17:35:58 -05:00
bunnei
6a0e258dde Merge pull request #1944 from FearlessTobi/port-4187
Port citra-emu/citra#4187: "Qt/Configure: Use sidebar to divide tabs into smaller groups"
2019-01-02 17:24:38 -05:00
bunnei
dd0b17a5f0 Merge pull request #1969 from lioncash/cast
yuzu: Fix truncation warnings within UI code
2019-01-02 17:20:52 -05:00
bunnei
c8c8412901 Merge pull request #1970 from lioncash/header
core/kernel: Remove unnecessary inclusions
2019-01-02 14:24:00 -05:00
Lioncash
cd8924520c yuzu/configure_general: Silence truncation warnings in loadConfiguration()
The QPixmap API expects an unsigned int.
2018-12-31 19:14:48 -05:00
Lioncash
f5f35caf20 yuzu/config: Silence truncation warnings 2018-12-31 19:11:29 -05:00
Lioncash
1f0c8bfeda core/kernel: Remove unnecessary inclusions
Gets rid of a few unnecessary header dependencies in some source files.
2018-12-31 19:04:16 -05:00
Zach Hilman
15501477e7 Update Qt MSVC external to 5.12.0 2018-12-31 13:19:23 -05:00
bunnei
f96bb2520b Merge pull request #1966 from lioncash/backtrace
arm_interface: Minor cleanup
2018-12-31 11:11:00 -05:00
bunnei
09ffa0cb21 Merge pull request #1967 from lioncash/thread
kernel/svc: Sanitize core and priority masks within CreateThread
2018-12-31 11:10:05 -05:00
bunnei
244b46af5a Merge pull request #1965 from lioncash/fmt
externals: Update fmt to 5.3.0
2018-12-31 11:09:37 -05:00
Lioncash
b4242633ad kernel/svc: Correct misleading error message within CreateThread()
This is a bounds check to ensure that the thread priority is within the
valid range of 0-64. If it exceeds 64, that doesn't necessarily mean
that an actual priority of 64 was expected (it actually means whoever
called the function screwed up their math).

Instead clarify the message to indicate the allowed range of thread
priorities.
2018-12-30 21:29:38 -05:00
Lioncash
3a8d38be7e kernel/svc: Sanitize core number and thread priorities in CreateThread()
Now that we handle the kernel capability descriptors we can correct
CreateThread to properly check against the core and priority masks
like the actual kernel does.
2018-12-30 21:23:56 -05:00
Lioncash
8769604144 kernel/process: Rename GetAllowedProcessorMask() and GetAllowedThreadPriorityMask()
Makes them consistent with their kernel capability counterparts.
2018-12-30 21:09:46 -05:00
Lioncash
205e6d3b97 kernel/svc: Simplify thread core ID sanitizing in CreateThread
Rather than use a switch here, this can be collapsed into a simple range
check, which is a little easier on the eyes.
2018-12-30 20:59:54 -05:00
Lioncash
039e58a984 arm_interface: Make include path relative for arm_interface.h
Makes it consistent with the rest of the includes.
2018-12-30 20:46:29 -05:00
Lioncash
a17dd30057 arm_interface: Make LogBacktrace() a const member function
This function doesn't modify instance state, so it can be made const.
2018-12-30 20:44:48 -05:00
Lioncash
776ce5d74c arm_interface: Mark variables as const where applicable in LogBacktrace()
Two of these variables have fixed values, so we can make that
immediately obvious from the get-go.
2018-12-30 20:43:17 -05:00
Lioncash
387bffda5e arm_interface: Remove unnecessary semicolon
Namespaces don't require the use of a semicolon. Silences a -Wextra-semi
warning.
2018-12-30 20:41:33 -05:00
Sebastian Valle
e5dfbe22ee Merge pull request #1956 from lioncash/process-thread
kernel/process: Start the main thread using the specified ideal core
2018-12-30 20:32:41 -05:00
bunnei
331c252509 Merge pull request #1847 from ogniK5377/backtrace-break
Print backtrace on svcBreak
2018-12-29 22:58:13 -05:00
bunnei
f80229b676 Merge pull request #1964 from lioncash/time
service/time: Minor cleanup
2018-12-29 22:57:38 -05:00
ReinUsesLisp
97fb6179b9 gl_rasterizer_cache: Texture view if shader samples array but OGL is not
When a shader samples a texture array but that texture in OpenGL is
created without layers, use a texture view to increase the texture
hierarchy. For example, instead of binding a GL_TEXTURE_2D bind a
GL_TEXTURE_2D_ARRAY view.
2018-12-29 23:49:12 -03:00
Lioncash
fa97f50bff service/time: Minor cleanup to GetClockSnapshot()
Moves some variables closer to their actual usage sites.
2018-12-29 21:42:13 -05:00
Lioncash
614de1dba7 externals: Update fmt to 5.3.0
Updates fmt from 5.2.1 to 5.3.0
2018-12-29 21:33:32 -05:00
Lioncash
af51f123fa service/time: Fill in some structures and remove padding where not necessary 2018-12-29 18:26:32 -05:00
David Marcec
22d4e10664 Moved log backtrace to arm_interface.cpp. Added printing of error code to fatal 2018-12-29 12:55:19 +11:00
Zach Hilman
0c5ede492f travis: Use correct package for linux Qt5WebEngine 2018-12-28 19:29:49 -05:00
Zach Hilman
cb930c4b5a web_browser: Add bounds checking to applet interface 2018-12-28 18:20:29 -05:00
Zach Hilman
ef4c4e239d cmake: Add USE_QT_WEB_ENGINE flag and update build system 2018-12-28 15:32:39 -05:00
Zach Hilman
45da3be40e main: Add main window integrations for QtWebBrowserApplet 2018-12-28 15:32:39 -05:00
Zach Hilman
e00e1fc755 qt: Implement Qt frontend to web browser
Using a custom reimplementation of QWebEngineView and an injector script.
2018-12-28 15:32:39 -05:00
Zach Hilman
32bfa92c71 core: Add getter and setter for WebBrowserApplet frontend 2018-12-28 15:32:39 -05:00
Zach Hilman
85a3368e6d frontend: Add frontend responder for web browser 2018-12-28 15:32:39 -05:00
Zach Hilman
69f622be36 applets: Implement LibAppletOff (Web) applet 2018-12-28 15:32:39 -05:00
Zach Hilman
4c20a39828 loader: Add accessor for Manual RomFS 2018-12-28 15:32:39 -05:00
Zach Hilman
621b25b6be hid: Make Hid service accessible and add GetPressState 2018-12-28 15:32:03 -05:00
Zach Hilman
abbcc8e61e romfs: Add SingleDiscard extraction type
Needed for manual RomFS extraction, as Full generates an extra directory and Truncated generates variable results.
2018-12-28 15:30:36 -05:00
Zach Hilman
94db6e5f3f am: Add size parameter to am:IStorage logging 2018-12-28 15:30:36 -05:00
Lioncash
a81ff6f54c kernel/process: Start the main thread using the specified ideal core
This matches kernel behavior in that processes are started using their
specified ideal core, rather than always starting on core 0.
2018-12-27 21:50:16 -05:00
Lioncash
f80bc712ea kernel: Rename 'default' CPU core to 'ideal' core
This makes the naming more closely match its meaning. It's just a
preferred core, not a required default core. This also makes the usages
of this term consistent across the thread and process implementations.
2018-12-27 21:48:49 -05:00
Lioncash
771431f625 kernel/thread: Move process thread initialization into process.cpp
This function isn't a general purpose function that should be exposed to
everything, given it's specific to initializing the main thread for a
Process instance.

Given that, it's a tad bit more sensible to place this within
process.cpp, which keeps it visible only to the code that actually needs
it.
2018-12-27 20:32:30 -05:00
spycrab
c6da772b24 Qt/Configure: Use sidebar to divide tabs into smaller groups 2018-12-28 01:14:57 +01:00
Zach Hilman
9c2d83cb9c qt: Use ProfileSelectionDialog when selecting user for save data
This allows us to present a much nicer UI to the user over a simple combo box and is made easy with the modular nature of the profile-selection applet frontend.
2018-12-25 10:52:50 -05:00
Zach Hilman
4d61ac08aa qt: Add setting to prompt for user on game boot
Using the QtProfileSelectorDialog, this implementation is trivial. This mimics the real switch behavior of asking which user on every game boot, but it is default disabled as that might get inconvenient.
2018-12-25 10:42:14 -05:00
David Marcec
08d5663cb8 Moved backtrace to ArmInterface 2018-12-19 14:10:51 +11:00
Frederic Laing
6d7514ccec game_list: Remove a reference of a reference 2018-12-03 19:26:36 +01:00
David Marcec
5102c91256 Moved backtrace to ArmInterface
Added to both dynarmic and unicorn
2018-12-03 20:13:48 +11:00
David Marcec
7149332712 Print backtrace on svcBreak
When we get an svcBreak we get a backtrace now
2018-12-03 19:12:09 +11:00
Tobias
4a32d92e6e CONTRIBUTING.md: migrate to the wiki 2018-11-17 17:48:40 +01:00
94 changed files with 2823 additions and 1597 deletions

View File

@@ -1,12 +1,12 @@
#!/bin/bash -ex
apt-get update
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev qtwebengine5-dev wget cmake ninja-build ccache
cd /yuzu
mkdir build && cd build
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
ninja
ccache -s

View File

@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
mkdir build && cd build
cmake --version
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
make -j4
ccache -s

View File

@@ -19,6 +19,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
@@ -302,7 +304,7 @@ endif()
if (ENABLE_QT)
if (YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.10.0-msvc2015_64)
set(QT_VER qt-5.12.0-msvc2017_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
@@ -319,6 +321,10 @@ if (ENABLE_QT)
endif()
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
if (YUZU_USE_QT_WEB_ENGINE)
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets ${QT_PREFIX_HINT})
endif ()
endif()
# Platform-specific library requirements

View File

@@ -5,6 +5,7 @@ function(copy_yuzu_Qt5_deps target_dir)
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
set(PLATFORMS ${DLL_DEST}platforms/)
set(STYLES ${DLL_DEST}styles/)
set(IMAGEFORMATS ${DLL_DEST}imageformats/)
@@ -17,6 +18,31 @@ function(copy_yuzu_Qt5_deps target_dir)
Qt5OpenGL$<$<CONFIG:Debug>:d>.*
Qt5Widgets$<$<CONFIG:Debug>:d>.*
)
if (YUZU_USE_QT_WEB_ENGINE)
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
Qt5Network$<$<CONFIG:Debug>:d>.*
Qt5Positioning$<$<CONFIG:Debug>:d>.*
Qt5PrintSupport$<$<CONFIG:Debug>:d>.*
Qt5Qml$<$<CONFIG:Debug>:d>.*
Qt5Quick$<$<CONFIG:Debug>:d>.*
Qt5QuickWidgets$<$<CONFIG:Debug>:d>.*
Qt5WebChannel$<$<CONFIG:Debug>:d>.*
Qt5WebEngine$<$<CONFIG:Debug>:d>.*
Qt5WebEngineCore$<$<CONFIG:Debug>:d>.*
Qt5WebEngineWidgets$<$<CONFIG:Debug>:d>.*
QtWebEngineProcess$<$<CONFIG:Debug>:d>.*
)
windows_copy_files(${target_dir} ${Qt5_RESOURCES_DIR} ${DLL_DEST}
qtwebengine_resources.pak
qtwebengine_devtools_resources.pak
qtwebengine_resources_100p.pak
qtwebengine_resources_200p.pak
icudtl.dat
)
endif ()
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)

View File

@@ -1,136 +1 @@
# Reporting Issues
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our [Discord server](https://discordapp.com/invite/u77vRWY), [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside yuzu) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable.
# Contributing
yuzu is a brand new project, so we have a great opportunity to keep things clean and well organized early on. As such, coding style is very important when making commits. We run clang-format on our CI to check the code. Please use it to format your code when contributing. However, it doesn't cover all the rules below. Some of them aren't very strict rules since we want to be flexible and we understand that under certain circumstances some of them can be counterproductive. Just try to follow as many of them as possible.
# Using clang format (version 6.0)
When generating the native build script for your toolset, cmake will try to find the correct version of clang format (or will download it on windows). Before running cmake, please install clang format version 6.0 for your platform as follows:
* Windows: do nothing; cmake will download a pre built binary for MSVC and MINGW. MSVC users can additionally install a clang format Visual Studio extension to add features like format on save.
* OSX: run `brew install clang-format`.
* Linux: use your package manager to get an appropriate binary.
If clang format is found, then cmake will add a custom build target that can be run at any time to run clang format against *all* source files and update the formatting in them. This should be used before making a pull request so that the reviewers can spend more time reviewing the code instead of having to worry about minor style violations. On MSVC, you can run clang format by building the clang-format project in the solution. On OSX, you can either use the Makefile target `make clang-format` or by building the clang-format target in XCode. For Makefile builds, you can use the clang-format target with `make clang-format`
### General Rules
* A lot of code was taken from other projects (e.g. Citra, Dolphin, PPSSPP, Gekko). In general, when editing other people's code, follow the style of the module you're in (or better yet, fix the style if it drastically differs from our guide).
* Line width is typically 100 characters. Please do not use 80-characters.
* Don't ever introduce new external dependencies into Core
* Don't use any platform specific code in Core
* Use namespaces often
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`.
### Naming Rules
* Functions: `PascalCase`
* Variables: `lower_case_underscored`. Prefix with `g_` if global.
* Classes: `PascalCase`
* Files and Directories: `lower_case_underscored`
* Namespaces: `PascalCase`, `_` may also be used for clarity (e.g. `ARM_InitCore`)
### Indentation/Whitespace Style
Follow the indentation/whitespace style shown below. Do not use tabs, use 4-spaces instead.
### Comments
* For regular comments, use C++ style (`//`) comments, even for multi-line ones.
* For doc-comments (Doxygen comments), use `/// ` if it's a single line, else use the `/**` `*/` style featured in the example. Start the text on the second line, not the first containing `/**`.
* For items that are both defined and declared in two separate files, put the doc-comment only next to the associated declaration. (In a header file, usually.) Otherwise, put it next to the implementation. Never duplicate doc-comments in both places.
```cpp
// Includes should be sorted lexicographically
// STD includes first
#include <map>
#include <memory>
// then, library includes
#include <nihstro/shared_binary.h>
// finally, yuzu includes
#include "common/math_util.h"
#include "common/vector_math.h"
// each major module is separated
#include "video_core/pica.h"
#include "video_core/video_core.h"
namespace Example {
// Namespace contents are not indented
// Declare globals at the top
int g_foo{}; // {} can be used to initialize types as 0, false, or nullptr
char* g_some_pointer{}; // Pointer * and reference & stick to the type name, and make sure to initialize as nullptr!
/// A colorful enum.
enum SomeEnum {
ColorRed, ///< The color of fire.
ColorGreen, ///< The color of grass.
ColorBlue, ///< Not actually the color of water.
};
/**
* Very important struct that does a lot of stuff.
* Note that the asterisks are indented by one space to align to the first line.
*/
struct Position {
int x{}, y{}; // Always intitialize member variables!
};
// Use "typename" rather than "class" here
template <typename T>
void FooBar() {
const std::string some_string{ "prefer uniform initialization" };
int some_array[]{
5,
25,
7,
42,
};
if (note == the_space_after_the_if) {
CallAfunction();
} else {
// Use a space after the // when commenting
}
// Place a single space after the for loop semicolons, prefer pre-increment
for (int i{}; i != 25; ++i) {
// This is how we write loops
}
DoStuff(this, function, call, takes, up, multiple,
lines, like, this);
if (this || condition_takes_up_multiple &&
lines && like && this || everything ||
alright || then) {
// Leave a blank space before the if block body if the condition was continued across
// several lines.
}
switch (var) {
// No indentation for case label
case 1: {
int case_var{ var + 3 };
DoSomething(case_var);
break;
}
case 3:
DoSomething(var);
return;
default:
// Yes, even break for the last case
break;
}
std::vector<T> you_can_declare, a_few, variables, like_this;
}
}
```
**The Contributor's Guide has moved to [the Citra wiki](https://github.com/citra-emu/citra/wiki/Contributing).**

View File

@@ -42,7 +42,7 @@ before_build:
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
if ($env:BUILD_TYPE -eq 'msvc') {
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
} else {
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
}
@@ -94,6 +94,7 @@ after_build:
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
rm "$RELEASE_DIST\*.exe"
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
Copy-Item .\license.txt -Destination $RELEASE_DIST
Copy-Item .\README.md -Destination $RELEASE_DIST
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*

2
externals/fmt vendored

View File

@@ -53,8 +53,8 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
// Place a lower limit of 5% speed. When a game boots up, there will be
// many silence samples. These do not need to be timestretched.
// Place a lower limit of 5% speed. When a game boots up, there will be
// many silence samples. These do not need to be timestretched.
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
m_sound_touch.setTempo(m_stretch_ratio);

View File

@@ -1,5 +1,6 @@
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
arm/exclusive_monitor.cpp
arm/exclusive_monitor.h
arm/unicorn/arm_unicorn.cpp
@@ -87,6 +88,8 @@ add_library(core STATIC
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
frontend/applets/software_keyboard.h
frontend/applets/web_browser.cpp
frontend/applets/web_browser.h
frontend/emu_window.cpp
frontend/emu_window.h
frontend/framebuffer_layout.cpp
@@ -172,6 +175,8 @@ add_library(core STATIC
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/stub_applet.cpp
hle/service/am/applets/stub_applet.h
hle/service/am/applets/web_browser.cpp
hle/service/am/applets/web_browser.h
hle/service/am/idle.cpp
hle/service/am/idle.h
hle/service/am/omm.cpp

View File

@@ -0,0 +1,27 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/memory.h"
namespace Core {
void ARM_Interface::LogBacktrace() const {
VAddr fp = GetReg(29);
VAddr lr = GetReg(30);
const VAddr sp = GetReg(13);
const VAddr pc = GetPC();
LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
while (true) {
LOG_ERROR(Core_ARM, "{:016X}", lr);
if (!fp) {
break;
}
lr = Memory::Read64(fp + 8) - 4;
fp = Memory::Read64(fp);
}
}
} // namespace Core

View File

@@ -141,6 +141,14 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
virtual void PrepareReschedule() = 0;
/// fp (= r29) points to the last frame record.
/// Note that this is the frame record for the *previous* frame, not the current one.
/// Note we need to subtract 4 from our last read to get the proper address
/// Frame records are two words long:
/// fp+0 : pointer to previous frame record
/// fp+8 : value of lr for frame
void LogBacktrace() const;
};
} // namespace Core

View File

@@ -10,6 +10,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
namespace Core {

View File

@@ -30,8 +30,11 @@
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.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/gpu.h"
#include "video_core/renderer_base.h"
@@ -94,6 +97,11 @@ struct System::Impl {
CoreTiming::Init();
kernel.Initialize();
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
Settings::values.custom_rtc_differential =
Settings::values.custom_rtc.value_or(current_time) - current_time;
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -103,6 +111,8 @@ struct System::Impl {
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
if (software_keyboard == nullptr)
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
if (web_browser == nullptr)
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
auto main_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(main_process.get());
@@ -199,6 +209,11 @@ struct System::Impl {
// Close app loader
app_loader.reset();
// Clear all applets
profile_selector.reset();
software_keyboard.reset();
web_browser.reset();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -233,6 +248,7 @@ struct System::Impl {
/// Frontend applets
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -443,6 +459,14 @@ const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() cons
return *impl->software_keyboard;
}
void System::SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet) {
impl->web_browser = std::move(applet);
}
const Core::Frontend::WebBrowserApplet& System::GetWebBrowser() const {
return *impl->web_browser;
}
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}

View File

@@ -11,11 +11,12 @@
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
#include "frontend/applets/profile_select.h"
namespace Core::Frontend {
class EmuWindow;
class ProfileSelectApplet;
class SoftwareKeyboardApplet;
class WebBrowserApplet;
} // namespace Core::Frontend
namespace FileSys {
@@ -250,6 +251,10 @@ public:
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
void SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet);
const Core::Frontend::WebBrowserApplet& GetWebBrowser() const;
private:
System();

View File

@@ -39,27 +39,4 @@ static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x31
static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
class DirectoryBackend : NonCopyable {
public:
DirectoryBackend() {}
virtual ~DirectoryBackend() {}
/**
* List files contained in the directory
* @param count Number of entries to return at once in entries
* @param entries Buffer to read data into
* @return Number of entries listed
*/
virtual u64 Read(const u64 count, Entry* entries) = 0;
/// Returns the number of entries still left to read.
virtual u64 GetEntryCount() const = 0;
/**
* Close the directory
* @return true if the directory closed correctly
*/
virtual bool Close() const = 0;
};
} // namespace FileSys

View File

@@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
VirtualDir out = std::move(root);
if (type == RomFSExtractionType::SingleDiscard)
return out->GetSubdirectories().front();
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
if (out->GetSubdirectories().front()->GetName() == "data" &&
type == RomFSExtractionType::Truncated)

View File

@@ -33,8 +33,9 @@ struct IVFCHeader {
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
enum class RomFSExtractionType {
Full, // Includes data directory
Truncated, // Traverses into data directory
Full, // Includes data directory
Truncated, // Traverses into data directory
SingleDiscard, // Traverses into the first subdirectory of root
};
// Converts a RomFS binary blob to VFS Filesystem

View File

@@ -0,0 +1,24 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/frontend/applets/web_browser.h"
namespace Core::Frontend {
WebBrowserApplet::~WebBrowserApplet() = default;
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) const {
LOG_INFO(Service_AM,
"(STUBBED) called - No suitable web browser implementation found to open website page "
"at '{}'!",
filename);
finished_callback();
}
} // namespace Core::Frontend

View File

@@ -0,0 +1,28 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <string_view>
namespace Core::Frontend {
class WebBrowserApplet {
public:
virtual ~WebBrowserApplet();
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) const = 0;
};
class DefaultWebBrowserApplet final : public WebBrowserApplet {
public:
~DefaultWebBrowserApplet() override;
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) const override;
};
} // namespace Core::Frontend

View File

@@ -18,7 +18,7 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
namespace IPC {

View File

@@ -8,6 +8,7 @@
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
namespace Kernel {

View File

@@ -6,9 +6,9 @@
#include <memory>
#include <string>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
union ResultCode;
namespace Kernel {

View File

@@ -22,11 +22,16 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
namespace Kernel {
SessionRequestHandler::SessionRequestHandler() = default;
SessionRequestHandler::~SessionRequestHandler() = default;
void SessionRequestHandler::ClientConnected(SharedPtr<ServerSession> server_session) {
server_session->SetHleHandler(shared_from_this());
connected_sessions.push_back(std::move(server_session));

View File

@@ -14,8 +14,6 @@
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
namespace Service {
class ServiceFrameworkBase;
@@ -27,9 +25,13 @@ class Domain;
class HandleTable;
class HLERequestContext;
class Process;
class ServerSession;
class Thread;
class ReadableEvent;
class WritableEvent;
enum class ThreadWakeupReason;
/**
* Interface implemented by HLE Session handlers.
* This can be provided to a ServerSession in order to hook into several relevant events
@@ -37,7 +39,8 @@ class WritableEvent;
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
virtual ~SessionRequestHandler() = default;
SessionRequestHandler();
virtual ~SessionRequestHandler();
/**
* Handles a sync request from the emulated application.

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <atomic>
#include <memory>
#include <mutex>

View File

@@ -36,7 +36,6 @@ enum class HandleType : u32 {
enum class ResetType {
OneShot, ///< Reset automatically on object acquisition
Sticky, ///< Never reset automatically
Pulse, ///< Reset automatically on wakeup
};
class Object : NonCopyable {

View File

@@ -20,6 +20,35 @@
#include "core/settings.h"
namespace Kernel {
namespace {
/**
* Sets up the primary application thread
*
* @param owner_process The parent process for the main thread
* @param kernel The kernel instance to create the main thread under.
* @param entry_point The address at which the thread should start execution
* @param priority The priority to give the main thread
*/
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
// Setup page table so we can write to memory
SetCurrentPageTable(&owner_process.VMManager().page_table);
// Initialize new "main" thread
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0,
owner_process.GetIdealCore(), stack_top, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
const Handle guest_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
thread->SetGuestHandle(guest_handle);
thread->GetContext().cpu_registers[1] = guest_handle;
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
thread->ResumeFromWait();
}
} // Anonymous namespace
CodeSet::CodeSet() = default;
CodeSet::~CodeSet() = default;
@@ -64,7 +93,7 @@ ResultCode Process::ClearSignalState() {
ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
ideal_processor = metadata.GetMainThreadCore();
ideal_core = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
@@ -86,7 +115,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
vm_manager.LogLayout();
ChangeStatus(ProcessStatus::Running);
Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this);
SetupMainThread(*this, kernel, entry_point, main_thread_priority);
}
void Process::PrepareForTermination() {

View File

@@ -14,7 +14,6 @@
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
@@ -27,6 +26,7 @@ namespace Kernel {
class KernelCore;
class ResourceLimit;
class Thread;
struct AddressMapping {
// Address and size must be page-aligned
@@ -168,18 +168,18 @@ public:
/// Gets the resource limit descriptor for this process
SharedPtr<ResourceLimit> GetResourceLimit() const;
/// Gets the default CPU ID for this process
u8 GetDefaultProcessorID() const {
return ideal_processor;
/// Gets the ideal CPU core ID for this process
u8 GetIdealCore() const {
return ideal_core;
}
/// Gets the bitmask of allowed CPUs that this process' threads can run on.
u64 GetAllowedProcessorMask() const {
/// Gets the bitmask of allowed cores that this process' threads can run on.
u64 GetCoreMask() const {
return capabilities.GetCoreMask();
}
/// Gets the bitmask of allowed thread priorities.
u64 GetAllowedThreadPriorityMask() const {
u64 GetPriorityMask() const {
return capabilities.GetPriorityMask();
}
@@ -280,8 +280,8 @@ private:
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
/// The default CPU for this process, threads are scheduled on this cpu by default.
u8 ideal_processor = 0;
/// The ideal CPU core for this process, threads are scheduled on this core by default.
u8 ideal_core = 0;
u32 is_virtual_address_memory_enabled = 0;
/// The Thread Local Storage area is allocated as processes create threads,

View File

@@ -46,9 +46,6 @@ ResultCode ReadableEvent::Reset() {
void ReadableEvent::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
if (reset_type == ResetType::Pulse)
signaled = false;
}
} // namespace Kernel

View File

@@ -6,6 +6,7 @@
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"

View File

@@ -8,7 +8,6 @@
#include <string>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"

View File

@@ -684,6 +684,9 @@ static void Break(u32 reason, u64 info1, u64 info2) {
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
reason, info1, info2);
handle_debug_buffer(info1, info2);
Core::System::GetInstance()
.ArmInterface(static_cast<std::size_t>(GetCurrentThread()->GetProcessorID()))
.LogBacktrace();
ASSERT(false);
Core::CurrentProcess()->PrepareForTermination();
@@ -712,8 +715,8 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
enum class GetInfoType : u64 {
// 1.0.0+
AllowedCpuIdBitmask = 0,
AllowedThreadPrioBitmask = 1,
AllowedCPUCoreMask = 0,
AllowedThreadPriorityMask = 1,
MapRegionBaseAddr = 2,
MapRegionSize = 3,
HeapRegionBaseAddr = 4,
@@ -744,8 +747,8 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
const auto info_id_type = static_cast<GetInfoType>(info_id);
switch (info_id_type) {
case GetInfoType::AllowedCpuIdBitmask:
case GetInfoType::AllowedThreadPrioBitmask:
case GetInfoType::AllowedCPUCoreMask:
case GetInfoType::AllowedThreadPriorityMask:
case GetInfoType::MapRegionBaseAddr:
case GetInfoType::MapRegionSize:
case GetInfoType::HeapRegionBaseAddr:
@@ -771,12 +774,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
}
switch (info_id_type) {
case GetInfoType::AllowedCpuIdBitmask:
*result = process->GetAllowedProcessorMask();
case GetInfoType::AllowedCPUCoreMask:
*result = process->GetCoreMask();
return RESULT_SUCCESS;
case GetInfoType::AllowedThreadPrioBitmask:
*result = process->GetAllowedThreadPriorityMask();
case GetInfoType::AllowedThreadPriorityMask:
*result = process->GetPriorityMask();
return RESULT_SUCCESS;
case GetInfoType::MapRegionBaseAddr:
@@ -1216,29 +1219,35 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
"threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
entry_point, arg, stack_top, priority, processor_id, *out_handle);
auto* const current_process = Core::CurrentProcess();
if (processor_id == THREADPROCESSORID_IDEAL) {
// Set the target CPU to the one specified by the process.
processor_id = current_process->GetIdealCore();
ASSERT(processor_id != THREADPROCESSORID_IDEAL);
}
if (processor_id < THREADPROCESSORID_0 || processor_id > THREADPROCESSORID_3) {
LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id);
return ERR_INVALID_PROCESSOR_ID;
}
const u64 core_mask = current_process->GetCoreMask();
if ((core_mask | (1ULL << processor_id)) != core_mask) {
LOG_ERROR(Kernel_SVC, "Invalid thread core specified ({})", processor_id);
return ERR_INVALID_PROCESSOR_ID;
}
if (priority > THREADPRIO_LOWEST) {
LOG_ERROR(Kernel_SVC, "An invalid priority was specified, expected {} but got {}",
THREADPRIO_LOWEST, priority);
LOG_ERROR(Kernel_SVC,
"Invalid thread priority specified ({}). Must be within the range 0-64",
priority);
return ERR_INVALID_THREAD_PRIORITY;
}
auto* const current_process = Core::CurrentProcess();
if (processor_id == THREADPROCESSORID_DEFAULT) {
// Set the target CPU to the one specified in the process' exheader.
processor_id = current_process->GetDefaultProcessorID();
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
}
switch (processor_id) {
case THREADPROCESSORID_0:
case THREADPROCESSORID_1:
case THREADPROCESSORID_2:
case THREADPROCESSORID_3:
break;
default:
LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id);
return ERR_INVALID_PROCESSOR_ID;
if (((1ULL << priority) & current_process->GetPriorityMask()) == 0) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority specified ({})", priority);
return ERR_INVALID_THREAD_PRIORITY;
}
const std::string name = fmt::format("thread-{:X}", entry_point);
@@ -1636,13 +1645,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
return ERR_INVALID_HANDLE;
}
if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) {
const u8 ideal_cpu_core = thread->GetOwnerProcess()->GetIdealCore();
ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
ASSERT(ideal_cpu_core != static_cast<u8>(THREADPROCESSORID_IDEAL));
// Set the target CPU to the one specified in the process' exheader.
core = default_processor_id;
// Set the target CPU to the ideal core specified by the process.
core = ideal_cpu_core;
mask = 1ULL << core;
}

View File

@@ -12,7 +12,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
@@ -232,29 +231,6 @@ void Thread::BoostPriority(u32 priority) {
current_priority = priority;
}
SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority,
Process& owner_process) {
// Setup page table so we can write to memory
SetCurrentPageTable(&owner_process.VMManager().page_table);
// Initialize new "main" thread
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0,
stack_top, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
const Handle guest_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
thread->SetGuestHandle(guest_handle);
thread->GetContext().cpu_registers[1] = guest_handle;
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
thread->ResumeFromWait();
return thread;
}
void Thread::SetWaitSynchronizationResult(ResultCode result) {
context.cpu_registers[0] = result.raw;
}

View File

@@ -30,12 +30,12 @@ enum ThreadPriority : u32 {
};
enum ThreadProcessorId : s32 {
THREADPROCESSORID_DEFAULT = -2, ///< Run thread on default core specified by exheader
THREADPROCESSORID_0 = 0, ///< Run thread on core 0
THREADPROCESSORID_1 = 1, ///< Run thread on core 1
THREADPROCESSORID_2 = 2, ///< Run thread on core 2
THREADPROCESSORID_3 = 3, ///< Run thread on core 3
THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this
THREADPROCESSORID_IDEAL = -2, ///< Run thread on the ideal core specified by the process.
THREADPROCESSORID_0 = 0, ///< Run thread on core 0
THREADPROCESSORID_1 = 1, ///< Run thread on core 1
THREADPROCESSORID_2 = 2, ///< Run thread on core 2
THREADPROCESSORID_3 = 3, ///< Run thread on core 3
THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this
/// Allowed CPU mask
THREADPROCESSORID_DEFAULT_MASK = (1 << THREADPROCESSORID_0) | (1 << THREADPROCESSORID_1) |
@@ -455,17 +455,6 @@ private:
ThreadActivity activity = ThreadActivity::Normal;
};
/**
* Sets up the primary application thread
* @param kernel The kernel instance to create the main thread under.
* @param entry_point The address at which the thread should start execution
* @param priority The priority to give the main thread
* @param owner_process The parent process for the main thread
* @return A shared pointer to the main thread
*/
SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority,
Process& owner_process);
/**
* Gets the current thread
*/

View File

@@ -68,9 +68,6 @@ void Timer::Clear() {
void Timer::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
if (reset_type == ResetType::Pulse)
signaled = false;
}
void Timer::Signal(int cycles_late) {

View File

@@ -4,11 +4,11 @@
#include <algorithm>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
namespace Kernel {

View File

@@ -6,7 +6,6 @@
#include <vector>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
namespace Kernel {

View File

@@ -4,9 +4,7 @@
#pragma once
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
namespace Kernel {

View File

@@ -23,6 +23,7 @@
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/am/applets/stub_applet.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/am/idle.h"
#include "core/hle/service/am/omm.h"
#include "core/hle/service/am/spsm.h"
@@ -38,11 +39,13 @@
namespace Service::AM {
constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
LibAppletOff = 0x17,
};
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
@@ -460,9 +463,17 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
const auto message = msg_queue->PopMessage();
IPC::ResponseBuilder rb{ctx, 3};
if (message == AppletMessageQueue::AppletMessage::NoMessage) {
LOG_ERROR(Service_AM, "Message queue is empty");
rb.Push(ERR_NO_MESSAGES);
rb.PushEnum<AppletMessageQueue::AppletMessage>(message);
return;
}
rb.Push(RESULT_SUCCESS);
rb.PushEnum<AppletMessageQueue::AppletMessage>(msg_queue->PopMessage());
rb.PushEnum<AppletMessageQueue::AppletMessage>(message);
}
void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
@@ -721,10 +732,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
LOG_DEBUG(Service_AM, "called, offset={}", offset);
const std::vector<u8> data{ctx.ReadBuffer()};
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
if (data.size() > backing.buffer.size() - offset) {
LOG_ERROR(Service_AM,
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
@@ -744,10 +755,10 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
LOG_DEBUG(Service_AM, "called, offset={}", offset);
const std::size_t size{ctx.GetWriteBufferSize()};
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
if (size > backing.buffer.size() - offset) {
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
backing.buffer.size(), size, offset);
@@ -782,6 +793,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
return std::make_shared<Applets::ProfileSelect>();
case AppletId::SoftwareKeyboard:
return std::make_shared<Applets::SoftwareKeyboard>();
case AppletId::LibAppletOff:
return std::make_shared<Applets::WebBrowser>();
default:
LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
static_cast<u32>(id));

View File

@@ -6,7 +6,7 @@
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"

View File

@@ -7,7 +7,7 @@
#include <memory>
#include <queue>
#include "common/swap.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/writable_event.h"
union ResultCode;

View File

@@ -7,7 +7,7 @@
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/profile_select.h"

View File

@@ -0,0 +1,184 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_paths.h"
#include "common/hex_util.h"
#include "common/logging/backend.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_types.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
namespace Service::AM::Applets {
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
// but some may be worth an implementation.
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
struct WebBufferHeader {
u16 count;
INSERT_PADDING_BYTES(6);
};
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
struct WebArgumentHeader {
u16 type;
u16 size;
u32 offset;
};
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
struct WebArgumentResult {
u32 result_code;
std::array<char, 0x1000> last_url;
u64 last_url_size;
};
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
WebBufferHeader header;
ASSERT(sizeof(WebBufferHeader) <= data.size());
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
u64 offset = sizeof(WebBufferHeader);
for (u16 i = 0; i < header.count; ++i) {
WebArgumentHeader arg;
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
offset += sizeof(WebArgumentHeader);
if (arg.type == type) {
std::vector<u8> out(arg.size);
offset += arg.offset;
ASSERT(offset + arg.size <= data.size());
std::memcpy(out.data(), data.data() + offset, out.size());
return out;
}
offset += arg.offset + arg.size;
}
return {};
}
static FileSys::VirtualFile GetManualRomFS() {
auto& loader{Core::System::GetInstance().GetAppLoader()};
FileSys::VirtualFile out;
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
return out;
const auto& installed{FileSystem::GetUnionContents()};
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
FileSys::ContentRecordType::Manual);
if (res != nullptr)
return res->GetRomFS();
return nullptr;
}
WebBrowser::WebBrowser() = default;
WebBrowser::~WebBrowser() = default;
void WebBrowser::Initialize() {
Applet::Initialize();
complete = false;
temporary_dir.clear();
filename.clear();
status = RESULT_SUCCESS;
const auto web_arg_storage = broker.PopNormalDataToApplet();
ASSERT(web_arg_storage != nullptr);
const auto& web_arg = web_arg_storage->GetData();
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
filename = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url_data.data()), url_data.size());
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
"web_applet_manual",
FileUtil::DirectorySeparator::PlatformDefault);
FileUtil::DeleteDirRecursively(temporary_dir);
manual_romfs = GetManualRomFS();
if (manual_romfs == nullptr) {
status = ResultCode(-1);
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
}
filename =
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
FileUtil::DirectorySeparator::PlatformDefault);
}
bool WebBrowser::TransactionComplete() const {
return complete;
}
ResultCode WebBrowser::GetStatus() const {
return status;
}
void WebBrowser::ExecuteInteractive() {
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
}
void WebBrowser::Execute() {
if (complete)
return;
if (status != RESULT_SUCCESS) {
complete = true;
return;
}
const auto& frontend{Core::System::GetInstance().GetWebBrowser()};
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
void WebBrowser::UnpackRomFS() {
if (unpacked)
return;
ASSERT(manual_romfs != nullptr);
const auto dir =
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(dir, temp_dir);
unpacked = true;
}
void WebBrowser::Finalize() {
complete = true;
WebArgumentResult out{};
out.result_code = 0;
out.last_url_size = 0;
std::vector<u8> data(sizeof(WebArgumentResult));
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
broker.PushNormalDataFromApplet(IStorage{data});
broker.SignalStateChanged();
FileUtil::DeleteDirRecursively(temporary_dir);
}
} // namespace Service::AM::Applets

View File

@@ -0,0 +1,44 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
class WebBrowser final : public Applet {
public:
WebBrowser();
~WebBrowser() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
// Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
// directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
// size. Attempting to access files at filename before invocation is likely to not work.
void UnpackRomFS();
// Callback to be fired when the frontend is finished browsing. This will delete the temporary
// manual RomFS extracted files, so ensure this is only called at actual finalization.
void Finalize();
private:
bool complete = false;
bool unpacked = false;
ResultCode status = RESULT_SUCCESS;
FileSys::VirtualFile manual_romfs;
std::string temporary_dir;
std::string filename;
};
} // namespace Service::AM::Applets

View File

@@ -111,7 +111,8 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
}
static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type));
LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}",
static_cast<u32>(fatal_type), error_code.raw);
switch (fatal_type) {
case FatalType::ErrorReportAndScreen:
GenerateErrorReport(error_code, info);

View File

@@ -410,6 +410,8 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw;
libnx_entry.pad.l_stick = pad_state.l_stick;
libnx_entry.pad.r_stick = pad_state.r_stick;
press_state |= static_cast<u32>(pad_state.pad_states.raw);
}
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
shared_memory_entries.size() * sizeof(NPadEntry));
@@ -636,6 +638,10 @@ void Controller_NPad::ClearAllControllers() {
});
}
u32 Controller_NPad::GetAndResetPressState() {
return std::exchange(press_state, 0);
}
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
const bool support_handheld =
std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=

View File

@@ -124,6 +124,10 @@ public:
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
// Logical OR for all buttons presses on all controllers
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
@@ -292,6 +296,8 @@ private:
bool is_connected;
};
u32 press_state{};
NPadType style{};
std::array<NPadEntry, 10> shared_memory_entries{};
std::array<

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,122 @@
#pragma once
#include "controllers/controller_base.h"
#include "core/hle/service/service.h"
namespace CoreTiming {
struct EventType;
}
namespace Kernel {
class SharedMemory;
}
namespace SM {
class ServiceManager;
}
namespace Service::HID {
enum class HidController : std::size_t {
DebugPad,
Touchscreen,
Mouse,
Keyboard,
XPad,
Unknown1,
Unknown2,
Unknown3,
SixAxisSensor,
NPad,
Gesture,
MaxControllers,
};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
IAppletResource();
~IAppletResource() override;
void ActivateController(HidController controller);
void DeactivateController(HidController controller);
template <typename T>
T& GetController(HidController controller) {
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
}
template <typename T>
const T& GetController(HidController controller) const {
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
}
private:
template <typename T>
void MakeController(HidController controller) {
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
void UpdateControllers(u64 userdata, int cycles_late);
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
CoreTiming::EventType* pad_update_event;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
controllers{};
};
class Hid final : public ServiceFramework<Hid> {
public:
Hid();
~Hid() override;
std::shared_ptr<IAppletResource> GetAppletResource();
private:
void CreateAppletResource(Kernel::HLERequestContext& ctx);
void ActivateXpad(Kernel::HLERequestContext& ctx);
void ActivateDebugPad(Kernel::HLERequestContext& ctx);
void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
void ActivateMouse(Kernel::HLERequestContext& ctx);
void ActivateKeyboard(Kernel::HLERequestContext& ctx);
void ActivateGesture(Kernel::HLERequestContext& ctx);
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
void ActivateNpad(Kernel::HLERequestContext& ctx);
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
void DisconnectNpad(Kernel::HLERequestContext& ctx);
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
void SendVibrationValue(Kernel::HLERequestContext& ctx);
void SendVibrationValues(Kernel::HLERequestContext& ctx);
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx);
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx);
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
std::shared_ptr<IAppletResource> applet_resource;
};
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();

View File

@@ -9,9 +9,9 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/lock.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
@@ -20,7 +20,8 @@ namespace Service::NFP {
namespace ErrCodes {
constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
-1); // TODO(ogniK): Find the actual error code
}
constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
} // namespace ErrCodes
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)) {
@@ -292,10 +293,9 @@ private:
}
void OpenApplicationArea(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
// We don't need to worry about this since we can just open the file
LOG_WARNING(Service_NFP, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
rb.Push(ErrCodes::ERR_NO_APPLICATION_AREA);
}
void GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {

View File

@@ -166,7 +166,7 @@ Layer::~Layer() = default;
Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
auto& kernel = Core::System::GetInstance().Kernel();
vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Pulse,
vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
fmt::format("Display VSync Event {}", id));
}

View File

@@ -12,9 +12,16 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
#include "core/settings.h"
namespace Service::Time {
static std::chrono::seconds GetSecondsSinceEpoch() {
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()) +
Settings::values.custom_rtc_differential;
}
static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time,
CalendarAdditionalInfo& additional_info,
[[maybe_unused]] const TimeZoneRule& /*rule*/) {
@@ -68,9 +75,7 @@ public:
private:
void GetCurrentTime(Kernel::HLERequestContext& ctx) {
const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count()};
const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -264,14 +269,9 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::RequestParser rp{ctx};
auto unknown_u8 = rp.PopRaw<u8>();
const auto initial_type = rp.PopRaw<u8>();
ClockSnapshot clock_snapshot{};
const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count()};
CalendarTime calendar_time{};
const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
const std::time_t time(time_since_epoch);
const std::tm* tm = std::localtime(&time);
if (tm == nullptr) {
@@ -280,16 +280,19 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
rb.Push(ResultCode(-1)); // TODO(ogniK): Find appropriate error code
return;
}
SteadyClockTimePoint steady_clock_time_point{CoreTiming::cyclesToMs(CoreTiming::GetTicks()) /
1000};
LocationName location_name{"UTC"};
const SteadyClockTimePoint steady_clock_time_point{
CoreTiming::cyclesToMs(CoreTiming::GetTicks()) / 1000, {}};
CalendarTime calendar_time{};
calendar_time.year = tm->tm_year + 1900;
calendar_time.month = tm->tm_mon + 1;
calendar_time.day = tm->tm_mday;
calendar_time.hour = tm->tm_hour;
calendar_time.minute = tm->tm_min;
calendar_time.second = tm->tm_sec;
ClockSnapshot clock_snapshot{};
clock_snapshot.system_posix_time = time_since_epoch;
clock_snapshot.network_posix_time = time_since_epoch;
clock_snapshot.system_calendar_time = calendar_time;
@@ -302,9 +305,10 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
clock_snapshot.network_calendar_info = additional_info;
clock_snapshot.steady_clock_timepoint = steady_clock_time_point;
clock_snapshot.location_name = location_name;
clock_snapshot.location_name = LocationName{"UTC"};
clock_snapshot.clock_auto_adjustment_enabled = 1;
clock_snapshot.ipc_u8 = unknown_u8;
clock_snapshot.type = initial_type;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot));

View File

@@ -22,7 +22,6 @@ struct CalendarTime {
u8 hour;
u8 minute;
u8 second;
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size");
@@ -30,7 +29,7 @@ struct CalendarAdditionalInfo {
u32_le day_of_week;
u32_le day_of_year;
std::array<u8, 8> name;
INSERT_PADDING_BYTES(1);
u8 is_dst;
s32_le utc_offset;
};
static_assert(sizeof(CalendarAdditionalInfo) == 0x18,
@@ -42,8 +41,10 @@ struct TimeZoneRule {
};
struct SteadyClockTimePoint {
using SourceID = std::array<u8, 16>;
u64_le value;
INSERT_PADDING_WORDS(4);
SourceID source_id;
};
static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
@@ -66,8 +67,9 @@ struct ClockSnapshot {
SteadyClockTimePoint steady_clock_timepoint;
LocationName location_name;
u8 clock_auto_adjustment_enabled;
u8 ipc_u8;
INSERT_PADDING_BYTES(2);
u8 type;
u8 version;
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(ClockSnapshot) == 0xd0, "ClockSnapshot is an invalid size");

View File

@@ -19,6 +19,7 @@
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
@@ -31,12 +32,26 @@
namespace Service::VI {
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
struct DisplayInfo {
/// The name of this particular display.
char display_name[0x40]{"Default"};
u64 unknown_1{1};
u64 unknown_2{1};
u64 width{1280};
u64 height{720};
/// Whether or not the display has a limited number of layers.
u8 has_limited_layers{1};
INSERT_PADDING_BYTES(7){};
/// Indicates the total amount of layers supported by the display.
/// @note This is only valid if has_limited_layers is set.
u64 max_layers{1};
/// Maximum width in pixels.
u64 width{1920};
/// Maximum height in pixels.
u64 height{1080};
};
static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
@@ -502,10 +517,12 @@ private:
void TransactParcel(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 id = rp.Pop<u32>();
auto transaction = static_cast<TransactionId>(rp.Pop<u32>());
u32 flags = rp.Pop<u32>();
LOG_DEBUG(Service_VI, "called, transaction={:X}", static_cast<u32>(transaction));
const u32 id = rp.Pop<u32>();
const auto transaction = static_cast<TransactionId>(rp.Pop<u32>());
const u32 flags = rp.Pop<u32>();
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
static_cast<u32>(transaction), flags);
auto buffer_queue = nv_flinger->GetBufferQueue(id);
@@ -593,9 +610,10 @@ private:
void AdjustRefcount(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 id = rp.Pop<u32>();
s32 addval = rp.PopRaw<s32>();
u32 type = rp.Pop<u32>();
const u32 id = rp.Pop<u32>();
const s32 addval = rp.PopRaw<s32>();
const u32 type = rp.Pop<u32>();
LOG_WARNING(Service_VI, "(STUBBED) called id={}, addval={:08X}, type={:08X}", id, addval,
type);
@@ -605,11 +623,12 @@ private:
void GetNativeHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 id = rp.Pop<u32>();
u32 unknown = rp.Pop<u32>();
const u32 id = rp.Pop<u32>();
const u32 unknown = rp.Pop<u32>();
LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
auto buffer_queue = nv_flinger->GetBufferQueue(id);
const auto buffer_queue = nv_flinger->GetBufferQueue(id);
// TODO(Subv): Find out what this actually is.
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -674,11 +693,12 @@ public:
private:
void SetLayerZ(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
u64 z_value = rp.Pop<u64>();
const u64 layer_id = rp.Pop<u64>();
const u64 z_value = rp.Pop<u64>();
LOG_WARNING(Service_VI, "(STUBBED) called. layer_id=0x{:016X}, z_value=0x{:016X}", layer_id,
z_value);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -686,8 +706,9 @@ private:
void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
bool visibility = rp.Pop<bool>();
const u64 layer_id = rp.Pop<u64>();
const bool visibility = rp.Pop<bool>();
LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:08X}, visibility={}", layer_id,
visibility);
@@ -796,25 +817,27 @@ public:
private:
void CloseDisplay(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display = rp.Pop<u64>();
const u64 display = rp.Pop<u64>();
LOG_WARNING(Service_VI, "(STUBBED) called. display=0x{:016X}", display);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void CreateManagedLayer(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 unknown = rp.Pop<u32>();
const u32 unknown = rp.Pop<u32>();
rp.Skip(1, false);
u64 display = rp.Pop<u64>();
u64 aruid = rp.Pop<u64>();
const u64 display = rp.Pop<u64>();
const u64 aruid = rp.Pop<u64>();
u64 layer_id = nv_flinger->CreateLayer(display);
LOG_WARNING(Service_VI,
"(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}",
unknown, display, aruid);
const u64 layer_id = nv_flinger->CreateLayer(display);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -822,11 +845,12 @@ private:
}
void AddToLayerStack(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 stack = rp.Pop<u32>();
u64 layer_id = rp.Pop<u64>();
const u32 stack = rp.Pop<u32>();
const u64 layer_id = rp.Pop<u64>();
LOG_WARNING(Service_VI, "(STUBBED) called. stack=0x{:08X}, layer_id=0x{:016X}", stack,
layer_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -834,8 +858,9 @@ private:
void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
bool visibility = rp.Pop<bool>();
const u64 layer_id = rp.Pop<u64>();
const bool visibility = rp.Pop<bool>();
LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:X}, visibility={}", layer_id,
visibility);
@@ -852,6 +877,22 @@ public:
~IApplicationDisplayService() = default;
private:
enum class ConvertedScaleMode : u64 {
Freeze = 0,
ScaleToWindow = 1,
ScaleAndCrop = 2,
None = 3,
PreserveAspectRatio = 4,
};
enum class NintendoScaleMode : u32 {
None = 0,
Freeze = 1,
ScaleToWindow = 2,
ScaleAndCrop = 3,
PreserveAspectRatio = 4,
};
void GetRelayService(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
@@ -888,10 +929,23 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
const auto name_buf = rp.PopRaw<std::array<char, 0x40>>();
std::string name(name_buf.begin(), end);
OpenDisplayImpl(ctx, std::string_view{name_buf.data(), name_buf.size()});
}
void OpenDefaultDisplay(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
OpenDisplayImpl(ctx, "Default");
}
void OpenDisplayImpl(Kernel::HLERequestContext& ctx, std::string_view name) {
const auto trim_pos = name.find('\0');
if (trim_pos != std::string_view::npos) {
name.remove_suffix(name.size() - trim_pos);
}
ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
@@ -901,45 +955,65 @@ private:
}
void CloseDisplay(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display_id = rp.Pop<u64>();
const u64 display_id = rp.Pop<u64>();
LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
// This literally does nothing internally in the actual service itself,
// and just returns a successful result code regardless of the input.
void SetDisplayEnabled(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetDisplayResolution(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display_id = rp.Pop<u64>();
const u64 display_id = rp.Pop<u64>();
LOG_DEBUG(Service_VI, "called. display_id=0x{:016X}", display_id);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
if (Settings::values.use_docked_mode) {
rb.Push(static_cast<u64>(DisplayResolution::DockedWidth) *
static_cast<u32>(Settings::values.resolution_factor));
rb.Push(static_cast<u64>(DisplayResolution::DockedHeight) *
static_cast<u32>(Settings::values.resolution_factor));
} else {
rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) *
static_cast<u32>(Settings::values.resolution_factor));
rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) *
static_cast<u32>(Settings::values.resolution_factor));
}
// This only returns the fixed values of 1280x720 and makes no distinguishing
// between docked and undocked dimensions. We take the liberty of applying
// the resolution scaling factor here.
rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) *
static_cast<u32>(Settings::values.resolution_factor));
rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) *
static_cast<u32>(Settings::values.resolution_factor));
}
void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 scaling_mode = rp.Pop<u32>();
u64 unknown = rp.Pop<u64>();
const auto scaling_mode = rp.PopEnum<NintendoScaleMode>();
const u64 unknown = rp.Pop<u64>();
LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}",
static_cast<u32>(scaling_mode), unknown);
IPC::ResponseBuilder rb{ctx, 2};
if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
rb.Push(ERR_OPERATION_FAILED);
return;
}
if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
rb.Push(ERR_UNSUPPORTED);
return;
}
rb.Push(RESULT_SUCCESS);
}
@@ -957,19 +1031,19 @@ private:
}
void OpenLayer(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
IPC::RequestParser rp{ctx};
auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
const auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
const auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
std::string display_name(name_buf.begin(), end);
const std::string display_name(name_buf.begin(), end);
u64 layer_id = rp.Pop<u64>();
u64 aruid = rp.Pop<u64>();
const u64 layer_id = rp.Pop<u64>();
const u64 aruid = rp.Pop<u64>();
u64 display_id = nv_flinger->OpenDisplay(display_name);
u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}, aruid=0x{:016X}", layer_id, aruid);
const u64 display_id = nv_flinger->OpenDisplay(display_name);
const u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
NativeWindow native_window{buffer_queue_id};
IPC::ResponseBuilder rb{ctx, 4};
@@ -978,17 +1052,17 @@ private:
}
void CreateStrayLayer(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
IPC::RequestParser rp{ctx};
u32 flags = rp.Pop<u32>();
const u32 flags = rp.Pop<u32>();
rp.Pop<u32>(); // padding
u64 display_id = rp.Pop<u64>();
const u64 display_id = rp.Pop<u64>();
LOG_DEBUG(Service_VI, "called. flags=0x{:08X}, display_id=0x{:016X}", flags, display_id);
// TODO(Subv): What's the difference between a Stray and a Managed layer?
u64 layer_id = nv_flinger->CreateLayer(display_id);
u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
const u64 layer_id = nv_flinger->CreateLayer(display_id);
const u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
NativeWindow native_window{buffer_queue_id};
IPC::ResponseBuilder rb{ctx, 6};
@@ -998,73 +1072,59 @@ private:
}
void DestroyStrayLayer(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
const u64 layer_id = rp.Pop<u64>();
LOG_WARNING(Service_VI, "(STUBBED) called. layer_id=0x{:016X}", layer_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display_id = rp.Pop<u64>();
const u64 display_id = rp.Pop<u64>();
auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
const auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(vsync_event);
}
enum class ConvertedScaleMode : u64 {
None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no
// scaling/default
Freeze = 1,
ScaleToWindow = 2,
Crop = 3,
NoCrop = 4,
};
// This struct is different, currently it's 1:1 but this might change in the future.
enum class NintendoScaleMode : u32 {
None = 0,
Freeze = 1,
ScaleToWindow = 2,
Crop = 3,
NoCrop = 4,
};
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto mode = rp.PopEnum<NintendoScaleMode>();
const auto mode = rp.PopEnum<NintendoScaleMode>();
LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
const auto converted_mode = ConvertScalingModeImpl(mode);
if (converted_mode.Succeeded()) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(*converted_mode);
} else {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(converted_mode.Code());
}
}
static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
rb.PushEnum(ConvertedScaleMode::None);
break;
return MakeResult(ConvertedScaleMode::None);
case NintendoScaleMode::Freeze:
rb.PushEnum(ConvertedScaleMode::Freeze);
break;
return MakeResult(ConvertedScaleMode::Freeze);
case NintendoScaleMode::ScaleToWindow:
rb.PushEnum(ConvertedScaleMode::ScaleToWindow);
break;
case NintendoScaleMode::Crop:
rb.PushEnum(ConvertedScaleMode::Crop);
break;
case NintendoScaleMode::NoCrop:
rb.PushEnum(ConvertedScaleMode::NoCrop);
break;
return MakeResult(ConvertedScaleMode::ScaleToWindow);
case NintendoScaleMode::ScaleAndCrop:
return MakeResult(ConvertedScaleMode::ScaleAndCrop);
case NintendoScaleMode::PreserveAspectRatio:
return MakeResult(ConvertedScaleMode::PreserveAspectRatio);
default:
UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode));
rb.PushEnum(ConvertedScaleMode::None);
break;
return ERR_OPERATION_FAILED;
}
}
@@ -1082,9 +1142,9 @@ IApplicationDisplayService::IApplicationDisplayService(
"GetIndirectDisplayTransactionService"},
{1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"},
{1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"},
{1011, nullptr, "OpenDefaultDisplay"},
{1011, &IApplicationDisplayService::OpenDefaultDisplay, "OpenDefaultDisplay"},
{1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"},
{1101, nullptr, "SetDisplayEnabled"},
{1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"},
{1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"},
{2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"},
{2021, nullptr, "CloseLayer"},

View File

@@ -259,6 +259,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the RomFS of the manual of the application
* @param file The raw manual RomFS of the game
* @return ResultStatus result of function
*/
virtual ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) {
return ResultStatus::ErrorNotImplemented;
}
protected:
FileSys::VirtualFile file;
bool is_loaded = false;

View File

@@ -158,4 +158,12 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
nacp = *nacp_file;
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) {
const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual);
if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr)
return ResultStatus::ErrorNoRomFS;
file = nca->GetRomFS();
return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
}
} // namespace Loader

View File

@@ -44,6 +44,7 @@ public:
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadControlData(FileSys::NACP& nacp) override;
ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
private:
std::unique_ptr<FileSys::NSP> nsp;

View File

@@ -128,4 +128,13 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) {
const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(),
FileSys::ContentRecordType::Manual);
if (xci->GetStatus() != ResultStatus::Success || nca == nullptr)
return ResultStatus::ErrorXCIMissingPartition;
file = nca->GetRomFS();
return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
}
} // namespace Loader

View File

@@ -44,6 +44,7 @@ public:
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadControlData(FileSys::NACP& control) override;
ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
private:
std::unique_ptr<FileSys::XCI> xci;

View File

@@ -6,6 +6,7 @@
#include <array>
#include <atomic>
#include <chrono>
#include <map>
#include <optional>
#include <string>
@@ -350,6 +351,11 @@ struct Values {
bool use_docked_mode;
bool enable_nfc;
std::optional<u32> rng_seed;
// Measured in seconds since epoch
std::optional<std::chrono::seconds> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
std::chrono::seconds custom_rtc_differential;
s32 current_user;
s32 language_index;

View File

@@ -30,6 +30,8 @@ add_library(video_core STATIC
renderer_base.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
renderer_opengl/gl_global_cache.cpp
renderer_opengl/gl_global_cache.h
renderer_opengl/gl_primitive_assembler.cpp
renderer_opengl/gl_primitive_assembler.h
renderer_opengl/gl_rasterizer.cpp

View File

@@ -135,6 +135,14 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
if (regs.reg_array[method_call.method] != method_call.argument) {
regs.reg_array[method_call.method] = method_call.argument;
// Shader
constexpr u32 shader_registers_count =
sizeof(regs.shader_config[0]) * Regs::MaxShaderProgram / sizeof(u32);
if (method_call.method >= MAXWELL3D_REG_INDEX(shader_config[0]) &&
method_call.method < MAXWELL3D_REG_INDEX(shader_config[0]) + shader_registers_count) {
dirty_flags.shaders = true;
}
// Vertex format
if (method_call.method >= MAXWELL3D_REG_INDEX(vertex_attrib_format) &&
method_call.method <

View File

@@ -1089,10 +1089,13 @@ public:
MemoryManager& memory_manager;
struct DirtyFlags {
bool shaders = true;
bool vertex_attrib_format = true;
u32 vertex_array = 0xFFFFFFFF;
void OnMemoryWrite() {
shaders = true;
vertex_array = 0xFFFFFFFF;
}
};

View File

@@ -0,0 +1,24 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <glad/glad.h>
#include "video_core/renderer_opengl/gl_global_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/utils.h"
namespace OpenGL {
CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} {
buffer.Create();
// Bind and unbind the buffer so it gets allocated by the driver
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory");
}
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
: RasterizerCache{rasterizer} {}
} // namespace OpenGL

View File

@@ -0,0 +1,60 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <glad/glad.h>
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
namespace GLShader {
class GlobalMemoryEntry;
} // namespace GLShader
class RasterizerOpenGL;
class CachedGlobalRegion;
using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
class CachedGlobalRegion final : public RasterizerCacheObject {
public:
explicit CachedGlobalRegion(VAddr addr, u32 size);
/// Gets the address of the shader in guest memory, required for cache management
VAddr GetAddr() const {
return addr;
}
/// Gets the size of the shader in guest memory, required for cache management
std::size_t GetSizeInBytes() const {
return size;
}
/// Gets the GL program handle for the buffer
GLuint GetBufferHandle() const {
return buffer.handle;
}
// TODO(Rodrigo): When global memory is written (STG), implement flushing
void Flush() override {
UNIMPLEMENTED();
}
private:
VAddr addr{};
u32 size{};
OGLBuffer buffer;
};
class GlobalRegionCacheOpenGL final : public RasterizerCache<GlobalRegion> {
public:
explicit GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer);
};
} // namespace OpenGL

View File

@@ -101,7 +101,7 @@ struct FramebufferCacheKey {
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info},
buffer_cache(*this, STREAM_BUFFER_SIZE) {
buffer_cache(*this, STREAM_BUFFER_SIZE), global_cache{*this} {
// Create sampler objects
for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -293,7 +293,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
@@ -376,6 +376,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
SyncClipEnabled(clip_distances);
gpu.dirty_flags.shaders = false;
}
void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
@@ -761,6 +763,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.InvalidateRegion(addr, size);
shader_cache.InvalidateRegion(addr, size);
global_cache.InvalidateRegion(addr, size);
buffer_cache.InvalidateRegion(addr, size);
}
@@ -1014,8 +1017,11 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
Surface surface = res_cache.GetTextureSurface(texture, entry);
if (surface != nullptr) {
state.texture_units[current_bindpoint].texture = surface->Texture().handle;
state.texture_units[current_bindpoint].target = surface->Target();
const GLuint handle =
entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle;
const GLenum target = entry.IsArray() ? surface->TargetLayer() : surface->Target();
state.texture_units[current_bindpoint].texture = handle;
state.texture_units[current_bindpoint].target = target;
state.texture_units[current_bindpoint].swizzle.r =
MaxwellToGL::SwizzleSource(texture.tic.x_source);
state.texture_units[current_bindpoint].swizzle.g =

View File

@@ -23,6 +23,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_global_cache.h"
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -66,6 +67,10 @@ public:
static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0,
"The maximum size of a constbuffer must be a multiple of the size of GLvec4");
static constexpr std::size_t MaxGlobalMemorySize = 0x10000;
static_assert(MaxGlobalMemorySize % sizeof(float) == 0,
"The maximum size of a global memory must be a multiple of the size of float");
private:
class SamplerInfo {
public:
@@ -105,7 +110,7 @@ private:
bool using_depth_fb = true, bool preserve_contents = true,
std::optional<std::size_t> single_color_target = {});
/*
/**
* Configures the current constbuffers to use for the draw command.
* @param stage The shader stage to configure buffers for.
* @param shader The shader object that contains the specified stage.
@@ -115,7 +120,7 @@ private:
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
GLenum primitive_mode, u32 current_bindpoint);
/*
/**
* Configures the current textures to use for the draw command.
* @param stage The shader stage to configure textures for.
* @param shader The shader object that contains the specified stage.
@@ -185,6 +190,7 @@ private:
RasterizerCacheOpenGL res_cache;
ShaderCacheOpenGL shader_cache;
GlobalRegionCacheOpenGL global_cache;
Core::Frontend::EmuWindow& emu_window;

View File

@@ -44,6 +44,17 @@ struct FormatTuple {
bool compressed;
};
static void ApplyTextureDefaults(GLenum target, u32 max_mip_level) {
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, max_mip_level - 1);
if (max_mip_level == 1) {
glTexParameterf(target, GL_TEXTURE_LOD_BIAS, 1000.0);
}
}
void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
@@ -441,7 +452,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_COPY);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
@@ -530,6 +541,9 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
glActiveTexture(GL_TEXTURE0);
const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type);
gl_internal_format = format_tuple.internal_format;
gl_is_compressed = format_tuple.compressed;
if (!format_tuple.compressed) {
// Only pre-create the texture for non-compressed textures.
switch (params.target) {
@@ -558,15 +572,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
}
}
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL,
params.max_mip_level - 1);
if (params.max_mip_level == 1) {
glTexParameterf(SurfaceTargetToGL(params.target), GL_TEXTURE_LOD_BIAS, 1000.0);
}
ApplyTextureDefaults(SurfaceTargetToGL(params.target), params.max_mip_level);
LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
SurfaceParams::SurfaceTargetName(params.target));
@@ -864,6 +870,31 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
void CachedSurface::EnsureTextureView() {
if (texture_view.handle != 0)
return;
// Compressed texture are not being created with immutable storage
UNIMPLEMENTED_IF(gl_is_compressed);
const GLenum target{TargetLayer()};
texture_view.Create();
glTextureView(texture_view.handle, target, texture.handle, gl_internal_format, 0,
params.max_mip_level, 0, 1);
OpenGLState cur_state = OpenGLState::GetCurState();
const auto& old_tex = cur_state.texture_units[0];
SCOPE_EXIT({
cur_state.texture_units[0] = old_tex;
cur_state.Apply();
});
cur_state.texture_units[0].texture = texture_view.handle;
cur_state.texture_units[0].target = target;
cur_state.Apply();
ApplyTextureDefaults(target, params.max_mip_level);
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
if (params.type == SurfaceType::Fill)

View File

@@ -293,10 +293,31 @@ public:
return texture;
}
const OGLTexture& TextureLayer() {
if (params.is_layered) {
return Texture();
}
EnsureTextureView();
return texture_view;
}
GLenum Target() const {
return gl_target;
}
GLenum TargetLayer() const {
using VideoCore::Surface::SurfaceTarget;
switch (params.target) {
case SurfaceTarget::Texture1D:
return GL_TEXTURE_1D_ARRAY;
case SurfaceTarget::Texture2D:
return GL_TEXTURE_2D_ARRAY;
case SurfaceTarget::TextureCubemap:
return GL_TEXTURE_CUBE_MAP_ARRAY;
}
return Target();
}
const SurfaceParams& GetSurfaceParams() const {
return params;
}
@@ -311,11 +332,16 @@ public:
private:
void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle);
void EnsureTextureView();
OGLTexture texture;
OGLTexture texture_view;
std::vector<std::vector<u8>> gl_buffer;
SurfaceParams params;
GLenum gl_target;
std::size_t cached_size_in_bytes;
SurfaceParams params{};
GLenum gl_target{};
GLenum gl_internal_format{};
bool gl_is_compressed{};
std::size_t cached_size_in_bytes{};
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {

View File

@@ -188,6 +188,10 @@ void CachedShader::CalculateProperties() {
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
return last_shaders[static_cast<u32>(program)];
}
const VAddr program_addr{GetShaderAddress(program)};
// Look up shader in the cache based on address
@@ -199,7 +203,7 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
Register(shader);
}
return shader;
return last_shaders[static_cast<u32>(program)] = shader;
}
} // namespace OpenGL

View File

@@ -4,6 +4,7 @@
#pragma once
#include <array>
#include <map>
#include <memory>
@@ -115,6 +116,9 @@ public:
/// Gets the current specified shader stage program
Shader GetStageProgram(Maxwell::ShaderProgram program);
private:
std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
};
} // namespace OpenGL

View File

@@ -11,6 +11,8 @@ add_executable(yuzu
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
applets/web_browser.cpp
applets/web_browser.h
bootmanager.cpp
bootmanager.h
compatibility_list.cpp
@@ -37,6 +39,8 @@ add_executable(yuzu
configuration/configure_input_simple.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_per_general.cpp
@@ -94,6 +98,7 @@ set(UIS
configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
configuration/configure_profile_manager.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
@@ -154,6 +159,11 @@ if (USE_DISCORD_PRESENCE)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets)
target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
endif ()
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()

View File

@@ -0,0 +1,113 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include <QKeyEvent>
#include "core/hle/lock.h"
#include "yuzu/applets/web_browser.h"
#include "yuzu/main.h"
#ifdef YUZU_USE_QT_WEB_ENGINE
constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(
window.nx = {};
window.nx.playReport = {};
window.nx.playReport.setCounterSetIdentifier = function () {
console.log("nx.playReport.setCounterSetIdentifier called - unimplemented");
};
window.nx.playReport.incrementCounter = function () {
console.log("nx.playReport.incrementCounter called - unimplemented");
};
window.nx.footer = {};
window.nx.footer.unsetAssign = function () {
console.log("nx.footer.unsetAssign called - unimplemented");
};
var yuzu_key_callbacks = [];
window.nx.footer.setAssign = function(key, discard1, func, discard2) {
switch (key) {
case 'A':
yuzu_key_callbacks[0] = func;
break;
case 'B':
yuzu_key_callbacks[1] = func;
break;
case 'X':
yuzu_key_callbacks[2] = func;
break;
case 'Y':
yuzu_key_callbacks[3] = func;
break;
case 'L':
yuzu_key_callbacks[6] = func;
break;
case 'R':
yuzu_key_callbacks[7] = func;
break;
}
};
var applet_done = false;
window.nx.endApplet = function() {
applet_done = true;
};
)";
QString GetNXShimInjectionScript() {
return QString::fromStdString(NX_SHIM_INJECT_SCRIPT);
}
NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {}
void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) {
parent()->event(event);
}
void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) {
parent()->event(event);
}
#endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage,
Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this,
&QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this,
&QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection);
}
QtWebBrowser::~QtWebBrowser() = default;
void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) const {
this->unpack_romfs_callback = unpack_romfs_callback;
this->finished_callback = finished_callback;
const auto index = url.find('?');
if (index == std::string::npos) {
emit MainWindowOpenPage(url, "");
} else {
const auto front = url.substr(0, index);
const auto back = url.substr(index);
emit MainWindowOpenPage(front, back);
}
}
void QtWebBrowser::MainWindowUnpackRomFS() {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
unpack_romfs_callback();
}
void QtWebBrowser::MainWindowFinishedBrowsing() {
// Acquire the HLE mutex
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
finished_callback();
}

View File

@@ -0,0 +1,53 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <QObject>
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QWebEngineView>
#endif
#include "core/frontend/applets/web_browser.h"
class GMainWindow;
#ifdef YUZU_USE_QT_WEB_ENGINE
QString GetNXShimInjectionScript();
class NXInputWebEngineView : public QWebEngineView {
public:
explicit NXInputWebEngineView(QWidget* parent = nullptr);
protected:
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
};
#endif
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
Q_OBJECT
public:
explicit QtWebBrowser(GMainWindow& main_window);
~QtWebBrowser() override;
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) const override;
signals:
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
public slots:
void MainWindowUnpackRomFS();
void MainWindowFinishedBrowsing();
private:
mutable std::function<void()> unpack_romfs_callback;
mutable std::function<void()> finished_callback;
};

View File

@@ -419,13 +419,21 @@ void Config::ReadValues() {
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
const auto enabled = qt_config->value("rng_seed_enabled", false).toBool();
if (enabled) {
const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
if (rng_seed_enabled) {
Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
} else {
Settings::values.rng_seed = std::nullopt;
}
const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
if (custom_rtc_enabled) {
Settings::values.custom_rtc =
std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
} else {
Settings::values.custom_rtc = std::nullopt;
}
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
@@ -470,6 +478,8 @@ void Config::ReadValues() {
qt_config->value("enable_discord_presence", true).toBool();
UISettings::values.screenshot_resolution_factor =
static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
UISettings::values.select_user_on_boot =
qt_config->value("select_user_on_boot", false).toBool();
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -651,6 +661,11 @@ void Config::SaveValues() {
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
qt_config->setValue("custom_rtc",
QVariant::fromValue<long long>(
Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
@@ -680,7 +695,7 @@ void Config::SaveValues() {
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
qt_config->beginWriteArray("disabled");
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(j);
qt_config->setArrayIndex(static_cast<int>(j));
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
}
qt_config->endArray();
@@ -693,6 +708,7 @@ void Config::SaveValues() {
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
qt_config->setValue("screenshot_resolution_factor",
UISettings::values.screenshot_resolution_factor);
qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>461</width>
<height>659</height>
<width>382</width>
<height>241</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,51 +15,76 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureGameList" name="gameListTab">
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="selectorList">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureGameList" name="gameListTab">
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureProfileManager" name="profileManagerTab">
<attribute name="title">
<string>Profiles</string>
</attribute>
</widget>
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
@@ -77,18 +102,18 @@
<header>configuration/configure_general.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGameList</class>
<extends>QWidget</extends>
<header>configuration/configure_gamelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
<header>configuration/configure_system.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureProfileManager</class>
<extends>QWidget</extends>
<header>configuration/configure_profile_manager.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
@@ -101,12 +126,6 @@
<header>configuration/configure_debug.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureInputSimple</class>
<extends>QWidget</extends>
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
@@ -119,6 +138,18 @@
<header>configuration/configure_web.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGameList</class>
<extends>QWidget</extends>
<header>configuration/configure_gamelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureInputSimple</class>
<extends>QWidget</extends>
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>

View File

@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QHash>
#include <QListWidgetItem>
#include "core/settings.h"
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
@@ -13,6 +15,13 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry
ui->setupUi(this);
ui->generalTab->PopulateHotkeyList(registry);
this->setConfiguration();
this->PopulateSelectionList();
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
adjustSize();
ui->selectorList->setCurrentRow(0);
}
ConfigureDialog::~ConfigureDialog() = default;
@@ -23,6 +32,7 @@ void ConfigureDialog::applyConfiguration() {
ui->generalTab->applyConfiguration();
ui->gameListTab->applyConfiguration();
ui->systemTab->applyConfiguration();
ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration();
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
@@ -30,3 +40,41 @@ void ConfigureDialog::applyConfiguration() {
ui->webTab->applyConfiguration();
Settings::Apply();
}
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")}}}};
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
item->setData(Qt::UserRole, entry.second);
ui->selectorList->addItem(item);
}
}
void ConfigureDialog::UpdateVisibleTabs() {
const auto items = ui->selectorList->selectedItems();
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("Graphics"), ui->graphicsTab},
{tr("Audio"), ui->audioTab},
{tr("Debug"), ui->debugTab},
{tr("Web"), ui->webTab},
{tr("Game List"), ui->gameListTab}};
ui->tabWidget->clear();
const QStringList tabs = items[0]->data(Qt::UserRole).toStringList();
for (const auto& tab : tabs)
ui->tabWidget->addTab(widgets.find(tab)->second, tab);
}

View File

@@ -24,6 +24,8 @@ public:
private:
void setConfiguration();
void UpdateVisibleTabs();
void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui;
};

View File

@@ -30,6 +30,7 @@ ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::setConfiguration() {
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->enable_nfc->setChecked(Settings::values.enable_nfc);
@@ -42,6 +43,7 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_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();
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();

View File

@@ -38,6 +38,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_user_on_boot">
<property name="text">
<string>Prompt for user on game boot</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@@ -120,7 +120,7 @@ void ConfigurePerGameGeneral::loadConfiguration() {
QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), bytes.size());
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
@@ -130,7 +130,7 @@ void ConfigurePerGameGeneral::loadConfiguration() {
scene->clear();
QPixmap map;
map.loadFromData(bytes.data(), bytes.size());
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));

View File

@@ -0,0 +1,300 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QHeaderView>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QTreeView>
#include <QVBoxLayout>
#include "common/assert.h"
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_profile_manager.h"
#include "yuzu/configuration/configure_profile_manager.h"
#include "yuzu/util/limitable_input_dialog.h"
namespace {
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
constexpr std::array<u8, 107> backup_jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
QString GetImagePath(Service::Account::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QString GetAccountUsername(const Service::Account::ProfileManager& manager,
Service::Account::UUID uuid) {
Service::Account::ProfileBase profile;
if (!manager.GetProfileBase(uuid, profile)) {
return {};
}
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
return QString::fromStdString(text);
}
QString FormatUserEntryText(const QString& username, Service::Account::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 icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"),
description_text, 1,
static_cast<int>(Service::Account::profile_username_size));
}
} // Anonymous namespace
ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureProfileManager),
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ui->setupUi(this);
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 1);
item_model->setHeaderData(0, Qt::Horizontal, "Users");
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser);
connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser);
connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser);
connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser);
connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage);
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
this->setConfiguration();
}
ConfigureProfileManager::~ConfigureProfileManager() = default;
void ConfigureProfileManager::setConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
item_model->removeRows(0, item_model->rowCount());
list_items.clear();
PopulateUserList();
UpdateCurrentUser();
}
void ConfigureProfileManager::PopulateUserList() {
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(user, profile))
continue;
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
item_model->appendRow(item);
}
void ConfigureProfileManager::UpdateCurrentUser() {
ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
ASSERT(current_user);
const auto username = GetAccountUsername(*profile_manager, *current_user);
scene->clear();
scene->addPixmap(
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
ui->current_user_username->setText(username);
}
void ConfigureProfileManager::applyConfiguration() {
if (!enabled)
return;
Settings::Apply();
}
void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
Settings::values.current_user =
std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
UpdateCurrentUser();
ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
ui->pm_rename->setEnabled(true);
ui->pm_set_image->setEnabled(true);
}
void ConfigureProfileManager::AddUser() {
const auto username =
GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
if (username.isEmpty()) {
return;
}
const auto uuid = Service::Account::UUID::Generate();
profile_manager->CreateNewUser(uuid, username.toStdString());
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
}
void ConfigureProfileManager::RenameUser() {
const auto user = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(user);
ASSERT(uuid);
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(*uuid, profile))
return;
const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
if (new_username.isEmpty()) {
return;
}
const auto username_std = new_username.toStdString();
std::fill(profile.username.begin(), profile.username.end(), '\0');
std::copy(username_std.begin(), username_std.end(), profile.username.begin());
profile_manager->SetProfileBase(*uuid, profile);
item_model->setItem(
user, 0,
new QStandardItem{GetIcon(*uuid),
FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
UpdateCurrentUser();
}
void ConfigureProfileManager::DeleteUser() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto username = GetAccountUsername(*profile_manager, *uuid);
const auto confirm = QMessageBox::question(
this, tr("Confirm Delete"),
tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
if (confirm == QMessageBox::No)
return;
if (Settings::values.current_user == tree_view->currentIndex().row())
Settings::values.current_user = 0;
UpdateCurrentUser();
if (!profile_manager->RemoveUser(*uuid))
return;
item_model->removeRows(tree_view->currentIndex().row(), 1);
tree_view->clearSelection();
ui->pm_remove->setEnabled(false);
ui->pm_rename->setEnabled(false);
}
void ConfigureProfileManager::SetUserImage() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
tr("JPEG Images (*.jpg *.jpeg)"));
if (file.isEmpty()) {
return;
}
const auto image_path = GetImagePath(*uuid);
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
QMessageBox::warning(
this, tr("Error deleting image"),
tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
return;
}
const auto raw_path = QString::fromStdString(
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
tr("Unable to delete existing file: %1.").arg(raw_path));
return;
}
const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
QMessageBox::warning(
this, tr("Error creating user image directory"),
tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
return;
}
if (!QFile::copy(file, image_path)) {
QMessageBox::warning(this, tr("Error copying user image"),
tr("Unable to copy image from %1 to %2").arg(file, image_path));
return;
}
const auto username = GetAccountUsername(*profile_manager, *uuid);
item_model->setItem(index, 0,
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
UpdateCurrentUser();
}

View File

@@ -0,0 +1,57 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QList>
#include <QWidget>
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Service::Account {
class ProfileManager;
}
namespace Ui {
class ConfigureProfileManager;
}
class ConfigureProfileManager : public QWidget {
Q_OBJECT
public:
explicit ConfigureProfileManager(QWidget* parent = nullptr);
~ConfigureProfileManager() override;
void applyConfiguration();
void setConfiguration();
private:
void PopulateUserList();
void UpdateCurrentUser();
void SelectUser(const QModelIndex& index);
void AddUser();
void RenameUser();
void DeleteUser();
void SetUserImage();
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
std::unique_ptr<Ui::ConfigureProfileManager> ui;
bool enabled = false;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureProfileManager</class>
<widget class="QWidget" name="ConfigureProfileManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Profile Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Current User</string>
</property>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="current_user_icon">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="current_user_username">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="widgetResizable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="pm_set_image">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Set Image</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pm_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_rename">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_remove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>Profile management is available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -15,7 +15,6 @@
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configure_system.h"
@@ -36,64 +35,9 @@ constexpr std::array<int, 12> days_in_month = {{
30,
31,
}};
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
constexpr std::array<u8, 107> backup_jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
QString GetImagePath(Service::Account::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QString GetAccountUsername(const Service::Account::ProfileManager& manager,
Service::Account::UUID uuid) {
Service::Account::ProfileBase profile;
if (!manager.GetProfileBase(uuid, profile)) {
return {};
}
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
return QString::fromStdString(text);
}
QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
return ConfigureSystem::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 icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"),
description_text, 1,
static_cast<int>(Service::Account::profile_username_size));
}
} // Anonymous namespace
ConfigureSystem::ConfigureSystem(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureSystem),
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@@ -101,50 +45,17 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 1);
item_model->setHeaderData(0, Qt::Horizontal, "Users");
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser);
connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->rng_seed_edit->setEnabled(checked);
if (!checked)
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
});
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->custom_rtc_edit->setEnabled(checked);
if (!checked)
ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
});
this->setConfiguration();
}
@@ -156,49 +67,19 @@ void ConfigureSystem::setConfiguration() {
ui->combo_language->setCurrentIndex(Settings::values.language_index);
item_model->removeRows(0, item_model->rowCount());
list_items.clear();
PopulateUserList();
UpdateCurrentUser();
ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
const auto rng_seed =
QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper();
ui->rng_seed_edit->setText(rng_seed);
}
void ConfigureSystem::PopulateUserList() {
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(user, profile))
continue;
ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
item_model->appendRow(item);
}
void ConfigureSystem::UpdateCurrentUser() {
ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
ASSERT(current_user);
const auto username = GetAccountUsername(*profile_manager, *current_user);
scene->clear();
scene->addPixmap(
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
ui->current_user_username->setText(username);
const auto rtc_time = Settings::values.custom_rtc.value_or(
std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
}
void ConfigureSystem::ReadSystemSettings() {}
@@ -214,6 +95,12 @@ void ConfigureSystem::applyConfiguration() {
else
Settings::values.rng_seed = std::nullopt;
if (ui->custom_rtc_checkbox->isChecked())
Settings::values.custom_rtc =
std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
else
Settings::values.custom_rtc = std::nullopt;
Settings::Apply();
}
@@ -256,130 +143,3 @@ void ConfigureSystem::RefreshConsoleID() {
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
void ConfigureSystem::SelectUser(const QModelIndex& index) {
Settings::values.current_user =
std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
UpdateCurrentUser();
ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
ui->pm_rename->setEnabled(true);
ui->pm_set_image->setEnabled(true);
}
void ConfigureSystem::AddUser() {
const auto username =
GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
if (username.isEmpty()) {
return;
}
const auto uuid = Service::Account::UUID::Generate();
profile_manager->CreateNewUser(uuid, username.toStdString());
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
}
void ConfigureSystem::RenameUser() {
const auto user = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(user);
ASSERT(uuid);
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(*uuid, profile))
return;
const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
if (new_username.isEmpty()) {
return;
}
const auto username_std = new_username.toStdString();
std::fill(profile.username.begin(), profile.username.end(), '\0');
std::copy(username_std.begin(), username_std.end(), profile.username.begin());
profile_manager->SetProfileBase(*uuid, profile);
item_model->setItem(
user, 0,
new QStandardItem{GetIcon(*uuid),
FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
UpdateCurrentUser();
}
void ConfigureSystem::DeleteUser() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto username = GetAccountUsername(*profile_manager, *uuid);
const auto confirm = QMessageBox::question(
this, tr("Confirm Delete"),
tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
if (confirm == QMessageBox::No)
return;
if (Settings::values.current_user == tree_view->currentIndex().row())
Settings::values.current_user = 0;
UpdateCurrentUser();
if (!profile_manager->RemoveUser(*uuid))
return;
item_model->removeRows(tree_view->currentIndex().row(), 1);
tree_view->clearSelection();
ui->pm_remove->setEnabled(false);
ui->pm_rename->setEnabled(false);
}
void ConfigureSystem::SetUserImage() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
tr("JPEG Images (*.jpg *.jpeg)"));
if (file.isEmpty()) {
return;
}
const auto image_path = GetImagePath(*uuid);
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
QMessageBox::warning(
this, tr("Error deleting image"),
tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
return;
}
const auto raw_path = QString::fromStdString(
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
tr("Unable to delete existing file: %1.").arg(raw_path));
return;
}
const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
QMessageBox::warning(
this, tr("Error creating user image directory"),
tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
return;
}
if (!QFile::copy(file, image_path)) {
QMessageBox::warning(this, tr("Error copying user image"),
tr("Unable to copy image from %1 to %2").arg(file, image_path));
return;
}
const auto username = GetAccountUsername(*profile_manager, *uuid);
item_model->setItem(index, 0,
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
UpdateCurrentUser();
}

View File

@@ -9,16 +9,6 @@
#include <QList>
#include <QWidget>
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Service::Account {
class ProfileManager;
}
namespace Ui {
class ConfigureSystem;
}
@@ -39,21 +29,6 @@ private:
void UpdateBirthdayComboBox(int birthmonth_index);
void RefreshConsoleID();
void PopulateUserList();
void UpdateCurrentUser();
void SelectUser(const QModelIndex& index);
void AddUser();
void RenameUser();
void DeleteUser();
void SetUserImage();
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
@@ -61,6 +36,4 @@ private:
int birthday = 0;
int language_index = 0;
int sound_index = 0;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};

View File

@@ -22,6 +22,13 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
@@ -114,27 +121,6 @@
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_console_id">
<property name="text">
<string>Console ID:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_birthday">
<property name="text">
<string>Birthday</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item>
@@ -206,6 +192,20 @@
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_console_id">
<property name="text">
<string>Console ID:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_birthday">
<property name="text">
<string>Birthday</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
@@ -241,6 +241,13 @@
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
<string>RNG Seed</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_language">
<property name="text">
@@ -248,14 +255,7 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
<string>RNG Seed</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QLineEdit" name="rng_seed_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@@ -276,145 +276,42 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="custom_rtc_checkbox">
<property name="text">
<string>Custom RTC</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDateTimeEdit" name="custom_rtc_edit">
<property name="minimumDate">
<date>
<year>1970</year>
<month>1</month>
<day>1</day>
</date>
</property>
<property name="displayFormat">
<string>d MMM yyyy h:mm:ss AP</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Profile Manager</string>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Current User</string>
</property>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="current_user_icon">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="current_user_username">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="widgetResizable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="pm_set_image">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Set Image</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pm_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_rename">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_remove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_disable_info">

View File

@@ -182,8 +182,6 @@ QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {
return tr("one shot");
case Kernel::ResetType::Sticky:
return tr("sticky");
case Kernel::ResetType::Pulse:
return tr("pulse");
}
UNREACHABLE();
return {};
@@ -293,8 +291,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
QString processor;
switch (thread.GetProcessorID()) {
case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:
processor = tr("default");
case Kernel::ThreadProcessorId::THREADPROCESSORID_IDEAL:
processor = tr("ideal");
break;
case Kernel::ThreadProcessorId::THREADPROCESSORID_0:
case Kernel::ThreadProcessorId::THREADPROCESSORID_1:

View File

@@ -376,7 +376,7 @@ void GameList::LoadCompatibilityList() {
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
QJsonArray arr = json.array();
for (const QJsonValueRef& value : arr) {
for (const QJsonValueRef value : arr) {
QJsonObject game = value.toObject();
if (game.contains("compatibility") && game["compatibility"].isDouble()) {
@@ -384,7 +384,7 @@ void GameList::LoadCompatibilityList() {
QString directory = game["directory"].toString();
QJsonArray ids = game["releases"].toArray();
for (const QJsonValueRef& id_ref : ids) {
for (const QJsonValueRef id_ref : ids) {
QJsonObject id_object = id_ref.toObject();
QString id = id_object["id"].toString();
compatibility_list.emplace(

View File

@@ -10,11 +10,14 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
#include "applets/web_browser.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -96,6 +99,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/discord_impl.h"
#endif
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QWebEngineSettings>
#include <QWebEngineView>
#endif
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog();
}
#ifdef YUZU_USE_QT_WEB_ENGINE
void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
NXInputWebEngineView web_browser_view(this);
// Scope to contain the QProgressDialog for initalization
{
QProgressDialog progress(this);
progress.setMinimumDuration(200);
progress.setLabelText(tr("Loading Web Applet..."));
progress.setRange(0, 4);
progress.setValue(0);
progress.show();
auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); });
while (!future.isFinished())
QApplication::processEvents();
progress.setValue(1);
// Load the special shim script to handle input and exit.
QWebEngineScript nx_shim;
nx_shim.setSourceCode(GetNXShimInjectionScript());
nx_shim.setWorldId(QWebEngineScript::MainWorld);
nx_shim.setName("nx_inject.js");
nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
nx_shim.setRunsOnSubFrames(true);
web_browser_view.page()->profile()->scripts()->insert(nx_shim);
web_browser_view.load(
QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() +
QString::fromStdString(std::string(additional_args))));
progress.setValue(2);
render_window->hide();
web_browser_view.setFocus();
const auto& layout = render_window->GetFramebufferLayout();
web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
Layout::ScreenUndocked::Width);
web_browser_view.settings()->setAttribute(
QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
web_browser_view.show();
progress.setValue(3);
QApplication::processEvents();
progress.setValue(4);
}
bool finished = false;
QAction* exit_action = new QAction(tr("Exit Web Applet"), this);
connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; });
ui.menubar->addAction(exit_action);
auto& npad =
Core::System::GetInstance()
.ServiceManager()
.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
web_browser_view.page()->runJavaScript(
QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
.arg(QString::fromStdString(std::to_string(key_code))));
};
bool running_exit_check = false;
while (!finished) {
QApplication::processEvents();
if (!running_exit_check) {
web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"),
[&](const QVariant& res) {
running_exit_check = false;
if (res.toBool())
finished = true;
});
running_exit_check = true;
}
const auto input = npad.GetAndResetPressState();
for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) {
if ((input & (1 << i)) != 0) {
LOG_DEBUG(Frontend, "firing input for button id={:02X}", i);
web_browser_view.page()->runJavaScript(
QStringLiteral("yuzu_key_callbacks[%1]();").arg(i));
}
}
if (input & 0x00888000) // RStick Down | LStick Down | DPad Down
fire_js_keypress(40); // Down Arrow Key
else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right
fire_js_keypress(39); // Right Arrow Key
else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up
fire_js_keypress(38); // Up Arrow Key
else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left
fire_js_keypress(37); // Left Arrow Key
else if (input & 0x00000001) // A Button
fire_js_keypress(13); // Enter Key
}
web_browser_view.hide();
render_window->show();
render_window->setFocus();
ui.menubar->removeAction(exit_action);
// Needed to update render window focus/show and remove menubar action
QApplication::processEvents();
emit WebBrowserFinishedBrowsing();
}
#else
void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
QMessageBox::warning(
this, tr("Web Applet"),
tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot "
"properly display the game manual or web page requested."),
QMessageBox::Ok, QMessageBox::Ok);
LOG_INFO(Frontend,
"(STUBBED) called - Missing QtWebEngine dependency needed to open website page at "
"'{}' with arguments '{}'!",
filename, additional_args);
emit WebBrowserFinishedBrowsing();
}
#endif
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
@@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -687,10 +837,26 @@ bool GMainWindow::LoadROM(const QString& filename) {
return true;
}
void GMainWindow::SelectAndSetCurrentUser() {
QtProfileSelectionDialog dialog(this);
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());
}
}
void GMainWindow::BootGame(const QString& filename) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
if (UISettings::values.select_user_on_boot) {
SelectAndSetCurrentUser();
}
if (!LoadROM(filename))
return;
@@ -827,31 +993,25 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
Service::Account::ProfileManager manager{};
const auto user_ids = manager.GetAllUsers();
QStringList list;
for (const auto& user_id : user_ids) {
if (user_id == Service::Account::UUID{})
continue;
Service::Account::ProfileBase base;
if (!manager.GetProfileBase(user_id, base))
continue;
const auto select_profile = [this]() -> s32 {
QtProfileSelectionDialog dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(base.username.data()), base.username.size())));
}
if (!dialog.GetStatus()) {
return -1;
}
bool ok = false;
const auto index_string =
QInputDialog::getItem(this, tr("Select User"),
tr("Please select the user's save data you would like to open."),
list, Settings::values.current_user, false, &ok);
if (!ok)
return dialog.GetIndex();
};
const auto index = select_profile();
if (index == -1)
return;
const auto index = list.indexOf(index_string);
ASSERT(index != -1 && index < 8);
Service::Account::ProfileManager manager;
const auto user_id = manager.GetUser(index);
ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
@@ -1315,6 +1475,7 @@ void GMainWindow::OnStartGame() {
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
qRegisterMetaType<std::string_view>("std::string_view");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);

View File

@@ -13,6 +13,7 @@
#include "common/common_types.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "ui_main.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -26,6 +27,7 @@ class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
class QLabel;
class WaitTreeWidget;
enum class GameListOpenTarget;
@@ -103,11 +105,16 @@ signals:
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
void WebBrowserUnpackRomFS();
void WebBrowserFinishedBrowsing();
public slots:
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
private:
void InitializeWidgets();
void InitializeDebugWidgets();
@@ -128,6 +135,8 @@ private:
void ShowTelemetryCallout();
void SetDiscordEnabled(bool state);
void SelectAndSetCurrentUser();
/**
* Stores the filename in the recently loaded files list.
* The new filename is stored at the beginning of the recently loaded files list.

View File

@@ -40,6 +40,8 @@ struct Values {
bool confirm_before_closing;
bool first_start;
bool select_user_on_boot;
// Discord RPC
bool enable_discord_presence;

View File

@@ -325,13 +325,21 @@ void Config::ReadValues() {
Settings::values.current_user = std::clamp<int>(
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
const auto enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
if (enabled) {
const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0);
} else {
Settings::values.rng_seed = std::nullopt;
}
const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) {
Settings::values.custom_rtc =
std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0));
} else {
Settings::values.custom_rtc = std::nullopt;
}
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);

View File

@@ -183,6 +183,12 @@ enable_nfc =
rng_seed_enabled =
rng_seed =
# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
# This will auto-increment, with the time set being the time the game is started
# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
custom_rtc_enabled =
custom_rtc =
# Sets the account username, max length is 32 characters
# yuzu (default)
username = yuzu