Compare commits

..

130 Commits

Author SHA1 Message Date
Lioncash
f0d9ab0717 maxwell_to_vk: Initialize usage variable in SurfaceFormat()
Silences a -Wmaybe-uninitialized warning
2020-12-30 13:25:03 -05:00
LC
da07977db0 Merge pull request #5251 from ReinUsesLisp/wuninitialized
cmake: Enforce -Wuninitialized
2020-12-30 06:34:42 -05:00
bunnei
d5fe722a30 Merge pull request #4967 from ReinUsesLisp/new-texcache
video_core/texture_cache: Rewrite the texture cache
2020-12-29 23:20:09 -08:00
ReinUsesLisp
9764c13d6d video_core: Rewrite the texture cache
The current texture cache has several points that hurt maintainability
and performance. It's easy to break unrelated parts of the cache
when doing minor changes. The cache can easily forget valuable
information about the cached textures by CPU writes or simply by its
normal usage.The current texture cache has several points that hurt
maintainability and performance. It's easy to break unrelated parts
of the cache when doing minor changes. The cache can easily forget
valuable information about the cached textures by CPU writes or simply
by its normal usage.

This commit aims to address those issues.
2020-12-30 03:38:50 -03:00
ReinUsesLisp
ac2e2ebe97 cmake: Enforce -Wuninitialized 2020-12-30 02:58:58 -03:00
ReinUsesLisp
157fc2d785 service/pcie: Fix invalid initialization argument 2020-12-30 02:58:38 -03:00
ReinUsesLisp
9106ac1e6b video_core: Add a delayed destruction ring abstraction 2020-12-30 02:10:19 -03:00
ReinUsesLisp
21b18057f7 host_shaders: Add Vulkan assembler compute shaders 2020-12-30 02:03:50 -03:00
ReinUsesLisp
87ff58b1d7 host_shaders: Add helper to blit depth stencil fragment shader 2020-12-30 02:02:07 -03:00
ReinUsesLisp
ae5725b709 host_shaders: Add texture color blit fragment shader 2020-12-30 02:00:48 -03:00
ReinUsesLisp
64fbf319f1 host_shaders: Add shaders to present to the swapchain 2020-12-30 01:59:12 -03:00
ReinUsesLisp
82b7daed9c host_shaders: Add shaders to convert between depth and color images 2020-12-30 01:48:44 -03:00
ReinUsesLisp
dc81a90640 host_shaders: Add compute shader to copy BC4 as RG32UI to RGBA8 2020-12-30 01:47:08 -03:00
ReinUsesLisp
5169ce9fcd host_shaders: Add shader to render a full screen triangle 2020-12-30 01:44:09 -03:00
ReinUsesLisp
59c46f9de9 host_shaders: Add pitch linear upload compute shader 2020-12-30 01:41:42 -03:00
ReinUsesLisp
12d16248dd host_shaders: Add block linear upload compute shaders 2020-12-30 01:39:35 -03:00
ReinUsesLisp
f20e18f60d host_shaders: Add copyright headers to OpenGL present shaders 2020-12-30 01:35:56 -03:00
ReinUsesLisp
95d156a150 video_core/host_shaders: Add support for prebuilt SPIR-V shaders
Add support for building SPIR-V shaders from GLSL and generating headers
to include the text of those same GLSL shaders to consume from OpenGL.
2020-12-30 01:29:07 -03:00
bunnei
85cfd96f62 Merge pull request #5247 from comex/xx-concepts
k_priority_queue: Fix concepts use
2020-12-29 16:50:20 -08:00
bunnei
b02464f685 Merge pull request #5246 from comex/xx-include
Add missing include of "core/hle/kernel/kernel.h"
2020-12-29 16:43:17 -08:00
LC
8d55c8c855 Merge pull request #5248 from ReinUsesLisp/update-dynarmic
externals: Update Dynarmic
2020-12-29 18:11:30 -05:00
ReinUsesLisp
3f048c8646 externals: Update Dynarmic
Keeps yuzu up to date with the latest changes and introduces a change
needed for a lock-free optimization our side.
2020-12-29 19:30:52 -03:00
comex
388cf58b31 k_priority_queue: Fix concepts use
- For `std::same_as`, add missing include of `<concepts>`.

- For `std::convertible_to`, create a replacement in `common/concepts.h`
  and use that instead.

  This would also be found in `<concepts>`, but unlike `std::same_as`,
  `std::convertible_to` is not yet implemented in libc++, LLVM's STL
  implementation - not even in master.  (In fact, `std::same_as` is the
  *only* concept currently implemented.  For some reason.)
2020-12-29 14:33:41 -05:00
comex
b36896b90e Add missing include of "core/hle/kernel/kernel.h"
This is needed as the header invokes methods on KernelCore.
2020-12-29 14:22:35 -05:00
LC
aa87278bf0 Merge pull request #5245 from ameerj/sleepthread-log
svc: demote SleepThread log to LOG_TRACE
2020-12-29 14:03:24 -05:00
ameerj
0383363a8f svc: demote SleepThread log to LOG_TRACE
This log is called often, and introduces a lot of noise when debug logging is enabled, making it difficult to see other debug logs.
2020-12-29 14:01:56 -05:00
bunnei
22ba437aa4 Merge pull request #5236 from gal20/udp_client_patch
input_common: process udp packets only for the correct pad
2020-12-29 02:51:40 -08:00
bunnei
7dbdda908c Merge pull request #5233 from german77/inverted
InputCommon: Allow to invert analog axis with right click
2020-12-28 14:06:21 -08:00
gal20
1defd0847a udp client: process packets only for the correct pad 2020-12-27 22:22:48 +02:00
german
80fece4e08 Allow to invert analog axis with right click 2020-12-26 17:46:14 -06:00
Rodrigo Locatti
0dc4ab42cc Merge pull request #5226 from ReinUsesLisp/c4715-vc
video_core: Enforce C4715 (not all control paths return a value)
2020-12-25 03:11:47 -03:00
Rodrigo Locatti
453560fb3a Merge pull request #5225 from ReinUsesLisp/always-vulkan
cmake: Always enable Vulkan
2020-12-25 02:52:29 -03:00
bunnei
c8a4967c9d core: memory: Ensure thread safe access when pages are rasterizer cached (#5206)
* core: memory: Ensure thread safe access when pages are rasterizer cached.
2020-12-24 21:51:49 -08:00
ReinUsesLisp
1b9e08ab78 cmake: Always enable Vulkan
Removes the unnecesary burden of maintaining separate #ifdef paths and
allows us sharing generic Vulkan code across APIs.
2020-12-24 21:07:24 -03:00
ReinUsesLisp
1e191cc837 video_core: Enforce C4715 (not all control paths return a value)
Most of the time people write code that always returns a value,
terminates execution, throws an exception, or uses an unconventional
jump primitive.

This is not always true when we build without asserts on mainline builds.
To avoid introducing undefined behavior on our most used builds, enforce
this warning signalling an error and stopping the build from shipping.
2020-12-24 21:01:23 -03:00
ReinUsesLisp
5dbda22659 vk_shader_decompiler: Silence warning when compiling without asserts 2020-12-24 21:01:09 -03:00
bunnei
5836530a87 Merge pull request #5217 from lat9nq/save-on-boot
yuzu/main: Save settings when starting guest
2020-12-23 01:45:24 -08:00
LC
868c397cb6 Merge pull request #5223 from lat9nq/menu-navigation
yuzu/main: Improve menubar access keys
2020-12-22 19:39:45 -05:00
lat9nq
17badbc442 yuzu/main: Improve menubar access keys
Adds a unique access key to each action within each menu. A few actions
already had their own access key, so those were untouched.
2020-12-22 19:32:58 -05:00
bunnei
d7f5e55f8e Merge pull request #5178 from german77/dockedresize
Add option to reset window size to 1080p
2020-12-22 16:06:20 -08:00
german
64fad8cfe9 Add option to reset window size to 1080p 2020-12-22 17:06:48 -06:00
bunnei
29ccc7673f Merge pull request #5042 from Morph1984/project-aether
Project Aether: Reimplementation of the Web Browser Applet
2020-12-21 23:47:10 -08:00
lat9nq
c243932b41 yuzu/main: Save settings when starting guest
Saves UISettings and Settings when booting a guest. Moves updating
UISettings::values from GMainWindow::closeEvent into its own function,
then reuses it in GMainWindow::BootGame.
2020-12-22 02:29:30 -05:00
bunnei
1279c7ce7a Merge pull request #5131 from bunnei/scheduler-rewrite
Rewrite Kernel scheduler based on Atmosphere
2020-12-20 20:57:54 -08:00
bunnei
c3e201a829 Merge pull request #5201 from ameerj/bufferq-refactor
vi/buffer_queue: Buffer queue management refactor
2020-12-20 15:48:39 -08:00
bunnei
d5984284ed Merge pull request #5207 from FearlessTobi/remove-gdb-config
yuzu: Remove gdbstub configuration
2020-12-20 11:56:08 -08:00
FearlessTobi
10b0ab7926 yuzu: Remove gdbstub configuration
The gdbstub itself was removed with https://github.com/yuzu-emu/yuzu/pull/5028.
This PR just removes the remaining gdb configuration code from the emulator and the UI.
2020-12-19 19:19:42 +01:00
Morph
82fa9f8d56 applets/web: Implement the online web browser applet 2020-12-18 10:33:28 -05:00
Morph
51cddcb8b8 applets/web: Fix keyboard to emulated controller input 2020-12-18 10:33:28 -05:00
Morph
2ddd83cdfe main: Add the ability to disable the web applet
This should only be used for Super Mario 3D All-Stars. This is a temporary solution until it can be implemented properly.
2020-12-18 10:33:28 -05:00
Morph
8b95bf041d main, applets/web: Re-add progress dialog for RomFS extraction 2020-12-18 10:33:28 -05:00
Morph
93cb783853 applets/web: Implement the Qt web browser applet frontend 2020-12-18 10:33:28 -05:00
Morph
d5e0923e3d web_browser_scripts: Add injection scripts for the web browser 2020-12-18 10:33:28 -05:00
Morph
d46ca5a015 pl_u, applets/web: Decrypt shared fonts to TTF files 2020-12-18 10:33:28 -05:00
Morph
46183294b2 ns_vm: Stub NeedsUpdateVulnerability
This is used to force system updates on launching the web browser. We do not care about system updates so this can be set to false.
2020-12-18 10:33:28 -05:00
Morph
f9653a4417 frontend/input_interpreter: Add InputInterpreter API
The InputInterpreter class interfaces with HID to retrieve button press states. Input is intended to be polled every 50ms so that a button is considered to be held down after 400ms has elapsed since the initial button press and subsequent repeated presses occur every 50ms.

Co-authored-by: Chloe <25727384+ogniK5377@users.noreply.github.com>
2020-12-18 10:33:28 -05:00
Morph
54ea3c47c8 controllers/npad: Make press_state atomic 2020-12-18 10:33:28 -05:00
Morph
5836786246 util: Add URL Request Interceptor for QWebEngine 2020-12-18 10:33:28 -05:00
Morph
51a7681957 bootmanager: Add a check whether loading is complete 2020-12-18 10:33:28 -05:00
Morph
d6d1a8e02c applets/web: Implement the default web browser applet frontend 2020-12-18 10:33:28 -05:00
Morph
89df483567 applets/web: Implement the offline browser applet backend 2020-12-18 10:33:27 -05:00
Morph
a5750f437d applets/web: Initial implementation of the web browser applet 2020-12-18 10:33:27 -05:00
Morph
ccb439efb0 applets: Remove the previous web browser applet implementation 2020-12-18 10:33:27 -05:00
LC
0b47f7a46b Merge pull request #5205 from Morph1984/oss-extended-plus-minus
system_archive: Add + and - buttons to the Nintendo Extended OSS font
2020-12-18 02:57:12 -05:00
Morph
79316be18c system_archive: Add + and - buttons to the Nintendo Extended OSS font 2020-12-18 02:55:48 -05:00
LC
ec100ca4db Merge pull request #5200 from Morph1984/oss-font-extended
system_archive: Update Nintendo Extended OSS font
2020-12-18 01:07:16 -05:00
ameerj
873ad1272e buffer_queue: better use of std::array 2020-12-18 00:12:14 -05:00
ameerj
8cb683f3b9 Overwrite slots instead of queuing them, add disconnect signal
Fix for Katana Zero and Yoshi's Crafted World
2020-12-17 14:22:46 -05:00
Morph
5d29d2111c system_archive: Update Nintendo Extended OSS font
Co-authored-by: Its-Rei <kupfel@gmail.com>
2020-12-17 08:58:13 -05:00
bunnei
ac3b4f918f Merge pull request #5196 from lat9nq/fix-conan-boost-2
cmake/conan: Conditionally add target Boost::context
2020-12-15 21:15:05 -08:00
lat9nq
9b023a56a3 cmake/conan: Conditionally add target Boost::context
Addresses an issue with the two competing versions of Conan's Boost
package that are currently floating around.

Adds the Boost::context target only if it's recognized by CMake as a
target.
2020-12-15 23:47:29 -05:00
bunnei
f3db273753 Merge pull request #5190 from Morph1984/validate_device_handle
controllers/npad: Validate device handles before use
2020-12-15 16:40:11 -08:00
bunnei
2e1b998d5e Merge pull request #5119 from Morph1984/fs-opendatastoragewithprogramindex
fsp_srv: Implement OpenDataStorageWithProgramIndex
2020-12-15 11:07:03 -08:00
bunnei
37bec068c2 Merge pull request #5157 from lioncash/array-dirty
maxwell_3d: Remove unused dirty_pointer array
2020-12-15 00:35:47 -08:00
bunnei
df6427d30b Merge pull request #5168 from Morph1984/aoc-PurchaseEventManager
aoc_u: Stub IPurchaseEventManager and its service commands
2020-12-14 16:08:38 -08:00
bunnei
c96930fd9d Merge pull request #5193 from lat9nq/fix-conan-boost
cmake: Fix generating CMake configs and linking with Boost
2020-12-12 23:42:14 -08:00
lat9nq
292dd642ce cmake: Fix generating CMake configs and linking with Boost
Fixes regression by 761206cf81, causing
yuzu to not build on Linux with any version of Boost except a cached
1.73 Conan version from before about a day ago.

Moves the Boost requirement out of the `REQUIRED_LIBS` psuedo-2D-array
for Conan to instead be manually configured, using Conan as a fallback
solution if the system does not meet our requirements.

Requires any update from the linux-fresh container in order to build.

**DO NOT MERGE** until someone with the MSVC toolchain can verify this
works there, too.
2020-12-13 01:28:51 -05:00
bunnei
761206cf81 common: Update CMakeList to fix build issue with Boost. 2020-12-12 11:50:07 -08:00
Morph
1c773c0869 controllers/npad: Validate device handles before use
Some games such as NEKOPARA Vol. 3 send invalid device handles when calling InitializeVibrationDevice. Introduce a check to validate the device handle before use.
2020-12-12 07:05:38 -05:00
bunnei
69b46dd607 Merge pull request #5183 from lioncash/alias2
vfs: Use existing type aliases consistently
2020-12-12 01:54:28 -08:00
bunnei
c918c6480f Merge pull request #5187 from Morph1984/revert-stdfs
fs: Revert all std::filesystem changes
2020-12-11 20:07:37 -08:00
bunnei
37194dd4e9 Merge pull request #5172 from lioncash/svc-wide
svc: Remove unnecessary casts
2020-12-11 17:39:30 -08:00
Morph
4de079b256 Revert "Merge pull request #5173 from lioncash/common-fs"
This reverts commit ce5fcb6bb2, reversing
changes made to 6f41763061.
2020-12-11 20:24:22 -05:00
Morph
8941cdb7d2 Revert "Merge pull request #5174 from ReinUsesLisp/fs-fix"
This reverts commit 5fe55b16a1, reversing
changes made to e94dd7e2c4.
2020-12-11 20:24:22 -05:00
Morph
dfee6321cd Revert "Merge pull request #5176 from Morph1984/fix-createfile"
This reverts commit 6d6115475b, reversing
changes made to 5fe55b16a1.
2020-12-11 20:24:22 -05:00
Morph
0195038c07 Revert "Merge pull request #5179 from ReinUsesLisp/fs-path"
This reverts commit 4e94d0d53a, reversing
changes made to 6d6115475b.
2020-12-11 20:21:46 -05:00
Morph
ac3ec5ed13 Revert "Merge pull request #5181 from Morph1984/5174-review"
This reverts commit cdb36aef9e, reversing
changes made to 5e9b77129f.
2020-12-11 20:21:21 -05:00
bunnei
cdb36aef9e Merge pull request #5181 from Morph1984/5174-review
common/file_util: Address review comments of #5174
2020-12-10 15:52:11 -08:00
LC
5e9b77129f Merge pull request #5184 from lat9nq/travis-linux-ccache-fix
travis/linux: Use correct ccache directory
2020-12-10 15:01:47 -05:00
bunnei
2d47a5fd41 Merge pull request #5123 from Morph1984/nim-IsLargeResourceAvailable
nim: Stub IsLargeResourceAvailable
2020-12-10 11:34:18 -08:00
lat9nq
3802474483 travis/linux: Use correct ccache directory
Changes the bound ccache directory to `/home/yuzu/.ccache` instead of
`/root/.ccache`, since the `/root` directory is not accessible by the
`yuzu` user in the guest container.
2020-12-10 03:34:16 -05:00
bunnei
d1a2b3fb18 Merge pull request #5162 from lioncash/copy-shader
gl_shader_decompiler: Elide unnecessary copies within DeclareConstantBuffers()
2020-12-10 00:11:11 -08:00
Lioncash
b1657b8c6b vfs: Use existing type aliases consistently
Makes use of the VirtualDir and VirtualFile aliases across the board
instead of having a few isolated places that don't use it.
2020-12-10 01:44:43 -05:00
Lioncash
2de124e223 svc: Remove unnecessary casts
Simplifies and removes some casts. In all cases, these were generally
widening from a 32-bit unsigned type to a 64-bit unsigned type, so no
information would be lost from the conversion.
2020-12-08 15:42:10 -05:00
Morph
deff708cbe IPurchaseEventManager: Implement GetPurchasedEventReadableHandle
- Used by Pokémon Café Mix
- Used by DOOM: Eternal
2020-12-08 13:39:19 -05:00
Morph
a9cfe06aaf IPurchaseEventManager: Stub Set(Default)DeliveryTarget
- Used by Pokémon Café Mix
- Used by DOOM: Eternal
2020-12-08 13:39:13 -05:00
Morph
009bdb3558 aoc_u: Stub Create(Permanent)EcPurchasedEventManager
- Used by Pokémon Café Mix
- Used by DOOM: Eternal
2020-12-08 13:39:07 -05:00
Morph
e15039372e fsp_srv: Implement OpenDataStorageWithProgramIndex
- Used by RollerCoaster Tycoon 3: Complete Edition
2020-12-08 08:19:05 -05:00
Morph
0eb6c6cd83 file_sys: Consolidate common Title ID operations 2020-12-08 08:19:05 -05:00
Lioncash
edcbd47800 gl_shader_decompiler: Elide unnecessary copies within DeclareConstantBuffers()
Resolves a -Wrange-loop-analysis warning.
2020-12-07 14:01:52 -05:00
Lioncash
9e7a1f1351 maxwell_3d: Move member variables to end of class
Follows our established coding style.
2020-12-06 20:56:00 -05:00
Lioncash
ce0712bf95 maxwell_3d: Resolve -Wdocumentation warning
Removes a documentation comment for a non-existent member.
2020-12-06 20:48:12 -05:00
Lioncash
bcc5c4403a maxwell_3d: Remove unused dirty_pointer array
This is unused and removing it shrinks the structure by 3584 bytes.
2020-12-06 20:46:57 -05:00
bunnei
1bdb756d28 hle: kernel: Process: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
d4ae0ae0e9 core: cpu_manager: Fix a typo in PreemptSingleCore, which broke many games.
- We were reload'ing the old current scheduler, which may have changed.
2020-12-06 00:27:13 -08:00
bunnei
9b492430bb hle: kernel: Thread: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
ed4d1e2ade hle: kernel: KScopedSchedulerLockAndSleep: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
b1b4f2337e hle: kernel: KScopedLock: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
165d8485f0 hle: kernel: KAbstractSchedulerLock: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
960500cfd2 hle: kernel: KScheduler: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
8fd921557f hle: kernel: KPriorityQueue: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
4d3be1816c hle: kernel: KAffinityMask: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
357d79fb6e hle: kernel: GlobalSchedulerContext: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
d2c0c94f0b common: BitSet: Various style fixes based on code review feedback. 2020-12-06 00:27:13 -08:00
bunnei
b1326d9230 hle: kernel: Use C++ style comments in KScheduler, etc. 2020-12-06 00:03:24 -08:00
bunnei
bc59ca92b6 kernel: KScopedSchedulerLockAndSleep: Remove unused ctor. 2020-12-06 00:03:24 -08:00
bunnei
b9b7e4f915 kernel: time_manager: Add missing lock guards. 2020-12-06 00:03:24 -08:00
bunnei
ccce6cb3be hle: kernel: Migrate to KScopedSchedulerLock. 2020-12-06 00:03:24 -08:00
bunnei
4756cb203e hle: kernel: Separate KScopedSchedulerLockAndSleep from k_scheduler. 2020-12-06 00:03:24 -08:00
bunnei
8d3e06349e hle: kernel: Separate KScheduler from GlobalSchedulerContext class. 2020-12-06 00:03:24 -08:00
bunnei
9e29e36a78 hle: kernel: Rewrite scheduler implementation based on Mesopshere. 2020-12-06 00:03:24 -08:00
bunnei
c10a37e5b6 hle: kernel: physical_core: Clear exclusive state after each run.
- This is closer to pre-multicore behavior, and works a bit better.
2020-12-06 00:03:24 -08:00
bunnei
7e5d0f1fe3 hle: kernel: Port KAbstractSchedulerLock from Mesosphere. 2020-12-06 00:03:24 -08:00
bunnei
39d356782e hle: kernel: svc: Remove reschedule on svcBreak.
- This breaks things, and is unnecessary, since emulation will be done at this point.
2020-12-06 00:03:24 -08:00
bunnei
d58a609ae4 hle: kernel: process: Add schedule count tracking, to be used for yield impl. 2020-12-06 00:03:24 -08:00
bunnei
493263f415 hle: kernel: svc: Remove unnecessary hack in svcSleep. 2020-12-06 00:03:24 -08:00
bunnei
a3ccac3eb7 common: Port KPriorityQueue from Mesosphere. 2020-12-06 00:03:24 -08:00
bunnei
8dbfa4e1a4 common: Port BitSet from Mesosphere. 2020-12-06 00:03:24 -08:00
bunnei
e18ee8d681 hle: kernel: Port KAffinityMask from Mesosphere. 2020-12-06 00:03:24 -08:00
Morph
c2f83c04cb nim: Stub IsLargeResourceAvailable
- Used by Immortals Fenyx Rising
2020-12-04 09:53:21 -05:00
305 changed files with 16909 additions and 12017 deletions

View File

@@ -1,4 +1,4 @@
#!/bin/bash -ex
mkdir -p "$HOME/.ccache"
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/home/yuzu/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh

View File

@@ -24,8 +24,6 @@ option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implemen
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(ENABLE_VULKAN "Enables Vulkan backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
# Default to a Release build
@@ -160,7 +158,6 @@ macro(yuzu_find_packages)
# Capitalization matters here. We need the naming to match the generated paths from Conan
set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg
"Boost 1.73 boost/1.73.0"
"Catch2 2.13 catch2/2.13.0"
"fmt 7.1 fmt/7.1.2"
# can't use until https://github.com/bincrafters/community/issues/1173
@@ -195,6 +192,22 @@ macro(yuzu_find_packages)
unset(FN_FORCE_REQUIRED)
endmacro()
find_package(Boost 1.73.0 COMPONENTS context headers QUIET)
if (Boost_FOUND)
set(Boost_LIBRARIES Boost::boost)
# Conditionally add Boost::context only if the active version of the Conan or system Boost package provides it
# The old version is missing Boost::context, so we want to avoid adding in that case
# The new version requires adding Boost::context to prevent linking issues
#
# This one is used by Conan on subsequent CMake configures, not the first configure.
if (TARGET Boost::context)
list(APPEND Boost_LIBRARIES Boost::context)
endif()
else()
message(STATUS "Boost 1.73.0 or newer not found, falling back to Conan")
list(APPEND CONAN_REQUIRED_LIBS "boost/1.73.0")
endif()
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
yuzu_find_packages()
@@ -299,6 +312,17 @@ if (CONAN_REQUIRED_LIBS)
# this time with required, so we bail if its not found.
yuzu_find_packages(FORCE_REQUIRED)
if (NOT Boost_FOUND)
find_package(Boost 1.73.0 REQUIRED COMPONENTS context headers)
set(Boost_LIBRARIES Boost::boost)
# Conditionally add Boost::context only if the active version of the Conan Boost package provides it
# The old version is missing Boost::context, so we want to avoid adding in that case
# The new version requires adding Boost::context to prevent linking issues
if (TARGET Boost::context)
list(APPEND Boost_LIBRARIES Boost::context)
endif()
endif()
# Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
if(ENABLE_QT)
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")

View File

@@ -61,9 +61,7 @@ if (USE_DISCORD_PRESENCE)
endif()
# Sirit
if (ENABLE_VULKAN)
add_subdirectory(sirit)
endif()
add_subdirectory(sirit)
# libzip
find_package(Libzip 1.5)

View File

@@ -62,6 +62,7 @@ else()
-Werror=implicit-fallthrough
-Werror=missing-declarations
-Werror=reorder
-Werror=uninitialized
-Werror=unused-result
-Wextra
-Wmissing-declarations

View File

@@ -104,6 +104,7 @@ add_library(common STATIC
detached_tasks.h
bit_cast.h
bit_field.h
bit_set.h
bit_util.h
cityhash.cpp
cityhash.h
@@ -140,7 +141,6 @@ add_library(common STATIC
microprofile.h
microprofileui.h
misc.cpp
multi_level_queue.h
page_table.cpp
page_table.h
param_package.cpp
@@ -209,7 +209,6 @@ else()
endif()
create_target_directory_groups(common)
find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
target_link_libraries(common PRIVATE lz4::lz4 xbyak)

99
src/common/bit_set.h Normal file
View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <array>
#include <bit>
#include "common/alignment.h"
#include "common/bit_util.h"
#include "common/common_types.h"
namespace Common {
namespace impl {
template <typename Storage, size_t N>
class BitSet {
public:
constexpr BitSet() = default;
constexpr void SetBit(size_t i) {
this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord);
}
constexpr void ClearBit(size_t i) {
this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord);
}
constexpr size_t CountLeadingZero() const {
for (size_t i = 0; i < NumWords; i++) {
if (this->words[i]) {
return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]);
}
}
return FlagsPerWord * NumWords;
}
constexpr size_t GetNextSet(size_t n) const {
for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) {
Storage word = this->words[i];
if (!IsAligned(n + 1, FlagsPerWord)) {
word &= GetBitMask(n % FlagsPerWord) - 1;
}
if (word) {
return FlagsPerWord * i + CountLeadingZeroImpl(word);
}
}
return FlagsPerWord * NumWords;
}
private:
static_assert(std::is_unsigned_v<Storage>);
static_assert(sizeof(Storage) <= sizeof(u64));
static constexpr size_t FlagsPerWord = BitSize<Storage>();
static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord;
static constexpr auto CountLeadingZeroImpl(Storage word) {
return std::countl_zero(static_cast<unsigned long long>(word)) -
(BitSize<unsigned long long>() - FlagsPerWord);
}
static constexpr Storage GetBitMask(size_t bit) {
return Storage(1) << (FlagsPerWord - 1 - bit);
}
std::array<Storage, NumWords> words{};
};
} // namespace impl
template <size_t N>
using BitSet8 = impl::BitSet<u8, N>;
template <size_t N>
using BitSet16 = impl::BitSet<u16, N>;
template <size_t N>
using BitSet32 = impl::BitSet<u32, N>;
template <size_t N>
using BitSet64 = impl::BitSet<u64, N>;
} // namespace Common

View File

@@ -31,4 +31,8 @@ concept DerivedFrom = requires {
std::is_convertible_v<const volatile Derived*, const volatile Base*>;
};
// TODO: Replace with std::convertible_to when libc++ implements it.
template <typename From, typename To>
concept ConvertibleTo = std::is_convertible_v<From, To>;
} // namespace Common

View File

@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <array>
#include <filesystem>
#include <limits>
#include <memory>
#include <sstream>
@@ -68,122 +67,290 @@
#include <algorithm>
#include <sys/stat.h>
#ifndef S_ISDIR
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
#endif
// This namespace has various generic functions related to files and paths.
// The code still needs a ton of cleanup.
// REMEMBER: strdup considered harmful!
namespace Common::FS {
namespace fs = std::filesystem;
bool Exists(const fs::path& path) {
std::error_code ec;
return fs::exists(path, ec);
// Remove any ending forward slashes from directory paths
// Modifies argument.
static void StripTailDirSlashes(std::string& fname) {
if (fname.length() <= 1) {
return;
}
std::size_t i = fname.length();
while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
--i;
}
fname.resize(i);
}
bool IsDirectory(const fs::path& path) {
std::error_code ec;
return fs::is_directory(path, ec);
bool Exists(const std::string& filename) {
struct stat file_info;
std::string copy(filename);
StripTailDirSlashes(copy);
#ifdef _WIN32
// Windows needs a slash to identify a driver root
if (copy.size() != 0 && copy.back() == ':')
copy += DIR_SEP_CHR;
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
#else
int result = stat(copy.c_str(), &file_info);
#endif
return (result == 0);
}
bool Delete(const fs::path& path) {
LOG_TRACE(Common_Filesystem, "file {}", path.string());
bool IsDirectory(const std::string& filename) {
struct stat file_info;
std::string copy(filename);
StripTailDirSlashes(copy);
#ifdef _WIN32
// Windows needs a slash to identify a driver root
if (copy.size() != 0 && copy.back() == ':')
copy += DIR_SEP_CHR;
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
#else
int result = stat(copy.c_str(), &file_info);
#endif
if (result < 0) {
LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
return S_ISDIR(file_info.st_mode);
}
bool Delete(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "file {}", filename);
// Return true because we care about the file no
// being there, not the actual delete.
if (!Exists(path)) {
LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string());
if (!Exists(filename)) {
LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
return true;
}
std::error_code ec;
return fs::remove(path, ec);
// We can't delete a directory
if (IsDirectory(filename)) {
LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
return false;
}
#ifdef _WIN32
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
#else
if (unlink(filename.c_str()) == -1) {
LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
#endif
return true;
}
bool CreateDir(const fs::path& path) {
LOG_TRACE(Common_Filesystem, "directory {}", path.string());
bool CreateDir(const std::string& path) {
LOG_TRACE(Common_Filesystem, "directory {}", path);
#ifdef _WIN32
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
return true;
DWORD error = GetLastError();
if (error == ERROR_ALREADY_EXISTS) {
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
return true;
}
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
return false;
#else
if (mkdir(path.c_str(), 0755) == 0)
return true;
if (Exists(path)) {
LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
int err = errno;
if (err == EEXIST) {
LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
return true;
}
std::error_code ec;
const bool success = fs::create_directory(path, ec);
if (!success) {
LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message());
return false;
}
return true;
LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
return false;
#endif
}
bool CreateDirs(const fs::path& path) {
LOG_TRACE(Common_Filesystem, "path {}", path.string());
bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
if (Exists(path)) {
LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
if (Exists(fullPath)) {
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
return true;
}
std::error_code ec;
const bool success = fs::create_directories(path, ec);
std::size_t position = 0;
while (true) {
// Find next sub path
position = fullPath.find(DIR_SEP_CHR, position);
if (!success) {
LOG_ERROR(Common_Filesystem, "Unable to create directories: {}", ec.message());
// we're done, yay!
if (position == fullPath.npos)
return true;
// Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
std::string const subPath(fullPath.substr(0, position + 1));
if (!IsDirectory(subPath) && !CreateDir(subPath)) {
LOG_ERROR(Common, "CreateFullPath: directory creation failed");
return false;
}
// A safety check
panicCounter--;
if (panicCounter <= 0) {
LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
return false;
}
position++;
}
}
bool DeleteDir(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "directory {}", filename);
// check if a directory
if (!IsDirectory(filename)) {
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
return false;
}
return true;
#ifdef _WIN32
if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
return true;
#else
if (rmdir(filename.c_str()) == 0)
return true;
#endif
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
return false;
}
bool CreateFullPath(const fs::path& path) {
LOG_TRACE(Common_Filesystem, "path {}", path);
if (path.has_extension()) {
return CreateDirs(path.parent_path());
} else {
return CreateDirs(path);
}
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
return true;
#else
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
return true;
#endif
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
GetLastErrorMsg());
return false;
}
bool Rename(const fs::path& src, const fs::path& dst) {
LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
return true;
std::error_code ec;
fs::rename(src, dst, ec);
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
GetLastErrorMsg());
return false;
#else
using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
if (ec) {
LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(),
dst.string(), ec.message());
// Open input file
CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
if (!input) {
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
destFilename, GetLastErrorMsg());
return false;
}
return true;
}
bool Copy(const fs::path& src, const fs::path& dst) {
LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
std::error_code ec;
const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
if (!success) {
LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(),
ec.message());
// open output file
CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
if (!output) {
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
destFilename, GetLastErrorMsg());
return false;
}
// copy loop
std::array<char, 1024> buffer;
while (!feof(input.get())) {
// read input
std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
if (rnum != buffer.size()) {
if (ferror(input.get()) != 0) {
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
srcFilename, destFilename, GetLastErrorMsg());
return false;
}
}
// write output
std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
if (wnum != rnum) {
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
destFilename, GetLastErrorMsg());
return false;
}
}
return true;
#endif
}
u64 GetSize(const fs::path& path) {
std::error_code ec;
const auto size = fs::file_size(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(),
ec.message());
u64 GetSize(const std::string& filename) {
if (!Exists(filename)) {
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
return 0;
}
return size;
if (IsDirectory(filename)) {
LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
return 0;
}
struct stat buf;
#ifdef _WIN32
if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
#else
if (stat(filename.c_str(), &buf) == 0)
#endif
{
LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
return buf.st_size;
}
LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
return 0;
}
u64 GetSize(const int fd) {
struct stat buf;
if (fstat(fd, &buf) != 0) {
LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
return 0;
}
return buf.st_size;
}
u64 GetSize(FILE* f) {
@@ -233,7 +400,7 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
}
// windows loop
do {
const std::string virtual_name = std::filesystem::path(ffd.cFileName).string();
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
#else
DIR* dirp = opendir(directory.c_str());
if (!dirp)
@@ -271,58 +438,132 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
return true;
}
bool DeleteDirRecursively(const fs::path& path) {
std::error_code ec;
fs::remove_all(path, ec);
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
unsigned int recursion) {
const auto callback = [recursion, &parent_entry](u64* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
FSTEntry entry;
entry.virtualName = virtual_name;
entry.physicalName = directory + DIR_SEP + virtual_name;
if (ec) {
LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(),
ec.message());
if (IsDirectory(entry.physicalName)) {
entry.isDirectory = true;
// is a directory, lets go inside if we didn't recurse to often
if (recursion > 0) {
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
*num_entries_out += entry.size;
} else {
entry.size = 0;
}
} else { // is a file
entry.isDirectory = false;
entry.size = GetSize(entry.physicalName);
}
(*num_entries_out)++;
// Push into the tree
parent_entry.children.push_back(std::move(entry));
return true;
};
u64 num_entries;
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
}
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
const auto callback = [recursion](u64*, const std::string& directory,
const std::string& virtual_name) {
const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path)) {
if (recursion == 0) {
return false;
}
return DeleteDirRecursively(new_path, recursion - 1);
}
return Delete(new_path);
};
if (!ForeachDirectoryEntry(nullptr, directory, callback))
return false;
}
// Delete the outermost directory
DeleteDir(directory);
return true;
}
void CopyDir(const fs::path& src, const fs::path& dst) {
constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive;
void CopyDir([[maybe_unused]] const std::string& source_path,
[[maybe_unused]] const std::string& dest_path) {
#ifndef _WIN32
if (source_path == dest_path) {
return;
}
if (!Exists(source_path)) {
return;
}
if (!Exists(dest_path)) {
CreateFullPath(dest_path);
}
std::error_code ec;
fs::copy(src, dst, copy_flags, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(),
dst.string(), ec.message());
DIR* dirp = opendir(source_path.c_str());
if (!dirp) {
return;
}
LOG_TRACE(Common_Filesystem, "Successfully copied directory.");
while (struct dirent* result = readdir(dirp)) {
const std::string virtualName(result->d_name);
// check for "." and ".."
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
continue;
}
std::string source, dest;
source = source_path + virtualName;
dest = dest_path + virtualName;
if (IsDirectory(source)) {
source += '/';
dest += '/';
if (!Exists(dest)) {
CreateFullPath(dest);
}
CopyDir(source, dest);
} else if (!Exists(dest)) {
Copy(source, dest);
}
}
closedir(dirp);
#endif
}
std::optional<fs::path> GetCurrentDir() {
std::error_code ec;
auto path = fs::current_path(ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}",
ec.message());
std::optional<std::string> GetCurrentDir() {
// Get the current working directory (getcwd uses malloc)
#ifdef _WIN32
wchar_t* dir = _wgetcwd(nullptr, 0);
if (!dir) {
#else
char* dir = getcwd(nullptr, 0);
if (!dir) {
#endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
return std::nullopt;
}
return {std::move(path)};
#ifdef _WIN32
std::string strDir = Common::UTF16ToUTF8(dir);
#else
std::string strDir = dir;
#endif
free(dir);
return strDir;
}
bool SetCurrentDir(const fs::path& path) {
std::error_code ec;
fs::current_path(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(),
ec.message());
return false;
}
return true;
bool SetCurrentDir(const std::string& directory) {
#ifdef _WIN32
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
#else
return chdir(directory.c_str()) == 0;
#endif
}
#if defined(__APPLE__)

View File

@@ -6,7 +6,6 @@
#include <array>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <functional>
#include <limits>
@@ -39,40 +38,48 @@ enum class UserPath {
UserDir,
};
// Returns true if the path exists
[[nodiscard]] bool Exists(const std::filesystem::path& path);
// FileSystem tree node/
struct FSTEntry {
bool isDirectory;
u64 size; // file length or number of entries from children
std::string physicalName; // name on disk
std::string virtualName; // name in FST names table
std::vector<FSTEntry> children;
};
// Returns true if path is a directory
[[nodiscard]] bool IsDirectory(const std::filesystem::path& path);
// Returns true if file filename exists
[[nodiscard]] bool Exists(const std::string& filename);
// Returns true if filename is a directory
[[nodiscard]] bool IsDirectory(const std::string& filename);
// Returns the size of filename (64bit)
[[nodiscard]] u64 GetSize(const std::filesystem::path& path);
[[nodiscard]] u64 GetSize(const std::string& filename);
// Overloaded GetSize, accepts file descriptor
[[nodiscard]] u64 GetSize(int fd);
// Overloaded GetSize, accepts FILE*
[[nodiscard]] u64 GetSize(FILE* f);
// Returns true if successful, or path already exists.
bool CreateDir(const std::filesystem::path& path);
bool CreateDir(const std::string& filename);
// Create all directories in path
// Returns true if successful, or path already exists.
[[nodiscard("Directory creation can fail and must be tested")]] bool CreateDirs(
const std::filesystem::path& path);
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const std::string& fullPath);
// Creates directories in path. Returns true on success.
[[deprecated("This function is deprecated, use CreateDirs")]] bool CreateFullPath(
const std::filesystem::path& path);
// Deletes a given filename, return true on success
// Doesn't supports deleting a directory
bool Delete(const std::string& filename);
// Deletes a given file at the path.
// This will also delete empty directories.
// Return true on success
bool Delete(const std::filesystem::path& path);
// Deletes a directory filename, returns true on success
bool DeleteDir(const std::string& filename);
// Renames file src to dst, returns true on success
bool Rename(const std::filesystem::path& src, const std::filesystem::path& dst);
// renames file srcFilename to destFilename, returns true on success
bool Rename(const std::string& srcFilename, const std::string& destFilename);
// copies file src to dst, returns true on success
bool Copy(const std::filesystem::path& src, const std::filesystem::path& dst);
// copies file srcFilename to destFilename, returns true on success
bool Copy(const std::string& srcFilename, const std::string& destFilename);
// creates an empty file filename, returns true on success
bool CreateEmptyFile(const std::string& filename);
@@ -99,17 +106,27 @@ using DirectoryEntryCallable = std::function<bool(
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
DirectoryEntryCallable callback);
// Deletes the given path and anything under it. Returns true on success.
bool DeleteDirRecursively(const std::filesystem::path& path);
/**
* Scans the directory tree, storing the results.
* @param directory the parent directory to start scanning from
* @param parent_entry FSTEntry where the filesystem tree results will be stored.
* @param recursion Number of children directories to read before giving up.
* @return the total number of files/directories found
*/
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
unsigned int recursion = 0);
// deletes the given directory and anything under it. Returns true on success.
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
// Returns the current directory
[[nodiscard]] std::optional<std::filesystem::path> GetCurrentDir();
[[nodiscard]] std::optional<std::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::filesystem::path& src, const std::filesystem::path& dst);
void CopyDir(const std::string& source_path, const std::string& dest_path);
// Set the current directory to given path
bool SetCurrentDir(const std::filesystem::path& path);
// Set the current directory to given directory
bool SetCurrentDir(const std::string& directory);
// Returns a pointer to a string with a yuzu data dir in the user's home
// directory. To be used in "multi-user" mode (that is, installed).

View File

@@ -1,345 +0,0 @@
// Copyright 2019 TuxSH
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <iterator>
#include <list>
#include <utility>
#include "common/bit_util.h"
#include "common/common_types.h"
namespace Common {
/**
* A MultiLevelQueue is a type of priority queue which has the following characteristics:
* - iteratable through each of its elements.
* - back can be obtained.
* - O(1) add, lookup (both front and back)
* - discrete priorities and a max of 64 priorities (limited domain)
* This type of priority queue is normaly used for managing threads within an scheduler
*/
template <typename T, std::size_t Depth>
class MultiLevelQueue {
public:
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
using size_type = std::size_t;
template <bool is_constant>
class iterator_impl {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using pointer = std::conditional_t<is_constant, T*, const T*>;
using reference = std::conditional_t<is_constant, const T&, T&>;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
if (lhs.IsEnd() && rhs.IsEnd())
return true;
return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
}
friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
return !operator==(lhs, rhs);
}
reference operator*() const {
return *it;
}
pointer operator->() const {
return it.operator->();
}
iterator_impl& operator++() {
if (IsEnd()) {
return *this;
}
++it;
if (it == GetEndItForPrio()) {
u64 prios = mlq.used_priorities;
prios &= ~((1ULL << (current_priority + 1)) - 1);
if (prios == 0) {
current_priority = static_cast<u32>(mlq.depth());
} else {
current_priority = CountTrailingZeroes64(prios);
it = GetBeginItForPrio();
}
}
return *this;
}
iterator_impl& operator--() {
if (IsEnd()) {
if (mlq.used_priorities != 0) {
current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
it = GetEndItForPrio();
--it;
}
} else if (it == GetBeginItForPrio()) {
u64 prios = mlq.used_priorities;
prios &= (1ULL << current_priority) - 1;
if (prios != 0) {
current_priority = CountTrailingZeroes64(prios);
it = GetEndItForPrio();
--it;
}
} else {
--it;
}
return *this;
}
iterator_impl operator++(int) {
const iterator_impl v{*this};
++(*this);
return v;
}
iterator_impl operator--(int) {
const iterator_impl v{*this};
--(*this);
return v;
}
// allow implicit const->non-const
iterator_impl(const iterator_impl<false>& other)
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
iterator_impl(const iterator_impl<true>& other)
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
iterator_impl& operator=(const iterator_impl<false>& other) {
mlq = other.mlq;
it = other.it;
current_priority = other.current_priority;
return *this;
}
friend class iterator_impl<true>;
iterator_impl() = default;
private:
friend class MultiLevelQueue;
using container_ref =
std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
typename std::list<T>::iterator>;
explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
: mlq(mlq), it(it), current_priority(current_priority) {}
explicit iterator_impl(container_ref mlq, u32 current_priority)
: mlq(mlq), it(), current_priority(current_priority) {}
bool IsEnd() const {
return current_priority == mlq.depth();
}
list_iterator GetBeginItForPrio() const {
return mlq.levels[current_priority].begin();
}
list_iterator GetEndItForPrio() const {
return mlq.levels[current_priority].end();
}
container_ref mlq;
list_iterator it;
u32 current_priority;
};
using iterator = iterator_impl<false>;
using const_iterator = iterator_impl<true>;
void add(const T& element, u32 priority, bool send_back = true) {
if (send_back)
levels[priority].push_back(element);
else
levels[priority].push_front(element);
used_priorities |= 1ULL << priority;
}
void remove(const T& element, u32 priority) {
auto it = ListIterateTo(levels[priority], element);
if (it == levels[priority].end())
return;
levels[priority].erase(it);
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
remove(element, old_priority);
add(element, new_priority, !adjust_front);
}
void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
adjust(*it, old_priority, new_priority, adjust_front);
}
void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
ListIterateTo(levels[priority], element));
other.used_priorities |= 1ULL << priority;
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
transfer_to_front(*it, priority, other);
}
void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
ListIterateTo(levels[priority], element));
other.used_priorities |= 1ULL << priority;
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
transfer_to_back(*it, priority, other);
}
void yield(u32 priority, std::size_t n = 1) {
ListShiftForward(levels[priority], n);
}
[[nodiscard]] std::size_t depth() const {
return Depth;
}
[[nodiscard]] std::size_t size(u32 priority) const {
return levels[priority].size();
}
[[nodiscard]] std::size_t size() const {
u64 priorities = used_priorities;
std::size_t size = 0;
while (priorities != 0) {
const u64 current_priority = CountTrailingZeroes64(priorities);
size += levels[current_priority].size();
priorities &= ~(1ULL << current_priority);
}
return size;
}
[[nodiscard]] bool empty() const {
return used_priorities == 0;
}
[[nodiscard]] bool empty(u32 priority) const {
return (used_priorities & (1ULL << priority)) == 0;
}
[[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const {
const u64 priorities =
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
}
[[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
const u64 priorities = min_priority >= Depth - 1
? used_priorities
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
}
[[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? cend()
: const_iterator{*this, levels[priority].cbegin(), priority};
}
[[nodiscard]] const_iterator begin(u32 max_prio = 0) const {
return cbegin(max_prio);
}
[[nodiscard]] iterator begin(u32 max_prio = 0) {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
}
[[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const {
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
}
[[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const {
return cend(min_prio);
}
[[nodiscard]] iterator end(u32 min_prio = Depth - 1) {
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
}
[[nodiscard]] T& front(u32 max_priority = 0) {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
[[nodiscard]] const T& front(u32 max_priority = 0) const {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
[[nodiscard]] T& back(u32 min_priority = Depth - 1) {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
[[nodiscard]] const T& back(u32 min_priority = Depth - 1) const {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
void clear() {
used_priorities = 0;
for (std::size_t i = 0; i < Depth; i++) {
levels[i].clear();
}
}
private:
using const_list_iterator = typename std::list<T>::const_iterator;
static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
if (shift >= list.size()) {
return;
}
const auto begin_range = list.begin();
const auto end_range = std::next(begin_range, shift);
list.splice(list.end(), list, begin_range, end_range);
}
static void ListSplice(std::list<T>& in_list, const_list_iterator position,
std::list<T>& out_list, const_list_iterator element) {
in_list.splice(position, out_list, element);
}
[[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list,
const T& element) {
auto it = list.cbegin();
while (it != list.cend() && *it != element) {
++it;
}
return it;
}
std::array<std::list<T>, Depth> levels;
u64 used_priorities = 0;
};
} // namespace Common

View File

@@ -41,6 +41,7 @@ add_library(core STATIC
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
file_sys/common_funcs.h
file_sys/content_archive.cpp
file_sys/content_archive.h
file_sys/control_metadata.cpp
@@ -134,6 +135,8 @@ add_library(core STATIC
frontend/emu_window.h
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
frontend/input_interpreter.cpp
frontend/input_interpreter.h
frontend/input.h
hardware_interrupt_manager.cpp
hardware_interrupt_manager.h
@@ -148,10 +151,19 @@ add_library(core STATIC
hle/kernel/code_set.cpp
hle/kernel/code_set.h
hle/kernel/errors.h
hle/kernel/global_scheduler_context.cpp
hle/kernel/global_scheduler_context.h
hle/kernel/handle_table.cpp
hle/kernel/handle_table.h
hle/kernel/hle_ipc.cpp
hle/kernel/hle_ipc.h
hle/kernel/k_affinity_mask.h
hle/kernel/k_priority_queue.h
hle/kernel/k_scheduler.cpp
hle/kernel/k_scheduler.h
hle/kernel/k_scheduler_lock.h
hle/kernel/k_scoped_lock.h
hle/kernel/k_scoped_scheduler_lock_and_sleep.h
hle/kernel/kernel.cpp
hle/kernel/kernel.h
hle/kernel/memory/address_space_info.cpp
@@ -186,8 +198,6 @@ add_library(core STATIC
hle/kernel/readable_event.h
hle/kernel/resource_limit.cpp
hle/kernel/resource_limit.h
hle/kernel/scheduler.cpp
hle/kernel/scheduler.h
hle/kernel/server_port.cpp
hle/kernel/server_port.h
hle/kernel/server_session.cpp

View File

@@ -294,6 +294,9 @@ void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) {
}
void ARM_Dynarmic_32::ClearExclusiveState() {
if (!jit) {
return;
}
jit->ClearExclusiveState();
}

View File

@@ -15,8 +15,8 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
#include "core/settings.h"
@@ -330,6 +330,9 @@ void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) {
}
void ARM_Dynarmic_64::ClearExclusiveState() {
if (!jit) {
return;
}
jit->ClearExclusiveState();
}

View File

@@ -27,10 +27,10 @@
#include "core/file_sys/vfs_real.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/apm/controller.h"
@@ -507,14 +507,6 @@ std::size_t System::CurrentCoreIndex() const {
return core;
}
Kernel::Scheduler& System::CurrentScheduler() {
return impl->kernel.CurrentScheduler();
}
const Kernel::Scheduler& System::CurrentScheduler() const {
return impl->kernel.CurrentScheduler();
}
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
return impl->kernel.CurrentPhysicalCore();
}
@@ -523,22 +515,14 @@ const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
return impl->kernel.CurrentPhysicalCore();
}
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
return impl->kernel.Scheduler(core_index);
}
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
return impl->kernel.Scheduler(core_index);
/// Gets the global scheduler
Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() {
return impl->kernel.GlobalSchedulerContext();
}
/// Gets the global scheduler
Kernel::GlobalScheduler& System::GlobalScheduler() {
return impl->kernel.GlobalScheduler();
}
/// Gets the global scheduler
const Kernel::GlobalScheduler& System::GlobalScheduler() const {
return impl->kernel.GlobalScheduler();
const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const {
return impl->kernel.GlobalSchedulerContext();
}
Kernel::Process* System::CurrentProcess() {

View File

@@ -26,11 +26,11 @@ class VfsFilesystem;
} // namespace FileSys
namespace Kernel {
class GlobalScheduler;
class GlobalSchedulerContext;
class KernelCore;
class PhysicalCore;
class Process;
class Scheduler;
class KScheduler;
} // namespace Kernel
namespace Loader {
@@ -213,12 +213,6 @@ public:
/// Gets the index of the currently running CPU core
[[nodiscard]] std::size_t CurrentCoreIndex() const;
/// Gets the scheduler for the CPU core that is currently running
[[nodiscard]] Kernel::Scheduler& CurrentScheduler();
/// Gets the scheduler for the CPU core that is currently running
[[nodiscard]] const Kernel::Scheduler& CurrentScheduler() const;
/// Gets the physical core for the CPU core that is currently running
[[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
@@ -261,17 +255,11 @@ public:
/// Gets an immutable reference to the renderer.
[[nodiscard]] const VideoCore::RendererBase& Renderer() const;
/// Gets the scheduler for the CPU core with the specified index
[[nodiscard]] Kernel::Scheduler& Scheduler(std::size_t core_index);
/// Gets the scheduler for the CPU core with the specified index
[[nodiscard]] const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
/// Gets the global scheduler
[[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
/// Gets the global scheduler
[[nodiscard]] Kernel::GlobalScheduler& GlobalScheduler();
/// Gets the global scheduler
[[nodiscard]] const Kernel::GlobalScheduler& GlobalScheduler() const;
[[nodiscard]] const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
/// Gets the manager for the guest device memory
[[nodiscard]] Core::DeviceMemory& DeviceMemory();

View File

@@ -10,9 +10,9 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "video_core/gpu.h"
@@ -109,11 +109,8 @@ void* CpuManager::GetStartFuncParamater() {
void CpuManager::MultiCoreRunGuestThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
kernel.CurrentScheduler()->OnThreadStart();
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
auto& host_context = thread->GetHostContext();
host_context->SetRewindPoint(GuestRewindFunction, this);
MultiCoreRunGuestLoop();
@@ -130,8 +127,8 @@ void CpuManager::MultiCoreRunGuestLoop() {
physical_core = &kernel.CurrentPhysicalCore();
}
system.ExitDynarmicProfile();
auto& scheduler = kernel.CurrentScheduler();
scheduler.TryDoContextSwitch();
physical_core->ArmInterface().ClearExclusiveState();
kernel.CurrentScheduler()->RescheduleCurrentCore();
}
}
@@ -140,25 +137,21 @@ void CpuManager::MultiCoreRunIdleThread() {
while (true) {
auto& physical_core = kernel.CurrentPhysicalCore();
physical_core.Idle();
auto& scheduler = kernel.CurrentScheduler();
scheduler.TryDoContextSwitch();
kernel.CurrentScheduler()->RescheduleCurrentCore();
}
}
void CpuManager::MultiCoreRunSuspendThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
kernel.CurrentScheduler()->OnThreadStart();
while (true) {
auto core = kernel.GetCurrentHostThreadID();
auto& scheduler = kernel.CurrentScheduler();
auto& scheduler = *kernel.CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
scheduler.TryDoContextSwitch();
scheduler.RescheduleCurrentCore();
}
}
@@ -206,11 +199,8 @@ void CpuManager::MultiCorePause(bool paused) {
void CpuManager::SingleCoreRunGuestThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
kernel.CurrentScheduler()->OnThreadStart();
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
auto& host_context = thread->GetHostContext();
host_context->SetRewindPoint(GuestRewindFunction, this);
SingleCoreRunGuestLoop();
@@ -218,7 +208,7 @@ void CpuManager::SingleCoreRunGuestThread() {
void CpuManager::SingleCoreRunGuestLoop() {
auto& kernel = system.Kernel();
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
system.EnterDynarmicProfile();
@@ -230,9 +220,10 @@ void CpuManager::SingleCoreRunGuestLoop() {
thread->SetPhantomMode(true);
system.CoreTiming().Advance();
thread->SetPhantomMode(false);
physical_core->ArmInterface().ClearExclusiveState();
PreemptSingleCore();
auto& scheduler = kernel.Scheduler(current_core);
scheduler.TryDoContextSwitch();
scheduler.RescheduleCurrentCore();
}
}
@@ -244,51 +235,53 @@ void CpuManager::SingleCoreRunIdleThread() {
system.CoreTiming().AddTicks(1000U);
idle_count++;
auto& scheduler = physical_core.Scheduler();
scheduler.TryDoContextSwitch();
scheduler.RescheduleCurrentCore();
}
}
void CpuManager::SingleCoreRunSuspendThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
kernel.CurrentScheduler()->OnThreadStart();
while (true) {
auto core = kernel.GetCurrentHostThreadID();
auto& scheduler = kernel.CurrentScheduler();
auto& scheduler = *kernel.CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
scheduler.TryDoContextSwitch();
scheduler.RescheduleCurrentCore();
}
}
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
std::size_t old_core = current_core;
auto& scheduler = system.Kernel().Scheduler(old_core);
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
if (idle_count >= 4 || from_running_enviroment) {
if (!from_running_enviroment) {
system.CoreTiming().Idle();
{
auto& scheduler = system.Kernel().Scheduler(current_core);
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
if (idle_count >= 4 || from_running_enviroment) {
if (!from_running_enviroment) {
system.CoreTiming().Idle();
idle_count = 0;
}
current_thread->SetPhantomMode(true);
system.CoreTiming().Advance();
current_thread->SetPhantomMode(false);
}
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
system.CoreTiming().ResetTicks();
scheduler.Unload(scheduler.GetCurrentThread());
auto& next_scheduler = system.Kernel().Scheduler(current_core);
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
}
// May have changed scheduler
{
auto& scheduler = system.Kernel().Scheduler(current_core);
scheduler.Reload(scheduler.GetCurrentThread());
auto* currrent_thread2 = scheduler.GetCurrentThread();
if (!currrent_thread2->IsIdleThread()) {
idle_count = 0;
}
current_thread->SetPhantomMode(true);
system.CoreTiming().Advance();
current_thread->SetPhantomMode(false);
}
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
system.CoreTiming().ResetTicks();
scheduler.Unload();
auto& next_scheduler = system.Kernel().Scheduler(current_core);
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
/// May have changed scheduler
auto& current_scheduler = system.Kernel().Scheduler(current_core);
current_scheduler.Reload();
auto* currrent_thread2 = current_scheduler.GetCurrentThread();
if (!currrent_thread2->IsIdleThread()) {
idle_count = 0;
}
}
@@ -369,8 +362,7 @@ void CpuManager::RunThread(std::size_t core) {
return;
}
auto& scheduler = system.Kernel().CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
data.is_running = true;
Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
data.is_running = false;

View File

@@ -0,0 +1,56 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
namespace FileSys {
constexpr u64 AOC_TITLE_ID_MASK = 0x7FF;
constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000;
constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
/**
* Gets the base title ID from a given title ID.
*
* @param title_id The title ID.
* @returns The base title ID.
*/
[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) {
return title_id & BASE_TITLE_ID_MASK;
}
/**
* Gets the base title ID with a program index offset from a given title ID.
*
* @param title_id The title ID.
* @param program_index The program index.
* @returns The base title ID with a program index offset.
*/
[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) {
return GetBaseTitleID(title_id) + program_index;
}
/**
* Gets the AOC (Add-On Content) base title ID from a given title ID.
*
* @param title_id The title ID.
* @returns The AOC base title ID.
*/
[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) {
return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET;
}
/**
* Gets the AOC (Add-On Content) ID from a given AOC title ID.
*
* @param aoc_title_id The AOC title ID.
* @returns The AOC ID.
*/
[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) {
return aoc_title_id & AOC_TITLE_ID_MASK;
}
} // namespace FileSys

View File

@@ -519,15 +519,17 @@ Loader::ResultStatus NCA::GetStatus() const {
return status;
}
std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
if (status != Loader::ResultStatus::Success)
std::vector<VirtualFile> NCA::GetFiles() const {
if (status != Loader::ResultStatus::Success) {
return {};
}
return files;
}
std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
if (status != Loader::ResultStatus::Success)
std::vector<VirtualDir> NCA::GetSubdirectories() const {
if (status != Loader::ResultStatus::Success) {
return {};
}
return dirs;
}
@@ -535,7 +537,7 @@ std::string NCA::GetName() const {
return file->GetName();
}
std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
VirtualDir NCA::GetParentDirectory() const {
return file->GetContainingDirectory();
}

View File

@@ -82,7 +82,7 @@ struct NCAHeader {
};
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
// According to switchbrew, an exefs must only contain these two files:
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
@@ -104,10 +104,10 @@ public:
Loader::ResultStatus GetStatus() const;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
VirtualDir GetParentDirectory() const override;
NCAContentType GetType() const;
u64 GetTitleId() const;

View File

@@ -191,7 +191,7 @@ bool BKTR::Resize(std::size_t new_size) {
return false;
}
std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
VirtualDir BKTR::GetContainingDirectory() const {
return base_romfs->GetContainingDirectory();
}

View File

@@ -106,7 +106,7 @@ public:
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;

View File

@@ -12,6 +12,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
@@ -30,7 +31,6 @@ namespace FileSys {
namespace {
constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2",
@@ -532,7 +532,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[this](const ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
return GetBaseTitleID(entry.title_id) == title_id &&
content_provider.GetEntry(entry)->GetStatus() ==
Loader::ResultStatus::Success;
});

View File

@@ -7,6 +7,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -47,6 +48,27 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_titl
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
auto nca = content_provider.GetEntry(title_id, type);
if (nca == nullptr) {
// TODO: Find the right error code to use here
return RESULT_UNKNOWN;
}
const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type));
}
ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, ContentRecordType type) const {
const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
return OpenPatchedRomFS(res_title_id, type);
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
ContentRecordType type) const {
const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);

View File

@@ -42,6 +42,10 @@ public:
void SetPackedUpdate(VirtualFile update_raw);
[[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
[[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id,
ContentRecordType type) const;
[[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, ContentRecordType type) const;
[[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
ContentRecordType type) const;

View File

@@ -6,191 +6,384 @@
namespace FileSys::SystemArchive::SharedFontData {
const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED{{
0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x03, 0x00, 0x70, 0x44, 0x53, 0x49, 0x47,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x4f, 0x53, 0x2f, 0x32,
0x33, 0x86, 0x1d, 0x9b, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70,
0xc2, 0x06, 0x20, 0xde, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x63, 0x76, 0x74, 0x20,
0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6d,
0x06, 0x59, 0x9c, 0x37, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0b, 0x64, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66,
0x10, 0x31, 0x88, 0x00, 0x00, 0x00, 0x04, 0x34, 0x00, 0x00, 0x04, 0x64, 0x68, 0x65, 0x61, 0x64,
0x15, 0x9d, 0xef, 0x91, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
0x09, 0x60, 0x03, 0x71, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78,
0x0d, 0x2e, 0x03, 0xa7, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x6f, 0x63, 0x61,
0x05, 0xc0, 0x04, 0x6c, 0x00, 0x00, 0x08, 0x98, 0x00, 0x00, 0x00, 0x1e, 0x6d, 0x61, 0x78, 0x70,
0x02, 0x1c, 0x00, 0x5f, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65,
0x7c, 0xe0, 0x84, 0x5c, 0x00, 0x00, 0x08, 0xb8, 0x00, 0x00, 0x02, 0x09, 0x70, 0x6f, 0x73, 0x74,
0x47, 0x4e, 0x74, 0x19, 0x00, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x72, 0x65, 0x70,
0x1c, 0xfc, 0x7d, 0x9c, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x7c, 0xc7, 0xb1, 0x63, 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x1b, 0x03, 0xe8,
0x00, 0x00, 0x00, 0x00, 0xd9, 0x44, 0x2f, 0x5d, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x45, 0x7b, 0x69,
0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x84, 0xff, 0x83, 0x01, 0xf4, 0x03, 0xe8,
0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x5e,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x74, 0x01, 0x90, 0x00, 0x05,
0x00, 0x04, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, 0x01, 0x1f, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00,
0x03, 0xc3, 0x00, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED{{
0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x80, 0x00, 0x03, 0x00, 0x60, 0x4F, 0x53, 0x2F, 0x32,
0x34, 0x00, 0x1E, 0x26, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70,
0xC1, 0xE7, 0xC8, 0xF3, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x01, 0x72, 0x63, 0x76, 0x74, 0x20,
0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6D,
0x06, 0x59, 0x9C, 0x37, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x80, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6C, 0x79, 0x66,
0x50, 0x0B, 0xEA, 0xFA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x0F, 0x04, 0x68, 0x65, 0x61, 0x64,
0x18, 0x65, 0x81, 0x09, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
0x09, 0x88, 0x03, 0x86, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78,
0x0A, 0xF0, 0x01, 0x94, 0x00, 0x00, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x42, 0x6C, 0x6F, 0x63, 0x61,
0x34, 0x80, 0x30, 0x6E, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x00, 0x3A, 0x6D, 0x61, 0x78, 0x70,
0x02, 0x2C, 0x00, 0x72, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65,
0xDB, 0xC5, 0x42, 0x4D, 0x00, 0x00, 0x14, 0x54, 0x00, 0x00, 0x01, 0xFE, 0x70, 0x6F, 0x73, 0x74,
0xF4, 0xB4, 0xAC, 0xAB, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x01, 0x2A, 0x70, 0x72, 0x65, 0x70,
0x1C, 0xFC, 0x7D, 0x9C, 0x00, 0x00, 0x04, 0xF4, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0xC9, 0x16, 0x5B, 0x71, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x0B, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0xD9, 0x44, 0x2F, 0x5D, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x02, 0x0D, 0xA7,
0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x9A, 0xFF, 0x80, 0x02, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xEC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x71,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xC4, 0x01, 0x90, 0x00, 0x05,
0x00, 0x04, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, 0x01, 0x26, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00,
0x03, 0xDA, 0x00, 0x68, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0xe9, 0x03, 0x84, 0xff, 0x83,
0x01, 0xf4, 0x02, 0xee, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
0x02, 0xbc, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x03, 0xe8, 0x00, 0xeb, 0x01, 0x21, 0x00, 0xff,
0x00, 0xff, 0x01, 0x3d, 0x01, 0x17, 0x00, 0x42, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x17, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x06, 0x00, 0x4c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xC0, 0x00, 0x0D, 0xE0, 0xF0, 0x03, 0x9A, 0xFF, 0x80,
0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x02, 0xCD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0x04, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C,
0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x10,
0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA9, 0xE0, 0xB4,
0xE0, 0xE9, 0xE0, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA0,
0xE0, 0xB3, 0xE0, 0xE0, 0xE0, 0xEF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF5, 0xFF, 0xE3, 0x1F, 0x64,
0x1F, 0x5B, 0x1F, 0x30, 0x1F, 0x2B, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a,
0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe9, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe0, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf5,
0xff, 0xe3, 0x1f, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb8, 0x00, 0x00, 0x2c, 0x4b, 0xb8, 0x00, 0x09, 0x50, 0x58, 0xb1, 0x01, 0x01, 0x8e, 0x59, 0xb8,
0x01, 0xff, 0x85, 0xb8, 0x00, 0x44, 0x1d, 0xb9, 0x00, 0x09, 0x00, 0x03, 0x5f, 0x5e, 0x2d, 0xb8,
0x00, 0x01, 0x2c, 0x20, 0x20, 0x45, 0x69, 0x44, 0xb0, 0x01, 0x60, 0x2d, 0xb8, 0x00, 0x02, 0x2c,
0xb8, 0x00, 0x01, 0x2a, 0x21, 0x2d, 0xb8, 0x00, 0x03, 0x2c, 0x20, 0x46, 0xb0, 0x03, 0x25, 0x46,
0x52, 0x58, 0x23, 0x59, 0x20, 0x8a, 0x20, 0x8a, 0x49, 0x64, 0x8a, 0x20, 0x46, 0x20, 0x68, 0x61,
0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8a, 0x59, 0x2f,
0x20, 0xb0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x59, 0x1b,
0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x65, 0x59, 0x59, 0x3a, 0x2d, 0xb8, 0x00,
0x04, 0x2c, 0x20, 0x46, 0xb0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8a, 0x59, 0x20, 0x46, 0x20,
0x6a, 0x61, 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x6a, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8a, 0x59,
0x2f, 0xfd, 0x2d, 0xb8, 0x00, 0x05, 0x2c, 0x4b, 0x20, 0xb0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
0xb0, 0x80, 0x44, 0x1b, 0xb0, 0x40, 0x44, 0x59, 0x1b, 0x21, 0x21, 0x20, 0x45, 0xb0, 0xc0, 0x50,
0x58, 0xb0, 0xc0, 0x44, 0x1b, 0x21, 0x59, 0x59, 0x2d, 0xb8, 0x00, 0x06, 0x2c, 0x20, 0x20, 0x45,
0x69, 0x44, 0xb0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0xb0, 0x01, 0x60, 0x2d,
0xb8, 0x00, 0x07, 0x2c, 0xb8, 0x00, 0x06, 0x2a, 0x2d, 0xb8, 0x00, 0x08, 0x2c, 0x4b, 0x20, 0xb0,
0x03, 0x26, 0x53, 0x58, 0xb0, 0x40, 0x1b, 0xb0, 0x00, 0x59, 0x8a, 0x8a, 0x20, 0xb0, 0x03, 0x26,
0x53, 0x58, 0x23, 0x21, 0xb0, 0x80, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, 0x26,
0x53, 0x58, 0x23, 0x21, 0xb8, 0x00, 0xc0, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03,
0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x00, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0,
0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x40, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20,
0xb8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xb0, 0x03, 0x25, 0x45, 0xb8, 0x01, 0x80, 0x50, 0x58, 0x23,
0x21, 0xb8, 0x01, 0x80, 0x23, 0x21, 0x1b, 0xb0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
0x1b, 0x21, 0x59, 0x44, 0x2d, 0xb8, 0x00, 0x09, 0x2c, 0x4b, 0x53, 0x58, 0x45, 0x44, 0x1b, 0x21,
0x21, 0x59, 0x2d, 0x00, 0xb8, 0x00, 0x00, 0x2b, 0x00, 0xba, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
0x2b, 0xb8, 0x00, 0x00, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x06,
0x00, 0x00, 0x35, 0x01, 0x33, 0x15, 0x01, 0x23, 0x35, 0x03, 0x52, 0x94, 0xfc, 0xa6, 0x8c, 0x90,
0x03, 0x58, 0x86, 0xfc, 0xa0, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0xeb, 0x00, 0xcc, 0x02, 0xfb,
0x03, 0x1e, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x33, 0x13, 0x23, 0x27, 0x23, 0x07, 0x23,
0x13, 0x17, 0x07, 0x06, 0x15, 0x33, 0x27, 0x07, 0x01, 0xbc, 0x6d, 0xd2, 0x7c, 0x26, 0xcc, 0x26,
0x7c, 0xd1, 0x35, 0x40, 0x02, 0x89, 0x45, 0x02, 0x03, 0x1e, 0xfd, 0xae, 0x77, 0x77, 0x02, 0x52,
0x9b, 0xcc, 0x08, 0x04, 0xda, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0x21, 0x00, 0xcc, 0x02, 0xc5,
0x03, 0x1e, 0x00, 0x15, 0x00, 0x1f, 0x00, 0x2b, 0x00, 0x00, 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02,
0x15, 0x14, 0x0e, 0x02, 0x07, 0x1e, 0x01, 0x15, 0x14, 0x0e, 0x02, 0x2b, 0x01, 0x13, 0x33, 0x32,
0x36, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x1d, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b,
0x01, 0x15, 0x01, 0x21, 0xea, 0x25, 0x3f, 0x2e, 0x1a, 0x0e, 0x15, 0x1b, 0x0e, 0x2d, 0x2d, 0x1a,
0x2e, 0x3f, 0x25, 0xf8, 0x76, 0x62, 0x20, 0x2a, 0x28, 0x22, 0x62, 0x76, 0x10, 0x18, 0x11, 0x09,
0x22, 0x22, 0x74, 0xcc, 0x02, 0x52, 0x18, 0x2b, 0x3c, 0x24, 0x1d, 0x1f, 0x17, 0x17, 0x14, 0x0f,
0x48, 0x2f, 0x24, 0x3f, 0x2e, 0x1a, 0x01, 0x5b, 0x29, 0x20, 0x20, 0x2b, 0x94, 0xf8, 0x0e, 0x16,
0x1c, 0x0e, 0x1f, 0x31, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7,
0x03, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x33, 0x17, 0x37, 0x33, 0x03, 0x13, 0x23, 0x27, 0x07,
0x23, 0x13, 0x03, 0x01, 0x04, 0x86, 0x69, 0x69, 0x86, 0xa3, 0xa8, 0x88, 0x6c, 0x6c, 0x88, 0xa8,
0xa3, 0x03, 0x1e, 0xcb, 0xcb, 0xfe, 0xda, 0xfe, 0xd4, 0xcf, 0xcf, 0x01, 0x2c, 0x01, 0x26, 0x00,
0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, 0x03, 0x1e, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x03,
0x33, 0x17, 0x32, 0x15, 0x1e, 0x01, 0x15, 0x1b, 0x01, 0x33, 0x03, 0x15, 0x23, 0x35, 0x01, 0xb8,
0xb9, 0x7e, 0x01, 0x01, 0x01, 0x03, 0x70, 0x75, 0x7f, 0xb9, 0x76, 0x01, 0xa3, 0x01, 0x7b, 0x01,
0x01, 0x01, 0x05, 0x02, 0xff, 0x00, 0x01, 0x0a, 0xfe, 0x85, 0xd7, 0xd7, 0x00, 0x01, 0x01, 0x3d,
0x00, 0xcc, 0x02, 0xa9, 0x03, 0x1e, 0x00, 0x06, 0x00, 0x00, 0x25, 0x11, 0x33, 0x11, 0x33, 0x15,
0x21, 0x01, 0x3d, 0x75, 0xf7, 0xfe, 0x94, 0xcc, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00,
0x00, 0x02, 0x01, 0x17, 0x00, 0xbc, 0x02, 0xcf, 0x03, 0x0e, 0x00, 0x15, 0x00, 0x21, 0x00, 0x00,
0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x1d, 0x01, 0x0e, 0x03, 0x1d, 0x01, 0x17, 0x15, 0x23, 0x27,
0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x15, 0x01, 0x17,
0xf4, 0x27, 0x40, 0x2e, 0x19, 0x01, 0x1f, 0x24, 0x1e, 0x78, 0x7d, 0x6a, 0x5c, 0x75, 0x76, 0x72,
0x12, 0x19, 0x11, 0x08, 0x26, 0x26, 0x6a, 0xbc, 0x02, 0x52, 0x1d, 0x31, 0x42, 0x25, 0x16, 0x18,
0x32, 0x2a, 0x1b, 0x02, 0x01, 0xef, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x10, 0x1a, 0x1e, 0x0f, 0x23,
0x36, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x42, 0x00, 0xbc, 0x03, 0xa4, 0x03, 0x0e, 0x00, 0x0a,
0x00, 0x11, 0x00, 0x00, 0x13, 0x35, 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01,
0x11, 0x33, 0x11, 0x33, 0x15, 0x21, 0x42, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, 0xfe, 0x53, 0x01,
0x15, 0xfe, 0xeb, 0x01, 0xf7, 0x75, 0xf6, 0xfe, 0x95, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62,
0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c,
0x00, 0xbc, 0x03, 0xca, 0x03, 0x0e, 0x00, 0x0a, 0x00, 0x21, 0x00, 0x2f, 0x00, 0x00, 0x13, 0x35,
0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15,
0x14, 0x06, 0x07, 0x0e, 0x03, 0x15, 0x17, 0x15, 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32,
0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, 0x15, 0x1c, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b,
0xfe, 0x53, 0x01, 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x01,
0x0d, 0x0e, 0x0a, 0x78, 0x7d, 0x69, 0x5c, 0x75, 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14,
0x1d, 0x13, 0x69, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02,
0x52, 0x1d, 0x31, 0x42, 0x25, 0x2b, 0x44, 0x1d, 0x01, 0x08, 0x09, 0x07, 0x01, 0xf1, 0x06, 0xd7,
0xd7, 0x01, 0x3f, 0x11, 0x19, 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x02, 0x00, 0x3e,
0x00, 0xb3, 0x03, 0xa8, 0x03, 0x17, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02,
0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x27, 0x2e, 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15,
0x14, 0x16, 0x15, 0x1e, 0x05, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, 0x02, 0x35, 0x33, 0x1e,
0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x04, 0x35, 0x01, 0x11, 0x33, 0x11, 0x33, 0x15,
0x21, 0x50, 0x24, 0x3b, 0x4a, 0x27, 0x28, 0x4b, 0x39, 0x22, 0x73, 0x01, 0x01, 0x08, 0x2b, 0x29,
0x10, 0x20, 0x19, 0x0f, 0x01, 0x0b, 0x35, 0x41, 0x46, 0x3b, 0x25, 0x23, 0x3a, 0x4b, 0x27, 0x2b,
0x50, 0x3f, 0x26, 0x74, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x11, 0x2c, 0x42, 0x4d, 0x42, 0x2c,
0x01, 0xef, 0x73, 0xf6, 0xfe, 0x97, 0x02, 0x70, 0x2a, 0x3f, 0x2a, 0x14, 0x18, 0x2e, 0x44, 0x2c,
0x02, 0x03, 0x01, 0x27, 0x27, 0x07, 0x10, 0x1a, 0x12, 0x02, 0x0b, 0x02, 0x1f, 0x22, 0x19, 0x17,
0x27, 0x3f, 0x34, 0x2c, 0x3e, 0x28, 0x13, 0x1a, 0x32, 0x48, 0x2e, 0x30, 0x30, 0x06, 0x0f, 0x1a,
0x13, 0x21, 0x27, 0x1e, 0x1b, 0x29, 0x3e, 0x31, 0xfe, 0x4c, 0x02, 0x53, 0xfe, 0x10, 0x63, 0x00,
0x00, 0x03, 0x00, 0x17, 0x00, 0xb3, 0x03, 0xce, 0x03, 0x17, 0x00, 0x38, 0x00, 0x4f, 0x00, 0x5d,
0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x23, 0x2e,
0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, 0x14, 0x1e, 0x04, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e,
0x02, 0x35, 0x33, 0x1e, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x27, 0x2e, 0x03, 0x35,
0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x14, 0x06, 0x07, 0x30, 0x0e, 0x02, 0x31, 0x17, 0x15,
0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01,
0x15, 0x2a, 0x24, 0x3a, 0x4a, 0x26, 0x29, 0x4b, 0x39, 0x23, 0x73, 0x01, 0x01, 0x08, 0x2a, 0x2a,
0x10, 0x1f, 0x1a, 0x10, 0x2c, 0x42, 0x4d, 0x42, 0x2c, 0x23, 0x39, 0x4b, 0x27, 0x2b, 0x51, 0x3f,
0x27, 0x75, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x10, 0x1f, 0x1c, 0x25, 0x53, 0x47, 0x2e, 0x01,
0xed, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x0c, 0x0e, 0x0c, 0x78, 0x7d, 0x68, 0x5d, 0x75,
0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, 0x1d, 0x13, 0x69, 0x02, 0x71, 0x2a, 0x3e, 0x2a,
0x14, 0x18, 0x2e, 0x44, 0x2c, 0x02, 0x02, 0x27, 0x29, 0x07, 0x11, 0x1a, 0x12, 0x1d, 0x24, 0x1c,
0x1d, 0x2b, 0x40, 0x32, 0x2c, 0x3f, 0x29, 0x13, 0x1a, 0x31, 0x49, 0x2e, 0x30, 0x30, 0x06, 0x0f,
0x19, 0x13, 0x1e, 0x22, 0x0b, 0x0e, 0x20, 0x2f, 0x43, 0x30, 0xfe, 0x4b, 0x02, 0x52, 0x1d, 0x32,
0x42, 0x25, 0x2c, 0x42, 0x1d, 0x08, 0x0a, 0x08, 0xf1, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x11, 0x19,
0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12,
0x00, 0x12, 0x00, 0x32, 0x00, 0x72, 0x00, 0x8e, 0x00, 0xac, 0x00, 0xbe, 0x00, 0xf0, 0x01, 0x14,
0x01, 0x5c, 0x01, 0xb6, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0xa2, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x2f,
0x00, 0x17, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x46, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x58, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x06, 0x00, 0x12, 0x00, 0x65, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x20,
0x00, 0x77, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x97, 0x00, 0x03,
0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x5e, 0x00, 0xa5, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x04, 0x00, 0x24, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1a,
0x01, 0x27, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x24, 0x01, 0x41, 0x00, 0x03,
0x00, 0x01, 0x04, 0x09, 0x00, 0x11, 0x00, 0x02, 0x01, 0x65, 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53,
0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61,
0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x3b, 0x3b,
0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x2d, 0x52, 0x3b, 0x32, 0x30, 0x31, 0x39, 0x3b, 0x46, 0x4c, 0x56, 0x49, 0x2d, 0x36, 0x31, 0x34,
0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x20, 0x52, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x59,
0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d,
0x52, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00,
0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
0x6e, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x72, 0x00,
0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0x59, 0x00,
0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00,
0x52, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x39, 0x00, 0x3b, 0x00, 0x46, 0x00,
0x4c, 0x00, 0x56, 0x00, 0x49, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x31, 0x00, 0x34, 0x00, 0x59, 0x00,
0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x52, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
0x20, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x59, 0x00, 0x75, 0x00,
0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00,
0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x52, 0x00,
0x52, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0a, 0x01, 0x0b, 0x01, 0x0c,
0x01, 0x0d, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30,
0x30, 0x44, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
0x45, 0x31, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
0x45, 0x33, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
0x45, 0x35, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
0x45, 0x37, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
0x45, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB8, 0x00, 0x00, 0x2C, 0x4B, 0xB8, 0x00, 0x09, 0x50, 0x58, 0xB1, 0x01, 0x01, 0x8E, 0x59, 0xB8,
0x01, 0xFF, 0x85, 0xB8, 0x00, 0x44, 0x1D, 0xB9, 0x00, 0x09, 0x00, 0x03, 0x5F, 0x5E, 0x2D, 0xB8,
0x00, 0x01, 0x2C, 0x20, 0x20, 0x45, 0x69, 0x44, 0xB0, 0x01, 0x60, 0x2D, 0xB8, 0x00, 0x02, 0x2C,
0xB8, 0x00, 0x01, 0x2A, 0x21, 0x2D, 0xB8, 0x00, 0x03, 0x2C, 0x20, 0x46, 0xB0, 0x03, 0x25, 0x46,
0x52, 0x58, 0x23, 0x59, 0x20, 0x8A, 0x20, 0x8A, 0x49, 0x64, 0x8A, 0x20, 0x46, 0x20, 0x68, 0x61,
0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8A, 0x59, 0x2F,
0x20, 0xB0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x59, 0x1B,
0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x65, 0x59, 0x59, 0x3A, 0x2D, 0xB8, 0x00,
0x04, 0x2C, 0x20, 0x46, 0xB0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8A, 0x59, 0x20, 0x46, 0x20,
0x6A, 0x61, 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x6A, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8A, 0x59,
0x2F, 0xFD, 0x2D, 0xB8, 0x00, 0x05, 0x2C, 0x4B, 0x20, 0xB0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
0xB0, 0x80, 0x44, 0x1B, 0xB0, 0x40, 0x44, 0x59, 0x1B, 0x21, 0x21, 0x20, 0x45, 0xB0, 0xC0, 0x50,
0x58, 0xB0, 0xC0, 0x44, 0x1B, 0x21, 0x59, 0x59, 0x2D, 0xB8, 0x00, 0x06, 0x2C, 0x20, 0x20, 0x45,
0x69, 0x44, 0xB0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0xB0, 0x01, 0x60, 0x2D,
0xB8, 0x00, 0x07, 0x2C, 0xB8, 0x00, 0x06, 0x2A, 0x2D, 0xB8, 0x00, 0x08, 0x2C, 0x4B, 0x20, 0xB0,
0x03, 0x26, 0x53, 0x58, 0xB0, 0x40, 0x1B, 0xB0, 0x00, 0x59, 0x8A, 0x8A, 0x20, 0xB0, 0x03, 0x26,
0x53, 0x58, 0x23, 0x21, 0xB0, 0x80, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, 0x26,
0x53, 0x58, 0x23, 0x21, 0xB8, 0x00, 0xC0, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03,
0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x00, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0,
0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x40, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20,
0xB8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xB0, 0x03, 0x25, 0x45, 0xB8, 0x01, 0x80, 0x50, 0x58, 0x23,
0x21, 0xB8, 0x01, 0x80, 0x23, 0x21, 0x1B, 0xB0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
0x1B, 0x21, 0x59, 0x44, 0x2D, 0xB8, 0x00, 0x09, 0x2C, 0x4B, 0x53, 0x58, 0x45, 0x44, 0x1B, 0x21,
0x21, 0x59, 0x2D, 0x00, 0xB8, 0x00, 0x00, 0x2B, 0x00, 0xBA, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
0x2B, 0xB8, 0x00, 0x00, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x70,
0x00, 0xDC, 0x01, 0x34, 0x01, 0x7C, 0x01, 0xA2, 0x01, 0xF4, 0x02, 0x3C, 0x02, 0xA8, 0x03, 0x4C,
0x03, 0xE2, 0x04, 0x20, 0x04, 0x58, 0x04, 0x9A, 0x04, 0xEE, 0x05, 0x32, 0x05, 0x64, 0x05, 0x80,
0x05, 0xC6, 0x05, 0xF6, 0x06, 0x54, 0x06, 0xB2, 0x07, 0x38, 0x07, 0x60, 0x07, 0x82, 0x00, 0x00,
0x00, 0x02, 0x00, 0xA4, 0xFF, 0xFF, 0x03, 0x5C, 0x03, 0x09, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00,
0x13, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0xCD, 0x02, 0x66, 0xFD, 0x71, 0x02, 0xB8, 0xFD,
0x48, 0x02, 0xE0, 0xFD, 0x48, 0x02, 0xB8, 0x29, 0xFC, 0xF6, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x39, 0x00, 0x00,
0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x32, 0x1E,
0x02, 0x14, 0x0E, 0x02, 0x22, 0x2E, 0x02, 0x34, 0x3E, 0x01, 0x13, 0x12, 0x37, 0x33, 0x13, 0x12,
0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, 0x27, 0x26, 0x2F, 0x01,
0x06, 0x07, 0x06, 0x32, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77,
0x46, 0x46, 0x77, 0xFE, 0x9E, 0xC8, 0xB7, 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0x4E,
0x4E, 0x83, 0x23, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, 0xD1, 0x2B, 0x37, 0x33,
0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, 0x3F, 0x46, 0x77, 0xA4,
0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x4E, 0x83, 0xB7, 0xC8, 0xB7,
0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0xFD, 0x64, 0x01, 0x1A, 0xEB, 0xFE, 0xFE, 0xFE,
0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, 0x02, 0xBE, 0x02, 0x00,
0x00, 0x05, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2F,
0x00, 0x3A, 0x00, 0x44, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x02,
0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x01,
0x16, 0x17, 0x14, 0x06, 0x07, 0x06, 0x2B, 0x01, 0x19, 0x01, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16,
0x07, 0x06, 0x0F, 0x01, 0x36, 0x37, 0x34, 0x2E, 0x01, 0x27, 0x23, 0x15, 0x33, 0x32, 0x27, 0x32,
0x37, 0x36, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x45, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
0xF4, 0xE2, 0x01, 0xF7, 0x61, 0x01, 0x4E, 0x3E, 0x29, 0xAF, 0x4E, 0x81, 0x8B, 0x1D, 0x3C, 0x1F,
0x19, 0x04, 0x06, 0x39, 0x57, 0x44, 0x01, 0x1B, 0x2D, 0x51, 0x46, 0x46, 0x47, 0x66, 0x70, 0x16,
0x1F, 0x01, 0x2C, 0x08, 0x4B, 0x4C, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4,
0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2,
0x84, 0x84, 0x01, 0x6D, 0x21, 0x5B, 0x40, 0x50, 0x05, 0x03, 0x01, 0x03, 0x01, 0x05, 0x01, 0x05,
0x09, 0x30, 0x25, 0x29, 0x40, 0x21, 0xC2, 0x06, 0x3E, 0x1A, 0x21, 0x0B, 0x01, 0x8C, 0xE1, 0x0A,
0x0E, 0x54, 0x0B, 0x02, 0x79, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x38, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E,
0x02, 0x34, 0x2E, 0x02, 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E,
0x01, 0x20, 0x26, 0x36, 0x34, 0x3F, 0x01, 0x27, 0x26, 0x27, 0x33, 0x17, 0x16, 0x33, 0x36, 0x3F,
0x02, 0x32, 0x14, 0x06, 0x16, 0x12, 0x14, 0x2B, 0x01, 0x27, 0x26, 0x06, 0x0F, 0x01, 0x23, 0x45,
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x7B, 0x58, 0x58, 0x4D, 0x4F, 0x05, 0x7A,
0x34, 0x34, 0x02, 0x01, 0x33, 0x32, 0x3C, 0x3C, 0xA1, 0x01, 0xB0, 0x3E, 0x3F, 0x39, 0x3B, 0x02,
0x3A, 0x38, 0x3F, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x60,
0x02, 0x87, 0x88, 0x79, 0x7A, 0x06, 0x54, 0x54, 0x01, 0x53, 0x53, 0x01, 0x01, 0xFB, 0x04, 0xFE,
0xF8, 0x02, 0x5B, 0x5A, 0x03, 0x59, 0x59, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E,
0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E,
0x01, 0x10, 0x36, 0x01, 0x35, 0x27, 0x26, 0x34, 0x3B, 0x01, 0x17, 0x16, 0x36, 0x3F, 0x01, 0x33,
0x03, 0x15, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01,
0x36, 0x5E, 0x5F, 0x3C, 0x3D, 0x3D, 0x3D, 0x03, 0x3B, 0x3B, 0x77, 0xBE, 0x68, 0x03, 0x3F, 0x46,
0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0xFE,
0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6E, 0x96, 0x95, 0x01, 0x67, 0x67,
0x03, 0x66, 0x65, 0xFE, 0xD3, 0xDA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC,
0x03, 0x4B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x00, 0x01, 0x21, 0x22, 0x15, 0x30, 0x11,
0x21, 0x17, 0x21, 0x11, 0x10, 0x25, 0x21, 0x01, 0x11, 0x33, 0x11, 0x21, 0x15, 0x03, 0xBB, 0xFD,
0x77, 0xED, 0x03, 0x76, 0x31, 0xFC, 0x28, 0x01, 0x1E, 0x02, 0xBA, 0xFD, 0x5C, 0x68, 0x01, 0x08,
0x03, 0x1A, 0xEE, 0xFD, 0xC2, 0x31, 0x02, 0x6F, 0x01, 0x1E, 0x01, 0xFD, 0x36, 0x02, 0x07, 0xFE,
0x50, 0x57, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, 0x03, 0x4B, 0x00, 0x06,
0x00, 0x0C, 0x00, 0x27, 0x00, 0x32, 0x00, 0x00, 0x05, 0x11, 0x34, 0x27, 0x30, 0x21, 0x11, 0x07,
0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x07,
0x06, 0x07, 0x06, 0x07, 0x1E, 0x02, 0x15, 0x07, 0x23, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x13,
0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x23, 0x15, 0x33, 0x36, 0x03, 0xBB, 0xED, 0xFD, 0x77, 0x31,
0x02, 0xBA, 0x01, 0x1E, 0xFD, 0x2A, 0x77, 0x76, 0x15, 0x49, 0x20, 0x35, 0x08, 0x04, 0x06, 0x13,
0x66, 0x0C, 0x01, 0x1F, 0x2E, 0x65, 0x3D, 0x3D, 0x2A, 0x56, 0x28, 0x2E, 0x19, 0x99, 0x3C, 0x20,
0x10, 0x56, 0x4F, 0x46, 0x47, 0x12, 0x02, 0x3E, 0xED, 0x01, 0xFC, 0xD4, 0x31, 0x03, 0x8E, 0xFE,
0xE1, 0xFD, 0x91, 0xC4, 0x02, 0x07, 0x01, 0x04, 0x13, 0x21, 0x44, 0x1D, 0x19, 0x58, 0x15, 0x02,
0x01, 0x13, 0x2D, 0xA2, 0x01, 0x01, 0x3D, 0x81, 0x1A, 0x01, 0x01, 0xDA, 0x01, 0x2D, 0x08, 0x3A,
0x29, 0x0F, 0x08, 0x01, 0x85, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
0x03, 0x13, 0x00, 0x09, 0x00, 0x11, 0x00, 0x26, 0x00, 0x32, 0x00, 0x00, 0x37, 0x21, 0x34, 0x10,
0x35, 0x34, 0x27, 0x21, 0x04, 0x11, 0x23, 0x10, 0x25, 0x21, 0x16, 0x15, 0x11, 0x21, 0x37, 0x35,
0x37, 0x36, 0x22, 0x2B, 0x01, 0x3D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x0F, 0x01, 0x3B, 0x01, 0x1D,
0x01, 0x2B, 0x01, 0x25, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x45,
0x03, 0x76, 0x45, 0xFE, 0x2D, 0xFE, 0xA2, 0x31, 0x01, 0x8F, 0x01, 0xD3, 0x76, 0xFC, 0x28, 0xA7,
0x68, 0x68, 0x01, 0x5B, 0x5D, 0x90, 0x91, 0x6C, 0x6D, 0x71, 0x70, 0xA0, 0xA0, 0x01, 0x75, 0x27,
0x28, 0x63, 0x63, 0x8B, 0x8A, 0x27, 0x69, 0x01, 0xA4, 0x69, 0x44, 0x01, 0x02, 0xFE, 0xA4, 0x01,
0x8C, 0x03, 0x01, 0x75, 0xFD, 0x58, 0xBB, 0x24, 0x80, 0x80, 0x21, 0x21, 0x1F, 0x1E, 0x85, 0x86,
0x20, 0x22, 0xC3, 0xC3, 0xA1, 0xA3, 0x20, 0x22, 0x00, 0x05, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
0x03, 0x13, 0x00, 0x08, 0x00, 0x10, 0x00, 0x2B, 0x00, 0x37, 0x00, 0x44, 0x00, 0x00, 0x37, 0x21,
0x11, 0x10, 0x25, 0x30, 0x21, 0x06, 0x15, 0x03, 0x11, 0x34, 0x37, 0x21, 0x04, 0x19, 0x01, 0x01,
0x35, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17,
0x16, 0x23, 0x2F, 0x01, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x23, 0x37, 0x32, 0x36, 0x37, 0x36, 0x35,
0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x05, 0x35, 0x37, 0x36, 0x26, 0x2B, 0x01, 0x35, 0x21, 0x15,
0x03, 0x17, 0x15, 0x45, 0x03, 0x76, 0xFE, 0xA2, 0xFE, 0x2D, 0x45, 0x31, 0x76, 0x01, 0xD3, 0x01,
0x8F, 0xFE, 0x1E, 0x65, 0x6F, 0x15, 0x46, 0x10, 0x05, 0x04, 0x0D, 0x4F, 0x09, 0x09, 0x1F, 0x1D,
0x3A, 0x06, 0x01, 0x30, 0x2F, 0x22, 0x37, 0x1E, 0x29, 0x14, 0x4E, 0x82, 0x34, 0x19, 0x0E, 0x13,
0x0A, 0x22, 0x07, 0x38, 0x37, 0xFE, 0x3E, 0x68, 0x68, 0x01, 0x5C, 0x5C, 0x01, 0x20, 0xD8, 0xE1,
0x27, 0x01, 0x5D, 0x01, 0x5B, 0x03, 0x01, 0x44, 0xFD, 0x58, 0x02, 0xA8, 0x75, 0x01, 0x03, 0xFE,
0x74, 0xFE, 0x71, 0x01, 0x5C, 0xC5, 0x01, 0x04, 0x0C, 0x43, 0x15, 0x1D, 0x44, 0x10, 0x04, 0x06,
0x14, 0x2B, 0x56, 0x10, 0x01, 0x01, 0x34, 0x52, 0x1C, 0x01, 0x01, 0xA5, 0xE3, 0x04, 0x06, 0x0A,
0x20, 0x2C, 0x04, 0x01, 0x65, 0xE3, 0x47, 0x80, 0x80, 0x01, 0x42, 0x3D, 0xFE, 0xF5, 0x01, 0x41,
0x00, 0x04, 0x00, 0x14, 0x00, 0x52, 0x03, 0xEC, 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x64,
0x00, 0x70, 0x00, 0x00, 0x25, 0x11, 0x21, 0x22, 0x15, 0x30, 0x15, 0x14, 0x33, 0x11, 0x21, 0x32,
0x15, 0x11, 0x14, 0x27, 0x21, 0x22, 0x26, 0x3D, 0x01, 0x34, 0x36, 0x13, 0x26, 0x27, 0x26, 0x27,
0x26, 0x37, 0x33, 0x36, 0x37, 0x36, 0x33, 0x16, 0x17, 0x16, 0x17, 0x16, 0x37, 0x36, 0x37, 0x36,
0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x34, 0x37, 0x36, 0x37,
0x36, 0x37, 0x36, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x0F, 0x01, 0x22, 0x06, 0x23,
0x27, 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16,
0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x27, 0x37, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B,
0x01, 0x1D, 0x01, 0x2B, 0x01, 0x03, 0xBB, 0xFD, 0x2A, 0xA0, 0xA0, 0x02, 0xEE, 0x19, 0x19, 0xFD,
0x12, 0x57, 0x7A, 0x7A, 0xCA, 0x38, 0x1D, 0x16, 0x08, 0x03, 0x01, 0x02, 0x0F, 0x0C, 0x1E, 0x01,
0x02, 0x04, 0x0C, 0x2B, 0x0F, 0x0E, 0x18, 0x0C, 0x09, 0x04, 0x15, 0x32, 0x23, 0x12, 0x1C, 0x0E,
0x09, 0x03, 0x01, 0x01, 0x09, 0x21, 0x0F, 0x14, 0x2E, 0x2A, 0x13, 0x0F, 0x0C, 0x08, 0x0B, 0x05,
0x02, 0x01, 0x02, 0x03, 0x36, 0x03, 0x02, 0x03, 0x08, 0x0D, 0x23, 0x16, 0x0E, 0x10, 0x01, 0x01,
0x07, 0x0B, 0x32, 0x25, 0x13, 0x26, 0x0F, 0x09, 0x01, 0x01, 0x0F, 0x11, 0x24, 0x21, 0x2A, 0xE3,
0x20, 0x20, 0x52, 0x50, 0x71, 0x71, 0x84, 0x02, 0x00, 0xAF, 0xA2, 0xAF, 0x02, 0x32, 0x19, 0xFD,
0xCE, 0x19, 0x01, 0x84, 0x5C, 0xA2, 0x5C, 0x85, 0xFE, 0x29, 0x04, 0x1E, 0x18, 0x26, 0x0F, 0x01,
0x02, 0x01, 0x03, 0x05, 0x0B, 0x29, 0x06, 0x02, 0x03, 0x04, 0x11, 0x0B, 0x0D, 0x0A, 0x06, 0x12,
0x0D, 0x0A, 0x07, 0x0C, 0x18, 0x0D, 0x10, 0x06, 0x18, 0x05, 0x27, 0x14, 0x09, 0x03, 0x0A, 0x0D,
0x06, 0x09, 0x09, 0x0D, 0x0F, 0x14, 0x0C, 0x06, 0x03, 0x02, 0x04, 0x10, 0x0A, 0x11, 0x08, 0x09,
0x0E, 0x0C, 0x07, 0x0C, 0x0C, 0x0A, 0x07, 0x0F, 0x20, 0x11, 0x18, 0x1E, 0x1A, 0x1E, 0x0C, 0x0B,
0x03, 0xAA, 0xA5, 0x89, 0x8A, 0x1C, 0x1B, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, 0x53, 0x03, 0xEC,
0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x38, 0x00, 0x65, 0x00, 0x00, 0x01, 0x30,
0x21, 0x11, 0x21, 0x32, 0x3D, 0x01, 0x34, 0x27, 0x32, 0x16, 0x1D, 0x01, 0x14, 0x06, 0x23, 0x21,
0x26, 0x35, 0x11, 0x34, 0x33, 0x01, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07,
0x17, 0x1E, 0x01, 0x1F, 0x01, 0x23, 0x2A, 0x01, 0x2E, 0x01, 0x23, 0x27, 0x15, 0x37, 0x32, 0x37,
0x36, 0x27, 0x2E, 0x01, 0x2B, 0x01, 0x15, 0x05, 0x26, 0x27, 0x37, 0x32, 0x3F, 0x01, 0x16, 0x17,
0x1E, 0x01, 0x37, 0x36, 0x27, 0x2E, 0x04, 0x37, 0x3E, 0x01, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14,
0x06, 0x27, 0x26, 0x27, 0x26, 0x0E, 0x01, 0x1E, 0x02, 0x17, 0x16, 0x06, 0x07, 0x06, 0x07, 0x06,
0x03, 0x1B, 0xFD, 0x2A, 0x02, 0xD6, 0xA0, 0xA0, 0x57, 0x7A, 0x7A, 0x57, 0xFD, 0x12, 0x19, 0x19,
0x01, 0xD3, 0x47, 0x44, 0x11, 0x3E, 0x18, 0x21, 0x0B, 0x0C, 0x43, 0x04, 0x17, 0x1C, 0x1E, 0x16,
0x26, 0x26, 0x03, 0x4D, 0x18, 0x1E, 0x11, 0x25, 0x3A, 0x0C, 0x22, 0x08, 0x03, 0x1B, 0x3E, 0x29,
0xFE, 0xAC, 0x0D, 0x04, 0x02, 0x02, 0x1E, 0x1D, 0x03, 0x02, 0x0C, 0x4C, 0x13, 0x20, 0x07, 0x04,
0x1B, 0x56, 0x2D, 0x1C, 0x01, 0x02, 0x44, 0x35, 0x49, 0x1F, 0x10, 0x03, 0x41, 0x01, 0x06, 0x0A,
0x16, 0x3C, 0x18, 0x0C, 0x16, 0x5D, 0x15, 0x33, 0x03, 0x2B, 0x1E, 0x34, 0x59, 0x02, 0x84, 0xFE,
0x00, 0xAF, 0xA2, 0xAF, 0x32, 0x85, 0x5C, 0xA2, 0x5C, 0x84, 0x01, 0x17, 0x02, 0x32, 0x19, 0xFE,
0x2F, 0x01, 0x45, 0x01, 0x02, 0x19, 0x22, 0x32, 0x39, 0x0B, 0x08, 0x0F, 0x27, 0x2F, 0x24, 0x75,
0x12, 0x01, 0x88, 0xBB, 0x04, 0x09, 0x2A, 0x0F, 0x0D, 0x53, 0x8A, 0x17, 0x1E, 0x04, 0x03, 0x03,
0x0C, 0x04, 0x26, 0x0E, 0x0C, 0x14, 0x1A, 0x0E, 0x0E, 0x16, 0x16, 0x2C, 0x1A, 0x2D, 0x2D, 0x2A,
0x16, 0x1D, 0x06, 0x04, 0x01, 0x1A, 0x09, 0x11, 0x09, 0x17, 0x18, 0x0D, 0x17, 0x0C, 0x1B, 0x71,
0x1B, 0x12, 0x01, 0x03, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F,
0x00, 0x1B, 0x00, 0x27, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02,
0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13,
0x33, 0x35, 0x33, 0x15, 0x33, 0x15, 0x23, 0x15, 0x23, 0x35, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77,
0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84,
0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC5, 0x4E, 0xC5, 0xC4, 0x50, 0xC4, 0x03, 0x3F,
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0xC4, 0xC5, 0x4E, 0xC5, 0xC5,
0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x1F,
0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24,
0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x35, 0x21, 0x15, 0x02,
0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C,
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, 0x03, 0x3F,
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0x71, 0x4E, 0x4E, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x1B, 0x00, 0x25,
0x00, 0x00, 0x00, 0x20, 0x0E, 0x01, 0x10, 0x1E, 0x01, 0x20, 0x3E, 0x01, 0x10, 0x26, 0x01, 0x12,
0x37, 0x33, 0x13, 0x12, 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30,
0x27, 0x26, 0x2F, 0x01, 0x06, 0x07, 0x06, 0x32, 0x02, 0x86, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2,
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xFD, 0xA0, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E,
0xD1, 0x2B, 0x37, 0x33, 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03,
0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0x9A, 0x01, 0x1A,
0xEB, 0xFE, 0xFE, 0xFE, 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C,
0x02, 0xBE, 0x02, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
0x00, 0x20, 0x00, 0x2B, 0x00, 0x35, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10,
0x0E, 0x01, 0x20, 0x26, 0x01, 0x30, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x23, 0x27,
0x19, 0x01, 0x33, 0x32, 0x37, 0x3E, 0x01, 0x35, 0x26, 0x07, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x1E,
0x02, 0x15, 0x06, 0x27, 0x23, 0x35, 0x33, 0x16, 0x17, 0x16, 0x14, 0x07, 0x06, 0x14, 0x84, 0xE2,
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x01, 0xF7, 0x0A, 0x3A, 0x05, 0x04, 0x19,
0x20, 0x3B, 0x1D, 0x8B, 0x81, 0x4E, 0xAF, 0x29, 0x3E, 0x4E, 0x01, 0xAE, 0x0D, 0x47, 0x46, 0x46,
0x52, 0x2C, 0x1B, 0x01, 0xB7, 0x27, 0x4C, 0x4C, 0x07, 0x2C, 0x1E, 0x16, 0xFE, 0x01, 0x0C, 0xE2,
0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x6D, 0x06, 0x21, 0x40, 0x2A, 0x24, 0x30,
0x09, 0x05, 0x01, 0xFE, 0xFB, 0xFE, 0xFD, 0x03, 0x05, 0x4F, 0x41, 0x5B, 0x9B, 0x01, 0x8C, 0x01,
0x0B, 0x21, 0x1A, 0x3E, 0xDA, 0x79, 0x01, 0x01, 0x0B, 0x54, 0x0E, 0x0A, 0x00, 0x02, 0x00, 0x14,
0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x29, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01,
0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x36, 0x14, 0x3B, 0x01, 0x37, 0x36, 0x37, 0x36,
0x1F, 0x01, 0x33, 0x32, 0x34, 0x02, 0x26, 0x36, 0x34, 0x23, 0x0F, 0x01, 0x06, 0x07, 0x22, 0x2F,
0x01, 0x23, 0x16, 0x1F, 0x01, 0x07, 0x14, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
0xF4, 0xE2, 0x7B, 0x3D, 0x3F, 0x38, 0x3A, 0x01, 0x02, 0x3A, 0x39, 0x3F, 0x3E, 0xB0, 0x01, 0xA1,
0x3C, 0x3C, 0x32, 0x33, 0x01, 0x02, 0x34, 0x34, 0x7A, 0x05, 0x4F, 0x4D, 0x58, 0xFE, 0x01, 0x0C,
0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x62, 0x02, 0x59, 0x59, 0x02, 0x01, 0x5A,
0x5B, 0x02, 0x01, 0x08, 0x04, 0xFB, 0x01, 0x01, 0x53, 0x53, 0x01, 0x54, 0x54, 0x06, 0x7A, 0x79,
0x88, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
0x00, 0x1B, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36,
0x01, 0x15, 0x33, 0x35, 0x13, 0x23, 0x07, 0x0E, 0x01, 0x2F, 0x01, 0x23, 0x22, 0x16, 0x1F, 0x01,
0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x36, 0x68,
0xBE, 0x77, 0x3B, 0x3C, 0x02, 0x3D, 0x3D, 0x3D, 0x3D, 0x01, 0x5F, 0x5E, 0x03, 0x70, 0x84, 0xE2,
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6D, 0xDA, 0x01, 0x2D, 0x65,
0x66, 0x03, 0x67, 0x67, 0x01, 0x95, 0x96, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC,
0x03, 0x4A, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x05, 0x21, 0x11, 0x10, 0x05, 0x21, 0x01, 0x21,
0x35, 0x21, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x14, 0x02, 0xC4, 0xFD, 0x5C, 0x01, 0x70,
0xFE, 0xF8, 0x68, 0x41, 0x02, 0x77, 0x01, 0x14, 0x01, 0xFD, 0x38, 0x57, 0x01, 0xB0, 0x00, 0x00,
0x00, 0x03, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, 0x03, 0x49, 0x00, 0x05, 0x00, 0x20, 0x00, 0x2B,
0x00, 0x00, 0x17, 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, 0x01,
0x33, 0x37, 0x2E, 0x02, 0x27, 0x34, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26,
0x2B, 0x01, 0x05, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x16, 0x17, 0x16, 0x15, 0x14, 0x14, 0x02, 0xC4,
0x01, 0x14, 0xFD, 0x2A, 0x69, 0x19, 0x2E, 0x28, 0x56, 0x2A, 0x3D, 0x3D, 0x01, 0x65, 0x2C, 0x20,
0x0D, 0x66, 0x13, 0x06, 0x04, 0x09, 0x34, 0x20, 0x49, 0x15, 0x76, 0x77, 0x01, 0x02, 0x0C, 0x47,
0x46, 0x4F, 0x56, 0x10, 0x20, 0x41, 0x03, 0x8A, 0xFE, 0xED, 0xFD, 0x89, 0xC2, 0xDA, 0x01, 0x01,
0x1A, 0x81, 0x3D, 0x01, 0x01, 0xA3, 0x2C, 0x13, 0x01, 0x02, 0x13, 0x5A, 0x1A, 0x1C, 0x44, 0x21,
0x13, 0x04, 0x01, 0xDA, 0x02, 0x85, 0x01, 0x08, 0x0F, 0x29, 0x3A, 0x00, 0x00, 0x03, 0x00, 0x14,
0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x08, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x00, 0x05, 0x21,
0x11, 0x10, 0x21, 0x30, 0x21, 0x32, 0x15, 0x01, 0x21, 0x35, 0x23, 0x13, 0x35, 0x21, 0x15, 0x33,
0x32, 0x22, 0x0F, 0x01, 0x05, 0x21, 0x35, 0x23, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x8A,
0x01, 0xEC, 0x62, 0xFC, 0xCF, 0x01, 0x40, 0xE1, 0xD9, 0xFE, 0xDF, 0x5D, 0x5C, 0x01, 0x67, 0x68,
0x01, 0x75, 0x01, 0x15, 0xC6, 0x4F, 0x05, 0x01, 0x89, 0x01, 0x8A, 0x63, 0xFD, 0xE1, 0x42, 0x01,
0x0B, 0x3D, 0x42, 0x80, 0x80, 0x48, 0x42, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x07, 0x00, 0x22, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x00,
0x17, 0x11, 0x34, 0x37, 0x21, 0x20, 0x19, 0x01, 0x01, 0x15, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F,
0x02, 0x32, 0x35, 0x26, 0x27, 0x26, 0x27, 0x26, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26,
0x23, 0x27, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, 0x07, 0x0E, 0x01, 0x05,
0x21, 0x35, 0x27, 0x13, 0x35, 0x21, 0x15, 0x33, 0x32, 0x14, 0x0F, 0x01, 0x14, 0x62, 0x01, 0xEC,
0x01, 0x8A, 0xFE, 0x1E, 0x4E, 0x14, 0x29, 0x1E, 0x37, 0x22, 0x2F, 0x2F, 0x06, 0x3A, 0x1D, 0x1F,
0x09, 0x09, 0x4E, 0x0E, 0x04, 0x05, 0x0F, 0x47, 0x15, 0x6F, 0x65, 0x82, 0x34, 0x37, 0x38, 0x07,
0x23, 0x09, 0x13, 0x0D, 0x1A, 0xFD, 0xD6, 0x01, 0x40, 0xE1, 0xD8, 0xFE, 0xE0, 0x5C, 0x5C, 0x67,
0x68, 0x05, 0x02, 0xB0, 0x62, 0x01, 0xFE, 0x76, 0xFE, 0x77, 0x01, 0x56, 0xC5, 0xA5, 0x01, 0x01,
0x1C, 0x52, 0x34, 0x01, 0x01, 0x0E, 0x58, 0x2C, 0x13, 0x06, 0x04, 0x0F, 0x45, 0x1E, 0x14, 0x42,
0x0D, 0x04, 0x01, 0xA7, 0x65, 0x01, 0x04, 0x2C, 0x21, 0x09, 0x07, 0x03, 0xE3, 0x41, 0x01, 0x01,
0x0B, 0x3D, 0x42, 0x01, 0x80, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC,
0x02, 0xAB, 0x00, 0x08, 0x00, 0x37, 0x00, 0x3D, 0x00, 0x00, 0x13, 0x30, 0x21, 0x11, 0x21, 0x22,
0x3D, 0x01, 0x34, 0x05, 0x37, 0x34, 0x27, 0x26, 0x27, 0x26, 0x07, 0x06, 0x07, 0x0E, 0x01, 0x17,
0x1E, 0x01, 0x17, 0x16, 0x14, 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x22, 0x17,
0x1E, 0x01, 0x17, 0x16, 0x37, 0x36, 0x27, 0x26, 0x27, 0x2E, 0x02, 0x37, 0x36, 0x33, 0x32, 0x1F,
0x02, 0x33, 0x35, 0x23, 0x11, 0x23, 0xD6, 0x03, 0x16, 0xFC, 0xEA, 0xC2, 0x01, 0xC6, 0x02, 0x01,
0x0C, 0x3A, 0x2B, 0x2D, 0x13, 0x10, 0x2B, 0x01, 0x33, 0x17, 0x55, 0x15, 0x04, 0x09, 0x14, 0x58,
0x0C, 0x04, 0x02, 0x02, 0x26, 0x14, 0x01, 0x03, 0x08, 0x33, 0x38, 0x5F, 0x20, 0x10, 0x01, 0x03,
0x3C, 0x12, 0x59, 0x11, 0x01, 0x02, 0x39, 0x2C, 0x09, 0x02, 0x9D, 0xE2, 0xA2, 0x40, 0x02, 0xAB,
0xFD, 0xB2, 0xD2, 0xAA, 0xD2, 0xDC, 0x03, 0x07, 0x0B, 0x38, 0x10, 0x0C, 0x09, 0x04, 0x08, 0x19,
0x6C, 0x17, 0x0B, 0x17, 0x11, 0x07, 0x17, 0x0A, 0x1A, 0x0A, 0x29, 0x0C, 0x04, 0x04, 0x02, 0x10,
0x25, 0x37, 0x04, 0x06, 0x37, 0x1D, 0x1C, 0x3F, 0x19, 0x08, 0x16, 0x13, 0x0B, 0x1F, 0x2B, 0x04,
0xE9, 0x37, 0x01, 0x13, 0x00, 0x04, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, 0x02, 0xAB, 0x00, 0x07,
0x00, 0x1F, 0x00, 0x2A, 0x00, 0x58, 0x00, 0x00, 0x01, 0x32, 0x1D, 0x01, 0x14, 0x23, 0x21, 0x11,
0x01, 0x33, 0x35, 0x17, 0x1E, 0x03, 0x3B, 0x01, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x36, 0x37, 0x36,
0x27, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x16, 0x17, 0x16, 0x07,
0x06, 0x05, 0x16, 0x37, 0x36, 0x37, 0x3E, 0x01, 0x27, 0x2E, 0x03, 0x3E, 0x01, 0x17, 0x16, 0x17,
0x30, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x06, 0x1E, 0x03, 0x17, 0x16,
0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x07, 0x06, 0x23, 0x07, 0x16, 0x03, 0x2A, 0xC2, 0xC2, 0xFC,
0xEA, 0x01, 0xEC, 0x41, 0x11, 0x1F, 0x17, 0x4D, 0x02, 0x27, 0x26, 0x16, 0x1E, 0x1C, 0x17, 0x04,
0x43, 0x0C, 0x0B, 0x21, 0x18, 0x3E, 0x0F, 0x46, 0x47, 0x66, 0x25, 0x29, 0x3E, 0x1B, 0x03, 0x08,
0x22, 0x0C, 0xFE, 0x4D, 0x22, 0x59, 0x34, 0x1E, 0x2B, 0x03, 0x33, 0x16, 0x5C, 0x16, 0x0C, 0x18,
0x3C, 0x16, 0x0B, 0x05, 0x22, 0x21, 0x01, 0x03, 0x10, 0x1F, 0x49, 0x36, 0x43, 0x02, 0x01, 0x1C,
0x2D, 0x56, 0x1B, 0x04, 0x07, 0x20, 0x13, 0x4B, 0x0D, 0x01, 0x04, 0x1D, 0x1E, 0x02, 0x02, 0x04,
0x02, 0xAB, 0xD2, 0xAA, 0xD2, 0x02, 0x4E, 0xFE, 0x39, 0x89, 0x01, 0x01, 0x11, 0x75, 0x01, 0x25,
0x2F, 0x27, 0x0F, 0x08, 0x0C, 0x38, 0x33, 0x21, 0x19, 0x02, 0x01, 0x8A, 0x53, 0x0D, 0x0F, 0x2A,
0x09, 0x04, 0x8A, 0x3A, 0x03, 0x01, 0x12, 0x1B, 0x71, 0x1B, 0x0C, 0x17, 0x0D, 0x18, 0x17, 0x09,
0x11, 0x09, 0x1A, 0x01, 0x01, 0x07, 0x1E, 0x15, 0x29, 0x01, 0x2D, 0x2D, 0x1A, 0x2C, 0x16, 0x16,
0x0D, 0x0F, 0x1A, 0x14, 0x0C, 0x0D, 0x27, 0x04, 0x0C, 0x03, 0x03, 0x04, 0x1E, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x00,
0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x15, 0x33, 0x15,
0x33, 0x35, 0x33, 0x35, 0x23, 0x35, 0x23, 0x15, 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2,
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC4, 0x50, 0xC4, 0xC5, 0x4E, 0x03, 0x70, 0x84, 0xE2, 0xFE,
0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0x4F, 0xC5, 0xC5, 0x4E, 0xC5, 0xC4,
0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00,
0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x21, 0x35, 0x21,
0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8,
0xFE, 0x28, 0x03, 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE,
0x71, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10,
0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x85, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0xAF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x10, 0x00, 0xE2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0D,
0x01, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x01, 0x3F, 0x00, 0x03,
0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x01, 0x00, 0x20, 0x00, 0x42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E,
0x00, 0x75, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x20, 0x00, 0x8D, 0x00, 0x03,
0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
0x00, 0x05, 0x00, 0x1A, 0x00, 0xF3, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x20,
0x01, 0x1D, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6D,
0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x20, 0x00, 0x50,
0x00, 0x72, 0x00, 0x6F, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x00, 0x59, 0x75,
0x7A, 0x75, 0x20, 0x45, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x50, 0x72, 0x6F, 0x6A,
0x65, 0x63, 0x74, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53,
0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69,
0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74,
0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00,
0x6C, 0x00, 0x61, 0x00, 0x72, 0x00, 0x00, 0x52, 0x65, 0x67, 0x75, 0x6C, 0x61, 0x72, 0x00, 0x00,
0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
0x6E, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53,
0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F,
0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E,
0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00,
0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
0x00, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x00, 0x00,
0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
0x6E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xB5, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B, 0x01, 0x0C,
0x01, 0x0D, 0x01, 0x0E, 0x01, 0x0F, 0x01, 0x10, 0x01, 0x11, 0x01, 0x12, 0x01, 0x13, 0x01, 0x14,
0x01, 0x15, 0x01, 0x16, 0x01, 0x17, 0x01, 0x18, 0x01, 0x19, 0x01, 0x1A, 0x01, 0x1B, 0x07, 0x75,
0x6E, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x30, 0x30, 0x30, 0x44, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x41, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x31, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x41, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x33, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x41, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x35, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x41, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x37, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x41, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x39, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x42, 0x33, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x42, 0x34, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x31, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x33, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x35, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x37, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x39, 0x07, 0x75,
0x6E, 0x69, 0x45, 0x30, 0x45, 0x46, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x46, 0x30, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F,
}};
} // namespace FileSys::SystemArchive::SharedFontData

View File

@@ -8,6 +8,6 @@
namespace FileSys::SystemArchive::SharedFontData {
extern const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED;
extern const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED;
} // namespace FileSys::SystemArchive::SharedFontData

View File

@@ -203,7 +203,7 @@ std::string VfsFile::GetFullPath() const {
return GetContainingDirectory()->GetFullPath() + "/" + GetName();
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -231,7 +231,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co
return dir->GetFile(vec.back());
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) const {
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
if (IsRoot()) {
return GetFileRelative(path);
}
@@ -239,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
return GetParentDirectory()->GetFileAbsolute(path);
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -261,7 +261,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie
return dir;
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
if (IsRoot()) {
return GetDirectoryRelative(path);
}
@@ -269,14 +269,14 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_vie
return GetParentDirectory()->GetDirectoryAbsolute(path);
}
std::shared_ptr<VfsFile> VfsDirectory::GetFile(std::string_view name) const {
VirtualFile VfsDirectory::GetFile(std::string_view name) const {
const auto& files = GetFiles();
const auto iter = std::find_if(files.begin(), files.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
return iter == files.end() ? nullptr : *iter;
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(std::string_view name) const {
VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
const auto& subs = GetSubdirectories();
const auto iter = std::find_if(subs.begin(), subs.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
@@ -301,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const {
return file_total + subdir_total;
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -324,7 +324,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
if (IsRoot()) {
return CreateFileRelative(path);
}
@@ -332,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
return GetParentDirectory()->CreateFileAbsolute(path);
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -355,7 +355,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
if (IsRoot()) {
return CreateDirectoryRelative(path);
}
@@ -446,27 +446,27 @@ bool ReadOnlyVfsDirectory::IsReadable() const {
return true;
}
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
return nullptr;
}
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
return nullptr;
}
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
return nullptr;
}
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
return nullptr;
}

View File

@@ -91,7 +91,7 @@ public:
// Resizes the file to new_size. Returns whether or not the operation was successful.
virtual bool Resize(std::size_t new_size) = 0;
// Gets a pointer to the directory containing this file, returning nullptr if there is none.
virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
virtual VirtualDir GetContainingDirectory() const = 0;
// Returns whether or not the file can be written to.
virtual bool IsWritable() const = 0;
@@ -183,27 +183,27 @@ public:
// Retrives the file located at path as if the current directory was root. Returns nullptr if
// not found.
virtual std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const;
virtual VirtualFile GetFileRelative(std::string_view path) const;
// Calls GetFileRelative(path) on the root of the current directory.
virtual std::shared_ptr<VfsFile> GetFileAbsolute(std::string_view path) const;
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
// Retrives the directory located at path as if the current directory was root. Returns nullptr
// if not found.
virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const;
virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
// Calls GetDirectoryRelative(path) on the root of the current directory.
virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(std::string_view path) const;
virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
// Returns a vector containing all of the files in this directory.
virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
virtual std::vector<VirtualFile> GetFiles() const = 0;
// Returns the file with filename matching name. Returns nullptr if directory dosen't have a
// file with name.
virtual std::shared_ptr<VfsFile> GetFile(std::string_view name) const;
virtual VirtualFile GetFile(std::string_view name) const;
// Returns a vector containing all of the subdirectories in this directory.
virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a
// directory with name.
virtual std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const;
virtual VirtualDir GetSubdirectory(std::string_view name) const;
// Returns whether or not the directory can be written to.
virtual bool IsWritable() const = 0;
@@ -219,31 +219,31 @@ public:
virtual std::size_t GetSize() const;
// Returns the parent directory of this directory. Returns nullptr if this directory is root or
// has no parent.
virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
virtual VirtualDir GetParentDirectory() const = 0;
// Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
// if the operation failed.
virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) = 0;
virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
// Creates a new file with name name. Returns a pointer to the new file or nullptr if the
// operation failed.
virtual std::shared_ptr<VfsFile> CreateFile(std::string_view name) = 0;
virtual VirtualFile CreateFile(std::string_view name) = 0;
// Creates a new file at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path);
virtual VirtualFile CreateFileRelative(std::string_view path);
// Creates a new file at the path relative to root of this directory. Also creates directories
// if they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path);
virtual VirtualFile CreateFileAbsolute(std::string_view path);
// Creates a new directory at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path);
virtual VirtualDir CreateDirectoryRelative(std::string_view path);
// Creates a new directory at the path relative to root of this directory. Also creates
// directories if they do not exist and is supported by this implementation. Returns nullptr on
// any failure.
virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path);
virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
// Deletes the subdirectory with the given name and returns true on success.
virtual bool DeleteSubdirectory(std::string_view name) = 0;
@@ -280,12 +280,12 @@ class ReadOnlyVfsDirectory : public VfsDirectory {
public:
bool IsWritable() const override;
bool IsReadable() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override;
std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override;
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
VirtualDir CreateSubdirectory(std::string_view name) override;
VirtualFile CreateFile(std::string_view name) override;
VirtualFile CreateFileAbsolute(std::string_view path) override;
VirtualFile CreateFileRelative(std::string_view path) override;
VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
VirtualDir CreateDirectoryRelative(std::string_view path) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteSubdirectoryRecursive(std::string_view name) override;
bool CleanSubdirectoryRecursive(std::string_view name) override;

View File

@@ -46,7 +46,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> f
if (files.size() == 1)
return files[0];
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
@@ -71,20 +71,23 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
if (files.begin()->first != 0)
files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
std::string ConcatenatedVfsFile::GetName() const {
if (files.empty())
if (files.empty()) {
return "";
if (!name.empty())
}
if (!name.empty()) {
return name;
}
return files.begin()->second->GetName();
}
std::size_t ConcatenatedVfsFile::GetSize() const {
if (files.empty())
if (files.empty()) {
return 0;
}
return files.rbegin()->first + files.rbegin()->second->GetSize();
}
@@ -92,9 +95,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
return false;
}
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
if (files.empty())
VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
if (files.empty()) {
return nullptr;
}
return files.begin()->second->GetContainingDirectory();
}

View File

@@ -31,7 +31,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;

View File

@@ -20,10 +20,10 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
if (dirs.size() == 1)
return dirs[0];
return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
for (const auto& layer : dirs) {
const auto file = layer->GetFileRelative(path);
if (file != nullptr)
@@ -33,23 +33,23 @@ std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view p
return nullptr;
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
std::string_view path) const {
VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
std::vector<VirtualDir> out;
for (const auto& layer : dirs) {
auto dir = layer->GetDirectoryRelative(path);
if (dir != nullptr)
if (dir != nullptr) {
out.push_back(std::move(dir));
}
}
return MakeLayeredDirectory(std::move(out));
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
VirtualFile LayeredVfsDirectory::GetFile(std::string_view name) const {
return GetFileRelative(name);
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
return GetDirectoryRelative(name);
}
@@ -57,7 +57,7 @@ std::string LayeredVfsDirectory::GetFullPath() const {
return dirs[0]->GetFullPath();
}
std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out;
for (const auto& layer : dirs) {
for (const auto& file : layer->GetFiles()) {
@@ -72,7 +72,7 @@ std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
return out;
}
std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<std::string> names;
for (const auto& layer : dirs) {
for (const auto& sd : layer->GetSubdirectories()) {
@@ -101,15 +101,15 @@ std::string LayeredVfsDirectory::GetName() const {
return name.empty() ? dirs[0]->GetName() : name;
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
return dirs[0]->GetParentDirectory();
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
VirtualFile LayeredVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}

View File

@@ -21,20 +21,20 @@ public:
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
VirtualFile GetFileRelative(std::string_view path) const override;
VirtualDir GetDirectoryRelative(std::string_view path) const override;
VirtualFile GetFile(std::string_view name) const override;
VirtualDir GetSubdirectory(std::string_view name) const override;
std::string GetFullPath() const override;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
VirtualDir GetParentDirectory() const override;
VirtualDir CreateSubdirectory(std::string_view name) override;
VirtualFile CreateFile(std::string_view name) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;

View File

@@ -9,7 +9,7 @@
namespace FileSys {
OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_,
OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
std::string name_, VirtualDir parent_)
: file(file_), offset(offset_), size(size_), name(std::move(name_)),
parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
@@ -37,7 +37,7 @@ bool OffsetVfsFile::Resize(std::size_t new_size) {
return true;
}
std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
VirtualDir OffsetVfsFile::GetContainingDirectory() const {
return parent;
}

View File

@@ -17,14 +17,14 @@ namespace FileSys {
// the size of this wrapper.
class OffsetVfsFile : public VfsFile {
public:
OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0,
OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
std::string new_name = "", VirtualDir new_parent = nullptr);
~OffsetVfsFile() override;
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -42,7 +42,7 @@ public:
private:
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
std::shared_ptr<VfsFile> file;
VirtualFile file;
std::size_t offset;
std::size_t size;
std::string name;

View File

@@ -94,13 +94,9 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
const auto parent_path = FS::GetParentPath(path);
const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
if (!FS::Exists(path)) {
if (!FS::CreateDirs(parent_path)) {
return nullptr;
}
FS::CreateFullPath(path_fwd);
if (!FS::CreateEmptyFile(path)) {
return nullptr;
}
@@ -267,7 +263,7 @@ bool RealVfsFile::Resize(std::size_t new_size) {
return backing->Resize(new_size);
}
std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
VirtualDir RealVfsFile::GetContainingDirectory() const {
return base.OpenDirectory(parent_path, perms);
}
@@ -356,7 +352,7 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
RealVfsDirectory::~RealVfsDirectory() = default;
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view path) const {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
return nullptr;
@@ -364,7 +360,7 @@ std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path
return base.OpenFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
return nullptr;
@@ -372,20 +368,20 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string
return base.OpenDirectory(full_path, perms);
}
std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const {
VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
return GetFileRelative(name);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const {
VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
return GetDirectoryRelative(name);
}
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view path) {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateDirectory(full_path, perms);
}
@@ -395,11 +391,11 @@ bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
return base.DeleteDirectory(full_path);
}
std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
return IterateEntries<RealVfsFile, VfsFile>();
}
std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
return IterateEntries<RealVfsDirectory, VfsDirectory>();
}
@@ -415,7 +411,7 @@ std::string RealVfsDirectory::GetName() const {
return path_components.back();
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
VirtualDir RealVfsDirectory::GetParentDirectory() const {
if (path_components.size() <= 1) {
return nullptr;
}
@@ -423,12 +419,12 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
return base.OpenDirectory(parent_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) {
VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
const std::string subdir_path = (path + DIR_SEP).append(name);
return base.CreateDirectory(subdir_path, perms);
}
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) {
VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
const std::string file_path = (path + DIR_SEP).append(name);
return base.CreateFile(file_path, perms);
}

View File

@@ -50,7 +50,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -79,21 +79,21 @@ class RealVfsDirectory : public VfsDirectory {
public:
~RealVfsDirectory() override;
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
VirtualFile GetFileRelative(std::string_view path) const override;
VirtualDir GetDirectoryRelative(std::string_view path) const override;
VirtualFile GetFile(std::string_view name) const override;
VirtualDir GetSubdirectory(std::string_view name) const override;
VirtualFile CreateFileRelative(std::string_view path) override;
VirtualDir CreateDirectoryRelative(std::string_view path) override;
bool DeleteSubdirectoryRecursive(std::string_view name) override;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
VirtualDir GetParentDirectory() const override;
VirtualDir CreateSubdirectory(std::string_view name) override;
VirtualFile CreateFile(std::string_view name) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;

View File

@@ -31,7 +31,7 @@ public:
return true;
}
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
VirtualDir GetContainingDirectory() const override {
return parent;
}

View File

@@ -25,7 +25,7 @@ bool VectorVfsFile::Resize(size_t new_size) {
return true;
}
std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
VirtualDir VectorVfsFile::GetContainingDirectory() const {
return parent;
}
@@ -68,11 +68,11 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
VectorVfsDirectory::~VectorVfsDirectory() = default;
std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const {
std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
return files;
}
std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const {
std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
return dirs;
}
@@ -88,7 +88,7 @@ std::string VectorVfsDirectory::GetName() const {
return name;
}
std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const {
VirtualDir VectorVfsDirectory::GetParentDirectory() const {
return parent;
}
@@ -116,11 +116,11 @@ bool VectorVfsDirectory::Rename(std::string_view name_) {
return true;
}
std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) {
VirtualFile VectorVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}

View File

@@ -33,7 +33,7 @@ public:
return false;
}
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
VirtualDir GetContainingDirectory() const override {
return parent;
}
@@ -82,7 +82,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -106,17 +106,17 @@ public:
VirtualDir parent = nullptr);
~VectorVfsDirectory() override;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
VirtualDir GetParentDirectory() const override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
VirtualDir CreateSubdirectory(std::string_view name) override;
VirtualFile CreateFile(std::string_view name) override;
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);

View File

@@ -152,11 +152,11 @@ NAXContentType NAX::GetContentType() const {
return type;
}
std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const {
std::vector<VirtualFile> NAX::GetFiles() const {
return {dec_file};
}
std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const {
std::vector<VirtualDir> NAX::GetSubdirectories() const {
return {};
}
@@ -164,7 +164,7 @@ std::string NAX::GetName() const {
return file->GetName();
}
std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
VirtualDir NAX::GetParentDirectory() const {
return file->GetContainingDirectory();
}

View File

@@ -47,13 +47,13 @@ public:
NAXContentType GetContentType() const;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
VirtualDir GetParentDirectory() const override;
private:
Loader::ResultStatus Parse(std::string_view path);

View File

@@ -53,72 +53,4 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
finished();
}
ECommerceApplet::~ECommerceApplet() = default;
DefaultECommerceApplet::~DefaultECommerceApplet() = default;
void DefaultECommerceApplet::ShowApplicationInformation(
std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show application information for EShop, "
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
title_id, value[1], value[0],
full_display.has_value() ? fmt::format("{}", *full_display) : "null",
extra_parameter.value_or("null"));
finished();
}
void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id,
std::optional<bool> full_display) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show add on content list for EShop, "
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
title_id, value[1], value[0],
full_display.has_value() ? fmt::format("{}", *full_display) : "null");
finished();
}
void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show subscription list for EShop, title_id={:016X}, "
"user_id={:016X}{:016X}",
title_id, value[1], value[0]);
finished();
}
void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) {
const auto value = user_id.value_or(u128{});
LOG_INFO(
Service_AM,
"Application requested frontend show consumable item list for EShop, title_id={:016X}, "
"user_id={:016X}{:016X}",
title_id, value[1], value[0]);
finished();
}
void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
bool full_display) {
LOG_INFO(Service_AM,
"Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
"full_display={}",
user_id[1], user_id[0], full_display);
finished();
}
void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
bool full_display) {
LOG_INFO(Service_AM,
"Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
"full_display={}",
user_id[1], user_id[0], full_display);
finished();
}
} // namespace Core::Frontend

View File

@@ -58,55 +58,4 @@ public:
void ShowAllPhotos(std::function<void()> finished) const override;
};
class ECommerceApplet {
public:
virtual ~ECommerceApplet();
// Shows a page with application icons, description, name, and price.
virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {},
std::optional<bool> full_display = {},
std::optional<std::string> extra_parameter = {}) = 0;
// Shows a page with all of the add on content available for a game, with name, description, and
// price.
virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {},
std::optional<bool> full_display = {}) = 0;
// Shows a page with all of the subscriptions (recurring payments) for a game, with name,
// description, price, and renewal period.
virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {}) = 0;
// Shows a page with a list of any additional game related purchasable items (DLC,
// subscriptions, etc) for a particular game, with name, description, type, and price.
virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {}) = 0;
// Shows the home page of the shop.
virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
// Shows the user settings page of the shop.
virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
};
class DefaultECommerceApplet : public ECommerceApplet {
public:
~DefaultECommerceApplet() override;
void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id, std::optional<bool> full_display,
std::optional<std::string> extra_parameter) override;
void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id,
std::optional<bool> full_display) override;
void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) override;
void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) override;
void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
};
} // namespace Core::Frontend

View File

@@ -11,14 +11,22 @@ WebBrowserApplet::~WebBrowserApplet() = default;
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) {
LOG_INFO(Service_AM,
"(STUBBED) called - No suitable web browser implementation found to open website page "
"at '{}'!",
filename);
finished_callback();
void DefaultWebBrowserApplet::OpenLocalWebPage(
std::string_view local_url, std::function<void()> extract_romfs_callback,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open local web page at {}",
local_url);
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
}
void DefaultWebBrowserApplet::OpenExternalWebPage(
std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}",
external_url);
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
}
} // namespace Core::Frontend

View File

@@ -7,22 +7,34 @@
#include <functional>
#include <string_view>
#include "core/hle/service/am/applets/web_types.h"
namespace Core::Frontend {
class WebBrowserApplet {
public:
virtual ~WebBrowserApplet();
virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) = 0;
virtual void OpenLocalWebPage(
std::string_view local_url, std::function<void()> extract_romfs_callback,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
virtual void OpenExternalWebPage(
std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
};
class DefaultWebBrowserApplet final : public WebBrowserApplet {
public:
~DefaultWebBrowserApplet() override;
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) override;
void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback) const override;
void OpenExternalWebPage(std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback) const override;
};
} // namespace Core::Frontend

View File

@@ -0,0 +1,45 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/frontend/input_interpreter.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
InputInterpreter::InputInterpreter(Core::System& system)
: npad{system.ServiceManager()
.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {}
InputInterpreter::~InputInterpreter() = default;
void InputInterpreter::PollInput() {
const u32 button_state = npad.GetAndResetPressState();
previous_index = current_index;
current_index = (current_index + 1) % button_states.size();
button_states[current_index] = button_state;
}
bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
const bool current_press =
(button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
const bool previous_press =
(button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
return current_press && !previous_press;
}
bool InputInterpreter::IsButtonHeld(HIDButton button) const {
u32 held_buttons{button_states[0]};
for (std::size_t i = 1; i < button_states.size(); ++i) {
held_buttons &= button_states[i];
}
return (held_buttons & (1U << static_cast<u8>(button))) != 0;
}

View File

@@ -0,0 +1,120 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Service::HID {
class Controller_NPad;
}
enum class HIDButton : u8 {
A,
B,
X,
Y,
LStick,
RStick,
L,
R,
ZL,
ZR,
Plus,
Minus,
DLeft,
DUp,
DRight,
DDown,
LStickLeft,
LStickUp,
LStickRight,
LStickDown,
RStickLeft,
RStickUp,
RStickRight,
RStickDown,
LeftSL,
LeftSR,
RightSL,
RightSR,
};
/**
* The InputInterpreter class interfaces with HID to retrieve button press states.
* Input is intended to be polled every 50ms so that a button is considered to be
* held down after 400ms has elapsed since the initial button press and subsequent
* repeated presses occur every 50ms.
*/
class InputInterpreter {
public:
explicit InputInterpreter(Core::System& system);
virtual ~InputInterpreter();
/// Gets a button state from HID and inserts it into the array of button states.
void PollInput();
/**
* The specified button is considered to be pressed once
* if it is currently pressed and not pressed previously.
*
* @param button The button to check.
*
* @returns True when the button is pressed once.
*/
[[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed once.
*
* @tparam HIDButton The buttons to check.
*
* @returns True when at least one of the buttons is pressed once.
*/
template <HIDButton... T>
[[nodiscard]] bool IsAnyButtonPressedOnce() {
return (IsButtonPressedOnce(T) || ...);
}
/**
* The specified button is considered to be held down if it is pressed in all 9 button states.
*
* @param button The button to check.
*
* @returns True when the button is held down.
*/
[[nodiscard]] bool IsButtonHeld(HIDButton button) const;
/**
* Checks whether any of the buttons in the parameter list is held down.
*
* @tparam HIDButton The buttons to check.
*
* @returns True when at least one of the buttons is held down.
*/
template <HIDButton... T>
[[nodiscard]] bool IsAnyButtonHeld() {
return (IsButtonHeld(T) || ...);
}
private:
Service::HID::Controller_NPad& npad;
/// Stores 9 consecutive button states polled from HID.
std::array<u32, 9> button_states{};
std::size_t previous_index{};
std::size_t current_index{};
};

View File

@@ -12,8 +12,9 @@
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
@@ -58,7 +59,7 @@ ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 v
}
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
const std::vector<std::shared_ptr<Thread>> waiting_threads =
GetThreadsWaitingOnAddress(address);
WakeThreads(waiting_threads, num_to_wake);
@@ -67,7 +68,7 @@ ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
auto& memory = system.Memory();
// Ensure that we can write to the address.
@@ -92,7 +93,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
auto& memory = system.Memory();
// Ensure that we can write to the address.
@@ -153,11 +154,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
bool should_decrement) {
auto& memory = system.Memory();
auto& kernel = system.Kernel();
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
Handle event_handle = InvalidHandle;
{
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
if (current_thread->IsPendingTermination()) {
lock.CancelSleep();
@@ -210,7 +211,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
}
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (current_thread->IsWaitingForArbitration()) {
RemoveThread(SharedFrom(current_thread));
current_thread->WaitForArbitration(false);
@@ -223,11 +224,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
auto& memory = system.Memory();
auto& kernel = system.Kernel();
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
Handle event_handle = InvalidHandle;
{
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
if (current_thread->IsPendingTermination()) {
lock.CancelSleep();
@@ -265,7 +266,7 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t
}
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (current_thread->IsWaitingForArbitration()) {
RemoveThread(SharedFrom(current_thread));
current_thread->WaitForArbitration(false);

View File

@@ -0,0 +1,52 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel)
: kernel{kernel}, scheduler_lock{kernel} {}
GlobalSchedulerContext::~GlobalSchedulerContext() = default;
void GlobalSchedulerContext::AddThread(std::shared_ptr<Thread> thread) {
std::scoped_lock lock{global_list_guard};
thread_list.push_back(std::move(thread));
}
void GlobalSchedulerContext::RemoveThread(std::shared_ptr<Thread> thread) {
std::scoped_lock lock{global_list_guard};
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
thread_list.end());
}
void GlobalSchedulerContext::PreemptThreads() {
// The priority levels at which the global scheduler preempts threads every 10 ms. They are
// ordered from Core 0 to Core 3.
static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities{
59,
59,
59,
63,
};
ASSERT(IsLocked());
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id];
kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
}
}
bool GlobalSchedulerContext::IsLocked() const {
return scheduler_lock.IsLockedByCurrentThread();
}
} // namespace Kernel

View File

@@ -0,0 +1,81 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <vector>
#include "common/common_types.h"
#include "common/spin_lock.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_priority_queue.h"
#include "core/hle/kernel/k_scheduler_lock.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
class KernelCore;
class SchedulerLock;
using KSchedulerPriorityQueue =
KPriorityQueue<Thread, Core::Hardware::NUM_CPU_CORES, THREADPRIO_LOWEST, THREADPRIO_HIGHEST>;
constexpr s32 HighestCoreMigrationAllowedPriority = 2;
class GlobalSchedulerContext final {
friend class KScheduler;
public:
using LockType = KAbstractSchedulerLock<KScheduler>;
explicit GlobalSchedulerContext(KernelCore& kernel);
~GlobalSchedulerContext();
/// Adds a new thread to the scheduler
void AddThread(std::shared_ptr<Thread> thread);
/// Removes a thread from the scheduler
void RemoveThread(std::shared_ptr<Thread> thread);
/// Returns a list of all threads managed by the scheduler
[[nodiscard]] const std::vector<std::shared_ptr<Thread>>& GetThreadList() const {
return thread_list;
}
/**
* Rotates the scheduling queues of threads at a preemption priority and then does
* some core rebalancing. Preemption priorities can be found in the array
* 'preemption_priorities'.
*
* @note This operation happens every 10ms.
*/
void PreemptThreads();
/// Returns true if the global scheduler lock is acquired
bool IsLocked() const;
[[nodiscard]] LockType& SchedulerLock() {
return scheduler_lock;
}
[[nodiscard]] const LockType& SchedulerLock() const {
return scheduler_lock;
}
private:
friend class KScopedSchedulerLock;
friend class KScopedSchedulerLockAndSleep;
KernelCore& kernel;
std::atomic_bool scheduler_update_needed{};
KSchedulerPriorityQueue priority_queue;
LockType scheduler_lock;
/// Lists all thread ids that aren't deleted/etc.
std::vector<std::shared_ptr<Thread>> thread_list;
Common::SpinLock global_list_guard{};
};
} // namespace Kernel

View File

@@ -8,9 +8,9 @@
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -105,7 +105,7 @@ bool HandleTable::IsValid(Handle handle) const {
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
return SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
} else if (handle == CurrentProcess) {
return SharedFrom(kernel.CurrentProcess());
}

View File

@@ -17,11 +17,12 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
@@ -56,9 +57,9 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
writable_event = pair.writable;
}
Handle event_handle = InvalidHandle;
{
Handle event_handle = InvalidHandle;
SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
thread->SetHLECallback(
[context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
@@ -74,9 +75,8 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
thread->SetStatus(ThreadStatus::WaitHLEEvent);
thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
readable_event->AddWaitingThread(thread);
lock.Release();
thread->SetHLETimeEvent(event_handle);
}
thread->SetHLETimeEvent(event_handle);
is_thread_waiting = true;

View File

@@ -0,0 +1,58 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hardware_properties.h"
namespace Kernel {
class KAffinityMask {
public:
constexpr KAffinityMask() = default;
[[nodiscard]] constexpr u64 GetAffinityMask() const {
return this->mask;
}
constexpr void SetAffinityMask(u64 new_mask) {
ASSERT((new_mask & ~AllowedAffinityMask) == 0);
this->mask = new_mask;
}
[[nodiscard]] constexpr bool GetAffinity(s32 core) const {
return this->mask & GetCoreBit(core);
}
constexpr void SetAffinity(s32 core, bool set) {
ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
if (set) {
this->mask |= GetCoreBit(core);
} else {
this->mask &= ~GetCoreBit(core);
}
}
constexpr void SetAll() {
this->mask = AllowedAffinityMask;
}
private:
[[nodiscard]] static constexpr u64 GetCoreBit(s32 core) {
ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
return (1ULL << core);
}
static constexpr u64 AllowedAffinityMask = (1ULL << Core::Hardware::NUM_CPU_CORES) - 1;
u64 mask{};
};
} // namespace Kernel

View File

@@ -0,0 +1,451 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include <array>
#include <concepts>
#include "common/assert.h"
#include "common/bit_set.h"
#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/concepts.h"
namespace Kernel {
class Thread;
template <typename T>
concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) {
{ t.GetAffinityMask() }
->Common::ConvertibleTo<u64>;
{t.SetAffinityMask(std::declval<u64>())};
{ t.GetAffinity(std::declval<int32_t>()) }
->std::same_as<bool>;
{t.SetAffinity(std::declval<int32_t>(), std::declval<bool>())};
{t.SetAll()};
};
template <typename T>
concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) {
{typename T::QueueEntry()};
{(typename T::QueueEntry()).Initialize()};
{(typename T::QueueEntry()).SetPrev(std::addressof(t))};
{(typename T::QueueEntry()).SetNext(std::addressof(t))};
{ (typename T::QueueEntry()).GetNext() }
->std::same_as<T*>;
{ (typename T::QueueEntry()).GetPrev() }
->std::same_as<T*>;
{ t.GetPriorityQueueEntry(std::declval<s32>()) }
->std::same_as<typename T::QueueEntry&>;
{t.GetAffinityMask()};
{ typename std::remove_cvref<decltype(t.GetAffinityMask())>::type() }
->KPriorityQueueAffinityMask;
{ t.GetActiveCore() }
->Common::ConvertibleTo<s32>;
{ t.GetPriority() }
->Common::ConvertibleTo<s32>;
};
template <typename Member, size_t _NumCores, int LowestPriority, int HighestPriority>
requires KPriorityQueueMember<Member> class KPriorityQueue {
public:
using AffinityMaskType = typename std::remove_cv_t<
typename std::remove_reference<decltype(std::declval<Member>().GetAffinityMask())>::type>;
static_assert(LowestPriority >= 0);
static_assert(HighestPriority >= 0);
static_assert(LowestPriority >= HighestPriority);
static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1;
static constexpr size_t NumCores = _NumCores;
static constexpr bool IsValidCore(s32 core) {
return 0 <= core && core < static_cast<s32>(NumCores);
}
static constexpr bool IsValidPriority(s32 priority) {
return HighestPriority <= priority && priority <= LowestPriority + 1;
}
private:
using Entry = typename Member::QueueEntry;
public:
class KPerCoreQueue {
private:
std::array<Entry, NumCores> root{};
public:
constexpr KPerCoreQueue() {
for (auto& per_core_root : root) {
per_core_root.Initialize();
}
}
constexpr bool PushBack(s32 core, Member* member) {
// Get the entry associated with the member.
Entry& member_entry = member->GetPriorityQueueEntry(core);
// Get the entry associated with the end of the queue.
Member* tail = this->root[core].GetPrev();
Entry& tail_entry =
(tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core];
// Link the entries.
member_entry.SetPrev(tail);
member_entry.SetNext(nullptr);
tail_entry.SetNext(member);
this->root[core].SetPrev(member);
return tail == nullptr;
}
constexpr bool PushFront(s32 core, Member* member) {
// Get the entry associated with the member.
Entry& member_entry = member->GetPriorityQueueEntry(core);
// Get the entry associated with the front of the queue.
Member* head = this->root[core].GetNext();
Entry& head_entry =
(head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core];
// Link the entries.
member_entry.SetPrev(nullptr);
member_entry.SetNext(head);
head_entry.SetPrev(member);
this->root[core].SetNext(member);
return (head == nullptr);
}
constexpr bool Remove(s32 core, Member* member) {
// Get the entry associated with the member.
Entry& member_entry = member->GetPriorityQueueEntry(core);
// Get the entries associated with next and prev.
Member* prev = member_entry.GetPrev();
Member* next = member_entry.GetNext();
Entry& prev_entry =
(prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core];
Entry& next_entry =
(next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core];
// Unlink.
prev_entry.SetNext(next);
next_entry.SetPrev(prev);
return (this->GetFront(core) == nullptr);
}
constexpr Member* GetFront(s32 core) const {
return this->root[core].GetNext();
}
};
class KPriorityQueueImpl {
public:
constexpr KPriorityQueueImpl() = default;
constexpr void PushBack(s32 priority, s32 core, Member* member) {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority > LowestPriority) {
return;
}
if (this->queues[priority].PushBack(core, member)) {
this->available_priorities[core].SetBit(priority);
}
}
constexpr void PushFront(s32 priority, s32 core, Member* member) {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority > LowestPriority) {
return;
}
if (this->queues[priority].PushFront(core, member)) {
this->available_priorities[core].SetBit(priority);
}
}
constexpr void Remove(s32 priority, s32 core, Member* member) {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority > LowestPriority) {
return;
}
if (this->queues[priority].Remove(core, member)) {
this->available_priorities[core].ClearBit(priority);
}
}
constexpr Member* GetFront(s32 core) const {
ASSERT(IsValidCore(core));
const s32 priority =
static_cast<s32>(this->available_priorities[core].CountLeadingZero());
if (priority <= LowestPriority) {
return this->queues[priority].GetFront(core);
} else {
return nullptr;
}
}
constexpr Member* GetFront(s32 priority, s32 core) const {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority <= LowestPriority) {
return this->queues[priority].GetFront(core);
} else {
return nullptr;
}
}
constexpr Member* GetNext(s32 core, const Member* member) const {
ASSERT(IsValidCore(core));
Member* next = member->GetPriorityQueueEntry(core).GetNext();
if (next == nullptr) {
const s32 priority = static_cast<s32>(
this->available_priorities[core].GetNextSet(member->GetPriority()));
if (priority <= LowestPriority) {
next = this->queues[priority].GetFront(core);
}
}
return next;
}
constexpr void MoveToFront(s32 priority, s32 core, Member* member) {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority <= LowestPriority) {
this->queues[priority].Remove(core, member);
this->queues[priority].PushFront(core, member);
}
}
constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) {
ASSERT(IsValidCore(core));
ASSERT(IsValidPriority(priority));
if (priority <= LowestPriority) {
this->queues[priority].Remove(core, member);
this->queues[priority].PushBack(core, member);
return this->queues[priority].GetFront(core);
} else {
return nullptr;
}
}
private:
std::array<KPerCoreQueue, NumPriority> queues{};
std::array<Common::BitSet64<NumPriority>, NumCores> available_priorities{};
};
private:
KPriorityQueueImpl scheduled_queue;
KPriorityQueueImpl suggested_queue;
private:
constexpr void ClearAffinityBit(u64& affinity, s32 core) {
affinity &= ~(u64(1) << core);
}
constexpr s32 GetNextCore(u64& affinity) {
const s32 core = Common::CountTrailingZeroes64(affinity);
ClearAffinityBit(affinity, core);
return core;
}
constexpr void PushBack(s32 priority, Member* member) {
ASSERT(IsValidPriority(priority));
// Push onto the scheduled queue for its core, if we can.
u64 affinity = member->GetAffinityMask().GetAffinityMask();
if (const s32 core = member->GetActiveCore(); core >= 0) {
this->scheduled_queue.PushBack(priority, core, member);
ClearAffinityBit(affinity, core);
}
// And suggest the thread for all other cores.
while (affinity) {
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
}
}
constexpr void PushFront(s32 priority, Member* member) {
ASSERT(IsValidPriority(priority));
// Push onto the scheduled queue for its core, if we can.
u64 affinity = member->GetAffinityMask().GetAffinityMask();
if (const s32 core = member->GetActiveCore(); core >= 0) {
this->scheduled_queue.PushFront(priority, core, member);
ClearAffinityBit(affinity, core);
}
// And suggest the thread for all other cores.
// Note: Nintendo pushes onto the back of the suggested queue, not the front.
while (affinity) {
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
}
}
constexpr void Remove(s32 priority, Member* member) {
ASSERT(IsValidPriority(priority));
// Remove from the scheduled queue for its core.
u64 affinity = member->GetAffinityMask().GetAffinityMask();
if (const s32 core = member->GetActiveCore(); core >= 0) {
this->scheduled_queue.Remove(priority, core, member);
ClearAffinityBit(affinity, core);
}
// Remove from the suggested queue for all other cores.
while (affinity) {
this->suggested_queue.Remove(priority, GetNextCore(affinity), member);
}
}
public:
constexpr KPriorityQueue() = default;
// Getters.
constexpr Member* GetScheduledFront(s32 core) const {
return this->scheduled_queue.GetFront(core);
}
constexpr Member* GetScheduledFront(s32 core, s32 priority) const {
return this->scheduled_queue.GetFront(priority, core);
}
constexpr Member* GetSuggestedFront(s32 core) const {
return this->suggested_queue.GetFront(core);
}
constexpr Member* GetSuggestedFront(s32 core, s32 priority) const {
return this->suggested_queue.GetFront(priority, core);
}
constexpr Member* GetScheduledNext(s32 core, const Member* member) const {
return this->scheduled_queue.GetNext(core, member);
}
constexpr Member* GetSuggestedNext(s32 core, const Member* member) const {
return this->suggested_queue.GetNext(core, member);
}
constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const {
return member->GetPriorityQueueEntry(core).GetNext();
}
// Mutators.
constexpr void PushBack(Member* member) {
this->PushBack(member->GetPriority(), member);
}
constexpr void Remove(Member* member) {
this->Remove(member->GetPriority(), member);
}
constexpr void MoveToScheduledFront(Member* member) {
this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
}
constexpr Thread* MoveToScheduledBack(Member* member) {
return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
member);
}
// First class fancy operations.
constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
ASSERT(IsValidPriority(prev_priority));
// Remove the member from the queues.
const s32 new_priority = member->GetPriority();
this->Remove(prev_priority, member);
// And enqueue. If the member is running, we want to keep it running.
if (is_running) {
this->PushFront(new_priority, member);
} else {
this->PushBack(new_priority, member);
}
}
constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
Member* member) {
// Get the new information.
const s32 priority = member->GetPriority();
const AffinityMaskType& new_affinity = member->GetAffinityMask();
const s32 new_core = member->GetActiveCore();
// Remove the member from all queues it was in before.
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
if (prev_affinity.GetAffinity(core)) {
if (core == prev_core) {
this->scheduled_queue.Remove(priority, core, member);
} else {
this->suggested_queue.Remove(priority, core, member);
}
}
}
// And add the member to all queues it should be in now.
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
if (new_affinity.GetAffinity(core)) {
if (core == new_core) {
this->scheduled_queue.PushBack(priority, core, member);
} else {
this->suggested_queue.PushBack(priority, core, member);
}
}
}
}
constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
// Get the new information.
const s32 new_core = member->GetActiveCore();
const s32 priority = member->GetPriority();
// We don't need to do anything if the core is the same.
if (prev_core != new_core) {
// Remove from the scheduled queue for the previous core.
if (prev_core >= 0) {
this->scheduled_queue.Remove(priority, prev_core, member);
}
// Remove from the suggested queue and add to the scheduled queue for the new core.
if (new_core >= 0) {
this->suggested_queue.Remove(priority, new_core, member);
if (to_front) {
this->scheduled_queue.PushFront(priority, new_core, member);
} else {
this->scheduled_queue.PushBack(priority, new_core, member);
}
}
// Add to the suggested queue for the previous core.
if (prev_core >= 0) {
this->suggested_queue.PushBack(priority, prev_core, member);
}
}
}
};
} // namespace Kernel

View File

@@ -0,0 +1,784 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/fiber.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
static void IncrementScheduledCount(Kernel::Thread* thread) {
if (auto process = thread->GetOwnerProcess(); process) {
process->IncrementScheduledCount();
}
}
void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule,
Core::EmuThreadHandle global_thread) {
u32 current_core = global_thread.host_handle;
bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
(current_core < Core::Hardware::NUM_CPU_CORES);
while (cores_pending_reschedule != 0) {
u32 core = Common::CountTrailingZeroes64(cores_pending_reschedule);
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
if (!must_context_switch || core != current_core) {
auto& phys_core = kernel.PhysicalCore(core);
phys_core.Interrupt();
} else {
must_context_switch = true;
}
cores_pending_reschedule &= ~(1ULL << core);
}
if (must_context_switch) {
auto core_scheduler = kernel.CurrentScheduler();
kernel.ExitSVCProfile();
core_scheduler->RescheduleCurrentCore();
kernel.EnterSVCProfile();
}
}
u64 KScheduler::UpdateHighestPriorityThread(Thread* highest_thread) {
std::scoped_lock lock{guard};
if (Thread* prev_highest_thread = this->state.highest_priority_thread;
prev_highest_thread != highest_thread) {
if (prev_highest_thread != nullptr) {
IncrementScheduledCount(prev_highest_thread);
prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks());
}
if (this->state.should_count_idle) {
if (highest_thread != nullptr) {
// if (Process* process = highest_thread->GetOwnerProcess(); process != nullptr) {
// process->SetRunningThread(this->core_id, highest_thread,
// this->state.idle_count);
//}
} else {
this->state.idle_count++;
}
}
this->state.highest_priority_thread = highest_thread;
this->state.needs_scheduling = true;
return (1ULL << this->core_id);
} else {
return 0;
}
}
u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Clear that we need to update.
ClearSchedulerUpdateNeeded(kernel);
u64 cores_needing_scheduling = 0, idle_cores = 0;
Thread* top_threads[Core::Hardware::NUM_CPU_CORES];
auto& priority_queue = GetPriorityQueue(kernel);
/// We want to go over all cores, finding the highest priority thread and determining if
/// scheduling is needed for that core.
for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
Thread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
if (top_thread != nullptr) {
// If the thread has no waiters, we need to check if the process has a thread pinned.
// TODO(bunnei): Implement thread pinning
} else {
idle_cores |= (1ULL << core_id);
}
top_threads[core_id] = top_thread;
cores_needing_scheduling |=
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
}
// Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
while (idle_cores != 0) {
u32 core_id = Common::CountTrailingZeroes64(idle_cores);
if (Thread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
size_t num_candidates = 0;
// While we have a suggested thread, try to migrate it!
while (suggested != nullptr) {
// Check if the suggested thread is the top thread on its core.
const s32 suggested_core = suggested->GetActiveCore();
if (Thread* top_thread =
(suggested_core >= 0) ? top_threads[suggested_core] : nullptr;
top_thread != suggested) {
// Make sure we're not dealing with threads too high priority for migration.
if (top_thread != nullptr &&
top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) {
break;
}
// The suggested thread isn't bound to its core, so we can migrate it!
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested);
top_threads[core_id] = suggested;
cores_needing_scheduling |=
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
break;
}
// Note this core as a candidate for migration.
ASSERT(num_candidates < Core::Hardware::NUM_CPU_CORES);
migration_candidates[num_candidates++] = suggested_core;
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
}
// If suggested is nullptr, we failed to migrate a specific thread. So let's try all our
// candidate cores' top threads.
if (suggested == nullptr) {
for (size_t i = 0; i < num_candidates; i++) {
// Check if there's some other thread that can run on the candidate core.
const s32 candidate_core = migration_candidates[i];
suggested = top_threads[candidate_core];
if (Thread* next_on_candidate_core =
priority_queue.GetScheduledNext(candidate_core, suggested);
next_on_candidate_core != nullptr) {
// The candidate core can run some other thread! We'll migrate its current
// top thread to us.
top_threads[candidate_core] = next_on_candidate_core;
cores_needing_scheduling |=
kernel.Scheduler(candidate_core)
.UpdateHighestPriorityThread(top_threads[candidate_core]);
// Perform the migration.
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(candidate_core, suggested);
top_threads[core_id] = suggested;
cores_needing_scheduling |=
kernel.Scheduler(core_id).UpdateHighestPriorityThread(
top_threads[core_id]);
break;
}
}
}
}
idle_cores &= ~(1ULL << core_id);
}
return cores_needing_scheduling;
}
void KScheduler::OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Check if the state has changed, because if it hasn't there's nothing to do.
const auto cur_state = thread->scheduling_state;
if (cur_state == old_state) {
return;
}
// Update the priority queues.
if (old_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// If we were previously runnable, then we're not runnable now, and we should remove.
GetPriorityQueue(kernel).Remove(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
} else if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// If we're now runnable, then we weren't previously, and we should add.
GetPriorityQueue(kernel).PushBack(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
}
}
void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread,
u32 old_priority) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// If the thread is runnable, we want to change its priority in the queue.
if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
GetPriorityQueue(kernel).ChangePriority(
old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
}
}
void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread,
const KAffinityMask& old_affinity, s32 old_core) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// If the thread is runnable, we want to change its affinity in the queue.
if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
GetPriorityQueue(kernel).ChangeAffinityMask(old_core, old_affinity, thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
}
}
void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) {
ASSERT(system.GlobalSchedulerContext().IsLocked());
// Get a reference to the priority queue.
auto& kernel = system.Kernel();
auto& priority_queue = GetPriorityQueue(kernel);
// Rotate the front of the queue to the end.
Thread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
Thread* next_thread = nullptr;
if (top_thread != nullptr) {
next_thread = priority_queue.MoveToScheduledBack(top_thread);
if (next_thread != top_thread) {
IncrementScheduledCount(top_thread);
IncrementScheduledCount(next_thread);
}
}
// While we have a suggested thread, try to migrate it!
{
Thread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
while (suggested != nullptr) {
// Check if the suggested thread is the top thread on its core.
const s32 suggested_core = suggested->GetActiveCore();
if (Thread* top_on_suggested_core =
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
: nullptr;
top_on_suggested_core != suggested) {
// If the next thread is a new thread that has been waiting longer than our
// suggestion, we prefer it to our suggestion.
if (top_thread != next_thread && next_thread != nullptr &&
next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) {
suggested = nullptr;
break;
}
// If we're allowed to do a migration, do one.
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion
// to the front of the queue.
if (top_on_suggested_core == nullptr ||
top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested, true);
IncrementScheduledCount(suggested);
break;
}
}
// Get the next suggestion.
suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
}
}
// Now that we might have migrated a thread with the same priority, check if we can do better.
{
Thread* best_thread = priority_queue.GetScheduledFront(core_id);
if (best_thread == GetCurrentThread()) {
best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
}
// If the best thread we can choose has a priority the same or worse than ours, try to
// migrate a higher priority thread.
if (best_thread != nullptr && best_thread->GetPriority() >= static_cast<u32>(priority)) {
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
while (suggested != nullptr) {
// If the suggestion's priority is the same as ours, don't bother.
if (suggested->GetPriority() >= best_thread->GetPriority()) {
break;
}
// Check if the suggested thread is the top thread on its core.
const s32 suggested_core = suggested->GetActiveCore();
if (Thread* top_on_suggested_core =
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
: nullptr;
top_on_suggested_core != suggested) {
// If we're allowed to do a migration, do one.
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
// suggestion to the front of the queue.
if (top_on_suggested_core == nullptr ||
top_on_suggested_core->GetPriority() >=
HighestCoreMigrationAllowedPriority) {
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested, true);
IncrementScheduledCount(suggested);
break;
}
}
// Get the next suggestion.
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
}
}
}
// After a rotation, we need a scheduler update.
SetSchedulerUpdateNeeded(kernel);
}
bool KScheduler::CanSchedule(KernelCore& kernel) {
return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1;
}
bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
}
void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
}
void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
}
void KScheduler::DisableScheduling(KernelCore& kernel) {
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0);
scheduler->GetCurrentThread()->DisableDispatch();
}
}
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling,
Core::EmuThreadHandle global_thread) {
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
scheduler->GetCurrentThread()->EnableDispatch();
}
RescheduleCores(kernel, cores_needing_scheduling, global_thread);
}
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
if (IsSchedulerUpdateNeeded(kernel)) {
return UpdateHighestPriorityThreadsImpl(kernel);
} else {
return 0;
}
}
KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
return kernel.GlobalSchedulerContext().priority_queue;
}
void KScheduler::YieldWithoutCoreMigration() {
auto& kernel = system.Kernel();
// Validate preconditions.
ASSERT(CanSchedule(kernel));
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
Thread& cur_thread = *GetCurrentThread();
Process& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
return;
}
// Get a reference to the priority queue.
auto& priority_queue = GetPriorityQueue(kernel);
// Perform the yield.
{
KScopedSchedulerLock lock(kernel);
const auto cur_state = cur_thread.scheduling_state;
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// Put the current thread at the back of the queue.
Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
IncrementScheduledCount(std::addressof(cur_thread));
// If the next thread is different, we have an update to perform.
if (next_thread != std::addressof(cur_thread)) {
SetSchedulerUpdateNeeded(kernel);
} else {
// Otherwise, set the thread's yield count so that we won't waste work until the
// process is scheduled again.
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
}
}
}
}
void KScheduler::YieldWithCoreMigration() {
auto& kernel = system.Kernel();
// Validate preconditions.
ASSERT(CanSchedule(kernel));
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
Thread& cur_thread = *GetCurrentThread();
Process& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
return;
}
// Get a reference to the priority queue.
auto& priority_queue = GetPriorityQueue(kernel);
// Perform the yield.
{
KScopedSchedulerLock lock(kernel);
const auto cur_state = cur_thread.scheduling_state;
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// Get the current active core.
const s32 core_id = cur_thread.GetActiveCore();
// Put the current thread at the back of the queue.
Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
IncrementScheduledCount(std::addressof(cur_thread));
// While we have a suggested thread, try to migrate it!
bool recheck = false;
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
while (suggested != nullptr) {
// Check if the suggested thread is the thread running on its core.
const s32 suggested_core = suggested->GetActiveCore();
if (Thread* running_on_suggested_core =
(suggested_core >= 0)
? kernel.Scheduler(suggested_core).state.highest_priority_thread
: nullptr;
running_on_suggested_core != suggested) {
// If the current thread's priority is higher than our suggestion's we prefer
// the next thread to the suggestion. We also prefer the next thread when the
// current thread's priority is equal to the suggestions, but the next thread
// has been waiting longer.
if ((suggested->GetPriority() > cur_thread.GetPriority()) ||
(suggested->GetPriority() == cur_thread.GetPriority() &&
next_thread != std::addressof(cur_thread) &&
next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) {
suggested = nullptr;
break;
}
// If we're allowed to do a migration, do one.
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
// suggestion to the front of the queue.
if (running_on_suggested_core == nullptr ||
running_on_suggested_core->GetPriority() >=
HighestCoreMigrationAllowedPriority) {
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested, true);
IncrementScheduledCount(suggested);
break;
} else {
// We couldn't perform a migration, but we should check again on a future
// yield.
recheck = true;
}
}
// Get the next suggestion.
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
}
// If we still have a suggestion or the next thread is different, we have an update to
// perform.
if (suggested != nullptr || next_thread != std::addressof(cur_thread)) {
SetSchedulerUpdateNeeded(kernel);
} else if (!recheck) {
// Otherwise if we don't need to re-check, set the thread's yield count so that we
// won't waste work until the process is scheduled again.
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
}
}
}
}
void KScheduler::YieldToAnyThread() {
auto& kernel = system.Kernel();
// Validate preconditions.
ASSERT(CanSchedule(kernel));
ASSERT(kernel.CurrentProcess() != nullptr);
// Get the current thread and process.
Thread& cur_thread = *GetCurrentThread();
Process& cur_process = *kernel.CurrentProcess();
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
return;
}
// Get a reference to the priority queue.
auto& priority_queue = GetPriorityQueue(kernel);
// Perform the yield.
{
KScopedSchedulerLock lock(kernel);
const auto cur_state = cur_thread.scheduling_state;
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// Get the current active core.
const s32 core_id = cur_thread.GetActiveCore();
// Migrate the current thread to core -1.
cur_thread.SetActiveCore(-1);
priority_queue.ChangeCore(core_id, std::addressof(cur_thread));
IncrementScheduledCount(std::addressof(cur_thread));
// If there's nothing scheduled, we can try to perform a migration.
if (priority_queue.GetScheduledFront(core_id) == nullptr) {
// While we have a suggested thread, try to migrate it!
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
while (suggested != nullptr) {
// Check if the suggested thread is the top thread on its core.
const s32 suggested_core = suggested->GetActiveCore();
if (Thread* top_on_suggested_core =
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
: nullptr;
top_on_suggested_core != suggested) {
// If we're allowed to do a migration, do one.
if (top_on_suggested_core == nullptr ||
top_on_suggested_core->GetPriority() >=
HighestCoreMigrationAllowedPriority) {
suggested->SetActiveCore(core_id);
priority_queue.ChangeCore(suggested_core, suggested);
IncrementScheduledCount(suggested);
}
// Regardless of whether we migrated, we had a candidate, so we're done.
break;
}
// Get the next suggestion.
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
}
// If the suggestion is different from the current thread, we need to perform an
// update.
if (suggested != std::addressof(cur_thread)) {
SetSchedulerUpdateNeeded(kernel);
} else {
// Otherwise, set the thread's yield count so that we won't waste work until the
// process is scheduled again.
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
}
} else {
// Otherwise, we have an update to perform.
SetSchedulerUpdateNeeded(kernel);
}
}
}
}
KScheduler::KScheduler(Core::System& system, std::size_t core_id)
: system(system), core_id(core_id) {
switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this);
this->state.needs_scheduling = true;
this->state.interrupt_task_thread_runnable = false;
this->state.should_count_idle = false;
this->state.idle_count = 0;
this->state.idle_thread_stack = nullptr;
this->state.highest_priority_thread = nullptr;
}
KScheduler::~KScheduler() = default;
Thread* KScheduler::GetCurrentThread() const {
if (current_thread) {
return current_thread;
}
return idle_thread;
}
u64 KScheduler::GetLastContextSwitchTicks() const {
return last_context_switch_time;
}
void KScheduler::RescheduleCurrentCore() {
ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
auto& phys_core = system.Kernel().PhysicalCore(core_id);
if (phys_core.IsInterrupted()) {
phys_core.ClearInterrupt();
}
guard.lock();
if (this->state.needs_scheduling) {
Schedule();
} else {
guard.unlock();
}
}
void KScheduler::OnThreadStart() {
SwitchContextStep2();
}
void KScheduler::Unload(Thread* thread) {
if (thread) {
thread->SetIsRunning(false);
if (thread->IsContinuousOnSVC() && !thread->IsHLEThread()) {
system.ArmInterface(core_id).ExceptionalExit();
thread->SetContinuousOnSVC(false);
}
if (!thread->IsHLEThread() && !thread->HasExited()) {
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
cpu_core.SaveContext(thread->GetContext32());
cpu_core.SaveContext(thread->GetContext64());
// Save the TPIDR_EL0 system register in case it was modified.
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
}
thread->context_guard.unlock();
}
}
void KScheduler::Reload(Thread* thread) {
if (thread) {
ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
"Thread must be runnable.");
// Cancel any outstanding wakeup events for this thread
thread->SetIsRunning(true);
thread->SetWasRunning(false);
auto* const thread_owner_process = thread->GetOwnerProcess();
if (thread_owner_process != nullptr) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
if (!thread->IsHLEThread()) {
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
cpu_core.LoadContext(thread->GetContext32());
cpu_core.LoadContext(thread->GetContext64());
cpu_core.SetTlsAddress(thread->GetTLSAddress());
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
}
}
}
void KScheduler::SwitchContextStep2() {
// Load context of new thread
Reload(current_thread);
RescheduleCurrentCore();
}
void KScheduler::ScheduleImpl() {
Thread* previous_thread = current_thread;
current_thread = state.highest_priority_thread;
this->state.needs_scheduling = false;
if (current_thread == previous_thread) {
guard.unlock();
return;
}
Process* const previous_process = system.Kernel().CurrentProcess();
UpdateLastContextSwitchTime(previous_thread, previous_process);
// Save context for previous thread
Unload(previous_thread);
std::shared_ptr<Common::Fiber>* old_context;
if (previous_thread != nullptr) {
old_context = &previous_thread->GetHostContext();
} else {
old_context = &idle_thread->GetHostContext();
}
guard.unlock();
Common::Fiber::YieldTo(*old_context, switch_fiber);
/// When a thread wakes up, the scheduler may have changed to other in another core.
auto& next_scheduler = *system.Kernel().CurrentScheduler();
next_scheduler.SwitchContextStep2();
}
void KScheduler::OnSwitch(void* this_scheduler) {
KScheduler* sched = static_cast<KScheduler*>(this_scheduler);
sched->SwitchToCurrent();
}
void KScheduler::SwitchToCurrent() {
while (true) {
{
std::scoped_lock lock{guard};
current_thread = state.highest_priority_thread;
this->state.needs_scheduling = false;
}
const auto is_switch_pending = [this] {
std::scoped_lock lock{guard};
return state.needs_scheduling.load(std::memory_order_relaxed);
};
do {
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
current_thread->context_guard.lock();
if (!current_thread->IsRunnable()) {
current_thread->context_guard.unlock();
break;
}
if (static_cast<u32>(current_thread->GetProcessorID()) != core_id) {
current_thread->context_guard.unlock();
break;
}
}
std::shared_ptr<Common::Fiber>* next_context;
if (current_thread != nullptr) {
next_context = &current_thread->GetHostContext();
} else {
next_context = &idle_thread->GetHostContext();
}
Common::Fiber::YieldTo(switch_fiber, *next_context);
} while (!is_switch_pending());
}
}
void KScheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time;
const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) {
thread->UpdateCPUTimeTicks(update_ticks);
}
if (process != nullptr) {
process->UpdateCPUTimeTicks(update_ticks);
}
last_context_switch_time = most_recent_switch_ticks;
}
void KScheduler::Initialize() {
std::string name = "Idle Thread Id:" + std::to_string(core_id);
std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
nullptr, std::move(init_func), init_func_parameter);
idle_thread = thread_res.Unwrap().get();
{
KScopedSchedulerLock lock{system.Kernel()};
idle_thread->SetStatus(ThreadStatus::Ready);
}
}
KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
: KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
KScopedSchedulerLock::~KScopedSchedulerLock() = default;
} // namespace Kernel

View File

@@ -0,0 +1,201 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include <atomic>
#include "common/common_types.h"
#include "common/spin_lock.h"
#include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_priority_queue.h"
#include "core/hle/kernel/k_scheduler_lock.h"
#include "core/hle/kernel/k_scoped_lock.h"
namespace Common {
class Fiber;
}
namespace Core {
class System;
}
namespace Kernel {
class KernelCore;
class Process;
class SchedulerLock;
class Thread;
class KScheduler final {
public:
explicit KScheduler(Core::System& system, std::size_t core_id);
~KScheduler();
/// Reschedules to the next available thread (call after current thread is suspended)
void RescheduleCurrentCore();
/// Reschedules cores pending reschedule, to be called on EnableScheduling.
static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule,
Core::EmuThreadHandle global_thread);
/// The next two are for SingleCore Only.
/// Unload current thread before preempting core.
void Unload(Thread* thread);
/// Reload current thread after core preemption.
void Reload(Thread* thread);
/// Gets the current running thread
[[nodiscard]] Thread* GetCurrentThread() const;
/// Gets the timestamp for the last context switch in ticks.
[[nodiscard]] u64 GetLastContextSwitchTicks() const;
[[nodiscard]] bool ContextSwitchPending() const {
return state.needs_scheduling.load(std::memory_order_relaxed);
}
void Initialize();
void OnThreadStart();
[[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() {
return switch_fiber;
}
[[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const {
return switch_fiber;
}
[[nodiscard]] u64 UpdateHighestPriorityThread(Thread* highest_thread);
/**
* Takes a thread and moves it to the back of the it's priority list.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
void YieldWithoutCoreMigration();
/**
* Takes a thread and moves it to the back of the it's priority list.
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
* a better priority than the next thread in the core.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
void YieldWithCoreMigration();
/**
* Takes a thread and moves it out of the scheduling queue.
* and into the suggested queue. If no thread can be scheduled afterwards in that core,
* a suggested thread is obtained instead.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
void YieldToAnyThread();
/// Notify the scheduler a thread's status has changed.
static void OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state);
/// Notify the scheduler a thread's priority has changed.
static void OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread,
u32 old_priority);
/// Notify the scheduler a thread's core and/or affinity mask has changed.
static void OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread,
const KAffinityMask& old_affinity, s32 old_core);
static bool CanSchedule(KernelCore& kernel);
static bool IsSchedulerUpdateNeeded(const KernelCore& kernel);
static void SetSchedulerUpdateNeeded(KernelCore& kernel);
static void ClearSchedulerUpdateNeeded(KernelCore& kernel);
static void DisableScheduling(KernelCore& kernel);
static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling,
Core::EmuThreadHandle global_thread);
[[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
private:
friend class GlobalSchedulerContext;
/**
* Takes care of selecting the new scheduled threads in three steps:
*
* 1. First a thread is selected from the top of the priority queue. If no thread
* is obtained then we move to step two, else we are done.
*
* 2. Second we try to get a suggested thread that's not assigned to any core or
* that is not the top thread in that core.
*
* 3. Third is no suggested thread is found, we do a second pass and pick a running
* thread in another core and swap it with its current thread.
*
* returns the cores needing scheduling.
*/
[[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
[[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
void RotateScheduledQueue(s32 core_id, s32 priority);
void Schedule() {
ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
this->ScheduleImpl();
}
/// Switches the CPU's active thread context to that of the specified thread
void ScheduleImpl();
/// When a thread wakes up, it must run this through it's new scheduler
void SwitchContextStep2();
/**
* Called on every context switch to update the internal timestamp
* This also updates the running time ticks for the given thread and
* process using the following difference:
*
* ticks += most_recent_ticks - last_context_switch_ticks
*
* The internal tick timestamp for the scheduler is simply the
* most recent tick count retrieved. No special arithmetic is
* applied to it.
*/
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
static void OnSwitch(void* this_scheduler);
void SwitchToCurrent();
Thread* current_thread{};
Thread* idle_thread{};
std::shared_ptr<Common::Fiber> switch_fiber{};
struct SchedulingState {
std::atomic<bool> needs_scheduling;
bool interrupt_task_thread_runnable{};
bool should_count_idle{};
u64 idle_count{};
Thread* highest_priority_thread{};
void* idle_thread_stack{};
};
SchedulingState state;
Core::System& system;
u64 last_context_switch_time{};
const std::size_t core_id;
Common::SpinLock guard{};
};
class KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> {
public:
explicit KScopedSchedulerLock(KernelCore& kernel);
~KScopedSchedulerLock();
};
} // namespace Kernel

View File

@@ -0,0 +1,75 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include "common/assert.h"
#include "common/spin_lock.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
class KernelCore;
template <typename SchedulerType>
class KAbstractSchedulerLock {
public:
explicit KAbstractSchedulerLock(KernelCore& kernel) : kernel{kernel} {}
bool IsLockedByCurrentThread() const {
return this->owner_thread == kernel.GetCurrentEmuThreadID();
}
void Lock() {
if (this->IsLockedByCurrentThread()) {
// If we already own the lock, we can just increment the count.
ASSERT(this->lock_count > 0);
this->lock_count++;
} else {
// Otherwise, we want to disable scheduling and acquire the spinlock.
SchedulerType::DisableScheduling(kernel);
this->spin_lock.lock();
// For debug, ensure that our state is valid.
ASSERT(this->lock_count == 0);
ASSERT(this->owner_thread == Core::EmuThreadHandle::InvalidHandle());
// Increment count, take ownership.
this->lock_count = 1;
this->owner_thread = kernel.GetCurrentEmuThreadID();
}
}
void Unlock() {
ASSERT(this->IsLockedByCurrentThread());
ASSERT(this->lock_count > 0);
// Release an instance of the lock.
if ((--this->lock_count) == 0) {
// We're no longer going to hold the lock. Take note of what cores need scheduling.
const u64 cores_needing_scheduling =
SchedulerType::UpdateHighestPriorityThreads(kernel);
Core::EmuThreadHandle leaving_thread = owner_thread;
// Note that we no longer hold the lock, and unlock the spinlock.
this->owner_thread = Core::EmuThreadHandle::InvalidHandle();
this->spin_lock.unlock();
// Enable scheduling, and perform a rescheduling operation.
SchedulerType::EnableScheduling(kernel, cores_needing_scheduling, leaving_thread);
}
}
private:
KernelCore& kernel;
Common::SpinLock spin_lock{};
s32 lock_count{};
Core::EmuThreadHandle owner_thread{Core::EmuThreadHandle::InvalidHandle()};
};
} // namespace Kernel

View File

@@ -0,0 +1,41 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include "common/common_types.h"
namespace Kernel {
template <typename T>
concept KLockable = !std::is_reference_v<T> && requires(T & t) {
{ t.Lock() }
->std::same_as<void>;
{ t.Unlock() }
->std::same_as<void>;
};
template <typename T>
requires KLockable<T> class KScopedLock {
public:
explicit KScopedLock(T* l) : lock_ptr(l) {
this->lock_ptr->Lock();
}
explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) { /* ... */
}
~KScopedLock() {
this->lock_ptr->Unlock();
}
KScopedLock(const KScopedLock&) = delete;
KScopedLock(KScopedLock&&) = delete;
private:
T* lock_ptr;
};
} // namespace Kernel

View File

@@ -0,0 +1,50 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file references various implementation details from Atmosphere, an open-source firmware for
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
#pragma once
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
class KScopedSchedulerLockAndSleep {
public:
explicit KScopedSchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* t,
s64 timeout)
: kernel(kernel), event_handle(event_handle), thread(t), timeout_tick(timeout) {
event_handle = InvalidHandle;
// Lock the scheduler.
kernel.GlobalSchedulerContext().scheduler_lock.Lock();
}
~KScopedSchedulerLockAndSleep() {
// Register the sleep.
if (this->timeout_tick > 0) {
kernel.TimeManager().ScheduleTimeEvent(event_handle, this->thread, this->timeout_tick);
}
// Unlock the scheduler.
kernel.GlobalSchedulerContext().scheduler_lock.Unlock();
}
void CancelSleep() {
this->timeout_tick = 0;
}
private:
KernelCore& kernel;
Handle& event_handle;
Thread* thread{};
s64 timeout_tick{};
};
} // namespace Kernel

View File

@@ -27,6 +27,7 @@
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_layout.h"
#include "core/hle/kernel/memory/memory_manager.h"
@@ -34,7 +35,6 @@
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h"
@@ -49,17 +49,18 @@ namespace Kernel {
struct KernelCore::Impl {
explicit Impl(Core::System& system, KernelCore& kernel)
: global_scheduler{kernel}, synchronization{system}, time_manager{system},
global_handle_table{kernel}, system{system} {}
: synchronization{system}, time_manager{system}, global_handle_table{kernel}, system{
system} {}
void SetMulticore(bool is_multicore) {
this->is_multicore = is_multicore;
}
void Initialize(KernelCore& kernel) {
Shutdown();
RegisterHostThread();
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
InitializePhysicalCores();
InitializeSystemResourceLimit(kernel);
InitializeMemoryLayout();
@@ -86,29 +87,20 @@ struct KernelCore::Impl {
}
}
for (std::size_t i = 0; i < cores.size(); i++) {
cores[i].Shutdown();
schedulers[i].reset();
}
cores.clear();
process_list.clear();
current_process = nullptr;
system_resource_limit = nullptr;
global_handle_table.Clear();
preemption_event = nullptr;
global_scheduler.Shutdown();
named_ports.clear();
for (auto& core : cores) {
core.Shutdown();
}
cores.clear();
exclusive_monitor.reset();
num_host_threads = 0;
@@ -121,7 +113,7 @@ struct KernelCore::Impl {
exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i);
schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i);
cores.emplace_back(i, system, *schedulers[i], interrupts);
}
}
@@ -154,8 +146,8 @@ struct KernelCore::Impl {
preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
{
SchedulerLock lock(kernel);
global_scheduler.PreemptThreads();
KScopedSchedulerLock lock(kernel);
global_scheduler_context->PreemptThreads();
}
const auto time_interval = std::chrono::nanoseconds{
Core::Timing::msToCycles(std::chrono::milliseconds(10))};
@@ -245,7 +237,7 @@ struct KernelCore::Impl {
if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) {
return result;
}
const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler();
const Kernel::KScheduler& sched = cores[result.host_handle].Scheduler();
const Kernel::Thread* current = sched.GetCurrentThread();
if (current != nullptr && !current->IsPhantomMode()) {
result.guest_handle = current->GetGlobalHandle();
@@ -314,7 +306,7 @@ struct KernelCore::Impl {
// Lists all processes that exist in the current session.
std::vector<std::shared_ptr<Process>> process_list;
Process* current_process = nullptr;
Kernel::GlobalScheduler global_scheduler;
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
Kernel::Synchronization synchronization;
Kernel::TimeManager time_manager;
@@ -355,7 +347,7 @@ struct KernelCore::Impl {
std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{};
std::thread::id single_core_thread_id{};
@@ -415,19 +407,19 @@ const std::vector<std::shared_ptr<Process>>& KernelCore::GetProcessList() const
return impl->process_list;
}
Kernel::GlobalScheduler& KernelCore::GlobalScheduler() {
return impl->global_scheduler;
Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {
return *impl->global_scheduler_context;
}
const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
return impl->global_scheduler;
const Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() const {
return *impl->global_scheduler_context;
}
Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) {
Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) {
return *impl->schedulers[id];
}
const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const {
const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const {
return *impl->schedulers[id];
}
@@ -451,16 +443,13 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
return impl->cores[core_id];
}
Kernel::Scheduler& KernelCore::CurrentScheduler() {
Kernel::KScheduler* KernelCore::CurrentScheduler() {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return *impl->schedulers[core_id];
}
const Kernel::Scheduler& KernelCore::CurrentScheduler() const {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return *impl->schedulers[core_id];
if (core_id >= Core::Hardware::NUM_CPU_CORES) {
// This is expected when called from not a guest thread
return {};
}
return impl->schedulers[core_id].get();
}
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
@@ -623,7 +612,7 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const {
void KernelCore::Suspend(bool in_suspention) {
const bool should_suspend = exception_exited || in_suspention;
{
SchedulerLock lock(*this);
KScopedSchedulerLock lock(*this);
ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep;
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
impl->suspend_threads[i]->SetStatus(status);

View File

@@ -35,12 +35,12 @@ class SlabHeap;
class AddressArbiter;
class ClientPort;
class GlobalScheduler;
class GlobalSchedulerContext;
class HandleTable;
class PhysicalCore;
class Process;
class ResourceLimit;
class Scheduler;
class KScheduler;
class SharedMemory;
class Synchronization;
class Thread;
@@ -102,16 +102,16 @@ public:
const std::vector<std::shared_ptr<Process>>& GetProcessList() const;
/// Gets the sole instance of the global scheduler
Kernel::GlobalScheduler& GlobalScheduler();
Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
/// Gets the sole instance of the global scheduler
const Kernel::GlobalScheduler& GlobalScheduler() const;
const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
Kernel::Scheduler& Scheduler(std::size_t id);
Kernel::KScheduler& Scheduler(std::size_t id);
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
const Kernel::Scheduler& Scheduler(std::size_t id) const;
const Kernel::KScheduler& Scheduler(std::size_t id) const;
/// Gets the an instance of the respective physical CPU core.
Kernel::PhysicalCore& PhysicalCore(std::size_t id);
@@ -120,10 +120,7 @@ public:
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
/// Gets the sole instance of the Scheduler at the current running core.
Kernel::Scheduler& CurrentScheduler();
/// Gets the sole instance of the Scheduler at the current running core.
const Kernel::Scheduler& CurrentScheduler() const;
Kernel::KScheduler* CurrentScheduler();
/// Gets the an instance of the current physical CPU core.
Kernel::PhysicalCore& CurrentPhysicalCore();

View File

@@ -11,11 +11,11 @@
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -73,9 +73,9 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
auto& kernel = system.Kernel();
std::shared_ptr<Thread> current_thread =
SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
return ERR_INVALID_ADDRESS;
@@ -114,7 +114,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
}
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
auto* owner = current_thread->GetLockOwner();
if (owner != nullptr) {
owner->RemoveMutexWaiter(current_thread);
@@ -153,10 +153,10 @@ std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thr
ResultCode Mutex::Release(VAddr address) {
auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
std::shared_ptr<Thread> current_thread =
SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
auto [result, new_owner] = Unlock(current_thread, address);

View File

@@ -7,14 +7,14 @@
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/core.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
namespace Kernel {
PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system,
Kernel::Scheduler& scheduler, Core::CPUInterrupts& interrupts)
Kernel::KScheduler& scheduler, Core::CPUInterrupts& interrupts)
: core_index{core_index}, system{system}, scheduler{scheduler},
interrupts{interrupts}, guard{std::make_unique<Common::SpinLock>()} {}
@@ -43,10 +43,6 @@ void PhysicalCore::Idle() {
interrupts[core_index].AwaitInterrupt();
}
void PhysicalCore::Shutdown() {
scheduler.Shutdown();
}
bool PhysicalCore::IsInterrupted() const {
return interrupts[core_index].IsInterrupted();
}

View File

@@ -15,7 +15,7 @@ class SpinLock;
}
namespace Kernel {
class Scheduler;
class KScheduler;
} // namespace Kernel
namespace Core {
@@ -28,7 +28,7 @@ namespace Kernel {
class PhysicalCore {
public:
PhysicalCore(std::size_t core_index, Core::System& system, Kernel::Scheduler& scheduler,
PhysicalCore(std::size_t core_index, Core::System& system, Kernel::KScheduler& scheduler,
Core::CPUInterrupts& interrupts);
~PhysicalCore();
@@ -55,9 +55,6 @@ public:
/// Check if this core is interrupted
bool IsInterrupted() const;
// Shutdown this physical core.
void Shutdown();
bool IsInitialized() const {
return arm_interface != nullptr;
}
@@ -82,18 +79,18 @@ public:
return core_index;
}
Kernel::Scheduler& Scheduler() {
Kernel::KScheduler& Scheduler() {
return scheduler;
}
const Kernel::Scheduler& Scheduler() const {
const Kernel::KScheduler& Scheduler() const {
return scheduler;
}
private:
const std::size_t core_index;
Core::System& system;
Kernel::Scheduler& scheduler;
Kernel::KScheduler& scheduler;
Core::CPUInterrupts& interrupts;
std::unique_ptr<Common::SpinLock> guard;
std::unique_ptr<Core::ARM_Interface> arm_interface;

View File

@@ -15,13 +15,13 @@
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_block_manager.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/memory/slab_heap.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/memory.h"
@@ -54,7 +54,7 @@ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority,
auto& kernel = system.Kernel();
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
{
SchedulerLock lock{kernel};
KScopedSchedulerLock lock{kernel};
thread->SetStatus(ThreadStatus::Ready);
}
}
@@ -213,7 +213,7 @@ void Process::UnregisterThread(const Thread* thread) {
}
ResultCode Process::ClearSignalState() {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
if (status == ProcessStatus::Exited) {
LOG_ERROR(Kernel, "called on a terminated process instance.");
return ERR_INVALID_STATE;
@@ -314,7 +314,7 @@ void Process::PrepareForTermination() {
if (thread->GetOwnerProcess() != this)
continue;
if (thread.get() == system.CurrentScheduler().GetCurrentThread())
if (thread.get() == kernel.CurrentScheduler()->GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
@@ -325,7 +325,7 @@ void Process::PrepareForTermination() {
}
};
stop_threads(system.GlobalScheduler().GetThreadList());
stop_threads(system.GlobalSchedulerContext().GetThreadList());
FreeTLSRegion(tls_region_address);
tls_region_address = 0;
@@ -347,7 +347,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
}
VAddr Process::CreateTLSRegion() {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
tls_page_iter != tls_pages.cend()) {
return *tls_page_iter->ReserveSlot();
@@ -378,7 +378,7 @@ VAddr Process::CreateTLSRegion() {
}
void Process::FreeTLSRegion(VAddr tls_address) {
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(system.Kernel());
const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
auto iter =
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {

View File

@@ -216,6 +216,16 @@ public:
total_process_running_time_ticks += ticks;
}
/// Gets the process schedule count, used for thread yelding
s64 GetScheduledCount() const {
return schedule_count;
}
/// Increments the process schedule count, used for thread yielding.
void IncrementScheduledCount() {
++schedule_count;
}
/// Gets 8 bytes of random data for svcGetInfo RandomEntropy
u64 GetRandomEntropy(std::size_t index) const {
return random_entropy.at(index);
@@ -397,6 +407,9 @@ private:
/// Name of this process
std::string name;
/// Schedule count of this process
s64 schedule_count{};
/// System context
Core::System& system;
};

View File

@@ -6,10 +6,10 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -39,7 +39,7 @@ void ReadableEvent::Clear() {
}
ResultCode ReadableEvent::Reset() {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (!is_signaled) {
LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}",
GetObjectId(), GetTypeName(), GetName());

View File

@@ -1,819 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//
// SelectThreads, Yield functions originally by TuxSH.
// licensed under GPLv2 or later under exception provided by the author.
#include <algorithm>
#include <mutex>
#include <set>
#include <unordered_set>
#include <utility>
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/fiber.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {}
GlobalScheduler::~GlobalScheduler() = default;
void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) {
std::scoped_lock lock{global_list_guard};
thread_list.push_back(std::move(thread));
}
void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) {
std::scoped_lock lock{global_list_guard};
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
thread_list.end());
}
u32 GlobalScheduler::SelectThreads() {
ASSERT(is_locked);
const auto update_thread = [](Thread* thread, Scheduler& sched) {
std::scoped_lock lock{sched.guard};
if (thread != sched.selected_thread_set.get()) {
if (thread == nullptr) {
++sched.idle_selection_count;
}
sched.selected_thread_set = SharedFrom(thread);
}
const bool reschedule_pending =
sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread);
sched.is_context_switch_pending = reschedule_pending;
std::atomic_thread_fence(std::memory_order_seq_cst);
return reschedule_pending;
};
if (!is_reselection_pending.load()) {
return 0;
}
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{};
u32 idle_cores{};
// Step 1: Get top thread in schedule queue.
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
Thread* top_thread =
scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
if (top_thread != nullptr) {
// TODO(Blinkhawk): Implement Thread Pinning
} else {
idle_cores |= (1U << core);
}
top_threads[core] = top_thread;
}
while (idle_cores != 0) {
u32 core_id = Common::CountTrailingZeroes32(idle_cores);
if (!suggested_queue[core_id].empty()) {
std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{};
std::size_t num_candidates = 0;
auto iter = suggested_queue[core_id].begin();
Thread* suggested = nullptr;
// Step 2: Try selecting a suggested thread.
while (iter != suggested_queue[core_id].end()) {
suggested = *iter;
iter++;
s32 suggested_core_id = suggested->GetProcessorID();
Thread* top_thread =
suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr;
if (top_thread != suggested) {
if (top_thread != nullptr &&
top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) {
suggested = nullptr;
break;
// There's a too high thread to do core migration, cancel
}
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested);
break;
}
suggested = nullptr;
migration_candidates[num_candidates++] = suggested_core_id;
}
// Step 3: Select a suggested thread from another core
if (suggested == nullptr) {
for (std::size_t i = 0; i < num_candidates; i++) {
s32 candidate_core = migration_candidates[i];
suggested = top_threads[candidate_core];
auto it = scheduled_queue[candidate_core].begin();
it++;
Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr;
if (next != nullptr) {
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id),
suggested);
top_threads[candidate_core] = next;
break;
} else {
suggested = nullptr;
}
}
}
top_threads[core_id] = suggested;
}
idle_cores &= ~(1U << core_id);
}
u32 cores_needing_context_switch{};
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
Scheduler& sched = kernel.Scheduler(core);
ASSERT(top_threads[core] == nullptr ||
static_cast<u32>(top_threads[core]->GetProcessorID()) == core);
if (update_thread(top_threads[core], sched)) {
cores_needing_context_switch |= (1U << core);
}
}
return cores_needing_context_switch;
}
bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should use critical section, etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority();
// Yield the thread
Reschedule(priority, core_id, yielding_thread);
const Thread* const winner = scheduled_queue[core_id].front();
if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority();
// Yield the thread
Reschedule(priority, core_id, yielding_thread);
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
for (std::size_t i = 0; i < current_threads.size(); i++) {
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
}
Thread* next_thread = scheduled_queue[core_id].front(priority);
Thread* winner = nullptr;
for (auto& thread : suggested_queue[core_id]) {
const s32 source_core = thread->GetProcessorID();
if (source_core >= 0) {
if (current_threads[source_core] != nullptr) {
if (thread == current_threads[source_core] ||
current_threads[source_core]->GetPriority() < min_regular_priority) {
continue;
}
}
}
if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() ||
next_thread->GetPriority() < thread->GetPriority()) {
if (thread->GetPriority() <= priority) {
winner = thread;
break;
}
}
}
if (winner != nullptr) {
if (winner != yielding_thread) {
TransferToCore(winner->GetPriority(), s32(core_id), winner);
}
} else {
winner = next_thread;
}
if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
Thread* winner = nullptr;
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
// Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead
TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread);
// If the core is idle, perform load balancing, excluding the threads that have just used this
// function...
if (scheduled_queue[core_id].empty()) {
// Here, "current_threads" is calculated after the ""yield"", unlike yield -1
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
for (std::size_t i = 0; i < current_threads.size(); i++) {
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
}
for (auto& thread : suggested_queue[core_id]) {
const s32 source_core = thread->GetProcessorID();
if (source_core < 0 || thread == current_threads[source_core]) {
continue;
}
if (current_threads[source_core] == nullptr ||
current_threads[source_core]->GetPriority() >= min_regular_priority) {
winner = thread;
}
break;
}
if (winner != nullptr) {
if (winner != yielding_thread) {
TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner);
}
} else {
winner = yielding_thread;
}
} else {
winner = scheduled_queue[core_id].front();
}
if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
}
void GlobalScheduler::PreemptThreads() {
ASSERT(is_locked);
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id];
if (scheduled_queue[core_id].size(priority) > 0) {
if (scheduled_queue[core_id].size(priority) > 1) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
}
scheduled_queue[core_id].yield(priority);
if (scheduled_queue[core_id].size(priority) > 1) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
}
}
Thread* current_thread =
scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front();
Thread* winner = nullptr;
for (auto& thread : suggested_queue[core_id]) {
const s32 source_core = thread->GetProcessorID();
if (thread->GetPriority() != priority) {
continue;
}
if (source_core >= 0) {
Thread* next_thread = scheduled_queue[source_core].empty()
? nullptr
: scheduled_queue[source_core].front();
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
break;
}
if (next_thread == thread) {
continue;
}
}
if (current_thread != nullptr &&
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
winner = thread;
break;
}
}
if (winner != nullptr) {
TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread =
winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
}
if (current_thread != nullptr && current_thread->GetPriority() > priority) {
for (auto& thread : suggested_queue[core_id]) {
const s32 source_core = thread->GetProcessorID();
if (thread->GetPriority() < priority) {
continue;
}
if (source_core >= 0) {
Thread* next_thread = scheduled_queue[source_core].empty()
? nullptr
: scheduled_queue[source_core].front();
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
break;
}
if (next_thread == thread) {
continue;
}
}
if (current_thread != nullptr &&
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
winner = thread;
break;
}
}
if (winner != nullptr) {
TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread = winner;
}
}
is_reselection_pending.store(true, std::memory_order_release);
}
}
void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule,
Core::EmuThreadHandle global_thread) {
u32 current_core = global_thread.host_handle;
bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
(current_core < Core::Hardware::NUM_CPU_CORES);
while (cores_pending_reschedule != 0) {
u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule);
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
if (!must_context_switch || core != current_core) {
auto& phys_core = kernel.PhysicalCore(core);
phys_core.Interrupt();
} else {
must_context_switch = true;
}
cores_pending_reschedule &= ~(1U << core);
}
if (must_context_switch) {
auto& core_scheduler = kernel.CurrentScheduler();
kernel.ExitSVCProfile();
core_scheduler.TryDoContextSwitch();
kernel.EnterSVCProfile();
}
}
void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
suggested_queue[core].add(thread, priority);
}
void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
suggested_queue[core].remove(thread, priority);
}
void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority);
}
void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority, false);
}
void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority);
scheduled_queue[core].add(thread, priority);
}
void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority);
}
void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
ASSERT(is_locked);
const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
const s32 source_core = thread->GetProcessorID();
if (source_core == destination_core || !schedulable) {
return;
}
thread->SetProcessorID(destination_core);
if (source_core >= 0) {
Unschedule(priority, static_cast<u32>(source_core), thread);
}
if (destination_core >= 0) {
Unsuggest(priority, static_cast<u32>(destination_core), thread);
Schedule(priority, static_cast<u32>(destination_core), thread);
}
if (source_core >= 0) {
Suggest(priority, static_cast<u32>(source_core), thread);
}
}
bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
const Thread* winner) {
if (current_thread == winner) {
current_thread->IncrementYieldCount();
return true;
} else {
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
}
void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) {
if (old_flags == thread->scheduling_state) {
return;
}
ASSERT(is_locked);
if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// In this case the thread was running, now it's pausing/exitting
if (thread->processor_id >= 0) {
Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Unsuggest(thread->current_priority, core, thread);
}
}
} else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// The thread is now set to running from being stopped
if (thread->processor_id >= 0) {
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Suggest(thread->current_priority, core, thread);
}
}
}
SetReselectionPending();
}
void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) {
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) {
return;
}
ASSERT(is_locked);
if (thread->processor_id >= 0) {
Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Unsuggest(old_priority, core, thread);
}
}
if (thread->processor_id >= 0) {
if (thread == kernel.CurrentScheduler().GetCurrentThread()) {
SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id),
thread);
} else {
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Suggest(thread->current_priority, core, thread);
}
}
thread->IncrementYieldCount();
SetReselectionPending();
}
void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask,
s32 old_core) {
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) ||
thread->current_priority >= THREADPRIO_COUNT) {
return;
}
ASSERT(is_locked);
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((old_affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(old_core)) {
Unschedule(thread->current_priority, core, thread);
} else {
Unsuggest(thread->current_priority, core, thread);
}
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((thread->affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(thread->processor_id)) {
Schedule(thread->current_priority, core, thread);
} else {
Suggest(thread->current_priority, core, thread);
}
}
}
thread->IncrementYieldCount();
SetReselectionPending();
}
void GlobalScheduler::Shutdown() {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
scheduled_queue[core].clear();
suggested_queue[core].clear();
}
thread_list.clear();
}
void GlobalScheduler::Lock() {
Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID();
ASSERT(!current_thread.IsInvalid());
if (current_thread == current_owner) {
++scope_lock;
} else {
inner_lock.lock();
is_locked = true;
current_owner = current_thread;
ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle());
scope_lock = 1;
}
}
void GlobalScheduler::Unlock() {
if (--scope_lock != 0) {
ASSERT(scope_lock > 0);
return;
}
u32 cores_pending_reschedule = SelectThreads();
Core::EmuThreadHandle leaving_thread = current_owner;
current_owner = Core::EmuThreadHandle::InvalidHandle();
scope_lock = 1;
is_locked = false;
inner_lock.unlock();
EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread);
}
Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) {
switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this);
}
Scheduler::~Scheduler() = default;
bool Scheduler::HaveReadyThreads() const {
return system.GlobalScheduler().HaveReadyThreads(core_id);
}
Thread* Scheduler::GetCurrentThread() const {
if (current_thread) {
return current_thread.get();
}
return idle_thread.get();
}
Thread* Scheduler::GetSelectedThread() const {
return selected_thread.get();
}
u64 Scheduler::GetLastContextSwitchTicks() const {
return last_context_switch_time;
}
void Scheduler::TryDoContextSwitch() {
auto& phys_core = system.Kernel().CurrentPhysicalCore();
if (phys_core.IsInterrupted()) {
phys_core.ClearInterrupt();
}
guard.lock();
if (is_context_switch_pending) {
SwitchContext();
} else {
guard.unlock();
}
}
void Scheduler::OnThreadStart() {
SwitchContextStep2();
}
void Scheduler::Unload(Thread* thread) {
if (thread) {
thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
thread->SetIsRunning(false);
if (thread->IsContinuousOnSVC() && !thread->IsHLEThread()) {
system.ArmInterface(core_id).ExceptionalExit();
thread->SetContinuousOnSVC(false);
}
if (!thread->IsHLEThread() && !thread->HasExited()) {
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
cpu_core.SaveContext(thread->GetContext32());
cpu_core.SaveContext(thread->GetContext64());
// Save the TPIDR_EL0 system register in case it was modified.
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
}
thread->context_guard.unlock();
}
}
void Scheduler::Unload() {
Unload(current_thread.get());
}
void Scheduler::Reload(Thread* thread) {
if (thread) {
ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
"Thread must be runnable.");
// Cancel any outstanding wakeup events for this thread
thread->SetIsRunning(true);
thread->SetWasRunning(false);
thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
auto* const thread_owner_process = thread->GetOwnerProcess();
if (thread_owner_process != nullptr) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
if (!thread->IsHLEThread()) {
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
cpu_core.LoadContext(thread->GetContext32());
cpu_core.LoadContext(thread->GetContext64());
cpu_core.SetTlsAddress(thread->GetTLSAddress());
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
}
}
}
void Scheduler::Reload() {
Reload(current_thread.get());
}
void Scheduler::SwitchContextStep2() {
// Load context of new thread
Reload(selected_thread.get());
TryDoContextSwitch();
}
void Scheduler::SwitchContext() {
current_thread_prev = current_thread;
selected_thread = selected_thread_set;
Thread* previous_thread = current_thread_prev.get();
Thread* new_thread = selected_thread.get();
current_thread = selected_thread;
is_context_switch_pending = false;
if (new_thread == previous_thread) {
guard.unlock();
return;
}
Process* const previous_process = system.Kernel().CurrentProcess();
UpdateLastContextSwitchTime(previous_thread, previous_process);
// Save context for previous thread
Unload(previous_thread);
std::shared_ptr<Common::Fiber>* old_context;
if (previous_thread != nullptr) {
old_context = &previous_thread->GetHostContext();
} else {
old_context = &idle_thread->GetHostContext();
}
guard.unlock();
Common::Fiber::YieldTo(*old_context, switch_fiber);
/// When a thread wakes up, the scheduler may have changed to other in another core.
auto& next_scheduler = system.Kernel().CurrentScheduler();
next_scheduler.SwitchContextStep2();
}
void Scheduler::OnSwitch(void* this_scheduler) {
Scheduler* sched = static_cast<Scheduler*>(this_scheduler);
sched->SwitchToCurrent();
}
void Scheduler::SwitchToCurrent() {
while (true) {
{
std::scoped_lock lock{guard};
selected_thread = selected_thread_set;
current_thread = selected_thread;
is_context_switch_pending = false;
}
const auto is_switch_pending = [this] {
std::scoped_lock lock{guard};
return is_context_switch_pending;
};
do {
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
current_thread->context_guard.lock();
if (!current_thread->IsRunnable()) {
current_thread->context_guard.unlock();
break;
}
if (static_cast<u32>(current_thread->GetProcessorID()) != core_id) {
current_thread->context_guard.unlock();
break;
}
}
std::shared_ptr<Common::Fiber>* next_context;
if (current_thread != nullptr) {
next_context = &current_thread->GetHostContext();
} else {
next_context = &idle_thread->GetHostContext();
}
Common::Fiber::YieldTo(switch_fiber, *next_context);
} while (!is_switch_pending());
}
}
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time;
const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) {
thread->UpdateCPUTimeTicks(update_ticks);
}
if (process != nullptr) {
process->UpdateCPUTimeTicks(update_ticks);
}
last_context_switch_time = most_recent_switch_ticks;
}
void Scheduler::Initialize() {
std::string name = "Idle Thread Id:" + std::to_string(core_id);
std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
nullptr, std::move(init_func), init_func_parameter);
idle_thread = std::move(thread_res).Unwrap();
}
void Scheduler::Shutdown() {
current_thread = nullptr;
selected_thread = nullptr;
}
SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} {
kernel.GlobalScheduler().Lock();
}
SchedulerLock::~SchedulerLock() {
kernel.GlobalScheduler().Unlock();
}
SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle,
Thread* time_task, s64 nanoseconds)
: SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{
nanoseconds} {
event_handle = InvalidHandle;
}
SchedulerLockAndSleep::~SchedulerLockAndSleep() {
if (sleep_cancelled) {
return;
}
auto& time_manager = kernel.TimeManager();
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
}
void SchedulerLockAndSleep::Release() {
if (sleep_cancelled) {
return;
}
auto& time_manager = kernel.TimeManager();
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
sleep_cancelled = true;
}
} // namespace Kernel

View File

@@ -1,320 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <vector>
#include "common/common_types.h"
#include "common/multi_level_queue.h"
#include "common/spin_lock.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/thread.h"
namespace Common {
class Fiber;
}
namespace Core {
class ARM_Interface;
class System;
} // namespace Core
namespace Kernel {
class KernelCore;
class Process;
class SchedulerLock;
class GlobalScheduler final {
public:
explicit GlobalScheduler(KernelCore& kernel);
~GlobalScheduler();
/// Adds a new thread to the scheduler
void AddThread(std::shared_ptr<Thread> thread);
/// Removes a thread from the scheduler
void RemoveThread(std::shared_ptr<Thread> thread);
/// Returns a list of all threads managed by the scheduler
const std::vector<std::shared_ptr<Thread>>& GetThreadList() const {
return thread_list;
}
/// Notify the scheduler a thread's status has changed.
void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags);
/// Notify the scheduler a thread's priority has changed.
void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority);
/// Notify the scheduler a thread's core and/or affinity mask has changed.
void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core);
/**
* Takes care of selecting the new scheduled threads in three steps:
*
* 1. First a thread is selected from the top of the priority queue. If no thread
* is obtained then we move to step two, else we are done.
*
* 2. Second we try to get a suggested thread that's not assigned to any core or
* that is not the top thread in that core.
*
* 3. Third is no suggested thread is found, we do a second pass and pick a running
* thread in another core and swap it with its current thread.
*
* returns the cores needing scheduling.
*/
u32 SelectThreads();
bool HaveReadyThreads(std::size_t core_id) const {
return !scheduled_queue[core_id].empty();
}
/**
* Takes a thread and moves it to the back of the it's priority list.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
bool YieldThread(Thread* thread);
/**
* Takes a thread and moves it to the back of the it's priority list.
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
* a better priority than the next thread in the core.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
bool YieldThreadAndBalanceLoad(Thread* thread);
/**
* Takes a thread and moves it out of the scheduling queue.
* and into the suggested queue. If no thread can be scheduled afterwards in that core,
* a suggested thread is obtained instead.
*
* @note This operation can be redundant and no scheduling is changed if marked as so.
*/
bool YieldThreadAndWaitForLoadBalancing(Thread* thread);
/**
* Rotates the scheduling queues of threads at a preemption priority and then does
* some core rebalancing. Preemption priorities can be found in the array
* 'preemption_priorities'.
*
* @note This operation happens every 10ms.
*/
void PreemptThreads();
u32 CpuCoresCount() const {
return Core::Hardware::NUM_CPU_CORES;
}
void SetReselectionPending() {
is_reselection_pending.store(true, std::memory_order_release);
}
bool IsReselectionPending() const {
return is_reselection_pending.load(std::memory_order_acquire);
}
void Shutdown();
private:
friend class SchedulerLock;
/// Lock the scheduler to the current thread.
void Lock();
/// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling
/// and reschedules current core if needed.
void Unlock();
void EnableInterruptAndSchedule(u32 cores_pending_reschedule,
Core::EmuThreadHandle global_thread);
/**
* Add a thread to the suggested queue of a cpu core. Suggested threads may be
* picked if no thread is scheduled to run on the core.
*/
void Suggest(u32 priority, std::size_t core, Thread* thread);
/**
* Remove a thread to the suggested queue of a cpu core. Suggested threads may be
* picked if no thread is scheduled to run on the core.
*/
void Unsuggest(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* back the queue in its priority level.
*/
void Schedule(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* front the queue in its priority level.
*/
void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
/// Reschedule an already scheduled thread based on a new priority
void Reschedule(u32 priority, std::size_t core, Thread* thread);
/// Unschedules a thread.
void Unschedule(u32 priority, std::size_t core, Thread* thread);
/**
* Transfers a thread into an specific core. If the destination_core is -1
* it will be unscheduled from its source code and added into its suggested
* queue.
*/
void TransferToCore(u32 priority, s32 destination_core, Thread* thread);
bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner);
static constexpr u32 min_regular_priority = 2;
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
scheduled_queue;
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
suggested_queue;
std::atomic<bool> is_reselection_pending{false};
// The priority levels at which the global scheduler preempts threads every 10 ms. They are
// ordered from Core 0 to Core 3.
std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
/// Scheduler lock mechanisms.
bool is_locked{};
std::mutex inner_lock;
std::atomic<s64> scope_lock{};
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
Common::SpinLock global_list_guard{};
/// Lists all thread ids that aren't deleted/etc.
std::vector<std::shared_ptr<Thread>> thread_list;
KernelCore& kernel;
};
class Scheduler final {
public:
explicit Scheduler(Core::System& system, std::size_t core_id);
~Scheduler();
/// Returns whether there are any threads that are ready to run.
bool HaveReadyThreads() const;
/// Reschedules to the next available thread (call after current thread is suspended)
void TryDoContextSwitch();
/// The next two are for SingleCore Only.
/// Unload current thread before preempting core.
void Unload(Thread* thread);
void Unload();
/// Reload current thread after core preemption.
void Reload(Thread* thread);
void Reload();
/// Gets the current running thread
Thread* GetCurrentThread() const;
/// Gets the currently selected thread from the top of the multilevel queue
Thread* GetSelectedThread() const;
/// Gets the timestamp for the last context switch in ticks.
u64 GetLastContextSwitchTicks() const;
bool ContextSwitchPending() const {
return is_context_switch_pending;
}
void Initialize();
/// Shutdowns the scheduler.
void Shutdown();
void OnThreadStart();
std::shared_ptr<Common::Fiber>& ControlContext() {
return switch_fiber;
}
const std::shared_ptr<Common::Fiber>& ControlContext() const {
return switch_fiber;
}
private:
friend class GlobalScheduler;
/// Switches the CPU's active thread context to that of the specified thread
void SwitchContext();
/// When a thread wakes up, it must run this through it's new scheduler
void SwitchContextStep2();
/**
* Called on every context switch to update the internal timestamp
* This also updates the running time ticks for the given thread and
* process using the following difference:
*
* ticks += most_recent_ticks - last_context_switch_ticks
*
* The internal tick timestamp for the scheduler is simply the
* most recent tick count retrieved. No special arithmetic is
* applied to it.
*/
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
static void OnSwitch(void* this_scheduler);
void SwitchToCurrent();
std::shared_ptr<Thread> current_thread = nullptr;
std::shared_ptr<Thread> selected_thread = nullptr;
std::shared_ptr<Thread> current_thread_prev = nullptr;
std::shared_ptr<Thread> selected_thread_set = nullptr;
std::shared_ptr<Thread> idle_thread = nullptr;
std::shared_ptr<Common::Fiber> switch_fiber = nullptr;
Core::System& system;
u64 last_context_switch_time = 0;
u64 idle_selection_count = 0;
const std::size_t core_id;
Common::SpinLock guard{};
bool is_context_switch_pending = false;
};
class SchedulerLock {
public:
[[nodiscard]] explicit SchedulerLock(KernelCore& kernel);
~SchedulerLock();
protected:
KernelCore& kernel;
};
class SchedulerLockAndSleep : public SchedulerLock {
public:
explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task,
s64 nanoseconds);
~SchedulerLockAndSleep();
void CancelSleep() {
sleep_cancelled = true;
}
void Release();
private:
Handle& event_handle;
Thread* time_task;
s64 nanoseconds;
bool sleep_cancelled{};
};
} // namespace Kernel

View File

@@ -14,9 +14,9 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
@@ -170,7 +170,7 @@ ResultCode ServerSession::CompleteSyncRequest() {
// Some service requests require the thread to block
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (!context.IsThreadWaiting()) {
context.GetThread().ResumeFromWait();
context.GetThread().SetSynchronizationResults(nullptr, result);

View File

@@ -24,6 +24,8 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_block.h"
#include "core/hle/kernel/memory/page_table.h"
@@ -32,7 +34,6 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_types.h"
@@ -234,8 +235,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
u32 attribute) {
return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size),
mask, attribute);
return SetMemoryAttribute(system, address, size, mask, attribute);
}
/// Maps a memory range into a different range.
@@ -255,8 +255,7 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr
}
static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
static_cast<std::size_t>(size));
return MapMemory(system, dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -276,8 +275,7 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
}
static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
static_cast<std::size_t>(size));
return UnmapMemory(system, dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -332,7 +330,8 @@ static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle,
/// Makes a blocking IPC call to an OS service.
static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto& kernel = system.Kernel();
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
std::shared_ptr<ClientSession> session = handle_table.Get<ClientSession>(handle);
if (!session) {
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
@@ -341,9 +340,9 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
auto thread = system.CurrentScheduler().GetCurrentThread();
auto thread = kernel.CurrentScheduler()->GetCurrentThread();
{
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(kernel);
thread->InvalidateHLECallback();
thread->SetStatus(ThreadStatus::WaitIPC);
session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
@@ -352,12 +351,12 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
if (thread->HasHLECallback()) {
Handle event_handle = thread->GetHLETimeEvent();
if (event_handle != InvalidHandle) {
auto& time_manager = system.Kernel().TimeManager();
auto& time_manager = kernel.TimeManager();
time_manager.UnscheduleTimeEvent(event_handle);
}
{
SchedulerLock lock(system.Kernel());
KScopedSchedulerLock lock(kernel);
auto* sync_object = thread->GetHLESyncObject();
sync_object->RemoveWaitingThread(SharedFrom(thread));
}
@@ -531,8 +530,7 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand
static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle,
u32 mutex_addr, Handle requesting_thread_handle) {
return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr),
requesting_thread_handle);
return ArbitrateLock(system, holding_thread_handle, mutex_addr, requesting_thread_handle);
}
/// Unlock a mutex
@@ -555,7 +553,7 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
}
static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) {
return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr));
return ArbitrateUnlock(system, mutex_addr);
}
enum class BreakType : u32 {
@@ -658,7 +656,6 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
if (!break_reason.signal_debugger) {
SchedulerLock lock(system.Kernel());
LOG_CRITICAL(
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@@ -666,18 +663,14 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
handle_debug_buffer(info1, info2);
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
const auto thread_processor_id = current_thread->GetProcessorID();
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
// Kill the current thread
system.Kernel().ExceptionalExit();
current_thread->Stop();
}
}
static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2));
Break(system, reason, info1, info2);
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
@@ -922,7 +915,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
}
const auto& core_timing = system.CoreTiming();
const auto& scheduler = system.CurrentScheduler();
const auto& scheduler = *system.Kernel().CurrentScheduler();
const auto* const current_thread = scheduler.GetCurrentThread();
const bool same_thread = current_thread == thread.get();
@@ -948,7 +941,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low,
u32 info_id, u32 handle, u32 sub_id_high) {
const u64 sub_id{static_cast<u64>(sub_id_low | (static_cast<u64>(sub_id_high) << 32))};
const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)};
u64 res_value{};
const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)};
@@ -1009,7 +1002,7 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
}
static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
return MapPhysicalMemory(system, addr, size);
}
/// Unmaps memory previously mapped via MapPhysicalMemory
@@ -1063,7 +1056,7 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
}
static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
return UnmapPhysicalMemory(system, addr, size);
}
/// Sets the thread activity
@@ -1090,7 +1083,7 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
return ERR_INVALID_HANDLE;
}
if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
return ERR_BUSY;
}
@@ -1123,7 +1116,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
return ERR_INVALID_HANDLE;
}
if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
return ERR_BUSY;
}
@@ -1144,7 +1137,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
}
static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) {
return GetThreadContext(system, static_cast<VAddr>(thread_context), handle);
return GetThreadContext(system, thread_context, handle);
}
/// Gets the priority for the specified thread
@@ -1281,8 +1274,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr,
u32 size, u32 permissions) {
return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr),
static_cast<std::size_t>(size), permissions);
return MapSharedMemory(system, shared_memory_handle, addr, size, permissions);
}
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
@@ -1480,7 +1472,7 @@ static void ExitProcess(Core::System& system) {
current_process->PrepareForTermination();
// Kill the current thread
system.CurrentScheduler().GetCurrentThread()->Stop();
system.Kernel().CurrentScheduler()->GetCurrentThread()->Stop();
}
static void ExitProcess32(Core::System& system) {
@@ -1552,8 +1544,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg),
static_cast<VAddr>(stack_top), priority, processor_id);
return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id);
}
/// Starts the thread for the provided handle
@@ -1581,8 +1572,8 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
static void ExitThread(Core::System& system) {
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
system.GlobalScheduler().RemoveThread(SharedFrom(current_thread));
auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
system.GlobalSchedulerContext().RemoveThread(SharedFrom(current_thread));
current_thread->Stop();
}
@@ -1592,53 +1583,39 @@ static void ExitThread32(Core::System& system) {
/// Sleep the current thread
static void SleepThread(Core::System& system, s64 nanoseconds) {
LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds);
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
enum class SleepType : s64 {
YieldWithoutLoadBalancing = 0,
YieldWithLoadBalancing = -1,
YieldWithoutCoreMigration = 0,
YieldWithCoreMigration = -1,
YieldAndWaitForLoadBalancing = -2,
};
auto& scheduler = system.CurrentScheduler();
auto* const current_thread = scheduler.GetCurrentThread();
bool is_redundant = false;
auto& scheduler = *system.Kernel().CurrentScheduler();
if (nanoseconds <= 0) {
switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing: {
auto pair = current_thread->YieldSimple();
is_redundant = pair.second;
case SleepType::YieldWithoutCoreMigration: {
scheduler.YieldWithoutCoreMigration();
break;
}
case SleepType::YieldWithLoadBalancing: {
auto pair = current_thread->YieldAndBalanceLoad();
is_redundant = pair.second;
case SleepType::YieldWithCoreMigration: {
scheduler.YieldWithCoreMigration();
break;
}
case SleepType::YieldAndWaitForLoadBalancing: {
auto pair = current_thread->YieldAndWaitForLoadBalancing();
is_redundant = pair.second;
scheduler.YieldToAnyThread();
break;
}
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
} else {
current_thread->Sleep(nanoseconds);
}
if (is_redundant && !system.Kernel().IsMulticore()) {
system.Kernel().ExitSVCProfile();
system.CoreTiming().AddTicks(1000U);
system.GetCpuManager().PreemptSingleCore();
system.Kernel().EnterSVCProfile();
scheduler.GetCurrentThread()->Sleep(nanoseconds);
}
}
static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) {
const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) |
(static_cast<u64>(nanoseconds_high) << 32));
const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32));
SleepThread(system, nanoseconds);
}
@@ -1668,10 +1645,10 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
auto& kernel = system.Kernel();
Handle event_handle;
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
auto* const current_process = system.Kernel().CurrentProcess();
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
auto* const current_process = kernel.CurrentProcess();
{
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
const auto& handle_table = current_process->GetHandleTable();
std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
@@ -1707,7 +1684,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
}
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
auto* owner = current_thread->GetLockOwner();
if (owner != nullptr) {
@@ -1724,10 +1701,8 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr,
u32 condition_variable_addr, Handle thread_handle,
u32 nanoseconds_low, u32 nanoseconds_high) {
const s64 nanoseconds =
static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32));
return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr),
static_cast<VAddr>(condition_variable_addr), thread_handle,
const auto nanoseconds = static_cast<s64>(nanoseconds_low | (u64{nanoseconds_high} << 32));
return WaitProcessWideKeyAtomic(system, mutex_addr, condition_variable_addr, thread_handle,
nanoseconds);
}
@@ -1740,7 +1715,7 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
// Retrieve a list of all threads that are waiting for this condition variable.
auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
auto* const current_process = kernel.CurrentProcess();
std::vector<std::shared_ptr<Thread>> waiting_threads =
current_process->GetConditionVariableThreads(condition_variable_addr);
@@ -1833,8 +1808,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value,
u32 timeout_low, u32 timeout_high) {
s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32));
return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout);
const auto timeout = static_cast<s64>(timeout_low | (u64{timeout_high} << 32));
return WaitForAddress(system, address, type, value, timeout);
}
// Signals to an address (via Address Arbiter)
@@ -1862,7 +1837,7 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type,
static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value,
s32 num_to_wake) {
return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake);
return SignalToAddress(system, address, type, value, num_to_wake);
}
static void KernelDebug([[maybe_unused]] Core::System& system,
@@ -1893,7 +1868,7 @@ static u64 GetSystemTick(Core::System& system) {
}
static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) {
u64 time = GetSystemTick(system);
const auto time = GetSystemTick(system);
*time_low = static_cast<u32>(time);
*time_high = static_cast<u32>(time >> 32);
}
@@ -1984,8 +1959,7 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size,
u32 permissions) {
return CreateTransferMemory(system, handle, static_cast<VAddr>(addr),
static_cast<std::size_t>(size), permissions);
return CreateTransferMemory(system, handle, addr, size, permissions);
}
static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
@@ -2003,7 +1977,7 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
}
*core = thread->GetIdealCore();
*mask = thread->GetAffinityMask();
*mask = thread->GetAffinityMask().GetAffinityMask();
return RESULT_SUCCESS;
}
@@ -2075,8 +2049,7 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core,
u32 affinity_mask_low, u32 affinity_mask_high) {
const u64 affinity_mask =
static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32);
const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32);
return SetThreadCoreMask(system, thread_handle, core, affinity_mask);
}
@@ -2341,9 +2314,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return RESULT_SUCCESS;
}
static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address,
u32 size) {
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope
static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system,
[[maybe_unused]] Handle handle,
[[maybe_unused]] u32 address, [[maybe_unused]] u32 size) {
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op,
// as all emulation is done in the same cache level in host architecture, thus data cache
// does not need flushing.
LOG_DEBUG(Kernel_SVC, "called");
@@ -2639,7 +2613,7 @@ void Call(Core::System& system, u32 immediate) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
auto* thread = system.CurrentScheduler().GetCurrentThread();
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
thread->SetContinuousOnSVC(true);
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)

View File

@@ -5,8 +5,9 @@
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/kernel/thread.h"
@@ -18,7 +19,7 @@ Synchronization::Synchronization(Core::System& system) : system{system} {}
void Synchronization::SignalObject(SynchronizationObject& obj) const {
auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (obj.IsSignaled()) {
for (auto thread : obj.GetWaitingThreads()) {
if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) {
@@ -37,10 +38,10 @@ void Synchronization::SignalObject(SynchronizationObject& obj) const {
std::pair<ResultCode, Handle> Synchronization::WaitFor(
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
auto& kernel = system.Kernel();
auto* const thread = system.CurrentScheduler().GetCurrentThread();
auto* const thread = kernel.CurrentScheduler()->GetCurrentThread();
Handle event_handle = InvalidHandle;
{
SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
const auto itr =
std::find_if(sync_objects.begin(), sync_objects.end(),
[thread](const std::shared_ptr<SynchronizationObject>& object) {
@@ -89,7 +90,7 @@ std::pair<ResultCode, Handle> Synchronization::WaitFor(
}
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
ResultCode signaling_result = thread->GetSignalingResult();
SynchronizationObject* signaling_object = thread->GetSignalingObject();
thread->SetSynchronizationObjects(nullptr);

View File

@@ -17,10 +17,11 @@
#include "core/hardware_properties.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
@@ -50,7 +51,7 @@ Thread::~Thread() = default;
void Thread::Stop() {
{
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
SetStatus(ThreadStatus::Dead);
Signal();
kernel.GlobalHandleTable().Close(global_handle);
@@ -67,7 +68,7 @@ void Thread::Stop() {
}
void Thread::ResumeFromWait() {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
switch (status) {
case ThreadStatus::Paused:
case ThreadStatus::WaitSynch:
@@ -99,19 +100,18 @@ void Thread::ResumeFromWait() {
}
void Thread::OnWakeUp() {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
SetStatus(ThreadStatus::Ready);
}
ResultCode Thread::Start() {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
SetStatus(ThreadStatus::Ready);
return RESULT_SUCCESS;
}
void Thread::CancelWait() {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) {
is_sync_cancelled = true;
return;
@@ -186,12 +186,14 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
thread->status = ThreadStatus::Dormant;
thread->entry_point = entry_point;
thread->stack_top = stack_top;
thread->disable_count = 1;
thread->tpidr_el0 = 0;
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = 0;
thread->schedule_count = -1;
thread->last_scheduled_tick = 0;
thread->processor_id = processor_id;
thread->ideal_core = processor_id;
thread->affinity_mask = 1ULL << processor_id;
thread->affinity_mask.SetAffinity(processor_id, true);
thread->wait_objects = nullptr;
thread->mutex_wait_address = 0;
thread->condvar_wait_address = 0;
@@ -201,7 +203,7 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
thread->owner_process = owner_process;
thread->type = type_flags;
if ((type_flags & THREADTYPE_IDLE) == 0) {
auto& scheduler = kernel.GlobalScheduler();
auto& scheduler = kernel.GlobalSchedulerContext();
scheduler.AddThread(thread);
}
if (owner_process) {
@@ -225,7 +227,7 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
}
void Thread::SetPriority(u32 priority) {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
"Invalid priority value.");
nominal_priority = priority;
@@ -362,7 +364,7 @@ bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) {
}
ResultCode Thread::SetActivity(ThreadActivity value) {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
auto sched_status = GetSchedulingStatus();
@@ -391,7 +393,7 @@ ResultCode Thread::SetActivity(ThreadActivity value) {
ResultCode Thread::Sleep(s64 nanoseconds) {
Handle event_handle{};
{
SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
KScopedSchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
SetStatus(ThreadStatus::WaitSleep);
}
@@ -402,39 +404,12 @@ ResultCode Thread::Sleep(s64 nanoseconds) {
return RESULT_SUCCESS;
}
std::pair<ResultCode, bool> Thread::YieldSimple() {
bool is_redundant = false;
{
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThread(this);
}
return {RESULT_SUCCESS, is_redundant};
}
std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() {
bool is_redundant = false;
{
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this);
}
return {RESULT_SUCCESS, is_redundant};
}
std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() {
bool is_redundant = false;
{
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this);
}
return {RESULT_SUCCESS, is_redundant};
}
void Thread::AddSchedulingFlag(ThreadSchedFlags flag) {
const u32 old_state = scheduling_state;
pausing_state |= static_cast<u32>(flag);
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
scheduling_state = base_scheduling | pausing_state;
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
KScheduler::OnThreadStateChanged(kernel, this, old_state);
}
void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
@@ -442,23 +417,24 @@ void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
pausing_state &= ~static_cast<u32>(flag);
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
scheduling_state = base_scheduling | pausing_state;
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
KScheduler::OnThreadStateChanged(kernel, this, old_state);
}
void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
const u32 old_state = scheduling_state;
scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
static_cast<u32>(new_status);
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
KScheduler::OnThreadStateChanged(kernel, this, old_state);
}
void Thread::SetCurrentPriority(u32 new_priority) {
const u32 old_priority = std::exchange(current_priority, new_priority);
kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority);
KScheduler::OnThreadPriorityChanged(kernel, this, kernel.CurrentScheduler()->GetCurrentThread(),
old_priority);
}
ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
SchedulerLock lock(kernel);
KScopedSchedulerLock lock(kernel);
const auto HighestSetCore = [](u64 mask, u32 max_cores) {
for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) {
if (((mask >> core) & 1) != 0) {
@@ -479,20 +455,21 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
}
if (use_override) {
ideal_core_override = new_core;
affinity_mask_override = new_affinity_mask;
} else {
const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask);
const auto old_affinity_mask = affinity_mask;
affinity_mask.SetAffinityMask(new_affinity_mask);
ideal_core = new_core;
if (old_affinity_mask != new_affinity_mask) {
if (old_affinity_mask.GetAffinityMask() != new_affinity_mask) {
const s32 old_core = processor_id;
if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
if (processor_id >= 0 && !affinity_mask.GetAffinity(processor_id)) {
if (static_cast<s32>(ideal_core) < 0) {
processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES);
processor_id = HighestSetCore(affinity_mask.GetAffinityMask(),
Core::Hardware::NUM_CPU_CORES);
} else {
processor_id = ideal_core;
}
}
kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core);
KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_affinity_mask, old_core);
}
}
return RESULT_SUCCESS;

View File

@@ -4,6 +4,7 @@
#pragma once
#include <array>
#include <functional>
#include <string>
#include <utility>
@@ -12,6 +13,7 @@
#include "common/common_types.h"
#include "common/spin_lock.h"
#include "core/arm/arm_interface.h"
#include "core/hle/kernel/k_affinity_mask.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h"
@@ -27,10 +29,10 @@ class System;
namespace Kernel {
class GlobalScheduler;
class GlobalSchedulerContext;
class KernelCore;
class Process;
class Scheduler;
class KScheduler;
enum ThreadPriority : u32 {
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
@@ -345,8 +347,12 @@ public:
void SetStatus(ThreadStatus new_status);
u64 GetLastRunningTicks() const {
return last_running_ticks;
s64 GetLastScheduledTick() const {
return this->last_scheduled_tick;
}
void SetLastScheduledTick(s64 tick) {
this->last_scheduled_tick = tick;
}
u64 GetTotalCPUTimeTicks() const {
@@ -361,10 +367,18 @@ public:
return processor_id;
}
s32 GetActiveCore() const {
return GetProcessorID();
}
void SetProcessorID(s32 new_core) {
processor_id = new_core;
}
void SetActiveCore(s32 new_core) {
processor_id = new_core;
}
Process* GetOwnerProcess() {
return owner_process;
}
@@ -469,7 +483,7 @@ public:
return ideal_core;
}
u64 GetAffinityMask() const {
const KAffinityMask& GetAffinityMask() const {
return affinity_mask;
}
@@ -478,21 +492,12 @@ public:
/// Sleeps this thread for the given amount of nanoseconds.
ResultCode Sleep(s64 nanoseconds);
/// Yields this thread without rebalancing loads.
std::pair<ResultCode, bool> YieldSimple();
/// Yields this thread and does a load rebalancing.
std::pair<ResultCode, bool> YieldAndBalanceLoad();
/// Yields this thread and if the core is left idle, loads are rebalanced
std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing();
void IncrementYieldCount() {
yield_count++;
s64 GetYieldScheduleCount() const {
return this->schedule_count;
}
u64 GetYieldCount() const {
return yield_count;
void SetYieldScheduleCount(s64 count) {
this->schedule_count = count;
}
ThreadSchedStatus GetSchedulingStatus() const {
@@ -568,9 +573,59 @@ public:
return has_exited;
}
class QueueEntry {
public:
constexpr QueueEntry() = default;
constexpr void Initialize() {
this->prev = nullptr;
this->next = nullptr;
}
constexpr Thread* GetPrev() const {
return this->prev;
}
constexpr Thread* GetNext() const {
return this->next;
}
constexpr void SetPrev(Thread* thread) {
this->prev = thread;
}
constexpr void SetNext(Thread* thread) {
this->next = thread;
}
private:
Thread* prev{};
Thread* next{};
};
QueueEntry& GetPriorityQueueEntry(s32 core) {
return this->per_core_priority_queue_entry[core];
}
const QueueEntry& GetPriorityQueueEntry(s32 core) const {
return this->per_core_priority_queue_entry[core];
}
s32 GetDisableDispatchCount() const {
return disable_count;
}
void DisableDispatch() {
ASSERT(GetDisableDispatchCount() >= 0);
disable_count++;
}
void EnableDispatch() {
ASSERT(GetDisableDispatchCount() > 0);
disable_count--;
}
private:
friend class GlobalScheduler;
friend class Scheduler;
friend class GlobalSchedulerContext;
friend class KScheduler;
friend class Process;
void SetSchedulingStatus(ThreadSchedStatus new_status);
void AddSchedulingFlag(ThreadSchedFlags flag);
@@ -583,12 +638,14 @@ private:
ThreadContext64 context_64{};
std::shared_ptr<Common::Fiber> host_context{};
u64 thread_id = 0;
ThreadStatus status = ThreadStatus::Dormant;
u32 scheduling_state = 0;
u64 thread_id = 0;
VAddr entry_point = 0;
VAddr stack_top = 0;
std::atomic_int disable_count = 0;
ThreadType type;
@@ -602,9 +659,8 @@ private:
u32 current_priority = 0;
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
u64 yield_count = 0; ///< Number of redundant yields carried by this thread.
///< a redundant yield is one where no scheduling is changed
s64 schedule_count{};
s64 last_scheduled_tick{};
s32 processor_id = 0;
@@ -646,16 +702,16 @@ private:
Handle hle_time_event;
SynchronizationObject* hle_object;
Scheduler* scheduler = nullptr;
KScheduler* scheduler = nullptr;
std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
KAffinityMask affinity_mask{};
s32 ideal_core_override = -1;
u64 affinity_mask_override = 0x1;
u32 affinity_override_count = 0;
u32 scheduling_state = 0;
u32 pausing_state = 0;
bool is_running = false;
bool is_waiting_on_sync = false;

View File

@@ -7,8 +7,8 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
@@ -18,12 +18,18 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type = Core::Timing::CreateEvent(
"Kernel::TimeManagerCallback",
[this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
const SchedulerLock lock(system.Kernel());
const KScopedSchedulerLock lock(system.Kernel());
const auto proper_handle = static_cast<Handle>(thread_handle);
if (cancelled_events[proper_handle]) {
return;
std::shared_ptr<Thread> thread;
{
std::lock_guard lock{mutex};
if (cancelled_events[proper_handle]) {
return;
}
thread = system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
}
auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
if (thread) {
// Thread can be null if process has exited
thread->OnWakeUp();
@@ -56,6 +62,7 @@ void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
}
void TimeManager::CancelTimeEvent(Thread* time_task) {
std::lock_guard lock{mutex};
const Handle event_handle = time_task->GetGlobalHandle();
UnscheduleTimeEvent(event_handle);
}

View File

@@ -142,14 +142,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce,
ErrorApplet error, ParentalControlsApplet parental_controls,
PhotoViewer photo_viewer, ProfileSelect profile_select,
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
: controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)},
parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)},
profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)},
web_browser{std::move(web_browser)} {}
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
parental_controls{std::move(parental_controls_applet)},
photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -170,10 +170,6 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
frontend.controller = std::move(set.controller);
}
if (set.e_commerce != nullptr) {
frontend.e_commerce = std::move(set.e_commerce);
}
if (set.error != nullptr) {
frontend.error = std::move(set.error);
}
@@ -210,10 +206,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
}
if (frontend.e_commerce == nullptr) {
frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
@@ -257,13 +249,14 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
case AppletId::Web:
case AppletId::Shop:
case AppletId::OfflineWeb:
case AppletId::LoginShare:
case AppletId::WebAuth:
return std::make_shared<WebBrowser>(system, *frontend.web_browser);
case AppletId::PhotoViewer:
return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
case AppletId::LibAppletShop:
return std::make_shared<WebBrowser>(system, *frontend.web_browser,
frontend.e_commerce.get());
case AppletId::LibAppletOff:
return std::make_shared<WebBrowser>(system, *frontend.web_browser);
default:
UNIMPLEMENTED_MSG(
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",

View File

@@ -50,13 +50,13 @@ enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
MiiEdit = 0x12,
LibAppletWeb = 0x13,
LibAppletShop = 0x14,
Web = 0x13,
Shop = 0x14,
PhotoViewer = 0x15,
Settings = 0x16,
LibAppletOff = 0x17,
LibAppletWhitelisted = 0x18,
LibAppletAuth = 0x19,
OfflineWeb = 0x17,
LoginShare = 0x18,
WebAuth = 0x19,
MyPage = 0x1A,
};
@@ -157,7 +157,6 @@ protected:
struct AppletFrontendSet {
using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
@@ -166,10 +165,10 @@ struct AppletFrontendSet {
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
AppletFrontendSet();
AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error,
ParentalControlsApplet parental_controls, PhotoViewer photo_viewer,
ProfileSelect profile_select, SoftwareKeyboard software_keyboard,
WebBrowser web_browser);
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
WebBrowser web_browser_);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -179,7 +178,6 @@ struct AppletFrontendSet {
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
ControllerApplet controller;
ECommerceApplet e_commerce;
ErrorApplet error;
ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;

View File

@@ -1,238 +1,271 @@
// Copyright 2018 yuzu emulator team
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cstring>
#include <vector>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.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/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs_types.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/file_sys/vfs_vector.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/hle/service/ns/pl_u.h"
namespace Service::AM::Applets {
enum class WebArgTLVType : u16 {
InitialURL = 0x1,
ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
CallbackURL = 0x3,
CallbackableURL = 0x4,
ApplicationID = 0x5,
DocumentPath = 0x6,
DocumentKind = 0x7,
SystemDataID = 0x8,
ShareStartPage = 0x9,
Whitelist = 0xA,
News = 0xB,
UserID = 0xE,
AlbumEntry0 = 0xF,
ScreenShotEnabled = 0x10,
EcClientCertEnabled = 0x11,
Unk12 = 0x12,
PlayReportEnabled = 0x13,
Unk14 = 0x14,
Unk15 = 0x15,
BootDisplayKind = 0x17,
BackgroundKind = 0x18,
FooterEnabled = 0x19,
PointerEnabled = 0x1A,
LeftStickMode = 0x1B,
KeyRepeatFrame1 = 0x1C,
KeyRepeatFrame2 = 0x1D,
BootAsMediaPlayerInv = 0x1E,
DisplayUrlKind = 0x1F,
BootAsMediaPlayer = 0x21,
ShopJumpEnabled = 0x22,
MediaAutoPlayEnabled = 0x23,
LobbyParameter = 0x24,
ApplicationAlbumEntry = 0x26,
JsExtensionEnabled = 0x27,
AdditionalCommentText = 0x28,
TouchEnabledOnContents = 0x29,
UserAgentAdditionalString = 0x2A,
AdditionalMediaData0 = 0x2B,
MediaPlayerAutoCloseEnabled = 0x2C,
PageCacheEnabled = 0x2D,
WebAudioEnabled = 0x2E,
Unk2F = 0x2F,
YouTubeVideoWhitelist = 0x31,
FooterFixedKind = 0x32,
PageFadeEnabled = 0x33,
MediaCreatorApplicationRatingAge = 0x34,
BootLoadingIconEnabled = 0x35,
PageScrollIndicationEnabled = 0x36,
MediaPlayerSpeedControlEnabled = 0x37,
AlbumEntry1 = 0x38,
AlbumEntry2 = 0x39,
AlbumEntry3 = 0x3A,
AdditionalMediaData1 = 0x3B,
AdditionalMediaData2 = 0x3C,
AdditionalMediaData3 = 0x3D,
BootFooterButton = 0x3E,
OverrideWebAudioVolume = 0x3F,
OverrideMediaAudioVolume = 0x40,
BootMode = 0x41,
WebSessionEnabled = 0x42,
};
enum class ShimKind : u32 {
Shop = 1,
Login = 2,
Offline = 3,
Share = 4,
Web = 5,
Wifi = 6,
Lobby = 7,
};
enum class ShopWebTarget {
ApplicationInfo,
AddOnContentList,
SubscriptionList,
ConsumableItemList,
Home,
Settings,
};
namespace {
constexpr std::size_t SHIM_KIND_COUNT = 0x8;
template <typename T>
void ParseRawValue(T& value, const std::vector<u8>& data) {
static_assert(std::is_trivially_copyable_v<T>,
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, data.data(), data.size());
}
struct WebArgHeader {
u16 count;
INSERT_PADDING_BYTES(2);
ShimKind kind;
};
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
template <typename T>
T ParseRawValue(const std::vector<u8>& data) {
T value;
ParseRawValue(value, data);
return value;
}
struct WebArgTLV {
WebArgTLVType type;
u16 size;
u32 offset;
};
static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
std::string ParseStringValue(const std::vector<u8>& data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
data.size());
}
struct WebCommonReturnValue {
u32 result_code;
INSERT_PADDING_BYTES(0x4);
std::array<char, 0x1000> last_url;
u64 last_url_size;
};
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
std::string GetMainURL(const std::string& url) {
const auto index = url.find('?');
struct WebWifiPageArg {
INSERT_PADDING_BYTES(4);
std::array<char, 0x100> connection_test_url;
std::array<char, 0x400> initial_url;
std::array<u8, 0x10> nifm_network_uuid;
u32 nifm_requirement;
};
static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
if (index == std::string::npos) {
return url;
}
struct WebWifiReturnValue {
INSERT_PADDING_BYTES(4);
u32 result;
};
static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
return url.substr(0, index);
}
enum class OfflineWebSource : u32 {
OfflineHtmlPage = 0x1,
ApplicationLegalInformation = 0x2,
SystemDataPage = 0x3,
};
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
if (arg.size() < sizeof(WebArgHeader))
if (web_arg.size() == sizeof(WebArgHeader)) {
return {};
WebArgHeader header{};
std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
std::map<WebArgTLVType, std::vector<u8>> out;
u64 offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < header.count; ++i) {
if (arg.size() < (offset + sizeof(WebArgTLV)))
return out;
WebArgTLV tlv{};
std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
offset += sizeof(WebArgTLV);
offset += tlv.offset;
if (arg.size() < (offset + tlv.size))
return out;
std::vector<u8> data(tlv.size);
std::memcpy(data.data(), arg.data() + offset, tlv.size);
offset += tlv.size;
out.insert_or_assign(tlv.type, data);
}
return out;
WebArgInputTLVMap input_tlv_map;
u64 current_offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
return input_tlv_map;
}
WebArgInputTLV input_tlv;
std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
current_offset += sizeof(WebArgInputTLV);
if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
return input_tlv_map;
}
std::vector<u8> data(input_tlv.arg_data_size);
std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
current_offset += input_tlv.arg_data_size;
input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
}
return input_tlv_map;
}
FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
FileSys::ContentRecordType type) {
const auto& installed{system.GetContentProvider()};
const auto res = installed.GetEntry(title_id, type);
FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
FileSys::ContentRecordType nca_type) {
if (nca_type == FileSys::ContentRecordType::Data) {
const auto nca =
system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type);
if (res != nullptr) {
return res->GetRomFS();
if (nca == nullptr) {
LOG_ERROR(Service_AM,
"NCA of type={} with title_id={:016X} is not found in the System NAND!",
nca_type, title_id);
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
}
return nca->GetRomFS();
} else {
const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type);
if (nca == nullptr) {
LOG_ERROR(Service_AM,
"NCA of type={} with title_id={:016X} is not found in the ContentProvider!",
nca_type, title_id);
return nullptr;
}
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
}
if (type == FileSys::ContentRecordType::Data) {
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
}
return nullptr;
}
} // Anonymous namespace
void ExtractSharedFonts(Core::System& system) {
static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
"FontStandard.ttf",
"FontChineseSimplified.ttf",
"FontExtendedChineseSimplified.ttf",
"FontChineseTraditional.ttf",
"FontKorean.ttf",
"FontNintendoExtended.ttf",
"FontNintendoExtended2.ttf",
};
WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
Core::Frontend::ECommerceApplet* frontend_e_commerce_)
: Applet{system_.Kernel()}, frontend(frontend_),
frontend_e_commerce(frontend_e_commerce_), system{system_} {}
for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
const auto fonts_dir = Common::FS::SanitizePath(
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
Common::FS::DirectorySeparator::PlatformDefault);
const auto font_file_path =
Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]),
Common::FS::DirectorySeparator::PlatformDefault);
if (Common::FS::Exists(font_file_path)) {
continue;
}
const auto font = NS::SHARED_FONTS[i];
const auto font_title_id = static_cast<u64>(font.first);
const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry(
font_title_id, FileSys::ContentRecordType::Data);
FileSys::VirtualFile romfs;
if (!nca) {
romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id);
} else {
romfs = nca->GetRomFS();
}
if (!romfs) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!",
font_title_id);
continue;
}
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
if (!extracted_romfs) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!",
font_title_id);
continue;
}
const auto font_file = extracted_romfs->GetFile(font.second);
if (!font_file) {
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!",
font_title_id, font.second);
continue;
}
std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32));
font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize());
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
Common::swap32);
std::vector<u8> decrypted_data(font_file->GetSize() - 8);
NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data);
FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
const auto temp_dir =
system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite);
const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
FileSys::VfsRawCopy(decrypted_font, out_file);
}
}
} // namespace
WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_)
: Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
WebBrowser::~WebBrowser() = default;
void WebBrowser::Initialize() {
Applet::Initialize();
complete = false;
temporary_dir.clear();
filename.clear();
status = RESULT_SUCCESS;
LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
LOG_DEBUG(Service_AM,
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
common_args.arguments_version, common_args.library_version,
common_args.play_startup_sound, common_args.size, common_args.system_tick,
common_args.theme_color);
web_applet_version = WebAppletVersion{common_args.library_version};
const auto web_arg_storage = broker.PopNormalDataToApplet();
ASSERT(web_arg_storage != nullptr);
const auto& web_arg = web_arg_storage->GetData();
ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });
ASSERT(web_arg.size() >= 0x8);
std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);
args = GetWebArguments(web_arg);
LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
web_arg_header.total_tlv_entries, web_arg_header.shim_kind);
InitializeInternal();
ExtractSharedFonts(system);
switch (web_arg_header.shim_kind) {
case ShimKind::Shop:
InitializeShop();
break;
case ShimKind::Login:
InitializeLogin();
break;
case ShimKind::Offline:
InitializeOffline();
break;
case ShimKind::Share:
InitializeShare();
break;
case ShimKind::Web:
InitializeWeb();
break;
case ShimKind::Wifi:
InitializeWifi();
break;
case ShimKind::Lobby:
InitializeLobby();
break;
default:
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
break;
}
}
bool WebBrowser::TransactionComplete() const {
@@ -244,315 +277,202 @@ ResultCode WebBrowser::GetStatus() const {
}
void WebBrowser::ExecuteInteractive() {
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
UNIMPLEMENTED_MSG("WebSession is not implemented");
}
void WebBrowser::Execute() {
if (complete) {
return;
}
if (status != RESULT_SUCCESS) {
complete = true;
// This is a workaround in order not to softlock yuzu when an error happens during the
// webapplet init. In order to avoid an svcBreak, the status is set to RESULT_SUCCESS
Finalize();
status = RESULT_SUCCESS;
return;
}
ExecuteInternal();
}
void WebBrowser::UnpackRomFS() {
if (unpacked)
return;
ASSERT(offline_romfs != nullptr);
const auto dir =
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
const auto& vfs{system.GetFilesystem()};
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(dir, temp_dir);
unpacked = true;
}
void WebBrowser::Finalize() {
complete = true;
WebCommonReturnValue out{};
out.result_code = 0;
out.last_url_size = 0;
std::vector<u8> data(sizeof(WebCommonReturnValue));
std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(data)));
broker.SignalStateChanged();
if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
Common::FS::DeleteDirRecursively(temporary_dir);
}
}
void WebBrowser::InitializeInternal() {
using WebAppletInitializer = void (WebBrowser::*)();
constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
nullptr, &WebBrowser::InitializeShop,
nullptr, &WebBrowser::InitializeOffline,
nullptr, nullptr,
nullptr, nullptr,
};
const auto index = static_cast<u32>(kind);
if (index > functions.size() || functions[index] == nullptr) {
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
return;
}
const auto function = functions[index];
(this->*function)();
}
void WebBrowser::ExecuteInternal() {
using WebAppletExecutor = void (WebBrowser::*)();
constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
nullptr, &WebBrowser::ExecuteShop,
nullptr, &WebBrowser::ExecuteOffline,
nullptr, nullptr,
nullptr, nullptr,
};
const auto index = static_cast<u32>(kind);
if (index > functions.size() || functions[index] == nullptr) {
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
return;
}
const auto function = functions[index];
(this->*function)();
}
void WebBrowser::InitializeShop() {
if (frontend_e_commerce == nullptr) {
LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
status = RESULT_UNKNOWN;
return;
}
const auto user_id_data = args.find(WebArgTLVType::UserID);
user_id = std::nullopt;
if (user_id_data != args.end()) {
user_id = u128{};
std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
}
const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
if (url == args.end()) {
LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
status = RESULT_UNKNOWN;
return;
}
std::vector<std::string> split_query;
Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url->second.data()), url->second.size()),
'?', split_query);
// 2 -> Main URL '?' Query Parameters
// Less is missing info, More is malformed
if (split_query.size() != 2) {
LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
status = RESULT_UNKNOWN;
return;
}
std::vector<std::string> queries;
Common::SplitString(split_query[1], '&', queries);
const auto split_single_query =
[](const std::string& in) -> std::pair<std::string, std::string> {
const auto index = in.find('=');
if (index == std::string::npos || index == in.size() - 1) {
return {in, ""};
}
return {in.substr(0, index), in.substr(index + 1)};
};
std::transform(queries.begin(), queries.end(),
std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
const auto scene = shop_query.find("scene");
if (scene == shop_query.end()) {
LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
status = RESULT_UNKNOWN;
return;
}
const std::map<std::string, ShopWebTarget, std::less<>> target_map{
{"product_detail", ShopWebTarget::ApplicationInfo},
{"aocs", ShopWebTarget::AddOnContentList},
{"subscriptions", ShopWebTarget::SubscriptionList},
{"consumption", ShopWebTarget::ConsumableItemList},
{"settings", ShopWebTarget::Settings},
{"top", ShopWebTarget::Home},
};
const auto target = target_map.find(scene->second);
if (target == target_map.end()) {
LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
status = RESULT_UNKNOWN;
return;
}
shop_web_target = target->second;
const auto title_id_data = shop_query.find("dst_app_id");
if (title_id_data != shop_query.end()) {
title_id = std::stoull(title_id_data->second, nullptr, 0x10);
}
const auto mode_data = shop_query.find("mode");
if (mode_data != shop_query.end()) {
shop_full_display = mode_data->second == "full";
}
}
void WebBrowser::InitializeOffline() {
if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
args.find(WebArgTLVType::DocumentKind) == args.end() ||
args.find(WebArgTLVType::ApplicationID) == args.end()) {
status = RESULT_UNKNOWN;
LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
}
const auto url_data = args[WebArgTLVType::DocumentPath];
filename = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url_data.data()), url_data.size());
OfflineWebSource source;
ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
"manual",
"legal",
"system",
};
temporary_dir =
Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
"web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
Common::FS::DirectorySeparator::PlatformDefault);
Common::FS::DeleteDirRecursively(temporary_dir);
u64 title_id = 0; // 0 corresponds to current process
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
switch (source) {
case OfflineWebSource::OfflineHtmlPage:
// While there is an AppID TLV field, in official SW this is always ignored.
title_id = 0;
type = FileSys::ContentRecordType::HtmlDocument;
switch (web_arg_header.shim_kind) {
case ShimKind::Shop:
ExecuteShop();
break;
case OfflineWebSource::ApplicationLegalInformation:
type = FileSys::ContentRecordType::LegalInformation;
case ShimKind::Login:
ExecuteLogin();
break;
case OfflineWebSource::SystemDataPage:
type = FileSys::ContentRecordType::Data;
case ShimKind::Offline:
ExecuteOffline();
break;
}
if (title_id == 0) {
title_id = system.CurrentProcess()->GetTitleID();
}
offline_romfs = GetApplicationRomFS(system, title_id, type);
if (offline_romfs == nullptr) {
status = RESULT_UNKNOWN;
LOG_ERROR(Service_AM, "Failed to find offline data for request!");
}
std::string path_additional_directory;
if (source == OfflineWebSource::OfflineHtmlPage) {
path_additional_directory = std::string(DIR_SEP).append("html-document");
}
filename =
Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
Common::FS::DirectorySeparator::PlatformDefault);
}
void WebBrowser::ExecuteShop() {
const auto callback = [this]() { Finalize(); };
const auto check_optional_parameter = [this](const auto& p) {
if (!p.has_value()) {
LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
status = RESULT_UNKNOWN;
return false;
}
return true;
};
switch (shop_web_target) {
case ShopWebTarget::ApplicationInfo:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
shop_full_display, shop_extra_parameter);
case ShimKind::Share:
ExecuteShare();
break;
case ShopWebTarget::AddOnContentList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
case ShimKind::Web:
ExecuteWeb();
break;
case ShopWebTarget::ConsumableItemList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
case ShimKind::Wifi:
ExecuteWifi();
break;
case ShopWebTarget::Home:
if (!check_optional_parameter(user_id))
return;
if (!check_optional_parameter(shop_full_display))
return;
frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
break;
case ShopWebTarget::Settings:
if (!check_optional_parameter(user_id))
return;
if (!check_optional_parameter(shop_full_display))
return;
frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
break;
case ShopWebTarget::SubscriptionList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
case ShimKind::Lobby:
ExecuteLobby();
break;
default:
UNREACHABLE();
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
WebBrowserExit(WebExitReason::EndButtonPressed);
break;
}
}
void WebBrowser::ExtractOfflineRomFS() {
LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir);
const auto extracted_romfs_dir =
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
const auto temp_dir =
system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
}
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
if ((web_arg_header.shim_kind == ShimKind::Share &&
web_applet_version >= WebAppletVersion::Version196608) ||
(web_arg_header.shim_kind == ShimKind::Web &&
web_applet_version >= WebAppletVersion::Version524288)) {
// TODO: Push Output TLVs instead of a WebCommonReturnValue
}
WebCommonReturnValue web_common_return_value;
web_common_return_value.exit_reason = exit_reason;
std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
web_common_return_value.last_url_size = last_url.size();
LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
exit_reason, last_url, last_url.size());
complete = true;
std::vector<u8> out_data(sizeof(WebCommonReturnValue));
std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
broker.SignalStateChanged();
}
bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const {
return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end();
}
std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) {
const auto map_it = web_arg_input_tlv_map.find(input_tlv_type);
if (map_it == web_arg_input_tlv_map.end()) {
return std::nullopt;
}
return map_it->second;
}
void WebBrowser::InitializeShop() {}
void WebBrowser::InitializeLogin() {}
void WebBrowser::InitializeOffline() {
const auto document_path =
ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value());
const auto document_kind =
ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value());
std::string additional_paths;
switch (document_kind) {
case DocumentKind::OfflineHtmlPage:
default:
title_id = system.CurrentProcess()->GetTitleID();
nca_type = FileSys::ContentRecordType::HtmlDocument;
additional_paths = "html-document";
break;
case DocumentKind::ApplicationLegalInformation:
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value());
nca_type = FileSys::ContentRecordType::LegalInformation;
break;
case DocumentKind::SystemDataPage:
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value());
nca_type = FileSys::ContentRecordType::Data;
break;
}
static constexpr std::array<const char*, 3> RESOURCE_TYPES{
"manual",
"legal_information",
"system_data",
};
offline_cache_dir = Common::FS::SanitizePath(
fmt::format("{}/offline_web_applet_{}/{:016X}",
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir),
RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id),
Common::FS::DirectorySeparator::PlatformDefault);
offline_document = Common::FS::SanitizePath(
fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path),
Common::FS::DirectorySeparator::PlatformDefault);
}
void WebBrowser::InitializeShare() {}
void WebBrowser::InitializeWeb() {
external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());
}
void WebBrowser::InitializeWifi() {}
void WebBrowser::InitializeLobby() {}
void WebBrowser::ExecuteShop() {
LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteLogin() {
LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteOffline() {
frontend.OpenPageLocal(
filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document),
Common::FS::DirectorySeparator::PlatformDefault);
if (!Common::FS::Exists(main_url)) {
offline_romfs = GetOfflineRomFS(system, title_id, nca_type);
if (offline_romfs == nullptr) {
LOG_ERROR(Service_AM,
"RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id,
nca_type);
WebBrowserExit(WebExitReason::WindowClosed);
return;
}
}
LOG_INFO(Service_AM, "Opening offline document at {}", offline_document);
frontend.OpenLocalWebPage(
offline_document, [this] { ExtractOfflineRomFS(); },
[this](WebExitReason exit_reason, std::string last_url) {
WebBrowserExit(exit_reason, last_url);
});
}
void WebBrowser::ExecuteShare() {
LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteWeb() {
LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
frontend.OpenExternalWebPage(external_url,
[this](WebExitReason exit_reason, std::string last_url) {
WebBrowserExit(exit_reason, last_url);
});
}
void WebBrowser::ExecuteWifi() {
LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
void WebBrowser::ExecuteLobby() {
LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
WebBrowserExit(WebExitReason::EndButtonPressed);
}
} // namespace Service::AM::Applets

View File

@@ -1,28 +1,31 @@
// Copyright 2018 yuzu emulator team
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <optional>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/am/am.h"
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/web_types.h"
namespace Core {
class System;
}
namespace Service::AM::Applets {
namespace FileSys {
enum class ContentRecordType : u8;
}
enum class ShimKind : u32;
enum class ShopWebTarget;
enum class WebArgTLVType : u16;
namespace Service::AM::Applets {
class WebBrowser final : public Applet {
public:
WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_);
~WebBrowser() override;
@@ -33,49 +36,50 @@ public:
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();
void ExtractOfflineRomFS();
// 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();
void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");
private:
void InitializeInternal();
void ExecuteInternal();
bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const;
// Specific initializers for the types of web applets
std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type);
// Initializers for the various types of browser applets
void InitializeShop();
void InitializeLogin();
void InitializeOffline();
void InitializeShare();
void InitializeWeb();
void InitializeWifi();
void InitializeLobby();
// Specific executors for the types of web applets
// Executors for the various types of browser applets
void ExecuteShop();
void ExecuteLogin();
void ExecuteOffline();
void ExecuteShare();
void ExecuteWeb();
void ExecuteWifi();
void ExecuteLobby();
Core::Frontend::WebBrowserApplet& frontend;
const Core::Frontend::WebBrowserApplet& frontend;
// Extra frontends for specialized functions
Core::Frontend::ECommerceApplet* frontend_e_commerce;
bool complete{false};
ResultCode status{RESULT_SUCCESS};
bool complete = false;
bool unpacked = false;
ResultCode status = RESULT_SUCCESS;
ShimKind kind;
std::map<WebArgTLVType, std::vector<u8>> args;
WebAppletVersion web_applet_version;
WebExitReason web_exit_reason;
WebArgHeader web_arg_header;
WebArgInputTLVMap web_arg_input_tlv_map;
u64 title_id;
FileSys::ContentRecordType nca_type;
std::string offline_cache_dir;
std::string offline_document;
FileSys::VirtualFile offline_romfs;
std::string temporary_dir;
std::string filename;
ShopWebTarget shop_web_target;
std::map<std::string, std::string, std::less<>> shop_query;
std::optional<u64> title_id = 0;
std::optional<u128> user_id;
std::optional<bool> shop_full_display;
std::string shop_extra_parameter;
std::string external_url;
Core::System& system;
};

View File

@@ -0,0 +1,178 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <unordered_map>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace Service::AM::Applets {
enum class WebAppletVersion : u32_le {
Version0 = 0x0, // Only used by WifiWebAuthApplet
Version131072 = 0x20000, // 1.0.0 - 2.3.0
Version196608 = 0x30000, // 3.0.0 - 4.1.0
Version327680 = 0x50000, // 5.0.0 - 5.1.0
Version393216 = 0x60000, // 6.0.0 - 7.0.1
Version524288 = 0x80000, // 8.0.0+
};
enum class ShimKind : u32 {
Shop = 1,
Login = 2,
Offline = 3,
Share = 4,
Web = 5,
Wifi = 6,
Lobby = 7,
};
enum class WebExitReason : u32 {
EndButtonPressed = 0,
BackButtonPressed = 1,
ExitRequested = 2,
CallbackURL = 3,
WindowClosed = 4,
ErrorDialog = 7,
};
enum class WebArgInputTLVType : u16 {
InitialURL = 0x1,
CallbackURL = 0x3,
CallbackableURL = 0x4,
ApplicationID = 0x5,
DocumentPath = 0x6,
DocumentKind = 0x7,
SystemDataID = 0x8,
ShareStartPage = 0x9,
Whitelist = 0xA,
News = 0xB,
UserID = 0xE,
AlbumEntry0 = 0xF,
ScreenShotEnabled = 0x10,
EcClientCertEnabled = 0x11,
PlayReportEnabled = 0x13,
BootDisplayKind = 0x17,
BackgroundKind = 0x18,
FooterEnabled = 0x19,
PointerEnabled = 0x1A,
LeftStickMode = 0x1B,
KeyRepeatFrame1 = 0x1C,
KeyRepeatFrame2 = 0x1D,
BootAsMediaPlayerInverted = 0x1E,
DisplayURLKind = 0x1F,
BootAsMediaPlayer = 0x21,
ShopJumpEnabled = 0x22,
MediaAutoPlayEnabled = 0x23,
LobbyParameter = 0x24,
ApplicationAlbumEntry = 0x26,
JsExtensionEnabled = 0x27,
AdditionalCommentText = 0x28,
TouchEnabledOnContents = 0x29,
UserAgentAdditionalString = 0x2A,
AdditionalMediaData0 = 0x2B,
MediaPlayerAutoCloseEnabled = 0x2C,
PageCacheEnabled = 0x2D,
WebAudioEnabled = 0x2E,
YouTubeVideoWhitelist = 0x31,
FooterFixedKind = 0x32,
PageFadeEnabled = 0x33,
MediaCreatorApplicationRatingAge = 0x34,
BootLoadingIconEnabled = 0x35,
PageScrollIndicatorEnabled = 0x36,
MediaPlayerSpeedControlEnabled = 0x37,
AlbumEntry1 = 0x38,
AlbumEntry2 = 0x39,
AlbumEntry3 = 0x3A,
AdditionalMediaData1 = 0x3B,
AdditionalMediaData2 = 0x3C,
AdditionalMediaData3 = 0x3D,
BootFooterButton = 0x3E,
OverrideWebAudioVolume = 0x3F,
OverrideMediaAudioVolume = 0x40,
BootMode = 0x41,
WebSessionEnabled = 0x42,
MediaPlayerOfflineEnabled = 0x43,
};
enum class WebArgOutputTLVType : u16 {
ShareExitReason = 0x1,
LastURL = 0x2,
LastURLSize = 0x3,
SharePostResult = 0x4,
PostServiceName = 0x5,
PostServiceNameSize = 0x6,
PostID = 0x7,
PostIDSize = 0x8,
MediaPlayerAutoClosedByCompletion = 0x9,
};
enum class DocumentKind : u32 {
OfflineHtmlPage = 1,
ApplicationLegalInformation = 2,
SystemDataPage = 3,
};
enum class ShareStartPage : u32 {
Default,
Settings,
};
enum class BootDisplayKind : u32 {
Default,
White,
Black,
};
enum class BackgroundKind : u32 {
Default,
};
enum class LeftStickMode : u32 {
Pointer,
Cursor,
};
enum class WebSessionBootMode : u32 {
AllForeground,
AllForegroundInitiallyHidden,
};
struct WebArgHeader {
u16 total_tlv_entries{};
INSERT_PADDING_BYTES(2);
ShimKind shim_kind{};
};
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
struct WebArgInputTLV {
WebArgInputTLVType input_tlv_type{};
u16 arg_data_size{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");
struct WebArgOutputTLV {
WebArgOutputTLVType output_tlv_type{};
u16 arg_data_size{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");
struct WebCommonReturnValue {
WebExitReason exit_reason{};
INSERT_PADDING_WORDS(1);
std::array<char, 0x1000> last_url{};
u64 last_url_size{};
};
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;
} // namespace Service::AM::Applets

View File

@@ -7,6 +7,7 @@
#include <vector>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
@@ -23,11 +24,8 @@
namespace Service::AOC {
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
return FileSys::GetBaseTitleID(title_id) == base;
}
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
@@ -48,6 +46,62 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
return add_on_content;
}
class IPurchaseEventManager final : public ServiceFramework<IPurchaseEventManager> {
public:
explicit IPurchaseEventManager(Core::System& system_)
: ServiceFramework{system_, "IPurchaseEventManager"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
{3, nullptr, "PopPurchasedProductInfo"},
{4, nullptr, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
RegisterHandlers(functions);
purchased_event = Kernel::WritableEvent::CreateEventPair(
system.Kernel(), "IPurchaseEventManager:PurchasedEvent");
}
private:
void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown_1 = rp.Pop<u64>();
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetDeliveryTarget(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown_1 = rp.Pop<u64>();
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(purchased_event.readable);
}
Kernel::EventPair purchased_event;
};
AOC_U::AOC_U(Core::System& system_)
: ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} {
// clang-format off
@@ -62,8 +116,8 @@ AOC_U::AOC_U(Core::System& system_)
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
{9, nullptr, "GetAddOnContentLostErrorCode"},
{100, nullptr, "CreateEcPurchasedEventManager"},
{101, nullptr, "CreatePermanentEcPurchasedEventManager"},
{100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},
{101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},
};
// clang-format on
@@ -123,11 +177,11 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
for (u64 content_id : add_on_content) {
if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) {
if (FileSys::GetBaseTitleID(content_id) != current) {
continue;
}
out.push_back(static_cast<u32>(content_id & 0x7FF));
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
}
}
@@ -169,7 +223,7 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
const auto res = pm.GetControlMetadata();
if (res.first == nullptr) {
rb.Push(title_id + DLC_BASE_TO_AOC_ID);
rb.Push(FileSys::GetAOCBaseTitleID(title_id));
return;
}
@@ -201,6 +255,22 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(aoc_change_event.readable);
}
void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IPurchaseEventManager>(system);
}
void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IPurchaseEventManager>(system);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<AOC_U>(system)->InstallAsService(service_manager);
}

View File

@@ -27,6 +27,8 @@ private:
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
std::vector<u64> add_on_content;
Kernel::EventPair aoc_change_event;

View File

@@ -298,6 +298,31 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()
return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());
}
ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS(
u64 title_id, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
if (romfs_factory == nullptr) {
// TODO: Find a better error code for this
return RESULT_UNKNOWN;
}
return romfs_factory->OpenPatchedRomFS(title_id, type);
}
ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
program_index);
if (romfs_factory == nullptr) {
// TODO: Find a better error code for this
return RESULT_UNKNOWN;
}
return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
}
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",

View File

@@ -66,6 +66,10 @@ public:
void SetPackedUpdate(FileSys::VirtualFile update_raw);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id,
FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualDir> CreateSaveData(

View File

@@ -717,7 +717,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
{203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"},
{204, nullptr, "OpenDataFileSystemByProgramIndex"},
{205, nullptr, "OpenDataStorageByProgramIndex"},
{205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"},
{400, nullptr, "OpenDeviceOperator"},
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
{501, nullptr, "OpenGameCardDetectionEventNotifier"},
@@ -994,6 +994,32 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
}
void FSP_SRV::OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto program_index = rp.PopRaw<u8>();
LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
auto romfs = fsc.OpenPatchedRomFSWithProgramIndex(
system.CurrentProcess()->GetTitleID(), program_index, FileSys::ContentRecordType::Program);
if (romfs.Failed()) {
// TODO: Find the right error code to use here
LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_UNKNOWN);
return;
}
auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap()));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(storage));
}
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
log_mode = rp.PopEnum<LogMode>();

View File

@@ -49,6 +49,7 @@ private:
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx);
void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);

View File

@@ -116,6 +116,31 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
}
}
bool Controller_NPad::IsNpadIdValid(u32 npad_id) {
switch (npad_id) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case NPAD_UNKNOWN:
case NPAD_HANDHELD:
return true;
default:
LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id);
return false;
}
}
bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) {
return IsNpadIdValid(device_handle.npad_id) &&
device_handle.npad_type < NpadType::MaxNpadType &&
device_handle.device_index < DeviceIndex::MaxDeviceIndex;
}
Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
Controller_NPad::~Controller_NPad() {
@@ -742,6 +767,10 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size
void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
const VibrationValue& vibration_value) {
if (!IsDeviceHandleValid(vibration_device_handle)) {
return;
}
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
return;
}
@@ -798,12 +827,20 @@ void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibrat
Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
const DeviceHandle& vibration_device_handle) const {
if (!IsDeviceHandleValid(vibration_device_handle)) {
return {};
}
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
return latest_vibration_values[npad_index][device_index];
}
void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
if (!IsDeviceHandleValid(vibration_device_handle)) {
return;
}
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
InitializeVibrationDeviceAtIndex(npad_index, device_index);
@@ -824,6 +861,10 @@ void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
}
bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
if (!IsDeviceHandleValid(vibration_device_handle)) {
return false;
}
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
return vibration_devices_mounted[npad_index][device_index];
@@ -1017,7 +1058,7 @@ void Controller_NPad::ClearAllControllers() {
}
u32 Controller_NPad::GetAndResetPressState() {
return std::exchange(press_state, 0);
return press_state.exchange(0);
}
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <atomic>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/frontend/input.h"
@@ -56,12 +57,14 @@ public:
JoyconLeft = 6,
JoyconRight = 7,
Pokeball = 9,
MaxNpadType = 10,
};
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
None = 2,
MaxDeviceIndex = 3,
};
enum class GyroscopeZeroDriftMode : u32 {
@@ -213,6 +216,8 @@ public:
static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
static bool IsNpadIdValid(u32 npad_id);
static bool IsDeviceHandleValid(const DeviceHandle& device_handle);
private:
struct CommonHeader {
@@ -411,7 +416,7 @@ private:
bool IsControllerSupported(NPadControllerType controller) const;
void RequestPadStateUpdate(u32 npad_id);
u32 press_state{};
std::atomic<u32> press_state{};
NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};

View File

@@ -217,7 +217,7 @@ public:
{1, nullptr, "RefreshDebugAvailability"},
{2, nullptr, "ClearDebugResponse"},
{3, nullptr, "RegisterDebugResponse"},
{4, nullptr, "IsLargeResourceAvailable"},
{4, &NIM_ECA::IsLargeResourceAvailable, "IsLargeResourceAvailable"},
};
// clang-format on
@@ -231,6 +231,18 @@ private:
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IShopServiceAccessServer>(system);
}
void IsLargeResourceAvailable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown{rp.Pop<u64>()};
LOG_INFO(Service_NIM, "(STUBBED) called, unknown={}", unknown);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(false);
}
};
class NIM_SHP final : public ServiceFramework<NIM_SHP> {

View File

@@ -673,7 +673,7 @@ public:
explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} {
// clang-format off
static const FunctionInfo functions[] = {
{1200, nullptr, "NeedsUpdateVulnerability"},
{1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"},
{1201, nullptr, "UpdateSafeSystemVersionForDebug"},
{1202, nullptr, "GetSafeSystemVersion"},
};
@@ -681,6 +681,15 @@ public:
RegisterHandlers(functions);
}
private:
void NeedsUpdateVulnerability(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NS, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(false);
}
};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {

View File

@@ -27,29 +27,11 @@
namespace Service::NS {
enum class FontArchives : u64 {
Extension = 0x0100000000000810,
Standard = 0x0100000000000811,
Korean = 0x0100000000000812,
ChineseTraditional = 0x0100000000000813,
ChineseSimple = 0x0100000000000814,
};
struct FontRegion {
u32 offset;
u32 size;
};
constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
};
// The below data is specific to shared font data dumped from Switch on f/w 2.2
// Virtual address and offsets/sizes likely will vary by dump
[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
@@ -80,6 +62,18 @@ static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMem
offset += transformed_font.size() * sizeof(u32);
}
void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
std::vector<u32> transformed_font(input.size());
// TODO(ogniK): Figure out a better way to do this
std::transform(input.begin(), input.end(), transformed_font.begin(),
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
std::memcpy(output.data(), transformed_font.data() + 2, transformed_font.size() * sizeof(u32));
}
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,

View File

@@ -16,6 +16,25 @@ class FileSystemController;
namespace NS {
enum class FontArchives : u64 {
Extension = 0x0100000000000810,
Standard = 0x0100000000000811,
Korean = 0x0100000000000812,
ChineseTraditional = 0x0100000000000813,
ChineseSimple = 0x0100000000000814,
};
constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
};
void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
class PL_U final : public ServiceFramework<PL_U> {

View File

@@ -22,10 +22,11 @@ BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id, u64 layer_id)
BufferQueue::~BufferQueue() = default;
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
ASSERT(slot < buffer_slots);
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
free_buffers.push_back(slot);
queue.push_back({
buffers[slot] = {
.slot = slot,
.status = Buffer::Status::Free,
.igbp_buffer = igbp_buffer,
@@ -33,7 +34,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
.crop_rect = {},
.swap_interval = 0,
.multi_fence = {},
});
};
buffer_wait_event.writable->Signal();
}
@@ -44,73 +45,57 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
if (free_buffers.empty()) {
return std::nullopt;
}
auto f_itr = free_buffers.begin();
auto itr = queue.end();
auto slot = buffers.size();
while (f_itr != free_buffers.end()) {
auto slot = *f_itr;
itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
// Only consider free buffers. Buffers become free once again after they've been
// Acquired and Released by the compositor, see the NVFlinger::Compose method.
if (buffer.status != Buffer::Status::Free) {
return false;
}
if (buffer.slot != slot) {
return false;
}
// Make sure that the parameters match.
return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height;
});
if (itr != queue.end()) {
const Buffer& buffer = buffers[*f_itr];
if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width &&
buffer.igbp_buffer.height == height) {
slot = *f_itr;
free_buffers.erase(f_itr);
break;
}
++f_itr;
}
if (itr == queue.end()) {
if (slot == buffers.size()) {
return std::nullopt;
}
itr->status = Buffer::Status::Dequeued;
return {{itr->slot, &itr->multi_fence}};
buffers[slot].status = Buffer::Status::Dequeued;
return {{buffers[slot].slot, &buffers[slot].multi_fence}};
}
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
auto itr = std::find_if(queue.begin(), queue.end(),
[&](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
ASSERT(itr->status == Buffer::Status::Dequeued);
return itr->igbp_buffer;
ASSERT(slot < buffers.size());
ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
ASSERT(buffers[slot].slot == slot);
return buffers[slot].igbp_buffer;
}
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence) {
auto itr = std::find_if(queue.begin(), queue.end(),
[&](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
ASSERT(itr->status == Buffer::Status::Dequeued);
itr->status = Buffer::Status::Queued;
itr->transform = transform;
itr->crop_rect = crop_rect;
itr->swap_interval = swap_interval;
itr->multi_fence = multi_fence;
ASSERT(slot < buffers.size());
ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
ASSERT(buffers[slot].slot == slot);
buffers[slot].status = Buffer::Status::Queued;
buffers[slot].transform = transform;
buffers[slot].crop_rect = crop_rect;
buffers[slot].swap_interval = swap_interval;
buffers[slot].multi_fence = multi_fence;
queue_sequence.push_back(slot);
}
void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) {
const auto itr = std::find_if(queue.begin(), queue.end(),
[slot](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
ASSERT(itr->status != Buffer::Status::Free);
itr->status = Buffer::Status::Free;
itr->multi_fence = multi_fence;
itr->swap_interval = 0;
ASSERT(slot < buffers.size());
ASSERT(buffers[slot].status != Buffer::Status::Free);
ASSERT(buffers[slot].slot == slot);
buffers[slot].status = Buffer::Status::Free;
buffers[slot].multi_fence = multi_fence;
buffers[slot].swap_interval = 0;
free_buffers.push_back(slot);
@@ -118,38 +103,39 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult
}
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
auto itr = queue.end();
std::size_t buffer_slot = buffers.size();
// Iterate to find a queued buffer matching the requested slot.
while (itr == queue.end() && !queue_sequence.empty()) {
const u32 slot = queue_sequence.front();
itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
});
while (buffer_slot == buffers.size() && !queue_sequence.empty()) {
const auto slot = static_cast<std::size_t>(queue_sequence.front());
ASSERT(slot < buffers.size());
if (buffers[slot].status == Buffer::Status::Queued) {
ASSERT(buffers[slot].slot == slot);
buffer_slot = slot;
}
queue_sequence.pop_front();
}
if (itr == queue.end()) {
if (buffer_slot == buffers.size()) {
return std::nullopt;
}
itr->status = Buffer::Status::Acquired;
return *itr;
buffers[buffer_slot].status = Buffer::Status::Acquired;
return {{buffers[buffer_slot]}};
}
void BufferQueue::ReleaseBuffer(u32 slot) {
auto itr = std::find_if(queue.begin(), queue.end(),
[&](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
ASSERT(itr->status == Buffer::Status::Acquired);
itr->status = Buffer::Status::Free;
ASSERT(slot < buffers.size());
ASSERT(buffers[slot].status == Buffer::Status::Acquired);
ASSERT(buffers[slot].slot == slot);
buffers[slot].status = Buffer::Status::Free;
free_buffers.push_back(slot);
buffer_wait_event.writable->Signal();
}
void BufferQueue::Disconnect() {
queue.clear();
buffers.fill({});
queue_sequence.clear();
id = 1;
layer_id = 1;
buffer_wait_event.writable->Signal();
}
u32 BufferQueue::Query(QueryType type) {

View File

@@ -21,6 +21,7 @@ class KernelCore;
namespace Service::NVFlinger {
constexpr u32 buffer_slots = 0x40;
struct IGBPBuffer {
u32_le magic;
u32_le width;
@@ -114,7 +115,7 @@ private:
u64 layer_id;
std::list<u32> free_buffers;
std::vector<Buffer> queue;
std::array<Buffer, buffer_slots> buffers;
std::list<u32> queue_sequence;
Kernel::EventPair buffer_wait_event;
};

View File

@@ -48,7 +48,7 @@ public:
class PCIe final : public ServiceFramework<PCIe> {
public:
explicit PCIe(Core::System& system_) : ServiceFramework{system, "pcie"} {
explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RegisterClassDriver"},

View File

@@ -10,8 +10,8 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/time/time_sharedmemory.h"

View File

@@ -282,18 +282,24 @@ public:
void DeserializeData() override {
[[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
buffer = Read<NVFlinger::IGBPBuffer>();
if (data.contains_object != 0) {
buffer_container = Read<BufferContainer>();
}
}
struct Data {
u32_le slot;
INSERT_PADDING_WORDS(1);
u32_le graphic_buffer_length;
INSERT_PADDING_WORDS(1);
u32_le contains_object;
};
Data data;
NVFlinger::IGBPBuffer buffer;
struct BufferContainer {
u32_le graphic_buffer_length;
INSERT_PADDING_WORDS(1);
NVFlinger::IGBPBuffer buffer{};
};
Data data{};
BufferContainer buffer_container{};
};
class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
@@ -547,7 +553,7 @@ private:
case TransactionId::SetPreallocatedBuffer: {
IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()};
buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer);
buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer);
IGBPSetPreallocatedBufferResponseParcel response{};
ctx.WriteBuffer(response.Serialize());

View File

@@ -32,7 +32,7 @@ public:
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);

View File

@@ -21,7 +21,7 @@ public:
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);

Some files were not shown because too many files have changed in this diff Show More