Compare commits

..

116 Commits

Author SHA1 Message Date
Markus Wick
993dbe49fc core/arm: Drop ChangeProcessorID.
This code was used to switch the CPU ID on thread switches.
However since "hle: kernel: multicore: Replace n-JITs impl. with 4 JITs.", the CPU ID is not a constant.
This has been dead code since this rewrite, and dropped in dynarmic as well. So there is no need to keep it.
2021-05-26 19:48:24 +02:00
Morph
02169406be Merge pull request #6331 from lioncash/gesture
hid/gesture: Simplify point related code
2021-05-26 09:19:00 -04:00
bunnei
3394f24728 Merge pull request #6339 from Morph1984/swkbd-queuedconnection
applets/swkbd: Make use of QueuedConnection in returnPressed signal
2021-05-25 18:23:08 -07:00
Morph
065867e2c2 common: fs: Rework the Common Filesystem interface to make use of std::filesystem (#6270)
* common: fs: fs_types: Create filesystem types

Contains various filesystem types used by the Common::FS library

* common: fs: fs_util: Add std::string to std::u8string conversion utility

* common: fs: path_util: Add utlity functions for paths

Contains various utility functions for getting or manipulating filesystem paths used by the Common::FS library

* common: fs: file: Rewrite the IOFile implementation

* common: fs: Reimplement Common::FS library using std::filesystem

* common: fs: fs_paths: Add fs_paths to replace common_paths

* common: fs: path_util: Add the rest of the path functions

* common: Remove the previous Common::FS implementation

* general: Remove unused fs includes

* string_util: Remove unused function and include

* nvidia_flags: Migrate to the new Common::FS library

* settings: Migrate to the new Common::FS library

* logging: backend: Migrate to the new Common::FS library

* core: Migrate to the new Common::FS library

* perf_stats: Migrate to the new Common::FS library

* reporter: Migrate to the new Common::FS library

* telemetry_session: Migrate to the new Common::FS library

* key_manager: Migrate to the new Common::FS library

* bis_factory: Migrate to the new Common::FS library

* registered_cache: Migrate to the new Common::FS library

* xts_archive: Migrate to the new Common::FS library

* service: acc: Migrate to the new Common::FS library

* applets/profile: Migrate to the new Common::FS library

* applets/web: Migrate to the new Common::FS library

* service: filesystem: Migrate to the new Common::FS library

* loader: Migrate to the new Common::FS library

* gl_shader_disk_cache: Migrate to the new Common::FS library

* nsight_aftermath_tracker: Migrate to the new Common::FS library

* vulkan_library: Migrate to the new Common::FS library

* configure_debug: Migrate to the new Common::FS library

* game_list_worker: Migrate to the new Common::FS library

* config: Migrate to the new Common::FS library

* configure_filesystem: Migrate to the new Common::FS library

* configure_per_game_addons: Migrate to the new Common::FS library

* configure_profile_manager: Migrate to the new Common::FS library

* configure_ui: Migrate to the new Common::FS library

* input_profiles: Migrate to the new Common::FS library

* yuzu_cmd: config: Migrate to the new Common::FS library

* yuzu_cmd: Migrate to the new Common::FS library

* vfs_real: Migrate to the new Common::FS library

* vfs: Migrate to the new Common::FS library

* vfs_libzip: Migrate to the new Common::FS library

* service: bcat: Migrate to the new Common::FS library

* yuzu: main: Migrate to the new Common::FS library

* vfs_real: Delete the contents of an existing file in CreateFile

Current usages of CreateFile expect to delete the contents of an existing file, retain this behavior for now.

* input_profiles: Don't iterate the input profile dir if it does not exist

Silences an error produced in the log if the directory does not exist.

* game_list_worker: Skip parsing file if the returned VfsFile is nullptr

Prevents crashes in GetLoader when the virtual file is nullptr

* common: fs: Validate paths for path length

* service: filesystem: Open the mod load directory as read only
2021-05-25 19:32:56 -04:00
bunnei
08a5cf0b5b Merge pull request #6349 from german77/suppress_config_warning
settings: Suppress duplicate label name warning
2021-05-25 15:51:51 -07:00
bunnei
8094743f63 Merge pull request #6348 from lioncash/zstd
CMakeLists: Update zstd to 1.5.0
2021-05-25 14:41:40 -07:00
bunnei
4bf53eb935 Merge pull request #6353 from german77/handheld_docked
settings: Forbid docked mode on handheld
2021-05-25 13:02:20 -07:00
Mai M
b6b73d9a5a Merge pull request #6358 from Morph1984/k_map_region
kernel: process_capability: Add MapRegion capability
2021-05-25 03:26:22 -04:00
Morph
ec28d3c439 kernel: process_capability: Add MapRegion capability
- Used by nx-hbloader
2021-05-25 01:44:46 -04:00
bunnei
d84a93c987 Merge pull request #6357 from lioncash/compression
common/compression: Make use of std::span
2021-05-24 18:50:26 -07:00
bunnei
aee3b57c44 Merge pull request #6312 from german77/analogMapping
input_common: Rewrite sdl analog mapping and fix controller disconnection crash
2021-05-24 12:02:37 -07:00
Lioncash
49bfd0c461 zstd_compression: Make use of std::span
Allows for the incoming data stream to be non-allocating.
2021-05-24 15:01:04 -04:00
Lioncash
00213377b1 lz4_compression: Make use of std::span
Allows making the incoming data stream non-allocating.
2021-05-24 15:00:59 -04:00
german77
7f445a59fa settings: Forbid docked mode on handheld 2021-05-23 20:34:46 -05:00
bunnei
3ead4a3494 Merge pull request #6347 from bunnei/ipc-improvements-next-2
Various improvements to IPC and session management (Part 2)
2021-05-23 16:28:40 -07:00
german77
bb22d6d8f7 settings: Suppress duplicate label name warning 2021-05-22 18:32:35 -05:00
Morph
ecacb002be applets/swkbd: Make use of QueuedConnection in returnPressed signal
Some users have reported rare crashes when pressing the Enter key on the keyboard to confirm input in the normal software keyboard, particularly in Super Smash Bros. Ultimate while entering the name of a ruleset or controller layout.

It is suspected that the QLineEdit::returnPressed signal is causing a race condition as confirming input through other means does not produce the crash. Since Qt::QueuedConnection posts an event to the event queue of the callee's thread instead of executing it directly on the caller's thread, this eliminates any potential race conditions from occurring in this scenario.
2021-05-22 03:28:54 -04:00
Lioncash
922d5187c4 CMakeLists: Update zstd to 1.5.0
zstd 1.5.0 brings numerous performance improvements to the library, as
can be seen here: https://github.com/facebook/zstd/releases/tag/v1.5.0
2021-05-21 13:24:11 -04:00
bunnei
5068279f23 Merge pull request #6248 from A-w-x/intelmesa
gl_device: Intel: Disable texture view formats workaround on mesa
2021-05-20 23:47:14 -07:00
bunnei
136e8e829f Merge pull request #6333 from Morph1984/swkbd-confirm-text
applets/swkbd: Send the correct text string on TextCheck::Confirm
2021-05-20 22:42:54 -07:00
bunnei
6418a42884 hle: kernel: service_thread: Take reference to KServerSession on service request. 2021-05-20 22:39:44 -07:00
bunnei
e33ffdc555 hle: kernel: k_port: Use AcceptSession to ensure SessionList state is correct.
- Fixes a use-after-free, work-around until we fixup session/port management.
2021-05-20 21:41:52 -07:00
bunnei
b4fc2e52a2 hle: kernel: Use host memory allocations for KSlabMemory.
- There are some issues with the current workaround, we will just use host memory until we have a complete kernel memory implementation.
2021-05-20 21:41:52 -07:00
bunnei
7331bb9d8d Revert "WORKAROUND: Do not use slab heap while we track down issues with resource management."
This reverts commit f2c26443f8.
2021-05-20 21:41:52 -07:00
bunnei
f4fe71c1c9 hle: kernel: hle_ipc: Simplify incoming/outgoing move/copy/domain objects. 2021-05-20 21:41:52 -07:00
bunnei
342170fcd3 common: tree: Avoid a crash on nullptr dereference. 2021-05-20 21:41:51 -07:00
bunnei
7361eac10f hle: kernel: Implement CloneCurrentObject and improve session management. 2021-05-20 21:41:49 -07:00
bunnei
c40e7593f5 Revert "WORKAROUND: temp. disable session resource limits while we work out issues"
This reverts commit fc086f93b2.
2021-05-20 21:40:30 -07:00
bunnei
ea4e4b05e4 Merge pull request #6320 from Morph1984/get-pid
hle_ipc: Add a getter for PID
2021-05-20 21:40:03 -07:00
bunnei
7626ca3343 Merge pull request #6321 from lat9nq/per-game-cpu
configuration: Add CPU tab to game properties and slight per-game settings rework
2021-05-20 20:10:56 -07:00
lat9nq
5153d5387a configure_cpu: Simplify UpdateGroup
Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>
2021-05-20 01:11:56 -04:00
bunnei
b5d21cc1b1 Merge pull request #6297 from lioncash/common-conv
parent_of_member: Make sign conversion explicit in OffsetOfImpl()
2021-05-19 18:43:47 -07:00
bunnei
41b1f8d616 Merge pull request #6310 from german77/nanMotion
input_common: Sanitize motion data
2021-05-19 15:47:48 -07:00
lat9nq
12ef74456c configuration_shared: Drop unused function and template another
Drops an unused variant of ApplyPerGameSetting, and turns the QComboBox
variants of SetPerGameSetting into a template.

Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>
2021-05-19 16:00:48 -04:00
Morph
5396593b55 applets/swkbd: Send the correct text string on TextCheck::Confirm
Previously the text string for the inline software keyboard was being sent instead of the normal software keyboard, leading to empty text being sent all the time.
2021-05-19 00:26:32 -04:00
bunnei
7d86a6ff02 Merge pull request #6317 from ameerj/fps-fix
perf_stats: Rework FPS counter to be more accurate
2021-05-18 19:56:29 -07:00
bunnei
61f293e5c9 Merge pull request #6337 from Morph1984/transfer-mem-size
KTransferMemory: Return size instead of size * PageSize in GetSize()
2021-05-18 11:00:34 -07:00
Morph
7f78b17e20 KTransferMemory: Return size instead of size * PageSize in GetSize()
size is already the size in bytes. We do not need to multiply it by the page size
2021-05-18 13:14:28 -04:00
Lioncash
44556dc21a hid/gesture: Factor out last gesture retrieval into its own function
Deduplicates a commonly repeated expression.
2021-05-18 03:59:44 -04:00
Lioncash
a9d8e24e47 hid/gesture: Ensure all ID arrays are initialized
Makes for deterministic initial state.
2021-05-18 03:39:21 -04:00
Lioncash
74f30c0223 hid/gesture: Make Point a template
We can now use this in a generic context to reuse it with the finger
position.
2021-05-18 03:39:18 -04:00
Lioncash
20699e90fa hid/gesture: Replace x,y members of GestureState with a Point
Simplifies assignments.
2021-05-18 03:32:42 -04:00
Lioncash
2f1ef3910b hid/gesture: Add default comparators to Point
Simplifies some comparisons.
2021-05-18 03:32:42 -04:00
Lioncash
60831eabd9 hid/gesture: Rename Points to Point
This only represents a single point
2021-05-18 03:32:38 -04:00
bunnei
93bc59b62d Merge pull request #6322 from ameerj/fast-null-buffer
buffer_cache: Ensure null buffers cannot take the fast uniform bind path
2021-05-17 15:45:36 -07:00
lat9nq
339dc4f806 general: Demote custom_rtc to regular setting 2021-05-17 15:54:30 -04:00
Mat M
b462618ed7 Merge pull request #6328 from Morph1984/enforce-c4715
CMakeLists: Enforce C4715 on MSVC
2021-05-17 13:20:58 -04:00
bunnei
e8269fe3bc Merge pull request #6327 from Morph1984/duplicate_labels
configure_debug: FIx duplicate labels
2021-05-17 06:18:10 -07:00
Morph
d001687ca6 CMakeLists: Enforce C4715 on MSVC
This is similar to -Werror=return-type
2021-05-17 03:48:58 -04:00
Morph
cd6dcef5aa configure_debug: FIx duplicate labels
Duplicate labels were unintentionally introduced due to copy-paste. This silences the compilation warning produced by the presence of these duplicates.
2021-05-16 23:32:51 -04:00
bunnei
0a74d8490a Merge pull request #6326 from Morph1984/fix-version
yuzu/main: Fix version info in logging and about dialog
2021-05-16 20:09:54 -07:00
Morph
af69b48390 yuzu/main: Fix version info in logging and about dialog 2021-05-16 22:17:17 -04:00
bunnei
440eb840ea Merge pull request #6319 from Morph1984/no-install-base
main: Prevent installing base titles into NAND
2021-05-16 16:33:33 -07:00
Ameer J
bfe8816f7c Merge pull request #6324 from lat9nq/appimage-freeze
ci: linux: Freeze AppImage binaries
2021-05-16 14:43:02 -04:00
ameerj
acf22336ec buffer_cache: Ensure null buffers cannot take the fast uniform bind path
Fixes a crash in New Pokemon Snap
2021-05-16 07:43:40 -04:00
lat9nq
9ec26a805a ci: linux: Freeze AppImage binaries
A regression was introduced on May 13 by linuxdeploy that causes file
open dialogs to crash yuzu in the AppImage (likely this commit
1e28ee38fa174279defe70cdaadf2a552c80258c from
linuxdeploy/linuxdeploy-desktopfile). Instead of downloading the latest
version from each of the repos we use to build the AppImage, just
download the ones hosted at yuzu-emu/ext-linux-bin, which are the same
binaries we have been using, but verified to be working and won't update
on us beyond our control.

This can eventually be moved into the container itself to remove the
need to download them at build time.
2021-05-16 05:07:49 -04:00
bunnei
d5131805ce Merge pull request #6284 from ameerj/shantae-fix
nvflinger: Create layers when they are queried but not found
2021-05-16 01:45:14 -07:00
bunnei
ad6e20cfde Merge pull request #6296 from lioncash/shadow-error
core: Make variable shadowing a compile-time error
2021-05-16 01:35:46 -07:00
bunnei
e8d2de1f99 Merge pull request #6307 from Morph1984/fix-response-push-size
nifm, ssl: Fix incorrect response sizes
2021-05-16 01:32:04 -07:00
Morph
a170aa16b6 main: Prevent installing base titles into NAND
Many users have been installing their base titles into NAND instead of adding them into the games list. This prevents users from installing any base titles and warns the user about the action.
2021-05-16 04:13:57 -04:00
Morph
049769a0c9 hle_ipc: unsigned -> u32
This is more concise and consistent with the rest of the codebase.
2021-05-16 04:11:00 -04:00
Morph
81a5ecdb18 hle_ipc: Add a getter for PID 2021-05-16 04:10:42 -04:00
Morph
9edfd88a8a Merge pull request #6293 from v1993/master
On Linux, build SDL2 from externals with HIDAPI support
2021-05-16 04:05:42 -04:00
Lioncash
9a07ed53eb core: Make variable shadowing a compile-time error
Now that we have most of core free of shadowing, we can enable the
warning as an error to catch anything that may be remaining and also
eliminate this class of logic bug entirely.
2021-05-16 03:43:16 -04:00
bunnei
06c410ee88 Merge pull request #6316 from ameerj/title-fix
main: Add running title's version to window name on EA/mainline
2021-05-15 22:40:35 -07:00
lat9nq
ab2677f0a1 configuration: Add CPU tab to game properties
Allows setting CPU accuracy to Accurate or Unsafe per-game, as well as
the accuracy options for Unsafe. Debug is not allowed here as a per-game
CPU accuracy.
2021-05-16 01:31:42 -04:00
bunnei
5a2b15bf75 Merge pull request #6299 from bunnei/ipc-improvements
Various improvements to IPC and session management
2021-05-15 22:30:21 -07:00
bunnei
a1138028a8 Merge pull request #6289 from ameerj/oob-blit
texture_cache: Handle out of bound texture blits
2021-05-15 21:32:37 -07:00
Morph
faaea00069 nifm, ssl: Fix incorrect response sizes 2021-05-16 00:20:48 -04:00
Morph
6c78c2ae38 Merge pull request #6244 from german77/sdlmotion
input_common: Implement SDL motion
2021-05-15 23:20:18 -04:00
lat9nq
4aac1ae4b1 configuration: Simplify applying per-game settings
Originally, every time we add a per-game setting, we'd have to guard for
it when setting it on the global config, and use a specific function to
do it for the per-game config.

This moves the global check into the ApplyPerGameSetting function so
that we can use it for changing both the global and per-game states.
Less work for the programmer.
2021-05-15 22:59:38 -04:00
lat9nq
59236b7d0f configuration_shared: Add some comments
Monke brain can't remember what all of these does a year later.
2021-05-15 22:07:20 -04:00
lat9nq
e169fdad4f general: Make CPU accuracy and related a Settings::Setting
Required to make CPU accuracy and unsafe settings available to use as a
per-game setting.
2021-05-15 20:46:48 -04:00
ameerj
5bef54618a perf_stats: Rework FPS counter to be more accurate
The FPS counter was based on metrics in the nvdisp swapbuffers call. This metric would be accurate if the gpu thread/renderer were synchronous with the nvdisp service, but that's no longer the case.

This commit moves the frame counting responsibility onto the concrete renderers after their frame draw calls. Resulting in more meaningful metrics.
The displayed FPS is now made up of the average framerate between the previous and most recent update, in order to avoid distracting FPS counter updates when framerate is oscillating between close values.

The status bar update frequency was also changed from 2 seconds to 500ms.
2021-05-15 20:34:20 -04:00
ameerj
a3e68dce56 main: Add title's version to window name on EA/mainline
Fixes the missing title version number on EA/mainline builds which override the title bar string.
2021-05-15 16:55:30 -04:00
german77
f20f4587e6 input_common: Implement SDL motion 2021-05-15 08:56:58 -05:00
german77
edd498f6e0 input_common: Fix crash when controller disconnects 2021-05-15 08:27:19 -05:00
german77
85eeae7aad input_common: Rewrite sdl analog mapping 2021-05-14 21:17:08 -05:00
Ameer J
904584e4ba Merge pull request #6300 from Morph1984/mbedtls
externals: Update mbedtls to 8c88150ca
2021-05-13 23:11:32 -04:00
german77
fd7c273fab input_common: Sanitize motion data 2021-05-13 13:41:32 -05:00
Morph
0949e38263 Merge pull request #6306 from lat9nq/ffmpeg-untagged
externals: Checkout 79e8d17024 for FFmpeg
2021-05-13 04:59:48 -04:00
lat9nq
0ecb6c6647 externals: Checkout 79e8d17024 for FFmpeg
6b6b9e593d does not exist on FFmpeg master, and tag n4.3.1 requires
manually fetching all of FFmpeg's tags. `git` reports that the commit
does not exist initially and can be confusing as a result. Instead,
checkout the immediately previous commit from n4.3.1 on their master
branch.
2021-05-13 04:53:59 -04:00
bunnei
e12ee020e7 Merge pull request #6301 from Morph1984/ssl-ImportClientPki
ssl: Stub Import(Client/Server)Pki
2021-05-12 22:11:19 -07:00
Morph
c8707628f6 Merge pull request #6298 from Kewlan/toggled-show-add-on-refresh
configure_ui: Call RequestGameListUpdate when toggling "Show Add-Ons Column"
2021-05-12 21:06:04 -04:00
Morph
271f2e2d78 ssl: Stub Import(Client/Server)Pki
- Used in JUMP FORCE Deluxe Edition
2021-05-12 21:04:13 -04:00
Morph
5a042bdaa1 Merge pull request #6267 from german77/gestureRewrite
hid: Improve hardware accuracy of gestures
2021-05-12 09:17:23 -04:00
bunnei
eee302b9b9 common: tree: Avoid a nullptr dereference. 2021-05-11 15:40:20 -07:00
bunnei
12d569e483 hle: kernel: hle_ipc: Fix outgoing IPC response size calculation. 2021-05-11 12:27:43 -07:00
bunnei
fc086f93b2 WORKAROUND: temp. disable session resource limits while we work out issues 2021-05-11 10:51:39 -07:00
bunnei
f2c26443f8 WORKAROUND: Do not use slab heap while we track down issues with resource management. 2021-05-11 10:27:18 -07:00
bunnei
b9f543b29f audren 2021-05-11 10:24:53 -07:00
Morph
02547439b1 externals: Update mbedtls to 8c88150ca 2021-05-11 00:43:04 -04:00
bunnei
343d92a092 core: hle: ipc_helpers: Fix cast on raw_data_size calculation. 2021-05-10 20:34:38 -07:00
bunnei
2c1e119c4a hle: service: sm: Add TIPC support.
- Fixes our error checking of names as well.
2021-05-10 20:34:38 -07:00
bunnei
913971417e hle: kernel: hle_ipc: Improve IPC code and add initial support for TIPC.
- Fixes our move handles implementation to actually move objects.
- Simplifies the traditional IPC path.
2021-05-10 20:34:38 -07:00
bunnei
49c4c329f6 hle: service: sm: GetService: Reserve session resource when we create a KSession. 2021-05-10 20:34:38 -07:00
bunnei
21671d05a3 hle: service: Add support for dispatching TIPC requests. 2021-05-10 20:34:38 -07:00
bunnei
da25a59866 hle: service: Implement IPC::CommandType::Close.
- This was not actually closing sessions before.
2021-05-10 20:34:38 -07:00
bunnei
41928dfdda hle: service: sm: Use RegisterNamedService to register the service. 2021-05-10 20:34:38 -07:00
bunnei
934b2d8842 hle: service: sm: Improve Initialize implementation. 2021-05-10 20:34:38 -07:00
bunnei
f54ea749a4 hle: kernel: svc: Update ConnectToNamedPort to use new CreateNamedServicePort interface. 2021-05-10 20:34:38 -07:00
bunnei
c6de9657be hle: kernel: Implement named service ports using service interface factory.
- This allows us to create a new interface each time ConnectToNamedPort is called, removing the assumption that these are static.
2021-05-10 20:34:38 -07:00
bunnei
44c763f9c6 hle: kernel: KSession: Improve implementation of CloneCurrentObject. 2021-05-10 20:33:53 -07:00
bunnei
cfed6936f3 hle: service: sm: Increase point buffer size. 2021-05-10 15:43:42 -07:00
bunnei
9f44a44f2f hle: ipc_helpers: Reserve session resource when we create a KSession. 2021-05-10 15:42:46 -07:00
bunnei
75f23ad494 hle: kernel: KClientPort: Cleanup comment format. 2021-05-10 15:41:46 -07:00
bunnei
7a06037c5f hle: ipc: Add declarations for TIPC. 2021-05-10 15:05:10 -07:00
bunnei
ed25191ee6 hle: kernel: Further cleanup and add TIPC helpers. 2021-05-10 15:05:10 -07:00
bunnei
d08bd3e062 hle: ipc_helpers: Update IPC response generation for TIPC. 2021-05-10 15:05:10 -07:00
Lioncash
0aff3ba2ff parent_of_member: Make sign conversion explicit in OffsetOfImpl()
Previously these conversions were implicit and causing quite a few
warnings on clang.
2021-05-10 08:07:33 -04:00
v1993
fa647cc0b9 Only build SDL2 subsystems that we use
While at it, use better way to enable HIDAPI.
2021-05-10 01:11:54 +03:00
v1993
648bef235e On Linux, build SDL2 from externals with HIDAPI support 2021-05-09 18:12:58 +03:00
ameerj
3671fd0a97 texture_cache: Handle out of bound texture blits
Some games interleave a texture blit using regions which are out-of-bounds. This addresses the interleaving to avoid oob reads from the src texture.
2021-05-07 22:14:21 -04:00
ameerj
da62e92784 nvflinger: Create layers when they are queried but not found
Fixes Shantae softlock on boot.
2021-05-06 11:20:52 -04:00
german77
8c30ed6d09 hid: Improve hardware accuracy of gestures 2021-05-05 10:13:09 -05:00
A-w-x
6a2084a204 gl_device: Intel: Disable texture view formats workaround on mesa 2021-04-26 18:14:10 +02:00
250 changed files with 5874 additions and 3575 deletions

View File

@@ -30,10 +30,10 @@ make install DESTDIR=AppDir
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
# Download tools needed to build an AppImage
wget -nc https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget -nc https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64
wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/linuxdeploy-x86_64.AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/linuxdeploy-plugin-qt-x86_64.AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/AppRun-patched-x86_64
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/exec-x86_64.so
# Set executable bit
chmod 755 \
AppRun-patched-x86_64 \

View File

@@ -21,7 +21,7 @@ cp build/bin/yuzu "$DIR_NAME"
# Build an AppImage
cd build
wget -nc https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
wget -nc https://github.com/yuzu-emu/ext-linux-bin/raw/main/appimage/appimagetool-x86_64.AppImage
chmod 755 appimagetool-x86_64.AppImage
if [ "${RELEASE_NAME}" = "mainline" ]; then

View File

@@ -12,6 +12,8 @@ project(yuzu)
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF)
# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
CMAKE_DEPENDENT_OPTION(YUZU_ALLOW_SYSTEM_SDL2 "Try using system SDL2 before fallling back to one from externals" NOT UNIX "ENABLE_SDL2" OFF)
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
@@ -172,7 +174,7 @@ macro(yuzu_find_packages)
"lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11"
"zstd 1.4 zstd/1.4.8"
"zstd 1.5 zstd/1.5.0"
# can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068
#"opus 1.3 opus/1.3.1"
)
@@ -292,20 +294,24 @@ if (ENABLE_SDL2)
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}")
target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}")
else()
find_package(SDL2 2.0.15 QUIET)
if (YUZU_ALLOW_SYSTEM_SDL2)
find_package(SDL2 2.0.15 QUIET)
if (SDL2_FOUND)
# Some installations don't set SDL2_LIBRARIES
if("${SDL2_LIBRARIES}" STREQUAL "")
message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
set(SDL2_LIBRARIES "SDL2::SDL2")
if (SDL2_FOUND)
# Some installations don't set SDL2_LIBRARIES
if("${SDL2_LIBRARIES}" STREQUAL "")
message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
set(SDL2_LIBRARIES "SDL2::SDL2")
endif()
include_directories(SYSTEM ${SDL2_INCLUDE_DIRS})
add_library(SDL2 INTERFACE)
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
else()
message(STATUS "SDL2 2.0.15 or newer not found, falling back to externals.")
endif()
include_directories(SYSTEM ${SDL2_INCLUDE_DIRS})
add_library(SDL2 INTERFACE)
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
else()
message(STATUS "SDL2 2.0.15 or newer not found, falling back to externals.")
message(STATUS "Using SDL2 from externals.")
endif()
endif()
endif()

View File

@@ -47,8 +47,20 @@ target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
# SDL2
if (NOT SDL2_FOUND AND ENABLE_SDL2)
# Yuzu itself needs: Events Joystick Haptic Sensor Timers
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
set(SDL_UNUSED_SUBSYSTEMS
Atomic Audio Render Power Threads
File CPUinfo Filesystem Locale)
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
string(TOUPPER ${_SUB} _OPT)
option(SDL_${_OPT} "" OFF)
endforeach()
set(SDL_STATIC ON)
set(SDL_SHARED OFF)
option(HIDAPI "" ON)
add_subdirectory(SDL EXCLUDE_FROM_ALL)
add_library(SDL2 ALIAS SDL2-static)
endif()

View File

@@ -54,6 +54,7 @@ if (MSVC)
/we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
/we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
/we4555 # Expression has no effect; expected expression with side-effect
/we4715 # 'function': not all control paths return a value
/we4834 # Discarding return value of function with 'nodiscard' attribute
/we5038 # data member 'member1' will be initialized after data member 'member2'
)

View File

@@ -109,7 +109,6 @@ add_library(common STATIC
cityhash.cpp
cityhash.h
common_funcs.h
common_paths.h
common_sizes.h
common_types.h
concepts.h
@@ -118,8 +117,16 @@ add_library(common STATIC
dynamic_library.h
fiber.cpp
fiber.h
file_util.cpp
file_util.h
fs/file.cpp
fs/file.h
fs/fs.cpp
fs/fs.h
fs/fs_paths.h
fs/fs_types.h
fs/fs_util.cpp
fs/fs_util.h
fs/path_util.cpp
fs/path_util.h
hash.h
hex_util.cpp
hex_util.h

View File

@@ -1,52 +0,0 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
// Directory separators, do we need this?
#define DIR_SEP "/"
#define DIR_SEP_CHR '/'
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
// The user data dir
#define ROOT_DIR "."
#define USERDATA_DIR "user"
#ifdef USER_DIR
#define EMU_DATA_DIR USER_DIR
#else
#define EMU_DATA_DIR "yuzu"
#endif
// Dirs in both User and Sys
#define EUR_DIR "EUR"
#define USA_DIR "USA"
#define JAP_DIR "JAP"
// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir)
#define CONFIG_DIR "config"
#define CACHE_DIR "cache"
#define SDMC_DIR "sdmc"
#define NAND_DIR "nand"
#define SYSDATA_DIR "sysdata"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define DUMP_DIR "dump"
#define SCREENSHOTS_DIR "screenshots"
#define SHADER_DIR "shader"
#define LOG_DIR "log"
// Filenames
// Files in the directory returned by GetUserPath(UserPath::ConfigDir)
#define EMU_CONFIG "emu.ini"
#define DEBUGGER_CONFIG "debugger.ini"
#define LOGGER_CONFIG "logger.ini"
// Files in the directory returned by GetUserPath(UserPath::LogDir)
#define LOG_FILE "yuzu_log.txt"
// Sys files
#define SHARED_FONT "shared_font.bin"
#define AES_KEYS "aes_keys.txt"

File diff suppressed because it is too large Load Diff

View File

@@ -1,298 +0,0 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstdio>
#include <fstream>
#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "common/common_types.h"
#ifdef _MSC_VER
#include "common/string_util.h"
#endif
namespace Common::FS {
// User paths for GetUserPath
enum class UserPath {
CacheDir,
ConfigDir,
KeysDir,
LogDir,
NANDDir,
RootDir,
SDMCDir,
LoadDir,
DumpDir,
ScreenshotsDir,
ShaderDir,
SysDataDir,
UserDir,
};
// 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 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::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::string& filename);
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const std::string& fullPath);
// Deletes a given filename, return true on success
// Doesn't supports deleting a directory
bool Delete(const std::string& filename);
// Deletes a directory filename, returns true on success
bool DeleteDir(const std::string& filename);
// renames file srcFilename to destFilename, returns true on success
bool Rename(const std::string& srcFilename, const std::string& destFilename);
// 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);
/**
* @param num_entries_out to be assigned by the callable with the number of iterated directory
* entries, never null
* @param directory the path to the enclosing directory
* @param virtual_name the entry name, without any preceding directory info
* @return whether handling the entry succeeded
*/
using DirectoryEntryCallable = std::function<bool(
u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
/**
* Scans a directory, calling the callback for each file/directory contained within.
* If the callback returns failure, scanning halts and this function returns failure as well
* @param num_entries_out assigned by the function with the number of iterated directory entries,
* can be null
* @param directory the directory to scan
* @param callback The callback which will be called for each entry
* @return whether scanning the directory succeeded
*/
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
DirectoryEntryCallable callback);
/**
* 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::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_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).
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
[[nodiscard]] std::string GetHactoolConfigurationPath();
[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false);
// Returns the path to where the sys file are
[[nodiscard]] std::string GetSysDirectory();
#ifdef __APPLE__
[[nodiscard]] std::string GetBundleDirectory();
#endif
#ifdef _WIN32
[[nodiscard]] const std::string& GetExeDirectory();
[[nodiscard]] std::string AppDataRoamingDirectory();
#endif
std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
/**
* Splits the filename into 8.3 format
* Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
* @param filename The normal filename to use
* @param short_name A 9-char array in which the short name will be written
* @param extension A 4-char array in which the extension will be written
*/
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension);
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
// Gets all of the text up to the last '/' or '\' in the path.
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
// Gets all of the text after the first '/' or '\' in the path.
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
// Gets the filename of the path
[[nodiscard]] std::string_view GetFilename(std::string_view path);
// Gets the extension of the filename
[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
// Removes the final '/' or '\' if one exists
[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
// Creates a new vector containing indices [first, last) from the original.
template <typename T>
[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first,
std::size_t last) {
if (first >= last) {
return {};
}
last = std::min<std::size_t>(last, vector.size());
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
}
enum class DirectorySeparator {
ForwardSlash,
BackwardSlash,
PlatformDefault,
};
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
[[nodiscard]] std::string SanitizePath(
std::string_view path,
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
// To deal with Windows being dumb at Unicode
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
fstream.open(Common::UTF8ToUTF16W(filename), openmode);
#else
fstream.open(filename, openmode);
#endif
}
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
// and make forgetting an fclose() harder
class IOFile : public NonCopyable {
public:
IOFile();
// flags is used for windows specific file open mode flags, which
// allows yuzu to open the logs in shared write mode, so that the file
// isn't considered "locked" while yuzu is open and people can open the log file and view it
IOFile(const std::string& filename, const char openmode[], int flags = 0);
~IOFile();
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
void Swap(IOFile& other) noexcept;
bool Open(const std::string& filename, const char openmode[], int flags = 0);
bool Close();
template <typename T>
std::size_t ReadArray(T* data, std::size_t length) const {
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
return ReadImpl(data, length, sizeof(T));
}
template <typename T>
std::size_t WriteArray(const T* data, std::size_t length) {
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
return WriteImpl(data, length, sizeof(T));
}
template <typename T>
std::size_t ReadBytes(T* data, std::size_t length) const {
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
return ReadArray(reinterpret_cast<char*>(data), length);
}
template <typename T>
std::size_t WriteBytes(const T* data, std::size_t length) {
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
return WriteArray(reinterpret_cast<const char*>(data), length);
}
template <typename T>
std::size_t WriteObject(const T& object) {
static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer");
return WriteArray(&object, 1);
}
std::size_t WriteString(std::string_view str) {
return WriteArray(str.data(), str.length());
}
[[nodiscard]] bool IsOpen() const {
return nullptr != m_file;
}
bool Seek(s64 off, int origin) const;
[[nodiscard]] u64 Tell() const;
[[nodiscard]] u64 GetSize() const;
bool Resize(u64 size);
bool Flush();
// clear error state
void Clear() {
std::clearerr(m_file);
}
private:
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const;
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
std::FILE* m_file = nullptr;
};
} // namespace Common::FS

392
src/common/fs/file.cpp Normal file
View File

@@ -0,0 +1,392 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#ifdef _WIN32
#include <io.h>
#include <share.h>
#else
#include <unistd.h>
#endif
#ifdef _MSC_VER
#define fileno _fileno
#define fseeko _fseeki64
#define ftello _ftelli64
#endif
namespace Common::FS {
namespace fs = std::filesystem;
namespace {
#ifdef _WIN32
/**
* Converts the file access mode and file type enums to a file access mode wide string.
*
* @param mode File access mode
* @param type File type
*
* @returns A pointer to a wide string representing the file access mode.
*/
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
switch (mode) {
case FileAccessMode::Read:
return L"rb";
case FileAccessMode::Write:
return L"wb";
case FileAccessMode::Append:
return L"ab";
case FileAccessMode::ReadWrite:
return L"r+b";
case FileAccessMode::ReadAppend:
return L"a+b";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return L"r";
case FileAccessMode::Write:
return L"w";
case FileAccessMode::Append:
return L"a";
case FileAccessMode::ReadWrite:
return L"r+";
case FileAccessMode::ReadAppend:
return L"a+";
}
break;
}
return L"";
}
/**
* Converts the file-share access flag enum to a Windows defined file-share access flag.
*
* @param flag File-share access flag
*
* @returns Windows defined file-share access flag.
*/
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
switch (flag) {
case FileShareFlag::ShareNone:
default:
return _SH_DENYRW;
case FileShareFlag::ShareReadOnly:
return _SH_DENYWR;
case FileShareFlag::ShareWriteOnly:
return _SH_DENYRD;
case FileShareFlag::ShareReadWrite:
return _SH_DENYNO;
}
}
#else
/**
* Converts the file access mode and file type enums to a file access mode string.
*
* @param mode File access mode
* @param type File type
*
* @returns A pointer to a string representing the file access mode.
*/
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
switch (mode) {
case FileAccessMode::Read:
return "rb";
case FileAccessMode::Write:
return "wb";
case FileAccessMode::Append:
return "ab";
case FileAccessMode::ReadWrite:
return "r+b";
case FileAccessMode::ReadAppend:
return "a+b";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return "r";
case FileAccessMode::Write:
return "w";
case FileAccessMode::Append:
return "a";
case FileAccessMode::ReadWrite:
return "r+";
case FileAccessMode::ReadAppend:
return "a+";
}
break;
}
return "";
}
#endif
/**
* Converts the seek origin enum to a seek origin integer.
*
* @param origin Seek origin
*
* @returns Seek origin integer.
*/
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
switch (origin) {
case SeekOrigin::SetOrigin:
default:
return SEEK_SET;
case SeekOrigin::CurrentPosition:
return SEEK_CUR;
case SeekOrigin::End:
return SEEK_END;
}
}
} // Anonymous namespace
std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) {
if (!IsFile(path)) {
return "";
}
IOFile io_file{path, FileAccessMode::Read, type};
return io_file.ReadString(io_file.GetSize());
}
size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
std::string_view string) {
if (!IsFile(path)) {
return 0;
}
IOFile io_file{path, FileAccessMode::Write, type};
return io_file.WriteString(string);
}
size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
std::string_view string) {
if (!Exists(path)) {
return WriteStringToFile(path, type, string);
}
if (!IsFile(path)) {
return 0;
}
IOFile io_file{path, FileAccessMode::Append, type};
return io_file.WriteString(string);
}
IOFile::IOFile() = default;
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::~IOFile() {
Close();
}
IOFile::IOFile(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
}
IOFile& IOFile::operator=(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
return *this;
}
fs::path IOFile::GetPath() const {
return file_path;
}
FileAccessMode IOFile::GetAccessMode() const {
return file_access_mode;
}
FileType IOFile::GetType() const {
return file_type;
}
void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Close();
file_path = path;
file_access_mode = mode;
file_type = type;
errno = 0;
#ifdef _WIN32
if (flag != FileShareFlag::ShareNone) {
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
} else {
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
}
#else
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
#endif
if (!IsOpen()) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
}
void IOFile::Close() {
if (!IsOpen()) {
return;
}
errno = 0;
const auto close_result = std::fclose(file) == 0;
if (!close_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
file = nullptr;
}
bool IOFile::IsOpen() const {
return file != nullptr;
}
std::string IOFile::ReadString(size_t length) const {
std::vector<char> string_buffer(length);
const auto chars_read = ReadSpan<char>(string_buffer);
const auto string_size = chars_read != length ? chars_read : length;
return std::string{string_buffer.data(), string_size};
}
size_t IOFile::WriteString(std::span<const char> string) const {
return WriteSpan(string);
}
bool IOFile::Flush() const {
if (!IsOpen()) {
return false;
}
errno = 0;
const auto flush_result = std::fflush(file) == 0;
if (!flush_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return flush_result;
}
bool IOFile::SetSize(u64 size) const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
#else
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
#endif
if (!set_size_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
PathToUTF8String(file_path), size, ec.message());
}
return set_size_result;
}
u64 IOFile::GetSize() const {
if (!IsOpen()) {
return 0;
}
std::error_code ec;
const auto file_size = fs::file_size(file_path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
return 0;
}
return file_size;
}
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
if (!IsOpen()) {
return false;
}
errno = 0;
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
if (!seek_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem,
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
PathToUTF8String(file_path), offset, origin, ec.message());
}
return seek_result;
}
s64 IOFile::Tell() const {
if (!IsOpen()) {
return 0;
}
errno = 0;
return ftello(file);
}
} // namespace Common::FS

450
src/common/fs/file.h Normal file
View File

@@ -0,0 +1,450 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <span>
#include <type_traits>
#include <vector>
#include "common/concepts.h"
#include "common/fs/fs_types.h"
#include "common/fs/fs_util.h"
namespace Common::FS {
enum class SeekOrigin {
SetOrigin, // Seeks from the start of the file.
CurrentPosition, // Seeks from the current file pointer position.
End, // Seeks from the end of the file.
};
/**
* Opens a file stream at path with the specified open mode.
*
* @param file_stream Reference to file stream
* @param path Filesystem path
* @param open_mode File stream open mode
*/
template <typename FileStream>
void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
std::ios_base::openmode open_mode) {
file_stream.open(path, open_mode);
}
#ifdef _WIN32
template <typename FileStream, typename Path>
void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) {
if constexpr (IsChar<typename Path::value_type>) {
file_stream.open(ToU8String(path), open_mode);
} else {
file_stream.open(std::filesystem::path{path}, open_mode);
}
}
#endif
/**
* Reads an entire file at path and returns a string of the contents read from the file.
* If the filesystem object at path is not a file, this function returns an empty string.
*
* @param path Filesystem path
* @param type File type
*
* @returns A string of the contents read from the file.
*/
[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) {
if constexpr (IsChar<typename Path::value_type>) {
return ReadStringFromFile(ToU8String(path), type);
} else {
return ReadStringFromFile(std::filesystem::path{path}, type);
}
}
#endif
/**
* Writes a string to a file at path and returns the number of characters successfully written.
* If an file already exists at path, its contents will be erased.
* If the filesystem object at path is not a file, this function returns 0.
*
* @param path Filesystem path
* @param type File type
*
* @returns Number of characters successfully written.
*/
[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
std::string_view string);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) {
if constexpr (IsChar<typename Path::value_type>) {
return WriteStringToFile(ToU8String(path), type, string);
} else {
return WriteStringToFile(std::filesystem::path{path}, type, string);
}
}
#endif
/**
* Appends a string to a file at path and returns the number of characters successfully written.
* If a file does not exist at path, WriteStringToFile is called instead.
* If the filesystem object at path is not a file, this function returns 0.
*
* @param path Filesystem path
* @param type File type
*
* @returns Number of characters successfully written.
*/
[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
std::string_view string);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) {
if constexpr (IsChar<typename Path::value_type>) {
return AppendStringToFile(ToU8String(path), type, string);
} else {
return AppendStringToFile(std::filesystem::path{path}, type, string);
}
}
#endif
class IOFile final : NonCopyable {
public:
IOFile();
explicit IOFile(const std::string& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(std::string_view path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
/**
* An IOFile is a lightweight wrapper on C Library file operations.
* Automatically closes an open file on the destruction of an IOFile object.
*
* @param path Filesystem path
* @param mode File access mode
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
*/
explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
virtual ~IOFile();
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
/**
* Gets the path of the file.
*
* @returns The path of the file.
*/
[[nodiscard]] std::filesystem::path GetPath() const;
/**
* Gets the access mode of the file.
*
* @returns The access mode of the file.
*/
[[nodiscard]] FileAccessMode GetAccessMode() const;
/**
* Gets the type of the file.
*
* @returns The type of the file.
*/
[[nodiscard]] FileType GetType() const;
/**
* Opens a file at path with the specified file access mode.
* This function behaves differently depending on the FileAccessMode.
* These behaviors are documented in each enum value of FileAccessMode.
*
* @param path Filesystem path
* @param mode File access mode
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
*/
void Open(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] void Open(const Path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly) {
using ValueType = typename Path::value_type;
if constexpr (IsChar<ValueType>) {
Open(ToU8String(path), mode, type, flag);
} else {
Open(std::filesystem::path{path}, mode, type, flag);
}
}
#endif
/// Closes the file if it is opened.
void Close();
/**
* Checks whether the file is open.
* Use this to check whether the calls to Open() or Close() succeeded.
*
* @returns True if the file is open, false otherwise.
*/
[[nodiscard]] bool IsOpen() const;
/**
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
* ReadObject and T must be a trivially copyable object.
*
* See ReadSpan for more details if T is a contiguous container.
* See ReadObject for more details if T is a trivially copyable object.
*
* @tparam T Contiguous container or trivially copyable object
*
* @param data Container of T::value_type data or reference to object
*
* @returns Count of T::value_type data or objects successfully read.
*/
template <typename T>
[[nodiscard]] size_t Read(T& data) const {
if constexpr (IsSTLContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
} else {
return ReadObject(data) ? 1 : 0;
}
}
/**
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
* WriteObject and T must be a trivially copyable object.
*
* See WriteSpan for more details if T is a contiguous container.
* See WriteObject for more details if T is a trivially copyable object.
*
* @tparam T Contiguous container or trivially copyable object
*
* @param data Container of T::value_type data or const reference to object
*
* @returns Count of T::value_type data or objects successfully written.
*/
template <typename T>
[[nodiscard]] size_t Write(const T& data) const {
if constexpr (IsSTLContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
} else {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
/**
* Reads a span of T data from a file sequentially.
* This function reads from the current position of the file pointer and
* advances it by the (count of T * sizeof(T)) bytes successfully read.
*
* Failures occur when:
* - The file is not open
* - The opened file lacks read permissions
* - Attempting to read beyond the end-of-file
*
* @tparam T Data type
*
* @param data Span of T data
*
* @returns Count of T data successfully read.
*/
template <typename T>
[[nodiscard]] size_t ReadSpan(std::span<T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fread(data.data(), sizeof(T), data.size(), file);
}
/**
* Writes a span of T data to a file sequentially.
* This function writes from the current position of the file pointer and
* advances it by the (count of T * sizeof(T)) bytes successfully written.
*
* Failures occur when:
* - The file is not open
* - The opened file lacks write permissions
*
* @tparam T Data type
*
* @param data Span of T data
*
* @returns Count of T data successfully written.
*/
template <typename T>
[[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), file);
}
/**
* Reads a T object from a file sequentially.
* This function reads from the current position of the file pointer and
* advances it by the sizeof(T) bytes successfully read.
*
* Failures occur when:
* - The file is not open
* - The opened file lacks read permissions
* - Attempting to read beyond the end-of-file
*
* @tparam T Data type
*
* @param object Reference to object
*
* @returns True if the object is successfully read from the file, false otherwise.
*/
template <typename T>
[[nodiscard]] bool ReadObject(T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fread(&object, sizeof(T), 1, file) == 1;
}
/**
* Writes a T object to a file sequentially.
* This function writes from the current position of the file pointer and
* advances it by the sizeof(T) bytes successfully written.
*
* Failures occur when:
* - The file is not open
* - The opened file lacks write permissions
*
* @tparam T Data type
*
* @param object Const reference to object
*
* @returns True if the object is successfully written to the file, false otherwise.
*/
template <typename T>
[[nodiscard]] bool WriteObject(const T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fwrite(&object, sizeof(T), 1, file) == 1;
}
/**
* Specialized function to read a string of a given length from a file sequentially.
* This function writes from the current position of the file pointer and
* advances it by the number of characters successfully read.
* The size of the returned string may not match length if not all bytes are successfully read.
*
* @param length Length of the string
*
* @returns A string read from the file.
*/
[[nodiscard]] std::string ReadString(size_t length) const;
/**
* Specialized function to write a string to a file sequentially.
* This function writes from the current position of the file pointer and
* advances it by the number of characters successfully written.
*
* @param string Span of const char backed std::string or std::string_view
*
* @returns Number of characters successfully written.
*/
[[nodiscard]] size_t WriteString(std::span<const char> string) const;
/**
* Flushes any unwritten buffered data into the file.
*
* @returns True if the flush was successful, false otherwise.
*/
[[nodiscard]] bool Flush() const;
/**
* Resizes the file to a given size.
* If the file is resized to a smaller size, the remainder of the file is discarded.
* If the file is resized to a larger size, the new area appears as if zero-filled.
*
* Failures occur when:
* - The file is not open
*
* @param size File size in bytes
*
* @returns True if the file resize succeeded, false otherwise.
*/
[[nodiscard]] bool SetSize(u64 size) const;
/**
* Gets the size of the file.
*
* Failures occur when:
* - The file is not open
*
* @returns The file size in bytes of the file. Returns 0 on failure.
*/
[[nodiscard]] u64 GetSize() const;
/**
* Moves the current position of the file pointer with the specified offset and seek origin.
*
* @param offset Offset from seek origin
* @param origin Seek origin
*
* @returns True if the file pointer has moved to the specified offset, false otherwise.
*/
[[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
/**
* Gets the current position of the file pointer.
*
* @returns The current position of the file pointer.
*/
[[nodiscard]] s64 Tell() const;
private:
std::filesystem::path file_path;
FileAccessMode file_access_mode;
FileType file_type;
std::FILE* file = nullptr;
};
} // namespace Common::FS

610
src/common/fs/fs.cpp Normal file
View File

@@ -0,0 +1,610 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
namespace Common::FS {
namespace fs = std::filesystem;
// File Operations
bool NewFile(const fs::path& path, u64 size) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path.parent_path())) {
LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist",
PathToUTF8String(path));
return false;
}
if (Exists(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path));
return false;
}
IOFile io_file{path, FileAccessMode::Write};
if (!io_file.IsOpen()) {
LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path));
return false;
}
if (!io_file.SetSize(size)) {
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}",
PathToUTF8String(path), size);
return false;
}
io_file.Close();
LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}",
PathToUTF8String(path), size);
return true;
}
bool RemoveFile(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return true;
}
if (!IsFile(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
PathToUTF8String(path));
return false;
}
std::error_code ec;
fs::remove(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}",
PathToUTF8String(path));
return true;
}
bool RenameFile(const fs::path& old_path, const fs::path& new_path) {
if (!ValidatePath(old_path) || !ValidatePath(new_path)) {
LOG_ERROR(Common_Filesystem,
"One or both input path(s) is not valid, old_path={}, new_path={}",
PathToUTF8String(old_path), PathToUTF8String(new_path));
return false;
}
if (!Exists(old_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist",
PathToUTF8String(old_path));
return false;
}
if (!IsFile(old_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file",
PathToUTF8String(old_path));
return false;
}
if (Exists(new_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists",
PathToUTF8String(new_path));
return false;
}
std::error_code ec;
fs::rename(old_path, new_path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to rename the file from old_path={} to new_path={}, ec_message={}",
PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}",
PathToUTF8String(old_path), PathToUTF8String(new_path));
return true;
}
std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, FileType type,
FileShareFlag flag) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return nullptr;
}
if (!IsFile(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
PathToUTF8String(path));
return nullptr;
}
auto io_file = std::make_shared<IOFile>(path, mode, type, flag);
if (!io_file->IsOpen()) {
io_file.reset();
LOG_ERROR(Common_Filesystem,
"Failed to open the file at path={} with mode={}, type={}, flag={}",
PathToUTF8String(path), mode, type, flag);
return nullptr;
}
LOG_DEBUG(Common_Filesystem,
"Successfully opened the file at path={} with mode={}, type={}, flag={}",
PathToUTF8String(path), mode, type, flag);
return io_file;
}
// Directory Operations
bool CreateDir(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path.parent_path())) {
LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist",
PathToUTF8String(path));
return false;
}
if (IsDir(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory",
PathToUTF8String(path));
return true;
}
std::error_code ec;
fs::create_directory(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}",
PathToUTF8String(path));
return true;
}
bool CreateDirs(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (IsDir(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory",
PathToUTF8String(path));
return true;
}
std::error_code ec;
fs::create_directories(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}",
PathToUTF8String(path));
return true;
}
bool CreateParentDir(const fs::path& path) {
return CreateDir(path.parent_path());
}
bool CreateParentDirs(const fs::path& path) {
return CreateDirs(path.parent_path());
}
bool RemoveDir(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return true;
}
if (!IsDir(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
PathToUTF8String(path));
return false;
}
std::error_code ec;
fs::remove(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}",
PathToUTF8String(path));
return true;
}
bool RemoveDirRecursively(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return true;
}
if (!IsDir(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
PathToUTF8String(path));
return false;
}
std::error_code ec;
fs::remove_all(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to remove the directory and its contents at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}",
PathToUTF8String(path));
return true;
}
bool RemoveDirContentsRecursively(const fs::path& path) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return false;
}
if (!Exists(path)) {
LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return true;
}
if (!IsDir(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
PathToUTF8String(path));
return false;
}
std::error_code ec;
for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to completely enumerate the directory at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
break;
}
fs::remove(entry.path(), ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to remove the filesystem object at path={}, ec_message={}",
PathToUTF8String(entry.path()), ec.message());
break;
}
}
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to remove all the contents of the directory at path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem,
"Successfully removed all the contents of the directory at path={}",
PathToUTF8String(path));
return true;
}
bool RenameDir(const fs::path& old_path, const fs::path& new_path) {
if (!ValidatePath(old_path) || !ValidatePath(new_path)) {
LOG_ERROR(Common_Filesystem,
"One or both input path(s) is not valid, old_path={}, new_path={}",
PathToUTF8String(old_path), PathToUTF8String(new_path));
return false;
}
if (!Exists(old_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist",
PathToUTF8String(old_path));
return false;
}
if (!IsDir(old_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory",
PathToUTF8String(old_path));
return false;
}
if (Exists(new_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists",
PathToUTF8String(new_path));
return false;
}
std::error_code ec;
fs::rename(old_path, new_path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to rename the file from old_path={} to new_path={}, ec_message={}",
PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message());
return false;
}
LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}",
PathToUTF8String(old_path), PathToUTF8String(new_path));
return true;
}
void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback,
DirEntryFilter filter) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return;
}
if (!Exists(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return;
}
if (!IsDir(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
PathToUTF8String(path));
return;
}
bool callback_error = false;
std::error_code ec;
for (const auto& entry : fs::directory_iterator(path, ec)) {
if (ec) {
break;
}
if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) {
if (!callback(entry.path())) {
callback_error = true;
break;
}
}
if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) {
if (!callback(entry.path())) {
callback_error = true;
break;
}
}
}
if (callback_error || ec) {
LOG_ERROR(Common_Filesystem,
"Failed to visit all the directory entries of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return;
}
LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}",
PathToUTF8String(path));
}
void IterateDirEntriesRecursively(const std::filesystem::path& path,
const DirEntryCallable& callback, DirEntryFilter filter) {
if (!ValidatePath(path)) {
LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
return;
}
if (!Exists(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist",
PathToUTF8String(path));
return;
}
if (!IsDir(path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
PathToUTF8String(path));
return;
}
bool callback_error = false;
std::error_code ec;
for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
if (ec) {
break;
}
if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) {
if (!callback(entry.path())) {
callback_error = true;
break;
}
}
if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) {
if (!callback(entry.path())) {
callback_error = true;
break;
}
}
}
if (callback_error || ec) {
LOG_ERROR(Common_Filesystem,
"Failed to visit all the directory entries of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return;
}
LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}",
PathToUTF8String(path));
}
// Generic Filesystem Operations
bool Exists(const fs::path& path) {
return fs::exists(path);
}
bool IsFile(const fs::path& path) {
return fs::is_regular_file(path);
}
bool IsDir(const fs::path& path) {
return fs::is_directory(path);
}
fs::path GetCurrentDir() {
std::error_code ec;
const auto current_path = fs::current_path(ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message());
return {};
}
return current_path;
}
bool SetCurrentDir(const fs::path& path) {
std::error_code ec;
fs::current_path(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return false;
}
return true;
}
fs::file_type GetEntryType(const fs::path& path) {
std::error_code ec;
const auto file_status = fs::status(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return fs::file_type::not_found;
}
return file_status.type();
}
u64 GetSize(const fs::path& path) {
std::error_code ec;
const auto file_size = fs::file_size(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return 0;
}
return file_size;
}
u64 GetFreeSpaceSize(const fs::path& path) {
std::error_code ec;
const auto space_info = fs::space(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to retrieve the available free space of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return 0;
}
return space_info.free;
}
u64 GetTotalSpaceSize(const fs::path& path) {
std::error_code ec;
const auto space_info = fs::space(path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to retrieve the total capacity of path={}, ec_message={}",
PathToUTF8String(path), ec.message());
return 0;
}
return space_info.capacity;
}
} // namespace Common::FS

582
src/common/fs/fs.h Normal file
View File

@@ -0,0 +1,582 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <filesystem>
#include <memory>
#include "common/fs/fs_types.h"
#include "common/fs/fs_util.h"
namespace Common::FS {
class IOFile;
// File Operations
/**
* Creates a new file at path with the specified size.
*
* Failures occur when:
* - Input path is not valid
* - The input path's parent directory does not exist
* - Filesystem object at path exists
* - Filesystem at path is read only
*
* @param path Filesystem path
* @param size File size
*
* @returns True if the file creation succeeds, false otherwise.
*/
[[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool NewFile(const Path& path, u64 size = 0) {
if constexpr (IsChar<typename Path::value_type>) {
return NewFile(ToU8String(path), size);
} else {
return NewFile(std::filesystem::path{path}, size);
}
}
#endif
/**
* Removes a file at path.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a file
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if file removal succeeds or file does not exist, false otherwise.
*/
[[nodiscard]] bool RemoveFile(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool RemoveFile(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveFile(ToU8String(path));
} else {
return RemoveFile(std::filesystem::path{path});
}
}
#endif
/**
* Renames a file from old_path to new_path.
*
* Failures occur when:
* - One or both input path(s) is not valid
* - Filesystem object at old_path does not exist
* - Filesystem object at old_path is not a file
* - Filesystem object at new_path exists
* - Filesystem at either path is read only
*
* @param old_path Old filesystem path
* @param new_path New filesystem path
*
* @returns True if file rename succeeds, false otherwise.
*/
[[nodiscard]] bool RenameFile(const std::filesystem::path& old_path,
const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return RenameFile(ToU8String(old_path), ToU8String(new_path));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return RenameFile(ToU8String(old_path), new_path);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return RenameFile(old_path, ToU8String(new_path));
} else {
return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path});
}
}
#endif
/**
* Opens a file at path with the specified file access mode.
* This function behaves differently depending on the FileAccessMode.
* These behaviors are documented in each enum value of FileAccessMode.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a file
* - The file is not opened
*
* @param path Filesystem path
* @param mode File access mode
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
*
* @returns A shared pointer to the opened file. Returns nullptr on failure.
*/
[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const std::filesystem::path& path,
FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly) {
if constexpr (IsChar<typename Path::value_type>) {
return FileOpen(ToU8String(path), mode, type, flag);
} else {
return FileOpen(std::filesystem::path{path}, mode, type, flag);
}
}
#endif
// Directory Operations
/**
* Creates a directory at path.
* Note that this function will *always* assume that the input path is a directory. For example,
* if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt".
* If you intend to create the parent directory of a file, use CreateParentDir instead.
*
* Failures occur when:
* - Input path is not valid
* - The input path's parent directory does not exist
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if directory creation succeeds or directory already exists, false otherwise.
*/
[[nodiscard]] bool CreateDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool CreateDir(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return CreateDir(ToU8String(path));
} else {
return CreateDir(std::filesystem::path{path});
}
}
#endif
/**
* Recursively creates a directory at path.
* Note that this function will *always* assume that the input path is a directory. For example,
* if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt".
* If you intend to create the parent directory of a file, use CreateParentDirs instead.
* Unlike CreateDir, this creates all of input path's parent directories if they do not exist.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if directory creation succeeds or directory already exists, false otherwise.
*/
[[nodiscard]] bool CreateDirs(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool CreateDirs(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return CreateDirs(ToU8String(path));
} else {
return CreateDirs(std::filesystem::path{path});
}
}
#endif
/**
* Creates the parent directory of a given path.
* This function calls CreateDir(path.parent_path()), see CreateDir for more details.
*
* @param path Filesystem path
*
* @returns True if directory creation succeeds or directory already exists, false otherwise.
*/
[[nodiscard]] bool CreateParentDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool CreateParentDir(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return CreateParentDir(ToU8String(path));
} else {
return CreateParentDir(std::filesystem::path{path});
}
}
#endif
/**
* Recursively creates the parent directory of a given path.
* This function calls CreateDirs(path.parent_path()), see CreateDirs for more details.
*
* @param path Filesystem path
*
* @returns True if directory creation succeeds or directory already exists, false otherwise.
*/
[[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool CreateParentDirs(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return CreateParentDirs(ToU8String(path));
} else {
return CreateParentDirs(std::filesystem::path{path});
}
}
#endif
/**
* Removes a directory at path.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a directory
* - The given directory is not empty
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if directory removal succeeds or directory does not exist, false otherwise.
*/
[[nodiscard]] bool RemoveDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool RemoveDir(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveDir(ToU8String(path));
} else {
return RemoveDir(std::filesystem::path{path});
}
}
#endif
/**
* Removes all the contents within the given directory and removes the directory itself.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a directory
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if the directory and all of its contents are removed successfully, false otherwise.
*/
[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool RemoveDirRecursively(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveDirRecursively(ToU8String(path));
} else {
return RemoveDirRecursively(std::filesystem::path{path});
}
}
#endif
/**
* Removes all the contents within the given directory without removing the directory itself.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a directory
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if all of the directory's contents are removed successfully, false otherwise.
*/
[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveDirContentsRecursively(ToU8String(path));
} else {
return RemoveDirContentsRecursively(std::filesystem::path{path});
}
}
#endif
/**
* Renames a directory from old_path to new_path.
*
* Failures occur when:
* - One or both input path(s) is not valid
* - Filesystem object at old_path does not exist
* - Filesystem object at old_path is not a directory
* - Filesystem object at new_path exists
* - Filesystem at either path is read only
*
* @param old_path Old filesystem path
* @param new_path New filesystem path
*
* @returns True if directory rename succeeds, false otherwise.
*/
[[nodiscard]] bool RenameDir(const std::filesystem::path& old_path,
const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return RenameDir(ToU8String(old_path), ToU8String(new_path));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return RenameDir(ToU8String(old_path), new_path);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return RenameDir(old_path, ToU8String(new_path));
} else {
return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path});
}
}
#endif
/**
* Iterates over the directory entries of a given directory.
* This does not iterate over the sub-directories of the given directory.
* The DirEntryCallable callback is called for each visited directory entry.
* A filter can be set to control which directory entries are visited based on their type.
* By default, both files and directories are visited.
* If the callback returns false or there is an error, the iteration is immediately halted.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path is not a directory
*
* @param path Filesystem path
* @param callback Callback to be called for each visited directory entry
* @param filter Directory entry type filter
*/
void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback,
DirEntryFilter filter = DirEntryFilter::All);
#ifdef _WIN32
template <typename Path>
void IterateDirEntries(const Path& path, const DirEntryCallable& callback,
DirEntryFilter filter = DirEntryFilter::All) {
if constexpr (IsChar<typename Path::value_type>) {
IterateDirEntries(ToU8String(path), callback, filter);
} else {
IterateDirEntries(std::filesystem::path{path}, callback, filter);
}
}
#endif
/**
* Iterates over the directory entries of a given directory and its sub-directories.
* The DirEntryCallable callback is called for each visited directory entry.
* A filter can be set to control which directory entries are visited based on their type.
* By default, both files and directories are visited.
* If the callback returns false or there is an error, the iteration is immediately halted.
*
* Failures occur when:
* - Input path is not valid
* - Filesystem object at path does not exist
* - Filesystem object at path is not a directory
*
* @param path Filesystem path
* @param callback Callback to be called for each visited directory entry
* @param filter Directory entry type filter
*/
void IterateDirEntriesRecursively(const std::filesystem::path& path,
const DirEntryCallable& callback,
DirEntryFilter filter = DirEntryFilter::All);
#ifdef _WIN32
template <typename Path>
void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback,
DirEntryFilter filter = DirEntryFilter::All) {
if constexpr (IsChar<typename Path::value_type>) {
IterateDirEntriesRecursively(ToU8String(path), callback, filter);
} else {
IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter);
}
}
#endif
// Generic Filesystem Operations
/**
* Returns whether a filesystem object at path exists.
*
* @param path Filesystem path
*
* @returns True if a filesystem object at path exists, false otherwise.
*/
[[nodiscard]] bool Exists(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool Exists(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return Exists(ToU8String(path));
} else {
return Exists(std::filesystem::path{path});
}
}
#endif
/**
* Returns whether a filesystem object at path is a file.
*
* @param path Filesystem path
*
* @returns True if a filesystem object at path is a file, false otherwise.
*/
[[nodiscard]] bool IsFile(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool IsFile(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return IsFile(ToU8String(path));
} else {
return IsFile(std::filesystem::path{path});
}
}
#endif
/**
* Returns whether a filesystem object at path is a directory.
*
* @param path Filesystem path
*
* @returns True if a filesystem object at path is a directory, false otherwise.
*/
[[nodiscard]] bool IsDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool IsDir(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return IsDir(ToU8String(path));
} else {
return IsDir(std::filesystem::path{path});
}
}
#endif
/**
* Gets the current working directory.
*
* @returns The current working directory. Returns an empty path on failure.
*/
[[nodiscard]] std::filesystem::path GetCurrentDir();
/**
* Sets the current working directory to path.
*
* @returns True if the current working directory is successfully set, false otherwise.
*/
[[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool SetCurrentDir(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return SetCurrentDir(ToU8String(path));
} else {
return SetCurrentDir(std::filesystem::path{path});
}
}
#endif
/**
* Gets the entry type of the filesystem object at path.
*
* @param path Filesystem path
*
* @returns The entry type of the filesystem object. Returns file_type::not_found on failure.
*/
[[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return GetEntryType(ToU8String(path));
} else {
return GetEntryType(std::filesystem::path{path});
}
}
#endif
/**
* Gets the size of the filesystem object at path.
*
* @param path Filesystem path
*
* @returns The size in bytes of the filesystem object. Returns 0 on failure.
*/
[[nodiscard]] u64 GetSize(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] u64 GetSize(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return GetSize(ToU8String(path));
} else {
return GetSize(std::filesystem::path{path});
}
}
#endif
/**
* Gets the free space size of the filesystem at path.
*
* @param path Filesystem path
*
* @returns The free space size in bytes of the filesystem at path. Returns 0 on failure.
*/
[[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] u64 GetFreeSpaceSize(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return GetFreeSpaceSize(ToU8String(path));
} else {
return GetFreeSpaceSize(std::filesystem::path{path});
}
}
#endif
/**
* Gets the total capacity of the filesystem at path.
*
* @param path Filesystem path
*
* @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure.
*/
[[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] u64 GetTotalSpaceSize(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return GetTotalSpaceSize(ToU8String(path));
} else {
return GetTotalSpaceSize(std::filesystem::path{path});
}
}
#endif
} // namespace Common::FS

27
src/common/fs/fs_paths.h Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
// yuzu data directories
#define YUZU_DIR "yuzu"
#define PORTABLE_DIR "user"
// Sub-directories contained within a yuzu data directory
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
#define DUMP_DIR "dump"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define LOG_DIR "log"
#define NAND_DIR "nand"
#define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
// yuzu-specific files
#define LOG_FILE "yuzu_log.txt"

73
src/common/fs/fs_types.h Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Common::FS {
enum class FileAccessMode {
/**
* If the file at path exists, it opens the file for reading.
* If the file at path does not exist, it fails to open the file.
*/
Read = 1 << 0,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
*/
Write = 1 << 1,
/**
* If the file at path exists, it opens the file for reading and writing.
* If the file at path does not exist, it fails to open the file.
*/
ReadWrite = Read | Write,
/**
* If the file at path exists, it opens the file for appending.
* If the file at path does not exist, it creates and opens a new empty file for appending.
*/
Append = 1 << 2,
/**
* If the file at path exists, it opens the file for both reading and appending.
* If the file at path does not exist, it creates and opens a new empty file for both
* reading and appending.
*/
ReadAppend = Read | Append,
};
enum class FileType {
BinaryFile,
TextFile,
};
enum class FileShareFlag {
ShareNone, // Provides exclusive access to the file.
ShareReadOnly, // Provides read only shared access to the file.
ShareWriteOnly, // Provides write only shared access to the file.
ShareReadWrite, // Provides read and write shared access to the file.
};
enum class DirEntryFilter {
File = 1 << 0,
Directory = 1 << 1,
All = File | Directory,
};
DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
/**
* A callback function which takes in the path of a directory entry.
*
* @param path The path of a directory entry
*
* @returns A boolean value.
* Return true to indicate whether the callback is successful, false otherwise.
*/
using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>;
} // namespace Common::FS

13
src/common/fs/fs_util.cpp Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/fs/fs_util.h"
namespace Common::FS {
std::u8string ToU8String(std::string_view utf8_string) {
return std::u8string{utf8_string.begin(), utf8_string.end()};
}
} // namespace Common::FS

25
src/common/fs/fs_util.h Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <concepts>
#include <string>
#include <string_view>
namespace Common::FS {
template <typename T>
concept IsChar = std::same_as<T, char>;
/**
* Converts a UTF-8 encoded std::string or std::string_view to a std::u8string.
*
* @param utf8_string UTF-8 encoded string
*
* @returns UTF-8 encoded std::u8string.
*/
[[nodiscard]] std::u8string ToU8String(std::string_view utf8_string);
} // namespace Common::FS

432
src/common/fs/path_util.cpp Normal file
View File

@@ -0,0 +1,432 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <unordered_map>
#include "common/fs/fs.h"
#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#ifdef _WIN32
#include <shlobj.h> // Used in GetExeDirectory()
#else
#include <cstdlib> // Used in Get(Home/Data)Directory()
#include <pwd.h> // Used in GetHomeDirectory()
#include <sys/types.h> // Used in GetHomeDirectory()
#include <unistd.h> // Used in GetDataDirectory()
#endif
#ifdef __APPLE__
#include <sys/param.h> // Used in GetBundleDirectory()
// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
// ignore them if we're not using clang. The macro is only used to prevent linking against
// functions that don't exist on older versions of macOS, and the worst case scenario is a linker
// error, so this is perfectly safe, just inconvenient.
#ifndef __clang__
#define availability(...)
#endif
#include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory()
#include <CoreFoundation/CFString.h> // Used in GetBundleDirectory()
#include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory()
#ifdef availability
#undef availability
#endif
#endif
#ifndef MAX_PATH
#ifdef _WIN32
// This is the maximum number of UTF-16 code units permissible in Windows file paths
#define MAX_PATH 260
#else
// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
#define MAX_PATH 1024
#endif
#endif
namespace Common::FS {
namespace fs = std::filesystem;
/**
* The PathManagerImpl is a singleton allowing to manage the mapping of
* YuzuPath enums to real filesystem paths.
* This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl.
* These are used by GetYuzuPath and SetYuzuPath respectively to get or modify
* the path mapped by the YuzuPath enum.
*/
class PathManagerImpl {
public:
static PathManagerImpl& GetInstance() {
static PathManagerImpl path_manager_impl;
return path_manager_impl;
}
PathManagerImpl(const PathManagerImpl&) = delete;
PathManagerImpl& operator=(const PathManagerImpl&) = delete;
PathManagerImpl(PathManagerImpl&&) = delete;
PathManagerImpl& operator=(PathManagerImpl&&) = delete;
[[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) {
return yuzu_paths.at(yuzu_path);
}
void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) {
yuzu_paths.insert_or_assign(yuzu_path, new_path);
}
private:
PathManagerImpl() {
#ifdef _WIN32
auto yuzu_path = GetExeDirectory() / PORTABLE_DIR;
if (!IsDir(yuzu_path)) {
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
}
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
#else
auto yuzu_path = GetCurrentDir() / PORTABLE_DIR;
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
} else {
yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR;
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR);
GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR);
}
#endif
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
}
~PathManagerImpl() = default;
void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
void(FS::CreateDir(new_path));
SetYuzuPathImpl(yuzu_path, new_path);
}
std::unordered_map<YuzuPath, fs::path> yuzu_paths;
};
std::string PathToUTF8String(const fs::path& path) {
const auto utf8_string = path.u8string();
return std::string{utf8_string.begin(), utf8_string.end()};
}
bool ValidatePath(const fs::path& path) {
if (path.empty()) {
LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
return false;
}
#ifdef _WIN32
if (path.u16string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#else
if (path.u8string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#endif
return true;
}
fs::path ConcatPath(const fs::path& first, const fs::path& second) {
const bool second_has_dir_sep = IsDirSeparator(second.u8string().front());
if (!second_has_dir_sep) {
return (first / second).lexically_normal();
}
fs::path concat_path = first;
concat_path += second;
return concat_path.lexically_normal();
}
fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) {
const auto concatenated_path = ConcatPath(base, offset);
if (!IsPathSandboxed(base, concatenated_path)) {
return base;
}
return concatenated_path;
}
bool IsPathSandboxed(const fs::path& base, const fs::path& path) {
const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string();
const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string();
if (path_string.size() < base_string.size()) {
return false;
}
return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0;
}
bool IsDirSeparator(char character) {
return character == '/' || character == '\\';
}
bool IsDirSeparator(char8_t character) {
return character == u8'/' || character == u8'\\';
}
fs::path RemoveTrailingSeparators(const fs::path& path) {
if (path.empty()) {
return path;
}
auto string_path = path.u8string();
while (IsDirSeparator(string_path.back())) {
string_path.pop_back();
}
return fs::path{string_path};
}
const fs::path& GetYuzuPath(YuzuPath yuzu_path) {
return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path);
}
std::string GetYuzuPathString(YuzuPath yuzu_path) {
return PathToUTF8String(GetYuzuPath(yuzu_path));
}
void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
if (!FS::IsDir(new_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
PathToUTF8String(new_path));
return;
}
PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path);
}
#ifdef _WIN32
fs::path GetExeDirectory() {
wchar_t exe_path[MAX_PATH];
GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
if (!exe_path) {
LOG_ERROR(Common_Filesystem,
"Failed to get the path to the executable of the current process");
}
return fs::path{exe_path}.parent_path();
}
fs::path GetAppDataRoamingDirectory() {
PWSTR appdata_roaming_path = nullptr;
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path);
auto fs_appdata_roaming_path = fs::path{appdata_roaming_path};
CoTaskMemFree(appdata_roaming_path);
if (fs_appdata_roaming_path.empty()) {
LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory");
}
return fs_appdata_roaming_path;
}
#else
fs::path GetHomeDirectory() {
const char* home_env_var = getenv("HOME");
if (home_env_var) {
return fs::path{home_env_var};
}
LOG_INFO(Common_Filesystem,
"$HOME is not defined in the environment variables, "
"attempting to query passwd to get the home path of the current user");
const auto* pw = getpwuid(getuid());
if (!pw) {
LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user");
return {};
}
return fs::path{pw->pw_dir};
}
fs::path GetDataDirectory(const std::string& env_name) {
const char* data_env_var = getenv(env_name.c_str());
if (data_env_var) {
return fs::path{data_env_var};
}
if (env_name == "XDG_DATA_HOME") {
return GetHomeDirectory() / ".local/share";
} else if (env_name == "XDG_CACHE_HOME") {
return GetHomeDirectory() / ".cache";
} else if (env_name == "XDG_CONFIG_HOME") {
return GetHomeDirectory() / ".config";
}
return {};
}
#endif
#ifdef __APPLE__
fs::path GetBundleDirectory() {
char app_bundle_path[MAXPATHLEN];
// Get the main bundle for the app
CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle());
CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle);
CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path));
CFRelease(bundle_ref);
CFRelease(bundle_path);
return fs::path{app_bundle_path};
}
#endif
// vvvvvvvvvv Deprecated vvvvvvvvvv //
std::string_view RemoveTrailingSlash(std::string_view path) {
if (path.empty()) {
return path;
}
if (path.back() == '\\' || path.back() == '/') {
path.remove_suffix(1);
return path;
}
return path;
}
std::vector<std::string> SplitPathComponents(std::string_view filename) {
std::string copy(filename);
std::replace(copy.begin(), copy.end(), '\\', '/');
std::vector<std::string> out;
std::stringstream stream(copy);
std::string item;
while (std::getline(stream, item, '/')) {
out.push_back(std::move(item));
}
return out;
}
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
std::string path(path_);
char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
if (directory_separator == DirectorySeparator::PlatformDefault) {
#ifdef _WIN32
type1 = '/';
type2 = '\\';
#endif
}
std::replace(path.begin(), path.end(), type1, type2);
auto start = path.begin();
#ifdef _WIN32
// allow network paths which start with a double backslash (e.g. \\server\share)
if (start != path.end())
++start;
#endif
path.erase(std::unique(start, path.end(),
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
path.end());
return std::string(RemoveTrailingSlash(path));
}
std::string_view GetParentPath(std::string_view path) {
const auto name_bck_index = path.rfind('\\');
const auto name_fwd_index = path.rfind('/');
std::size_t name_index;
if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
name_index = std::min(name_bck_index, name_fwd_index);
} else {
name_index = std::max(name_bck_index, name_fwd_index);
}
return path.substr(0, name_index);
}
std::string_view GetPathWithoutTop(std::string_view path) {
if (path.empty()) {
return path;
}
while (path[0] == '\\' || path[0] == '/') {
path.remove_prefix(1);
if (path.empty()) {
return path;
}
}
const auto name_bck_index = path.find('\\');
const auto name_fwd_index = path.find('/');
return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
}
std::string_view GetFilename(std::string_view path) {
const auto name_index = path.find_last_of("\\/");
if (name_index == std::string_view::npos) {
return {};
}
return path.substr(name_index + 1);
}
std::string_view GetExtensionFromFilename(std::string_view name) {
const std::size_t index = name.rfind('.');
if (index == std::string_view::npos) {
return {};
}
return name.substr(index + 1);
}
} // namespace Common::FS

309
src/common/fs/path_util.h Normal file
View File

@@ -0,0 +1,309 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <filesystem>
#include <vector>
#include "common/fs/fs_util.h"
namespace Common::FS {
enum class YuzuPath {
YuzuDir, // Where yuzu stores its data.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
DumpDir, // Where dumped data is stored.
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
LogDir, // Where log files are stored.
NANDDir, // Where the emulated NAND is stored.
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
};
/**
* Converts a filesystem path to a UTF-8 encoded std::string.
*
* @param path Filesystem path
*
* @returns UTF-8 encoded std::string.
*/
[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
/**
* Validates a given path.
*
* A given path is valid if it meets these conditions:
* - The path is not empty
* - The path is not too long
*
* @param path Filesystem path
*
* @returns True if the path is valid, false otherwise.
*/
[[nodiscard]] bool ValidatePath(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool ValidatePath(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return ValidatePath(ToU8String(path));
} else {
return ValidatePath(std::filesystem::path{path});
}
}
#endif
/**
* Concatenates two filesystem paths together.
*
* This is needed since the following occurs when using std::filesystem::path's operator/:
* first: "/first/path"
* second: "/second/path" (Note that the second path has a directory separator in the front)
* first / second yields "/second/path" when the desired result is first/path/second/path
*
* @param first First filesystem path
* @param second Second filesystem path
*
* @returns A concatenated filesystem path.
*/
[[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first,
const std::filesystem::path& second);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPath(ToU8String(first), ToU8String(second));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return ConcatPath(ToU8String(first), second);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPath(first, ToU8String(second));
} else {
return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second});
}
}
#endif
/**
* Safe variant of ConcatPath that takes in a base path and an offset path from the given base path.
*
* If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path,
* this will return the concatenated path. Otherwise this will return the base path.
*
* @param base Base filesystem path
* @param offset Offset filesystem path
*
* @returns A concatenated filesystem path if it is within the base path,
* returns the base path otherwise.
*/
[[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base,
const std::filesystem::path& offset);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPathSafe(ToU8String(base), ToU8String(offset));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return ConcatPathSafe(ToU8String(base), offset);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPathSafe(base, ToU8String(offset));
} else {
return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset});
}
}
#endif
/**
* Checks whether a given path is sandboxed within a given base path.
*
* @param base Base filesystem path
* @param path Filesystem path
*
* @returns True if the given path is sandboxed within the given base path, false otherwise.
*/
[[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base,
const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return IsPathSandboxed(ToU8String(base), ToU8String(path));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return IsPathSandboxed(ToU8String(base), path);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return IsPathSandboxed(base, ToU8String(path));
} else {
return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path});
}
}
#endif
/**
* Checks if a character is a directory separator (either a forward slash or backslash).
*
* @param character Character
*
* @returns True if the character is a directory separator, false otherwise.
*/
[[nodiscard]] bool IsDirSeparator(char character);
/**
* Checks if a character is a directory separator (either a forward slash or backslash).
*
* @param character Character
*
* @returns True if the character is a directory separator, false otherwise.
*/
[[nodiscard]] bool IsDirSeparator(char8_t character);
/**
* Removes any trailing directory separators if they exist in the given path.
*
* @param path Filesystem path
*
* @returns The filesystem path without any trailing directory separators.
*/
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveTrailingSeparators(ToU8String(path));
} else {
return RemoveTrailingSeparators(std::filesystem::path{path});
}
}
#endif
/**
* Gets the filesystem path associated with the YuzuPath enum.
*
* @param yuzu_path YuzuPath enum
*
* @returns The filesystem path associated with the YuzuPath enum.
*/
[[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path);
/**
* Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
*
* @param yuzu_path YuzuPath enum
*
* @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
*/
[[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path);
/**
* Sets a new filesystem path associated with the YuzuPath enum.
* If the filesystem object at new_path is not a directory, this function will not do anything.
*
* @param yuzu_path YuzuPath enum
* @param new_path New filesystem path
*/
void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
if constexpr (IsChar<typename Path::value_type>) {
SetYuzuPath(yuzu_path, ToU8String(new_path));
} else {
SetYuzuPath(yuzu_path, std::filesystem::path{new_path});
}
}
#endif
#ifdef _WIN32
/**
* Gets the path of the directory containing the executable of the current process.
*
* @returns The path of the directory containing the executable of the current process.
*/
[[nodiscard]] std::filesystem::path GetExeDirectory();
/**
* Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming).
*
* @returns The path of the current user's %APPDATA% directory.
*/
[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory();
#else
/**
* Gets the path of the directory specified by the #HOME environment variable.
* If $HOME is not defined, it will attempt to query the user database in passwd instead.
*
* @returns The path of the current user's home directory.
*/
[[nodiscard]] std::filesystem::path GetHomeDirectory();
/**
* Gets the relevant paths for yuzu to store its data based on the given XDG environment variable.
* See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
* Defaults to $HOME/.local/share for main application data,
* $HOME/.cache for cached data, and $HOME/.config for configuration files.
*
* @param env_name XDG environment variable name
*
* @returns The path where yuzu should store its data.
*/
[[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name);
#endif
#ifdef __APPLE__
[[nodiscard]] std::filesystem::path GetBundleDirectory();
#endif
// vvvvvvvvvv Deprecated vvvvvvvvvv //
// Removes the final '/' or '\' if one exists
[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
enum class DirectorySeparator {
ForwardSlash,
BackwardSlash,
PlatformDefault,
};
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
[[nodiscard]] std::string SanitizePath(
std::string_view path,
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
// Gets all of the text up to the last '/' or '\' in the path.
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
// Gets all of the text after the first '/' or '\' in the path.
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
// Gets the filename of the path
[[nodiscard]] std::string_view GetFilename(std::string_view path);
// Gets the extension of the filename
[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
} // namespace Common::FS

View File

@@ -11,13 +11,13 @@
#include <mutex>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <share.h> // For _SH_DENYWR
#include <windows.h> // For OutputDebugStringW
#else
#define _SH_DENYWR 0
#endif
#include "common/assert.h"
#include "common/fs/fs.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
@@ -148,19 +148,16 @@ void ColorConsoleBackend::Write(const Entry& entry) {
PrintColoredMessage(entry);
}
FileBackend::FileBackend(const std::string& filename) {
const auto old_filename = filename + ".old.txt";
FileBackend::FileBackend(const std::filesystem::path& filename) {
auto old_filename = filename;
old_filename += ".old.txt";
if (FS::Exists(old_filename)) {
FS::Delete(old_filename);
}
if (FS::Exists(filename)) {
FS::Rename(filename, old_filename);
}
// Existence checks are done within the functions themselves.
// We don't particularly care if these succeed or not.
void(FS::RemoveFile(old_filename));
void(FS::RenameFile(filename, old_filename));
// _SH_DENYWR allows read only access to the file for other programs.
// It is #defined to 0 on other platforms
file = FS::IOFile(filename, "w", _SH_DENYWR);
file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
}
void FileBackend::Write(const Entry& entry) {
@@ -181,7 +178,7 @@ void FileBackend::Write(const Entry& entry) {
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
if (entry.log_level >= Level::Error) {
file.Flush();
void(file.Flush());
}
}

View File

@@ -4,10 +4,11 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include "common/file_util.h"
#include "common/fs/file.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
@@ -81,7 +82,7 @@ public:
*/
class FileBackend : public Backend {
public:
explicit FileBackend(const std::string& filename);
explicit FileBackend(const std::filesystem::path& filename);
static const char* Name() {
return "file";

View File

@@ -59,8 +59,7 @@ std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size)
return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX);
}
std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
std::size_t uncompressed_size) {
std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t uncompressed_size) {
std::vector<u8> uncompressed(uncompressed_size);
const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()),
reinterpret_cast<char*>(uncompressed.data()),

View File

@@ -4,6 +4,7 @@
#pragma once
#include <span>
#include <vector>
#include "common/common_types.h"
@@ -53,7 +54,7 @@ namespace Common::Compression {
*
* @return the decompressed data.
*/
[[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed,
std::size_t uncompressed_size);
} // namespace Common::Compression
} // namespace Common::Compression

View File

@@ -2,24 +2,30 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <filesystem>
#include <stdlib.h>
#include <cstdlib>
#include <fmt/format.h>
#include "common/file_util.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/nvidia_flags.h"
namespace Common {
void ConfigureNvidiaEnvironmentFlags() {
#ifdef _WIN32
const std::string shader_path = Common::FS::SanitizePath(
fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)));
const std::string windows_path =
Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash);
void(Common::FS::CreateFullPath(shader_path + '/'));
void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str()));
const auto nvidia_shader_dir =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "nvidia";
if (!Common::FS::CreateDirs(nvidia_shader_dir)) {
return;
}
const auto windows_path_string =
Common::FS::PathToUTF8String(nvidia_shader_dir.lexically_normal());
void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str()));
void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
#endif
}

View File

@@ -109,7 +109,8 @@ struct OffsetOfCalculator {
}
}
return (next - start) * sizeof(MemberType) + Offset;
return static_cast<ptrdiff_t>(static_cast<size_t>(next - start) * sizeof(MemberType) +
Offset);
}
static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) {

View File

@@ -5,7 +5,7 @@
#include <string_view>
#include "common/assert.h"
#include "common/file_util.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
@@ -34,6 +34,10 @@ void LogSettings() {
LOG_INFO(Config, "{}: {}", name, value);
};
const auto log_path = [](std::string_view name, const std::filesystem::path& path) {
LOG_INFO(Config, "{}: {}", name, Common::FS::PathToUTF8String(path));
};
LOG_INFO(Config, "yuzu Configuration:");
log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
@@ -42,7 +46,7 @@ void LogSettings() {
log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
log_setting("CPU_Accuracy", values.cpu_accuracy);
log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue());
log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue());
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
@@ -59,11 +63,11 @@ void LogSettings() {
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
log_setting("Audio_OutputDevice", values.audio_device_id);
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir));
log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir));
log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir));
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
log_setting("Debugging_ProgramArgs", values.program_args);
log_setting("Services_BCATBackend", values.bcat_backend);
log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local);
@@ -106,6 +110,12 @@ void RestoreGlobalState(bool is_powered_on) {
// Core
values.use_multi_core.SetGlobal(true);
// CPU
values.cpu_accuracy.SetGlobal(true);
values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
// Renderer
values.renderer_backend.SetGlobal(true);
values.vulkan_device.SetGlobal(true);
@@ -130,7 +140,6 @@ void RestoreGlobalState(bool is_powered_on) {
values.region_index.SetGlobal(true);
values.time_zone_index.SetGlobal(true);
values.rng_seed.SetGlobal(true);
values.custom_rtc.SetGlobal(true);
values.sound_index.SetGlobal(true);
// Controls

View File

@@ -115,7 +115,7 @@ struct Values {
Setting<bool> use_multi_core;
// Cpu
CPUAccuracy cpu_accuracy;
Setting<CPUAccuracy> cpu_accuracy;
bool cpuopt_page_tables;
bool cpuopt_block_linking;
@@ -126,9 +126,9 @@ struct Values {
bool cpuopt_misc_ir;
bool cpuopt_reduce_misalign_checks;
bool cpuopt_unsafe_unfuse_fma;
bool cpuopt_unsafe_reduce_fp_error;
bool cpuopt_unsafe_inaccurate_nan;
Setting<bool> cpuopt_unsafe_unfuse_fma;
Setting<bool> cpuopt_unsafe_reduce_fp_error;
Setting<bool> cpuopt_unsafe_inaccurate_nan;
// Renderer
Setting<RendererBackend> renderer_backend;
@@ -157,7 +157,7 @@ struct Values {
// System
Setting<std::optional<u32>> rng_seed;
// Measured in seconds since epoch
Setting<std::optional<std::chrono::seconds>> custom_rtc;
std::optional<std::chrono::seconds> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
std::chrono::seconds custom_rtc_differential;

View File

@@ -9,7 +9,6 @@
#include <locale>
#include <sstream>
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -93,18 +92,6 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
return true;
}
void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
const std::string& _Filename) {
_CompleteFilename = _Path;
// check for seperator
if (DIR_SEP_CHR != *_CompleteFilename.rbegin())
_CompleteFilename += DIR_SEP_CHR;
// add the filename
_CompleteFilename += _Filename;
}
void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) {
std::istringstream iss(str);
output.resize(1);

View File

@@ -32,8 +32,6 @@ void SplitString(const std::string& str, char delim, std::vector<std::string>& o
bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename,
std::string* _pExtension);
void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
const std::string& _Filename);
[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
const std::string& dest);

View File

@@ -43,6 +43,8 @@
* The maximum height of a red-black tree is 2lg (n+1).
*/
#include "common/assert.h"
namespace Common {
template <typename T>
class RBHead {
@@ -322,9 +324,13 @@ void RB_INSERT_COLOR(RBHead<Node>* head, Node* elm) {
template <typename Node>
void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
Node* tmp;
while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root()) {
while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root() && parent != nullptr) {
if (RB_LEFT(parent) == elm) {
tmp = RB_RIGHT(parent);
if (!tmp) {
ASSERT_MSG(false, "tmp is invalid!");
break;
}
if (RB_IS_RED(tmp)) {
RB_SET_BLACKRED(tmp, parent);
RB_ROTATE_LEFT(head, parent, tmp);
@@ -366,6 +372,11 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
tmp = RB_LEFT(parent);
}
if (!tmp) {
ASSERT_MSG(false, "tmp is invalid!");
break;
}
if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) &&
(RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) {
RB_SET_COLOR(tmp, EntryColor::Red);

View File

@@ -32,7 +32,7 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz
return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT);
}
std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) {
std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) {
const std::size_t decompressed_size =
ZSTD_getDecompressedSize(compressed.data(), compressed.size());
std::vector<u8> decompressed(decompressed_size);

View File

@@ -4,6 +4,7 @@
#pragma once
#include <span>
#include <vector>
#include "common/common_types.h"
@@ -40,6 +41,6 @@ namespace Common::Compression {
*
* @return the decompressed data.
*/
[[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
[[nodiscard]] std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed);
} // namespace Common::Compression
} // namespace Common::Compression

View File

@@ -651,20 +651,17 @@ endif()
if (MSVC)
target_compile_options(core PRIVATE
# 'expression' : signed/unsigned mismatch
/we4018
# 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
/we4244
# 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
/we4245
# 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4254
# 'var' : conversion from 'size_t' to 'type', possible loss of data
/we4267
# 'context' : truncation from 'type1' to 'type2'
/we4305
# 'function' : not all control paths return a value
/we4715
/we4018 # 'expression' : signed/unsigned mismatch
/we4244 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
/we4245 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data
/we4305 # 'context' : truncation from 'type1' to 'type2'
/we4456 # Declaration of 'identifier' hides previous local declaration
/we4457 # Declaration of 'identifier' hides function parameter
/we4458 # Declaration of 'identifier' hides class member
/we4459 # Declaration of 'identifier' hides global declaration
/we4715 # 'function' : not all control paths return a value
)
else()
target_compile_options(core PRIVATE
@@ -672,6 +669,7 @@ else()
-Werror=ignored-qualifiers
-Werror=implicit-fallthrough
-Werror=sign-compare
-Werror=shadow
$<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>

View File

@@ -159,8 +159,6 @@ public:
*/
virtual void SetTPIDR_EL0(u64 value) = 0;
virtual void ChangeProcessorID(std::size_t new_core_id) = 0;
virtual void SaveContext(ThreadContext32& ctx) = 0;
virtual void SaveContext(ThreadContext64& ctx) = 0;
virtual void LoadContext(const ThreadContext32& ctx) = 0;

View File

@@ -24,7 +24,7 @@ namespace Core {
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent) : parent(parent) {}
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) : parent{parent_} {}
u8 MemoryRead8(u32 vaddr) override {
return parent.system.Memory().Read8(vaddr);
@@ -142,7 +142,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
config.far_code_offset = 256 * 1024 * 1024;
// Safe optimizations
if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -170,15 +170,15 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
// Unsafe optimizations
if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
}
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
}
}
@@ -255,10 +255,6 @@ void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
cp15->uprw = static_cast<u32>(value);
}
void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) {
jit->ChangeProcessorID(new_core_id);
}
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
Dynarmic::A32::Context context;
jit->SaveContext(context);

View File

@@ -48,7 +48,6 @@ public:
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
bool IsInThumbMode() const {
return (GetPSTATE() & 0x20) != 0;

View File

@@ -27,7 +27,7 @@ using Vector = Dynarmic::A64::Vector;
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent) : parent(parent) {}
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) : parent{parent_} {}
u8 MemoryRead8(u64 vaddr) override {
return parent.system.Memory().Read8(vaddr);
@@ -182,7 +182,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
config.far_code_offset = 256 * 1024 * 1024;
// Safe optimizations
if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -210,15 +210,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
// Unsafe optimizations
if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
}
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
}
}
@@ -296,10 +296,6 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
cb->tpidr_el0 = value;
}
void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) {
jit->ChangeProcessorID(new_core_id);
}
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
ctx.cpu_registers = jit->GetRegisters();
ctx.sp = jit->GetSP();

View File

@@ -45,7 +45,6 @@ public:
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
void SaveContext(ThreadContext32& ctx) override {}
void SaveContext(ThreadContext64& ctx) override;

View File

@@ -18,7 +18,7 @@ class DynarmicCP15 final : public Dynarmic::A32::Coprocessor {
public:
using CoprocReg = Dynarmic::A32::CoprocReg;
explicit DynarmicCP15(ARM_Dynarmic_32& parent) : parent(parent) {}
explicit DynarmicCP15(ARM_Dynarmic_32& parent_) : parent{parent_} {}
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
CoprocReg CRn, CoprocReg CRm,

View File

@@ -9,8 +9,8 @@
namespace Core {
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
: monitor(core_count), memory{memory} {}
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_)
: monitor{core_count_}, memory{memory_} {}
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;

View File

@@ -22,7 +22,7 @@ namespace Core {
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
public:
explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
explicit DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_);
~DynarmicExclusiveMonitor() override;
u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;

View File

@@ -6,7 +6,7 @@
#include <memory>
#include <utility>
#include "common/file_util.h"
#include "common/fs/fs.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/settings.h"
@@ -121,7 +121,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
dir->GetName());
}
if (Common::FS::IsDirectory(path)) {
if (Common::FS::IsDir(path)) {
return vfs->OpenFile(path + "/main", FileSys::Mode::Read);
}
@@ -173,7 +173,7 @@ struct System::Impl {
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
Settings::values.custom_rtc_differential =
Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time;
Settings::values.custom_rtc.value_or(current_time) - current_time;
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
@@ -289,7 +289,8 @@ struct System::Impl {
telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
perf_results.emulation_speed * 100.0);
telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps);
telemetry_session->AddField(performance, "Shutdown_Framerate",
perf_results.average_game_fps);
telemetry_session->AddField(performance, "Shutdown_Frametime",
perf_results.frametime * 1000.0);
telemetry_session->AddField(performance, "Mean_Frametime_MS",

View File

@@ -18,7 +18,7 @@
namespace Core {
CpuManager::CpuManager(System& system) : system{system} {}
CpuManager::CpuManager(System& system_) : system{system_} {}
CpuManager::~CpuManager() = default;
void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) {

View File

@@ -25,7 +25,7 @@ class System;
class CpuManager {
public:
explicit CpuManager(System& system);
explicit CpuManager(System& system_);
CpuManager(const CpuManager&) = delete;
CpuManager(CpuManager&&) = delete;

View File

@@ -10,8 +10,8 @@
namespace Core::Crypto {
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_,
std::size_t base_offset)
: EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR) {}
std::size_t base_offset_)
: EncryptionLayer(std::move(base_)), base_offset(base_offset_), cipher(key_, Mode::CTR) {}
std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const {
if (length == 0)

View File

@@ -17,7 +17,7 @@ class CTREncryptionLayer : public EncryptionLayer {
public:
using IVData = std::array<u8, 16>;
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, std::size_t base_offset);
CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, std::size_t base_offset_);
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;

View File

@@ -18,8 +18,9 @@
#include <mbedtls/cmac.h>
#include <mbedtls/sha256.h>
#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
@@ -325,46 +326,55 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
}
std::optional<Key128> DeriveSDSeed() {
const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000043",
"rb+");
const auto system_save_43_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000043";
const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!save_43.IsOpen()) {
return std::nullopt;
}
const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
"/Nintendo/Contents/private",
"rb+");
const auto sd_private_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/private";
const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!sd_private.IsOpen()) {
return std::nullopt;
}
std::array<u8, 0x10> private_seed{};
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
if (sd_private.Read(private_seed) != private_seed.size()) {
return std::nullopt;
}
std::array<u8, 0x10> buffer{};
std::size_t offset = 0;
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
if (!save_43.Seek(offset, SEEK_SET)) {
s64 offset = 0;
for (; offset + 0x10 < static_cast<s64>(save_43.GetSize()); ++offset) {
if (!save_43.Seek(offset)) {
return std::nullopt;
}
if (save_43.Read(buffer) != buffer.size()) {
return std::nullopt;
}
save_43.ReadBytes(buffer.data(), buffer.size());
if (buffer == private_seed) {
break;
}
}
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
if (!save_43.Seek(offset + 0x10)) {
return std::nullopt;
}
Key128 seed{};
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
if (save_43.Read(seed) != seed.size()) {
return std::nullopt;
}
return seed;
}
@@ -435,7 +445,7 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
}
std::vector<u8> buffer(ticket_save.GetSize());
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
if (ticket_save.Read(buffer) != buffer.size()) {
return {};
}
@@ -458,7 +468,7 @@ static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
const std::array<u8, size>& rhs) {
std::array<u8, size> out;
std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(),
[](u8 lhs, u8 rhs) { return u8(lhs ^ rhs); });
[](u8 lhs_elem, u8 rhs_elem) { return u8(lhs_elem ^ rhs_elem); });
return out;
}
@@ -566,27 +576,26 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
if (!Common::FS::Exists(yuzu_keys_dir)) {
Common::FS::CreateDir(yuzu_keys_dir);
if (!Common::FS::CreateDir(yuzu_keys_dir)) {
LOG_ERROR(Core, "Failed to create the keys directory.");
}
if (Settings::values.use_dev_keys) {
dev_mode = true;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false);
LoadFromFile(yuzu_keys_dir / "dev.keys", false);
LoadFromFile(yuzu_keys_dir / "dev.keys_autogenerated", false);
} else {
dev_mode = false;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false);
LoadFromFile(yuzu_keys_dir / "prod.keys", false);
LoadFromFile(yuzu_keys_dir / "prod.keys_autogenerated", false);
}
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
LoadFromFile(yuzu_keys_dir / "title.keys", true);
LoadFromFile(yuzu_keys_dir / "title.keys_autogenerated", true);
LoadFromFile(yuzu_keys_dir / "console.keys", false);
LoadFromFile(yuzu_keys_dir / "console.keys_autogenerated", false);
}
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
@@ -597,9 +606,14 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
[](u8 c) { return std::isxdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) {
if (!Common::FS::Exists(file_path)) {
return;
}
std::ifstream file;
Common::FS::OpenFStream(file, filename, std::ios_base::in);
Common::FS::OpenFileStream(file, file_path, std::ios_base::in);
if (!file.is_open()) {
return;
}
@@ -694,15 +708,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
}
}
void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title) {
if (Common::FS::Exists(dir1 + DIR_SEP + filename)) {
LoadFromFile(dir1 + DIR_SEP + filename, title);
} else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) {
LoadFromFile(dir2 + DIR_SEP + filename, title);
}
}
bool KeyManager::BaseDeriveNecessary() const {
const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
return !HasKey(key_type, index1, index2);
@@ -766,30 +771,35 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
template <size_t Size>
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key) {
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
std::string filename = "title.keys_autogenerated";
if (category == KeyCategory::Standard) {
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
} else if (category == KeyCategory::Console) {
filename = "console.keys_autogenerated";
}
const auto path = yuzu_keys_dir + DIR_SEP + filename;
const auto path = yuzu_keys_dir / filename;
const auto add_info_text = !Common::FS::Exists(path);
Common::FS::CreateFullPath(path);
Common::FS::IOFile file{path, "a"};
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append,
Common::FS::FileType::TextFile};
if (!file.IsOpen()) {
return;
}
if (add_info_text) {
file.WriteString(
void(file.WriteString(
"# This file is autogenerated by Yuzu\n"
"# It serves to store keys that were automatically generated from the normal keys\n"
"# If you are experiencing issues involving keys, it may help to delete this file\n");
"# If you are experiencing issues involving keys, it may help to delete this file\n"));
}
file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key)));
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))));
LoadFromFile(path, category == KeyCategory::Title);
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -861,20 +871,17 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
}
bool KeyManager::KeyFileExists(bool title) {
const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
if (title) {
return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
return Common::FS::Exists(yuzu_keys_dir / "title.keys");
}
if (Settings::values.use_dev_keys) {
return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
return Common::FS::Exists(yuzu_keys_dir / "dev.keys");
}
return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
return Common::FS::Exists(yuzu_keys_dir / "prod.keys");
}
void KeyManager::DeriveSDSeedLazy() {
@@ -1115,15 +1122,21 @@ void KeyManager::PopulateTickets() {
return;
}
const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/80000000000000e1",
"rb+");
const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/80000000000000e2",
"rb+");
const auto system_save_e1_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1";
const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
const auto system_save_e2_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2";
const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
const auto blob2 = GetTicketblob(save_e2);
auto res = GetTicketblob(save_e1);
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
const auto idx = res.size();
res.insert(res.end(), blob2.begin(), blob2.end());

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <filesystem>
#include <map>
#include <optional>
#include <string>
@@ -283,9 +284,8 @@ private:
std::array<u8, 576> eticket_extended_kek{};
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title);
void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
template <size_t Size>
void WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key);

View File

@@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/file_util.h"
#include "common/fs/path_util.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/registered_cache.h"
@@ -85,7 +85,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
VirtualFilesystem file_system) const {
auto& keys = Core::Crypto::KeyManager::Instance();
Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)};
keys.PopulateFromPartitionData(pdm);
switch (id) {

View File

@@ -10,11 +10,13 @@
namespace FileSys {
enum class Mode : u32 {
Read = 1,
Write = 2,
Read = 1 << 0,
Write = 1 << 1,
ReadWrite = Read | Write,
Append = 4,
Append = 1 << 2,
ReadAppend = Read | Append,
WriteAppend = Write | Append,
All = ReadWrite | Append,
};
DECLARE_ENUM_FLAG_OPERATORS(Mode)

View File

@@ -39,10 +39,10 @@ CNMT::CNMT(VirtualFile file) {
}
}
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records)
: header(std::move(header)), opt_header(std::move(opt_header)),
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_)
: header(std::move(header_)), opt_header(std::move(opt_header_)),
content_records(std::move(content_records_)), meta_records(std::move(meta_records_)) {}
CNMT::~CNMT() = default;

View File

@@ -87,8 +87,8 @@ static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
class CNMT {
public:
explicit CNMT(VirtualFile file);
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records);
CNMT(CNMTHeader header_, OptionalHeader opt_header_,
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
~CNMT();
u64 GetTitleID() const;

View File

@@ -8,7 +8,6 @@
#include <iterator>
#include <utility>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h"

View File

@@ -7,7 +7,6 @@
#include <cstddef>
#include <cstring>
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/settings.h"

View File

@@ -7,11 +7,12 @@
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/file_util.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/key_manager.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/registered_cache.h"
@@ -592,6 +593,12 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
const CNMT cnmt(cnmt_file);
const auto title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
if (title_id == GetBaseTitleID(title_id) && version == 0) {
return InstallResult::ErrorBaseInstall;
}
const auto result = RemoveExistingEntry(title_id);
// Install Metadata File

View File

@@ -38,6 +38,7 @@ enum class InstallResult {
ErrorAlreadyExists,
ErrorCopyFailed,
ErrorMetaFailed,
ErrorBaseInstall,
};
struct ContentProviderEntry {

View File

@@ -20,8 +20,8 @@
namespace FileSys {
NSP::NSP(VirtualFile file_, std::size_t program_index)
: file(std::move(file_)), program_index(program_index), status{Loader::ResultStatus::Success},
NSP::NSP(VirtualFile file_, std::size_t program_index_)
: file(std::move(file_)), program_index(program_index_), status{Loader::ResultStatus::Success},
pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus();

View File

@@ -27,7 +27,7 @@ enum class ContentRecordType : u8;
class NSP : public ReadOnlyVfsDirectory {
public:
explicit NSP(VirtualFile file, std::size_t program_index = 0);
explicit NSP(VirtualFile file_, std::size_t program_index_ = 0);
~NSP() override;
Loader::ResultStatus GetStatus() const;

View File

@@ -5,8 +5,7 @@
#include <algorithm>
#include <numeric>
#include <string>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/vfs.h"
@@ -122,15 +121,14 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
return nullptr;
for (const auto& file : old_dir->GetFiles()) {
const auto x =
CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName());
const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName());
if (x == nullptr)
return nullptr;
}
for (const auto& dir : old_dir->GetSubdirectories()) {
const auto x =
CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName());
CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName());
if (x == nullptr)
return nullptr;
}

View File

@@ -23,8 +23,8 @@ static bool VerifyConcatenationMapContinuity(const std::multimap<u64, VirtualFil
return map.begin()->first == 0;
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
: name(std::move(name)) {
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name_)
: name(std::move(name_)) {
std::size_t next_offset = 0;
for (const auto& file : files_) {
files.emplace(next_offset, file);
@@ -32,8 +32,8 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
}
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files_, std::string name)
: files(std::move(files_)), name(std::move(name)) {
ConcatenatedVfsFile::ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files_, std::string name_)
: files(std::move(files_)), name(std::move(name_)) {
ASSERT(VerifyConcatenationMapContinuity(files));
}

View File

@@ -14,8 +14,8 @@ namespace FileSys {
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
// read-only.
class ConcatenatedVfsFile : public VfsFile {
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files, std::string name);
explicit ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name_);
explicit ConcatenatedVfsFile(std::multimap<u64, VirtualFile> files, std::string name_);
public:
~ConcatenatedVfsFile() override;

View File

@@ -8,8 +8,8 @@
namespace FileSys {
LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
: dirs(std::move(dirs)), name(std::move(name)) {}
LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_)
: dirs(std::move(dirs_)), name(std::move(name_)) {}
LayeredVfsDirectory::~LayeredVfsDirectory() = default;

View File

@@ -13,7 +13,7 @@ namespace FileSys {
// one and falling back to the one after. The highest priority directory (overwrites all others)
// should be element 0 in the dirs vector.
class LayeredVfsDirectory : public VfsDirectory {
LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
explicit LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_);
public:
~LayeredVfsDirectory() override;

View File

@@ -3,7 +3,17 @@
// Refer to the license.txt file included.
#include <string>
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#include <zip.h>
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_libzip.h"

View File

@@ -7,8 +7,9 @@
#include <iterator>
#include <utility>
#include "common/assert.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "core/file_sys/vfs_real.h"
@@ -16,33 +17,31 @@ namespace FileSys {
namespace FS = Common::FS;
static std::string ModeFlagsToString(Mode mode) {
std::string mode_str;
namespace {
// Calculate the correct open mode for the file.
if (True(mode & Mode::Read) && True(mode & Mode::Write)) {
if (True(mode & Mode::Append)) {
mode_str = "a+";
} else {
mode_str = "r+";
}
} else {
if (True(mode & Mode::Read)) {
mode_str = "r";
} else if (True(mode & Mode::Append)) {
mode_str = "a";
} else if (True(mode & Mode::Write)) {
mode_str = "w";
} else {
UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode));
}
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
switch (mode) {
case Mode::Read:
return FS::FileAccessMode::Read;
case Mode::Write:
return FS::FileAccessMode::Write;
case Mode::ReadWrite:
return FS::FileAccessMode::ReadWrite;
case Mode::Append:
return FS::FileAccessMode::Append;
case Mode::ReadAppend:
return FS::FileAccessMode::ReadAppend;
case Mode::WriteAppend:
return FS::FileAccessMode::Append;
case Mode::All:
return FS::FileAccessMode::ReadAppend;
default:
return {};
}
mode_str += "b";
return mode_str;
}
} // Anonymous namespace
RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
RealVfsFilesystem::~RealVfsFilesystem() = default;
@@ -63,7 +62,7 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
if (!FS::Exists(path)) {
return VfsEntryType::None;
}
if (FS::IsDirectory(path)) {
if (FS::IsDir(path)) {
return VfsEntryType::Directory;
}
@@ -81,12 +80,13 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
}
}
if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
FS::CreateEmptyFile(path);
auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
if (!backing) {
return nullptr;
}
auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str());
cache.insert_or_assign(path, backing);
cache.insert_or_assign(path, std::move(backing));
// Cannot use make_shared as RealVfsFile constructor is private
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
@@ -94,25 +94,29 @@ 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 path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
if (!FS::Exists(path)) {
FS::CreateFullPath(path_fwd);
if (!FS::CreateEmptyFile(path)) {
// Current usages of CreateFile expect to delete the contents of an existing file.
if (FS::IsFile(path)) {
FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
if (!temp.IsOpen()) {
return nullptr;
}
temp.Close();
return OpenFile(path, perms);
}
if (!FS::NewFile(path)) {
return nullptr;
}
return OpenFile(path, perms);
}
VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
!FS::Copy(old_path, new_path)) {
return nullptr;
}
return OpenFile(new_path, Mode::ReadWrite);
// Unused
return nullptr;
}
VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
@@ -127,13 +131,13 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
file->Close();
}
if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
!FS::Rename(old_path, new_path)) {
if (!FS::RenameFile(old_path, new_path)) {
return nullptr;
}
cache.erase(old_path);
if (file->Open(new_path, "r+b")) {
file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
if (file->IsOpen()) {
cache.insert_or_assign(new_path, std::move(file));
} else {
LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path);
@@ -157,7 +161,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
cache.erase(path);
}
return FS::Delete(path);
return FS::RemoveFile(path);
}
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
@@ -168,12 +172,8 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
if (!FS::Exists(path)) {
FS::CreateFullPath(path_fwd);
if (!FS::CreateDir(path)) {
return nullptr;
}
if (!FS::CreateDirs(path)) {
return nullptr;
}
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
@@ -181,13 +181,8 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
std::string_view new_path_) {
const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) {
return nullptr;
}
FS::CopyDir(old_path, new_path);
return OpenDirectory(new_path, Mode::ReadWrite);
// Unused
return nullptr;
}
VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
@@ -195,8 +190,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
!FS::Rename(old_path, new_path)) {
if (!FS::RenameDir(old_path, new_path)) {
return nullptr;
}
@@ -208,7 +202,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
const auto file_old_path =
FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault);
auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()),
FS::DirectorySeparator::PlatformDefault);
const auto& cached = cache[file_old_path];
@@ -218,7 +212,8 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
auto file = cached.lock();
cache.erase(file_old_path);
if (file->Open(file_new_path, "r+b")) {
file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
if (file->IsOpen()) {
cache.insert_or_assign(std::move(file_new_path), std::move(file));
} else {
LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path);
@@ -245,15 +240,13 @@ bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
cache.erase(kv.first);
}
return FS::DeleteDirRecursively(path);
return FS::RemoveDirRecursively(path);
}
RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_,
const std::string& path_, Mode perms_)
: base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)),
path_components(FS::SplitPathComponents(path_)),
parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {}
path_components(FS::SplitPathComponents(path_)), perms(perms_) {}
RealVfsFile::~RealVfsFile() = default;
@@ -266,7 +259,7 @@ std::size_t RealVfsFile::GetSize() const {
}
bool RealVfsFile::Resize(std::size_t new_size) {
return backing->Resize(new_size);
return backing->SetSize(new_size);
}
VirtualDir RealVfsFile::GetContainingDirectory() const {
@@ -274,33 +267,33 @@ VirtualDir RealVfsFile::GetContainingDirectory() const {
}
bool RealVfsFile::IsWritable() const {
return True(perms & Mode::WriteAppend);
return True(perms & Mode::Write);
}
bool RealVfsFile::IsReadable() const {
return True(perms & Mode::ReadWrite);
return True(perms & Mode::Read);
}
std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
if (!backing->Seek(static_cast<s64>(offset))) {
return 0;
}
return backing->ReadBytes(data, length);
return backing->ReadSpan(std::span{data, length});
}
std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
if (!backing->Seek(static_cast<s64>(offset))) {
return 0;
}
return backing->WriteBytes(data, length);
return backing->WriteSpan(std::span{data, length});
}
bool RealVfsFile::Rename(std::string_view name) {
return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr;
return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
}
bool RealVfsFile::Close() {
return backing->Close();
void RealVfsFile::Close() {
backing->Close();
}
// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
@@ -313,15 +306,16 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
}
std::vector<VirtualFile> out;
FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
if (!FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenFile(full_path, perms));
}
return true;
});
const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
const auto full_path_string = FS::PathToUTF8String(full_path);
out.emplace_back(base.OpenFile(full_path_string, perms));
return true;
};
FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File);
return out;
}
@@ -333,42 +327,41 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
}
std::vector<VirtualDir> out;
FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
if (FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenDirectory(full_path, perms));
}
return true;
});
const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
const auto full_path_string = FS::PathToUTF8String(full_path);
out.emplace_back(base.OpenDirectory(full_path_string, perms));
return true;
};
FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory);
return out;
}
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
: base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
path_components(FS::SplitPathComponents(path)),
parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {
if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
FS::CreateDir(path);
path_components(FS::SplitPathComponents(path)), perms(perms_) {
if (!FS::Exists(path) && True(perms & Mode::Write)) {
void(FS::CreateDirs(path));
}
}
RealVfsDirectory::~RealVfsDirectory() = default;
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path));
if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
return nullptr;
}
return base.OpenFile(full_path, perms);
}
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path));
if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
return nullptr;
}
return base.OpenDirectory(full_path, perms);
@@ -383,17 +376,20 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
}
VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path));
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
if (!FS::CreateParentDirs(full_path)) {
return nullptr;
}
return base.CreateFile(full_path, perms);
}
VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) {
const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path));
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
return base.CreateDirectory(full_path, perms);
}
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name));
const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
return base.DeleteDirectory(full_path);
}
@@ -406,11 +402,11 @@ std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
}
bool RealVfsDirectory::IsWritable() const {
return True(perms & Mode::WriteAppend);
return True(perms & Mode::Write);
}
bool RealVfsDirectory::IsReadable() const {
return True(perms & Mode::ReadWrite);
return True(perms & Mode::Read);
}
std::string RealVfsDirectory::GetName() const {
@@ -426,27 +422,27 @@ VirtualDir RealVfsDirectory::GetParentDirectory() const {
}
VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
const std::string subdir_path = (path + DIR_SEP).append(name);
const std::string subdir_path = (path + '/').append(name);
return base.CreateDirectory(subdir_path, perms);
}
VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
const std::string file_path = (path + DIR_SEP).append(name);
const std::string file_path = (path + '/').append(name);
return base.CreateFile(file_path, perms);
}
bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
const std::string subdir_path = (path + DIR_SEP).append(name);
const std::string subdir_path = (path + '/').append(name);
return base.DeleteDirectory(subdir_path);
}
bool RealVfsDirectory::DeleteFile(std::string_view name) {
const std::string file_path = (path + DIR_SEP).append(name);
const std::string file_path = (path + '/').append(name);
return base.DeleteFile(file_path);
}
bool RealVfsDirectory::Rename(std::string_view name) {
const std::string new_name = (parent_path + DIR_SEP).append(name);
const std::string new_name = (parent_path + '/').append(name);
return base.MoveFile(path, new_name) != nullptr;
}
@@ -462,14 +458,17 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
}
std::map<std::string, VfsEntryType, std::less<>> out;
FS::ForeachDirectoryEntry(
nullptr, path,
[&out](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
out.emplace(filename,
FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
return true;
});
const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) {
const auto filename = FS::PathToUTF8String(full_path.filename());
out.insert_or_assign(filename,
FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
return true;
};
FS::IterateDirEntries(path, callback);
return out;
}

View File

@@ -61,14 +61,13 @@ private:
RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing,
const std::string& path, Mode perms = Mode::Read);
bool Close();
void Close();
RealVfsFilesystem& base;
std::shared_ptr<Common::FS::IOFile> backing;
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
std::vector<std::string> parent_components;
Mode perms;
};
@@ -110,7 +109,6 @@ private:
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
std::vector<std::string> parent_components;
Mode perms;
};

View File

@@ -14,9 +14,9 @@ namespace FileSys {
class StaticVfsFile : public VfsFile {
public:
explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "",
VirtualDir parent = nullptr)
: value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {}
explicit StaticVfsFile(u8 value_, std::size_t size_ = 0, std::string name_ = "",
VirtualDir parent_ = nullptr)
: value{value_}, size{size_}, name{std::move(name_)}, parent{std::move(parent_)} {}
std::string GetName() const override {
return name;

View File

@@ -7,8 +7,8 @@
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
: data(std::move(initial_data)), parent(std::move(parent)), name(std::move(name)) {}
VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name_, VirtualDir parent_)
: data(std::move(initial_data)), parent(std::move(parent_)), name(std::move(name_)) {}
VectorVfsFile::~VectorVfsFile() = default;

View File

@@ -75,8 +75,8 @@ std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& da
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:
explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
VirtualDir parent = nullptr);
explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name_ = "",
VirtualDir parent_ = nullptr);
~VectorVfsFile() override;
std::string GetName() const override;

View File

@@ -11,7 +11,7 @@
#include <mbedtls/md.h>
#include <mbedtls/sha256.h>
#include "common/file_util.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"

View File

@@ -26,7 +26,7 @@ public:
private:
class Device : public Input::TouchDevice {
public:
explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
explicit Device(std::weak_ptr<TouchState>&& touch_state_) : touch_state(touch_state_) {}
Input::TouchStatus GetStatus() const override {
if (auto state = touch_state.lock()) {
std::lock_guard guard{state->mutex};

View File

@@ -32,7 +32,8 @@ enum class CommandType : u32 {
Control = 5,
RequestWithContext = 6,
ControlWithContext = 7,
Unspecified,
TIPC_Close = 15,
TIPC_CommandRegion = 16, // Start of TIPC commands, this is an offset.
};
struct CommandHeader {
@@ -57,6 +58,20 @@ struct CommandHeader {
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
BitField<31, 1, u32> enable_handle_descriptor;
};
bool IsTipc() const {
return type.Value() >= CommandType::TIPC_CommandRegion;
}
bool IsCloseCommand() const {
switch (type.Value()) {
case CommandType::Close:
case CommandType::TIPC_Close:
return true;
default:
return false;
}
}
};
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");

View File

@@ -15,6 +15,8 @@
#include "core/hle/ipc.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/result.h"
@@ -26,19 +28,19 @@ class RequestHelperBase {
protected:
Kernel::HLERequestContext* context = nullptr;
u32* cmdbuf;
ptrdiff_t index = 0;
u32 index = 0;
public:
explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {}
explicit RequestHelperBase(Kernel::HLERequestContext& context)
: context(&context), cmdbuf(context.CommandBuffer()) {}
explicit RequestHelperBase(Kernel::HLERequestContext& ctx)
: context(&ctx), cmdbuf(ctx.CommandBuffer()) {}
void Skip(u32 size_in_words, bool set_to_null) {
if (set_to_null) {
memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
}
index += static_cast<ptrdiff_t>(size_in_words);
index += size_in_words;
}
/**
@@ -51,11 +53,11 @@ public:
}
u32 GetCurrentOffset() const {
return static_cast<u32>(index);
return index;
}
void SetCurrentOffset(u32 offset) {
index = static_cast<ptrdiff_t>(offset);
index = offset;
}
};
@@ -69,23 +71,21 @@ public:
AlwaysMoveHandles = 1,
};
explicit ResponseBuilder(Kernel::HLERequestContext& ctx, u32 normal_params_size,
u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0,
explicit ResponseBuilder(Kernel::HLERequestContext& ctx, u32 normal_params_size_,
u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0,
Flags flags = Flags::None)
: RequestHelperBase(ctx), normal_params_size(normal_params_size),
num_handles_to_copy(num_handles_to_copy),
num_objects_to_move(num_objects_to_move), kernel{ctx.kernel} {
: RequestHelperBase(ctx), normal_params_size(normal_params_size_),
num_handles_to_copy(num_handles_to_copy_),
num_objects_to_move(num_objects_to_move_), kernel{ctx.kernel} {
memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
ctx.ClearIncomingObjects();
IPC::CommandHeader header{};
// The entire size of the raw data section in u32 units, including the 16 bytes of mandatory
// padding.
u64 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
u32 raw_data_size = ctx.write_size =
ctx.IsTipc() ? normal_params_size - 1 : normal_params_size;
u32 num_handles_to_move{};
u32 num_domain_objects{};
const bool always_move_handles{
@@ -97,10 +97,19 @@ public:
}
if (ctx.Session()->IsDomain()) {
raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects;
raw_data_size +=
static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects);
ctx.write_size += num_domain_objects;
}
header.data_size.Assign(static_cast<u32>(raw_data_size));
if (ctx.IsTipc()) {
header.type.Assign(ctx.GetCommandType());
} else {
raw_data_size += static_cast<u32>(sizeof(IPC::DataPayloadHeader) / sizeof(u32) + 4 +
normal_params_size);
}
header.data_size.Assign(raw_data_size);
if (num_handles_to_copy || num_handles_to_move) {
header.enable_handle_descriptor.Assign(1);
}
@@ -108,25 +117,34 @@ public:
if (header.enable_handle_descriptor) {
IPC::HandleDescriptorHeader handle_descriptor_header{};
handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy);
handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_);
handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move);
PushRaw(handle_descriptor_header);
ctx.handles_offset = index;
Skip(num_handles_to_copy + num_handles_to_move, true);
}
AlignWithPadding();
if (!ctx.IsTipc()) {
AlignWithPadding();
if (ctx.Session()->IsDomain() && ctx.HasDomainMessageHeader()) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
if (ctx.Session()->IsDomain() && ctx.HasDomainMessageHeader()) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
}
IPC::DataPayloadHeader data_payload_header{};
data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O');
PushRaw(data_payload_header);
}
IPC::DataPayloadHeader data_payload_header{};
data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O');
PushRaw(data_payload_header);
data_payload_index = index;
datapayload_index = index;
ctx.data_payload_offset = index;
ctx.write_size += index;
ctx.domain_offset = static_cast<u32>(index + raw_data_size / sizeof(u32));
}
template <class T>
@@ -134,6 +152,9 @@ public:
if (context->Session()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
kernel.CurrentProcess()->GetResourceLimit()->Reserve(
Kernel::LimitableResource::Sessions, 1);
auto* session = Kernel::KSession::Create(kernel);
session->Initialize(nullptr, iface->GetServiceName());
@@ -147,24 +168,6 @@ public:
PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...));
}
void ValidateHeader() {
const std::size_t num_domain_objects = context->NumDomainObjects();
const std::size_t num_move_objects = context->NumMoveObjects();
ASSERT_MSG(!num_domain_objects || !num_move_objects,
"cannot move normal handles and domain objects");
ASSERT_MSG((index - datapayload_index) == normal_params_size,
"normal_params_size value is incorrect");
ASSERT_MSG((num_domain_objects + num_move_objects) == num_objects_to_move,
"num_objects_to_move value is incorrect");
ASSERT_MSG(context->NumCopyObjects() == num_handles_to_copy,
"num_handles_to_copy value is incorrect");
}
// Validate on destruction, as there shouldn't be any case where we don't want it
~ResponseBuilder() {
ValidateHeader();
}
void PushImpl(s8 value);
void PushImpl(s16 value);
void PushImpl(s32 value);
@@ -229,14 +232,14 @@ private:
u32 normal_params_size{};
u32 num_handles_to_copy{};
u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent
std::ptrdiff_t datapayload_index{};
u32 data_payload_index{};
Kernel::KernelCore& kernel;
};
/// Push ///
inline void ResponseBuilder::PushImpl(s32 value) {
cmdbuf[index++] = static_cast<u32>(value);
cmdbuf[index++] = value;
}
inline void ResponseBuilder::PushImpl(u32 value) {
@@ -384,7 +387,7 @@ public:
std::shared_ptr<T> PopIpcInterface() {
ASSERT(context->Session()->IsDomain());
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
return context->GetDomainHandler<T>(Pop<u32>() - 1);
}
};

View File

@@ -12,8 +12,8 @@
namespace Kernel {
GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel)
: kernel{kernel}, scheduler_lock{kernel} {}
GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel_)
: kernel{kernel_}, scheduler_lock{kernel_} {}
GlobalSchedulerContext::~GlobalSchedulerContext() = default;

View File

@@ -34,7 +34,7 @@ class GlobalSchedulerContext final {
public:
using LockType = KAbstractSchedulerLock<KScheduler>;
explicit GlobalSchedulerContext(KernelCore& kernel);
explicit GlobalSchedulerContext(KernelCore& kernel_);
~GlobalSchedulerContext();
/// Adds a new thread to the scheduler

View File

@@ -35,11 +35,11 @@ SessionRequestHandler::SessionRequestHandler() = default;
SessionRequestHandler::~SessionRequestHandler() = default;
void SessionRequestHandler::ClientConnected(KServerSession* session) {
session->SetHleHandler(shared_from_this());
session->SetSessionHandler(shared_from_this());
}
void SessionRequestHandler::ClientDisconnected(KServerSession* session) {
session->SetHleHandler(nullptr);
session->SetSessionHandler(nullptr);
}
HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_,
@@ -55,7 +55,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
IPC::RequestParser rp(src_cmdbuf);
command_header = rp.PopRaw<IPC::CommandHeader>();
if (command_header->type == IPC::CommandType::Close) {
if (command_header->IsCloseCommand()) {
// Close does not populate the rest of the IPC header
return;
}
@@ -64,19 +64,15 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
if (command_header->enable_handle_descriptor) {
handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
if (handle_descriptor_header->send_current_pid) {
rp.Skip(2, false);
pid = rp.Pop<u64>();
}
if (incoming) {
// Populate the object lists with the data in the IPC request.
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
const u32 copy_handle{rp.Pop<Handle>()};
copy_handles.push_back(copy_handle);
copy_objects.push_back(handle_table.GetObject(copy_handle).GetPointerUnsafe());
incoming_copy_handles.push_back(rp.Pop<Handle>());
}
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) {
const u32 move_handle{rp.Pop<Handle>()};
move_handles.push_back(move_handle);
move_objects.push_back(handle_table.GetObject(move_handle).GetPointerUnsafe());
incoming_move_handles.push_back(rp.Pop<Handle>());
}
} else {
// For responses we just ignore the handles, they're empty and will be populated when
@@ -86,52 +82,56 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
}
}
for (unsigned i = 0; i < command_header->num_buf_x_descriptors; ++i) {
for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
}
for (unsigned i = 0; i < command_header->num_buf_a_descriptors; ++i) {
for (u32 i = 0; i < command_header->num_buf_a_descriptors; ++i) {
buffer_a_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
for (unsigned i = 0; i < command_header->num_buf_b_descriptors; ++i) {
for (u32 i = 0; i < command_header->num_buf_b_descriptors; ++i) {
buffer_b_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
for (unsigned i = 0; i < command_header->num_buf_w_descriptors; ++i) {
for (u32 i = 0; i < command_header->num_buf_w_descriptors; ++i) {
buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
// Padding to align to 16 bytes
rp.AlignWithPadding();
if (!command_header->IsTipc()) {
// Padding to align to 16 bytes
rp.AlignWithPadding();
if (Session()->IsDomain() && ((command_header->type == IPC::CommandType::Request ||
command_header->type == IPC::CommandType::RequestWithContext) ||
!incoming)) {
// If this is an incoming message, only CommandType "Request" has a domain header
// All outgoing domain messages have the domain header, if only incoming has it
if (incoming || domain_message_header) {
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
} else {
if (Session()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
if (Session()->IsDomain() &&
((command_header->type == IPC::CommandType::Request ||
command_header->type == IPC::CommandType::RequestWithContext) ||
!incoming)) {
// If this is an incoming message, only CommandType "Request" has a domain header
// All outgoing domain messages have the domain header, if only incoming has it
if (incoming || domain_message_header) {
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
} else {
if (Session()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
}
}
}
}
data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
data_payload_offset = rp.GetCurrentOffset();
data_payload_offset = rp.GetCurrentOffset();
if (domain_message_header && domain_message_header->command ==
IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) {
// CloseVirtualHandle command does not have SFC* or any data
return;
}
if (domain_message_header &&
domain_message_header->command ==
IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) {
// CloseVirtualHandle command does not have SFC* or any data
return;
}
if (incoming) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
} else {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
if (incoming) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
} else {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
}
}
rp.SetCurrentOffset(buffer_c_offset);
@@ -144,14 +144,14 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) {
buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
} else {
unsigned num_buf_c_descriptors =
static_cast<unsigned>(command_header->buf_c_descriptor_flags.Value()) - 2;
u32 num_buf_c_descriptors =
static_cast<u32>(command_header->buf_c_descriptor_flags.Value()) - 2;
// This is used to detect possible underflows, in case something is broken
// with the two ifs above and the flags value is == 0 || == 1.
ASSERT(num_buf_c_descriptors < 14);
for (unsigned i = 0; i < num_buf_c_descriptors; ++i) {
for (u32 i = 0; i < num_buf_c_descriptors; ++i) {
buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
}
}
@@ -166,84 +166,55 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const KHandleTable& handle_table,
u32_le* src_cmdbuf) {
ParseCommandBuffer(handle_table, src_cmdbuf, true);
if (command_header->type == IPC::CommandType::Close) {
if (command_header->IsCloseCommand()) {
// Close does not populate the rest of the IPC header
return RESULT_SUCCESS;
}
// The data_size already includes the payload header, the padding and the domain header.
std::size_t size = data_payload_offset + command_header->data_size -
sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4;
if (domain_message_header)
size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32);
std::copy_n(src_cmdbuf, size, cmd_buf.begin());
std::copy_n(src_cmdbuf, IPC::COMMAND_BUFFER_LENGTH, cmd_buf.begin());
return RESULT_SUCCESS;
}
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_thread) {
auto current_offset = handles_offset;
auto& owner_process = *requesting_thread.GetOwnerProcess();
auto& handle_table = owner_process.GetHandleTable();
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
memory.ReadBlock(owner_process, requesting_thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
// The header was already built in the internal command buffer. Attempt to parse it to verify
// the integrity and then copy it over to the target command buffer.
ParseCommandBuffer(handle_table, cmd_buf.data(), false);
// The data_size already includes the payload header, the padding and the domain header.
std::size_t size = data_payload_offset + command_header->data_size -
sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4;
if (domain_message_header)
size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32);
std::copy_n(cmd_buf.begin(), size, dst_cmdbuf.data());
if (command_header->enable_handle_descriptor) {
ASSERT_MSG(!move_objects.empty() || !copy_objects.empty(),
"Handle descriptor bit set but no handles to translate");
// We write the translated handles at a specific offset in the command buffer, this space
// was already reserved when writing the header.
std::size_t current_offset =
(sizeof(IPC::CommandHeader) + sizeof(IPC::HandleDescriptorHeader)) / sizeof(u32);
ASSERT_MSG(!handle_descriptor_header->send_current_pid, "Sending PID is not implemented");
ASSERT(copy_objects.size() == handle_descriptor_header->num_handles_to_copy);
ASSERT(move_objects.size() == handle_descriptor_header->num_handles_to_move);
// We don't make a distinction between copy and move handles when translating since HLE
// services don't deal with handles directly. However, the guest applications might check
// for specific values in each of these descriptors.
for (auto& object : copy_objects) {
ASSERT(object != nullptr);
R_TRY(handle_table.Add(&dst_cmdbuf[current_offset++], object));
for (auto& object : outgoing_copy_objects) {
Handle handle{};
if (object) {
R_TRY(handle_table.Add(&handle, object));
}
cmd_buf[current_offset++] = handle;
}
for (auto& object : outgoing_move_objects) {
Handle handle{};
if (object) {
R_TRY(handle_table.Add(&handle, object));
for (auto& object : move_objects) {
ASSERT(object != nullptr);
R_TRY(handle_table.Add(&dst_cmdbuf[current_offset++], object));
// Close our reference to the object, as it is being moved to the caller.
object->Close();
}
cmd_buf[current_offset++] = handle;
}
// TODO(Subv): Translate the X/A/B/W buffers.
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
if (Session()->IsDomain() && domain_message_header) {
ASSERT(domain_message_header->num_objects == domain_objects.size());
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
std::size_t domain_offset = size - domain_message_header->num_objects;
for (const auto& object : domain_objects) {
server_session->AppendDomainRequestHandler(object);
dst_cmdbuf[domain_offset++] =
if (Session()->IsDomain()) {
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
for (const auto& object : outgoing_domain_objects) {
server_session->AppendDomainHandler(object);
cmd_buf[current_offset++] =
static_cast<u32_le>(server_session->NumDomainRequestHandlers());
}
}
// Copy the translated command buffer back into the thread's command buffer area.
memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), cmd_buf.data(),
write_size * sizeof(u32));
return RESULT_SUCCESS;
}

View File

@@ -11,7 +11,8 @@
#include <string>
#include <type_traits>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/concepts.h"
#include "common/swap.h"
@@ -66,7 +67,8 @@ public:
* this request (ServerSession, Originator thread, Translated command buffer, etc).
* @returns ResultCode the result code of the translate operation.
*/
virtual ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) = 0;
virtual ResultCode HandleSyncRequest(Kernel::KServerSession& session,
Kernel::HLERequestContext& context) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
@@ -83,6 +85,69 @@ public:
void ClientDisconnected(KServerSession* session);
};
using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>;
/**
* Manages the underlying HLE requests for a session, and whether (or not) the session should be
* treated as a domain. This is managed separately from server sessions, as this state is shared
* when objects are cloned.
*/
class SessionRequestManager final {
public:
SessionRequestManager() = default;
bool IsDomain() const {
return is_domain;
}
void ConvertToDomain() {
domain_handlers = {session_handler};
is_domain = true;
}
std::size_t DomainHandlerCount() const {
return domain_handlers.size();
}
bool HasSessionHandler() const {
return session_handler != nullptr;
}
SessionRequestHandler& SessionHandler() {
return *session_handler;
}
const SessionRequestHandler& SessionHandler() const {
return *session_handler;
}
void CloseDomainHandler(std::size_t index) {
if (index < DomainHandlerCount()) {
domain_handlers[index] = nullptr;
} else {
UNREACHABLE_MSG("Unexpected handler index {}", index);
}
}
SessionRequestHandlerPtr DomainHandler(std::size_t index) const {
ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index);
return domain_handlers.at(index);
}
void AppendDomainHandler(SessionRequestHandlerPtr&& handler) {
domain_handlers.emplace_back(std::move(handler));
}
void SetSessionHandler(SessionRequestHandlerPtr&& handler) {
session_handler = std::move(handler);
}
private:
bool is_domain{};
SessionRequestHandlerPtr session_handler;
std::vector<SessionRequestHandlerPtr> domain_handlers;
};
/**
* Class containing information about an in-flight IPC request being handled by an HLE service
* implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and
@@ -128,15 +193,32 @@ public:
/// Writes data from this context back to the requesting process/thread.
ResultCode WriteToOutgoingCommandBuffer(KThread& requesting_thread);
u32_le GetCommand() const {
u32_le GetHipcCommand() const {
return command;
}
u32_le GetTipcCommand() const {
return static_cast<u32_le>(command_header->type.Value()) -
static_cast<u32_le>(IPC::CommandType::TIPC_CommandRegion);
}
u32_le GetCommand() const {
return command_header->IsTipc() ? GetTipcCommand() : GetHipcCommand();
}
bool IsTipc() const {
return command_header->IsTipc();
}
IPC::CommandType GetCommandType() const {
return command_header->type;
}
unsigned GetDataPayloadOffset() const {
u64 GetPID() const {
return pid;
}
u32 GetDataPayloadOffset() const {
return data_payload_offset;
}
@@ -206,53 +288,32 @@ public:
bool CanWriteBuffer(std::size_t buffer_index = 0) const;
Handle GetCopyHandle(std::size_t index) const {
return copy_handles.at(index);
return incoming_copy_handles.at(index);
}
Handle GetMoveHandle(std::size_t index) const {
return move_handles.at(index);
return incoming_move_handles.at(index);
}
void AddMoveObject(KAutoObject* object) {
move_objects.emplace_back(object);
outgoing_move_objects.emplace_back(object);
}
void AddCopyObject(KAutoObject* object) {
copy_objects.emplace_back(object);
outgoing_copy_objects.emplace_back(object);
}
void AddDomainObject(std::shared_ptr<SessionRequestHandler> object) {
domain_objects.emplace_back(std::move(object));
void AddDomainObject(SessionRequestHandlerPtr object) {
outgoing_domain_objects.emplace_back(std::move(object));
}
template <typename T>
std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const {
return std::static_pointer_cast<T>(domain_request_handlers.at(index));
std::shared_ptr<T> GetDomainHandler(std::size_t index) const {
return std::static_pointer_cast<T>(manager->DomainHandler(index));
}
void SetDomainRequestHandlers(
const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) {
domain_request_handlers = handlers;
}
/// Clears the list of objects so that no lingering objects are written accidentally to the
/// response buffer.
void ClearIncomingObjects() {
move_objects.clear();
copy_objects.clear();
domain_objects.clear();
}
std::size_t NumMoveObjects() const {
return move_objects.size();
}
std::size_t NumCopyObjects() const {
return copy_objects.size();
}
std::size_t NumDomainObjects() const {
return domain_objects.size();
void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) {
manager = std::move(manager_);
}
std::string Description() const;
@@ -274,12 +335,12 @@ private:
Kernel::KServerSession* server_session{};
KThread* thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<Handle, 8> move_handles;
boost::container::small_vector<Handle, 8> copy_handles;
boost::container::small_vector<KAutoObject*, 8> move_objects;
boost::container::small_vector<KAutoObject*, 8> copy_objects;
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
std::vector<Handle> incoming_move_handles;
std::vector<Handle> incoming_copy_handles;
std::vector<KAutoObject*> outgoing_move_objects;
std::vector<KAutoObject*> outgoing_copy_objects;
std::vector<SessionRequestHandlerPtr> outgoing_domain_objects;
std::optional<IPC::CommandHeader> command_header;
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
@@ -291,11 +352,14 @@ private:
std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors;
std::vector<IPC::BufferDescriptorC> buffer_c_desciptors;
unsigned data_payload_offset{};
unsigned buffer_c_offset{};
u32_le command{};
u64 pid{};
u32 write_size{};
u32 data_payload_offset{};
u32 handles_offset{};
u32 domain_offset{};
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
std::shared_ptr<SessionRequestManager> manager;
bool is_thread_waiting{};
KernelCore& kernel;

View File

@@ -70,14 +70,22 @@ constexpr size_t SlabCountExtraKThread = 160;
template <typename T>
VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAddr address,
size_t num_objects) {
// TODO(bunnei): This is just a place holder. We should initialize the appropriate KSlabHeap for
// kernel object type T with the backing kernel memory pointer once we emulate kernel memory.
const size_t size = Common::AlignUp(sizeof(T) * num_objects, alignof(void*));
VAddr start = Common::AlignUp(address, alignof(T));
// This is intentionally empty. Once KSlabHeap is fully implemented, we can replace this with
// the pointer to emulated memory to pass along. Until then, KSlabHeap will just allocate/free
// host memory.
void* backing_kernel_memory{};
if (size > 0) {
const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1);
ASSERT(region != nullptr);
ASSERT(region->IsDerivedFrom(KMemoryRegionType_KernelSlab));
T::InitializeSlabHeap(system.Kernel(), system.Memory().GetKernelBuffer(start, size), size);
T::InitializeSlabHeap(system.Kernel(), backing_kernel_memory, size);
}
return start + size;

View File

@@ -91,7 +91,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out) {
// Create a new session.
KSession* session = KSession::Create(kernel);
if (session == nullptr) {
/* Decrement the session count. */
// Decrement the session count.
const auto prev = num_sessions--;
if (prev == max_sessions) {
this->NotifyAvailable();

View File

@@ -31,6 +31,9 @@ public:
const KPort* GetParent() const {
return parent;
}
KPort* GetParent() {
return parent;
}
s32 GetNumSessions() const {
return num_sessions;

View File

@@ -18,7 +18,8 @@ class KernelCore;
class KLightConditionVariable {
public:
explicit KLightConditionVariable(KernelCore& kernel) : thread_queue(kernel), kernel(kernel) {}
explicit KLightConditionVariable(KernelCore& kernel_)
: thread_queue(kernel_), kernel(kernel_) {}
void Wait(KLightLock* lock, s64 timeout = -1) {
WaitImpl(lock, timeout);

View File

@@ -201,10 +201,10 @@ public:
}
iterator insert(const_iterator pos, reference ref) {
KLinkedListNode* node = KLinkedListNode::Allocate(kernel);
ASSERT(node != nullptr);
node->Initialize(std::addressof(ref));
return iterator(BaseList::insert(pos.m_base_it, *node));
KLinkedListNode* new_node = KLinkedListNode::Allocate(kernel);
ASSERT(new_node != nullptr);
new_node->Initialize(std::addressof(ref));
return iterator(BaseList::insert(pos.m_base_it, *new_node));
}
void push_back(reference ref) {

View File

@@ -7,8 +7,8 @@
namespace Kernel {
KMemoryBlockManager::KMemoryBlockManager(VAddr start_addr, VAddr end_addr)
: start_addr{start_addr}, end_addr{end_addr} {
KMemoryBlockManager::KMemoryBlockManager(VAddr start_addr_, VAddr end_addr_)
: start_addr{start_addr_}, end_addr{end_addr_} {
const u64 num_pages{(end_addr - start_addr) / PageSize};
memory_block_tree.emplace_back(start_addr, num_pages, KMemoryState::Free,
KMemoryPermission::None, KMemoryAttribute::None);

View File

@@ -19,7 +19,7 @@ public:
using const_iterator = MemoryBlockTree::const_iterator;
public:
KMemoryBlockManager(VAddr start_addr, VAddr end_addr);
KMemoryBlockManager(VAddr start_addr_, VAddr end_addr_);
iterator end() {
return memory_block_tree.end();

View File

@@ -17,7 +17,7 @@ class KPageLinkedList final {
public:
class Node final {
public:
constexpr Node(u64 addr, std::size_t num_pages) : addr{addr}, num_pages{num_pages} {}
constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
constexpr u64 GetAddress() const {
return addr;

View File

@@ -58,7 +58,7 @@ constexpr std::size_t GetSizeInRange(const KMemoryInfo& info, VAddr start, VAddr
} // namespace
KPageTable::KPageTable(Core::System& system) : system{system} {}
KPageTable::KPageTable(Core::System& system_) : system{system_} {}
ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type,
bool enable_aslr, VAddr code_addr,
@@ -906,8 +906,8 @@ ResultCode KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
block_manager->UpdateLock(
addr, size / PageSize,
[](KMemoryBlockManager::iterator block, KMemoryPermission perm) {
block->ShareToDevice(perm);
[](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
block->ShareToDevice(permission);
},
perm);
@@ -929,8 +929,8 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
block_manager->UpdateLock(
addr, size / PageSize,
[](KMemoryBlockManager::iterator block, KMemoryPermission perm) {
block->UnshareToDevice(perm);
[](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
block->UnshareToDevice(permission);
},
perm);

View File

@@ -24,7 +24,7 @@ class KMemoryBlockManager;
class KPageTable final : NonCopyable {
public:
explicit KPageTable(Core::System& system);
explicit KPageTable(Core::System& system_);
ResultCode InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
VAddr code_addr, std::size_t code_size,

View File

@@ -56,11 +56,8 @@ ResultCode KPort::EnqueueSession(KServerSession* session) {
R_UNLESS(state == State::Normal, ResultPortClosed);
if (server.HasHLEHandler()) {
server.GetHLEHandler()->ClientConnected(session);
} else {
server.EnqueueSession(session);
}
server.EnqueueSession(session);
server.GetSessionRequestHandler()->ClientConnected(server.AcceptSession());
return RESULT_SUCCESS;
}

View File

@@ -607,7 +607,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
}
}
KScheduler::KScheduler(Core::System& system, s32 core_id) : system(system), core_id(core_id) {
KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} {
switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this);
state.needs_scheduling.store(true);
state.interrupt_task_thread_runnable = false;

View File

@@ -30,7 +30,7 @@ class KThread;
class KScheduler final {
public:
explicit KScheduler(Core::System& system, s32 core_id);
explicit KScheduler(Core::System& system_, s32 core_id_);
~KScheduler();
/// Reschedules to the next available thread (call after current thread is suspended)

View File

@@ -17,8 +17,8 @@ namespace Kernel {
class [[nodiscard]] KScopedSchedulerLockAndSleep {
public:
explicit KScopedSchedulerLockAndSleep(KernelCore & kernel, KThread * t, s64 timeout)
: kernel(kernel), thread(t), timeout_tick(timeout) {
explicit KScopedSchedulerLockAndSleep(KernelCore & kernel_, KThread * t, s64 timeout)
: kernel(kernel_), thread(t), timeout_tick(timeout) {
// Lock the scheduler.
kernel.GlobalSchedulerContext().scheduler_lock.Lock();
}

View File

@@ -32,26 +32,24 @@ public:
explicit KServerPort(KernelCore& kernel_);
virtual ~KServerPort() override;
using HLEHandler = std::shared_ptr<SessionRequestHandler>;
void Initialize(KPort* parent_, std::string&& name_);
/// Whether or not this server port has an HLE handler available.
bool HasHLEHandler() const {
return hle_handler != nullptr;
bool HasSessionRequestHandler() const {
return session_handler != nullptr;
}
/// Gets the HLE handler for this port.
HLEHandler GetHLEHandler() const {
return hle_handler;
SessionRequestHandlerPtr GetSessionRequestHandler() const {
return session_handler;
}
/**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
void SetHleHandler(HLEHandler hle_handler_) {
hle_handler = std::move(hle_handler_);
void SetSessionHandler(SessionRequestHandlerPtr&& handler) {
session_handler = std::move(handler);
}
void EnqueueSession(KServerSession* pending_session);
@@ -73,7 +71,7 @@ private:
private:
SessionList session_list;
HLEHandler hle_handler;
SessionRequestHandlerPtr session_handler;
KPort* parent{};
};

View File

@@ -23,7 +23,8 @@
namespace Kernel {
KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
KServerSession::KServerSession(KernelCore& kernel_)
: KSynchronizationObject{kernel_}, manager{std::make_shared<SessionRequestManager>()} {}
KServerSession::~KServerSession() {
kernel.ReleaseServiceThread(service_thread);
@@ -43,14 +44,8 @@ void KServerSession::Destroy() {
}
void KServerSession::OnClientClosed() {
// We keep a shared pointer to the hle handler to keep it alive throughout
// the call to ClientDisconnected, as ClientDisconnected invalidates the
// hle_handler member itself during the course of the function executing.
std::shared_ptr<SessionRequestHandler> handler = hle_handler;
if (handler) {
// Note that after this returns, this server session's hle_handler is
// invalidated (set to null).
handler->ClientDisconnected(this);
if (manager->HasSessionHandler()) {
manager->SessionHandler().ClientDisconnected(this);
}
}
@@ -66,12 +61,12 @@ bool KServerSession::IsSignaled() const {
return false;
}
void KServerSession::AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler) {
domain_request_handlers.push_back(std::move(handler));
void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) {
manager->AppendDomainHandler(std::move(handler));
}
std::size_t KServerSession::NumDomainRequestHandlers() const {
return domain_request_handlers.size();
return manager->DomainHandlerCount();
}
ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
@@ -80,14 +75,14 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co
}
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
context.SetDomainRequestHandlers(domain_request_handlers);
context.SetSessionRequestManager(manager);
// If there is a DomainMessageHeader, then this is CommandType "Request"
const auto& domain_message_header = context.GetDomainMessageHeader();
const u32 object_id{domain_message_header.object_id};
switch (domain_message_header.command) {
case IPC::DomainMessageHeader::CommandType::SendMessage:
if (object_id > domain_request_handlers.size()) {
if (object_id > manager->DomainHandlerCount()) {
LOG_CRITICAL(IPC,
"object_id {} is too big! This probably means a recent service call "
"to {} needed to return a new interface!",
@@ -95,12 +90,12 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co
UNREACHABLE();
return RESULT_SUCCESS; // Ignore error if asserts are off
}
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
return manager->DomainHandler(object_id - 1)->HandleSyncRequest(*this, context);
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
domain_request_handlers[object_id - 1] = nullptr;
manager->CloseDomainHandler(object_id - 1);
IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
@@ -133,14 +128,14 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
if (IsDomain() && context.HasDomainMessageHeader()) {
result = HandleDomainSyncRequest(context);
// If there is no domain header, the regular session handler is used
} else if (hle_handler != nullptr) {
} else if (manager->HasSessionHandler()) {
// If this ServerSession has an associated HLE handler, forward the request to it.
result = hle_handler->HandleSyncRequest(context);
result = manager->SessionHandler().HandleSyncRequest(*this, context);
}
if (convert_to_domain) {
ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
domain_request_handlers = {hle_handler};
ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance.");
manager->ConvertToDomain();
convert_to_domain = false;
}

View File

@@ -12,6 +12,7 @@
#include <boost/intrusive/list.hpp>
#include "common/threadsafe_queue.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/service_thread.h"
#include "core/hle/result.h"
@@ -64,8 +65,8 @@ public:
* instead of the regular IPC machinery. (The regular IPC machinery is currently not
* implemented.)
*/
void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
hle_handler = std::move(hle_handler_);
void SetSessionHandler(SessionRequestHandlerPtr handler) {
manager->SetSessionHandler(std::move(handler));
}
/**
@@ -82,7 +83,7 @@ public:
/// Adds a new domain request handler to the collection of request handlers within
/// this ServerSession instance.
void AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler);
void AppendDomainHandler(SessionRequestHandlerPtr handler);
/// Retrieves the total number of domain request handlers that have been
/// appended to this ServerSession instance.
@@ -90,12 +91,7 @@ public:
/// Returns true if the session has been converted to a domain, otherwise False
bool IsDomain() const {
return !IsSession();
}
/// Returns true if this session has not been converted to a domain, otherwise false.
bool IsSession() const {
return domain_request_handlers.empty();
return manager->IsDomain();
}
/// Converts the session to a domain at the end of the current command
@@ -103,6 +99,21 @@ public:
convert_to_domain = true;
}
/// Gets the session request manager, which forwards requests to the underlying service
std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() {
return manager;
}
/// Gets the session request manager, which forwards requests to the underlying service
const std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() const {
return manager;
}
/// Sets the session request manager, which forwards requests to the underlying service
void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) {
manager = std::move(manager_);
}
private:
/// Queues a sync request from the emulated application.
ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
@@ -114,11 +125,8 @@ private:
/// object handle.
ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
/// This session's HLE request handler (applicable when not a domain)
std::shared_ptr<SessionRequestHandler> hle_handler;
/// This is the list of domain request handlers (after conversion to a domain)
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
/// This session's HLE request handlers
std::shared_ptr<SessionRequestManager> manager;
/// When set to True, converts the session to a domain at the end of the command
bool convert_to_domain{};

View File

@@ -66,6 +66,10 @@ public:
return port;
}
KClientPort* GetParent() {
return port;
}
private:
enum class State : u8 {
Invalid = 0,

View File

@@ -4,165 +4,33 @@
#pragma once
#include <atomic>
#include "common/assert.h"
#include "common/common_types.h"
namespace Kernel {
namespace impl {
class KernelCore;
class KSlabHeapImpl final : NonCopyable {
public:
struct Node {
Node* next{};
};
constexpr KSlabHeapImpl() = default;
void Initialize(std::size_t size) {
ASSERT(head == nullptr);
obj_size = size;
}
constexpr std::size_t GetObjectSize() const {
return obj_size;
}
Node* GetHead() const {
return head;
}
void* Allocate() {
Node* ret = head.load();
do {
if (ret == nullptr) {
break;
}
} while (!head.compare_exchange_weak(ret, ret->next));
return ret;
}
void Free(void* obj) {
Node* node = static_cast<Node*>(obj);
Node* cur_head = head.load();
do {
node->next = cur_head;
} while (!head.compare_exchange_weak(cur_head, node));
}
private:
std::atomic<Node*> head{};
std::size_t obj_size{};
};
} // namespace impl
class KSlabHeapBase : NonCopyable {
public:
constexpr KSlabHeapBase() = default;
constexpr bool Contains(uintptr_t addr) const {
return start <= addr && addr < end;
}
constexpr std::size_t GetSlabHeapSize() const {
return (end - start) / GetObjectSize();
}
constexpr std::size_t GetObjectSize() const {
return impl.GetObjectSize();
}
constexpr uintptr_t GetSlabHeapAddress() const {
return start;
}
std::size_t GetObjectIndexImpl(const void* obj) const {
return (reinterpret_cast<uintptr_t>(obj) - start) / GetObjectSize();
}
std::size_t GetPeakIndex() const {
return GetObjectIndexImpl(reinterpret_cast<const void*>(peak));
}
void* AllocateImpl() {
return impl.Allocate();
}
void FreeImpl(void* obj) {
// Don't allow freeing an object that wasn't allocated from this heap
ASSERT(Contains(reinterpret_cast<uintptr_t>(obj)));
impl.Free(obj);
}
void InitializeImpl(std::size_t obj_size, void* memory, std::size_t memory_size) {
// Ensure we don't initialize a slab using null memory
ASSERT(memory != nullptr);
// Initialize the base allocator
impl.Initialize(obj_size);
// Set our tracking variables
const std::size_t num_obj = (memory_size / obj_size);
start = reinterpret_cast<uintptr_t>(memory);
end = start + num_obj * obj_size;
peak = start;
// Free the objects
u8* cur = reinterpret_cast<u8*>(end);
for (std::size_t i{}; i < num_obj; i++) {
cur -= obj_size;
impl.Free(cur);
}
}
private:
using Impl = impl::KSlabHeapImpl;
Impl impl;
uintptr_t peak{};
uintptr_t start{};
uintptr_t end{};
};
/// This is a placeholder class to manage slab heaps for kernel objects. For now, we just allocate
/// these with new/delete, but this can be re-implemented later to allocate these in emulated
/// memory.
template <typename T>
class KSlabHeap final : public KSlabHeapBase {
class KSlabHeap final : NonCopyable {
public:
constexpr KSlabHeap() : KSlabHeapBase() {}
KSlabHeap() = default;
void Initialize(void* memory, std::size_t memory_size) {
InitializeImpl(sizeof(T), memory, memory_size);
void Initialize([[maybe_unused]] void* memory, [[maybe_unused]] std::size_t memory_size) {
// Placeholder that should initialize the backing slab heap implementation.
}
T* Allocate() {
T* obj = static_cast<T*>(AllocateImpl());
if (obj != nullptr) {
new (obj) T();
}
return obj;
return new T();
}
T* AllocateWithKernel(KernelCore& kernel) {
T* obj = static_cast<T*>(AllocateImpl());
if (obj != nullptr) {
new (obj) T(kernel);
}
return obj;
return new T(kernel);
}
void Free(T* obj) {
FreeImpl(obj);
}
constexpr std::size_t GetObjectIndex(const T* obj) const {
return GetObjectIndexImpl(obj);
delete obj;
}
};

View File

@@ -10,7 +10,7 @@ namespace Kernel {
class KThreadQueue {
public:
explicit KThreadQueue(KernelCore& kernel) : kernel{kernel} {}
explicit KThreadQueue(KernelCore& kernel_) : kernel{kernel_} {}
bool IsEmpty() const {
return wait_list.empty();

View File

@@ -52,7 +52,7 @@ public:
}
size_t GetSize() const {
return is_initialized ? size * PageSize : 0;
return is_initialized ? size : 0;
}
private:

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