Compare commits

...

140 Commits

Author SHA1 Message Date
c02b0a0113 logging: backend: Migrate to the new Common::FS library 2022-10-31 20:57:49 +02:00
97cbcc6e49 common: fs: Initial port of yuzu fs library 2022-10-31 20:52:07 +02:00
9d6aa2db81 file_util: Migrate remaining file handling functions over to std::filesystem
Converts creation and deletion functions over to std::filesystem,
simplifying our file-handling code.

Notably with this, CopyDir will now function on Windows.
2022-10-31 19:49:16 +02:00
0675bd946c file_util: Migrate Exists() and IsDirectory() over to std::filesystem
Greatly simplifies our file-handling code for these functions.
2022-10-30 21:04:16 +02:00
281f2926bb common/fileutil: Convert namespace to Common::FS
Migrates a remaining common file over to the Common namespace, making it
consistent with the rest of common files.

This also allows for high-traffic FS related code to alias the
filesystem function namespace as

namespace FS = Common::FS;

for more concise typing.
2022-10-30 21:03:49 +02:00
349ac6ac05 vk_texture_runtime: Implement RGBA4 converter
* Fixes graphics in NES Remix. Need to also do a reinterpreter some time, but this will suffice for now
2022-10-30 19:10:18 +02:00
16571a96a2 texture_downloader_es: Remove invalid operations 2022-10-30 18:51:27 +02:00
a0e9836386 gl_texture_runtime: Use OGLStreamBuffer for uploads/downloads
* Much better than the current implementation
2022-10-30 18:41:47 +02:00
fb10d05f97 vk_stream_buffer: Cleanup flush barrier 2022-10-30 09:34:08 +02:00
182bebf272 video_core: Reorder microprofile defines 2022-10-29 20:49:39 +03:00
7626804a90 citra_qt: Add shader cache options 2022-10-29 15:04:05 +03:00
20496e8ca4 renderer_vulkan: Rewrite stream buffer, again...
* The previous implemention was fine, but it wasted space. Buckets now are just ticks attached to a particular buffer region, which means we can flush/map arbitrary regions

* A bug in the texture runtime is also fixed which commited to the same buffer twice
2022-10-29 15:03:55 +03:00
b4184a3c2b renderer_vulkan: Pack PicaFSConfig
* Using bitfields the struct size was reduced from 420 to 190 bytes. which should speed up hashing and copying to the worker thread
2022-10-29 15:02:13 +03:00
2251d96d4d renderer_vulkan: Cleanup vertex array setup
* Also the function would commit more data then it requested leading to out of bound crashes
2022-10-29 15:02:12 +03:00
c028a8c7c5 renderer_vulkan: Remove AttribType
* Use VertexAttributeFormat to avoid unnecessary enum casts
2022-10-29 15:01:49 +03:00
359f97be22 video_core: Move HardwareVertex to RasterizerAccelerated 2022-10-29 15:01:49 +03:00
748f8a0658 video_core: Move api agnostic uniform updates to RasterizerAccelerated 2022-10-29 15:01:48 +03:00
a7611bb2d3 renderer_vulkan: Fix swapchain resizing 2022-10-29 15:01:13 +03:00
6f35a3bf37 renderer_vulkan: Add single-thread record ability to the scheduler
* Async is pretty nice but games that do a lot of flushes might have worse performance due to thread synchronization overhead

* I haven't noticed any cases of this yet but it doesn't hurt making this a UI option
2022-10-29 14:59:21 +03:00
58573dd7b3 citra_qt: Refuse to enable debug option if the layers are not available 2022-10-29 14:59:21 +03:00
11728d6772 renderer_vulkan: Scheduler and presentation rewrite
* This commit ports yuzu's async scheduler replacing our older and crummier version
  Commands are recorded by the scheduler and processed by a separate worker thread.

* Queue submission is also moved to the worker thread which should alliviate slowdowns related to vkQueueSubmit stalls

* Fragment shader compilation and queue submission are also moved to that thread to reduce stutters
2022-10-28 23:05:37 +03:00
a99be221b2 externals: Update vulkan-headers 2022-10-28 23:05:37 +03:00
f656610a41 common: Remove concepts usage 2022-10-28 23:05:37 +03:00
cfa1a7b91c citra_qt: Include unordered_map to fix macOS compilation 2022-10-28 23:05:37 +03:00
52b1fc4889 renderer_vulkan: Fix shader hash type 2022-10-28 23:05:37 +03:00
01e2b6cdaa code: Remove usages of std::ranges
* MacOS is still runining my C++ 20 fun
2022-10-28 23:05:37 +03:00
f75380fc93 renderer_vulkan: Prefer immediate over mailbox present mode 2022-10-28 23:05:37 +03:00
18af49a0ca renderer_vulkan: Bump vertex buffer size
* So software shaders don't crash
2022-10-28 23:05:37 +03:00
c7e64f6c7b renderer_vulkan: Add more microprofile targets 2022-10-28 23:05:37 +03:00
0a40a513a6 renderer_vulkan: Improve StreamBuffer API and use it in TextureRuntime
* Also use separate upload and download buffers optimized for write and readback respectively. This gives a huge 20+ FPS boost in most games which were bottlenecked by slow reads
2022-10-28 23:05:37 +03:00
a4dc6a55b7 renderer_vulkan: Fix allocation caching bug 2022-10-28 23:05:37 +03:00
7dce5be263 renderer_opengl: Port scaled upload/download code from vulkan 2022-10-28 23:05:37 +03:00
3f7d97da4c renderer_vulkan: Include algorithm in vk_common
* Appears to be a bug in vulkan-hpp
2022-10-28 23:05:37 +03:00
a11b4dd051 renderer_vulkan: Use linear filtering when possible
* Fixes blocky artifacts in Samus Returns
2022-10-28 23:05:37 +03:00
c78847b2b6 renderer_vulkan: Abstract descriptor management
* The pipeline cache was starting to get cluttered
2022-10-28 23:05:37 +03:00
8faa7a6e02 renderer_vulkan: Bump descriptor set allocation limit 2022-10-28 23:05:37 +03:00
acf4b4e5fb renderer_vulkan: Fix storage descriptor binding and respect color mask
* RGBA8 surfaces now expose an additional R32Uint view used for storage descriptors. The format is guaranteed by the spec to support atomic loads/stores. This requires the mutable flag which incurs a performance cost, but might be better than breaking the current renderpass each draw when rendering shadows, especially on mobile

* Color mask is also implemented which fixes Street Fighter and Monster Hunter Stories
2022-10-28 23:05:37 +03:00
3a0ca63d91 renderer_vulkan: Implement depth uploads with blit 2022-10-28 23:05:37 +03:00
1e96775203 renderer_vulkan: Use intermediate copy when framebuffer is used both as attachment and shader input 2022-10-28 23:05:37 +03:00
6d27e8be8d renderer_vulkan: Respect disk shader option 2022-10-28 23:05:37 +03:00
c357a8b9b6 renderer_vulkan: Fix staging buffer size 2022-10-28 23:05:37 +03:00
7ad982f123 renderer_vulkan: Catch and log more runtime errors
* Also add the ability to enable command buffer dumping which is very useful
2022-10-28 23:05:37 +03:00
dd3d24bfec renderer_vulkan: Batch allocate descriptor sets
* Less driver calls should lead to better performance
2022-10-28 23:05:37 +03:00
489bbb98b2 renderer_vulkan: Emulate border color if possible 2022-10-28 23:05:37 +03:00
5c9543e39d renderer_vulkan: Implement scaled uploads and downloads
* This commit includes large changes to have textures are handling. Instead of using ImageAlloc, Surface is used instead which provides multiple benefits: automatic recycling on destruction and ability to use the TextureRuntime interface to simplify operations

* Layout tracking is also implemented which allows transitioning of individual mip levels without errors

* This fixes graphical errors in multiple games which relied on framebuffer uploads
2022-10-28 23:05:37 +03:00
49085d400c renderer_vulkan: Fix renderpass issues
* The cache didn't take into account the framebuffer and render area used, so if these changed the renderpass wouldn't restart. This caused graphical bugs in Pokemon X/Y
2022-10-28 23:05:37 +03:00
8be9ea4f4a renderer_vulkan: Update stencil compare mask 2022-10-28 23:05:37 +03:00
a71c288252 citra_qt: Fix graphics api indicator alignment 2022-10-28 23:04:38 +03:00
159809eb32 renderer_opengl: Fix OpenGLES issues
* Always request a 4.4 context until I figure out how to get Qt to cooperate

* Use RGBA for BGR since the converted table will do that conversion
2022-10-28 23:04:38 +03:00
2a4f0ce8de renderer_vulkan: Report perf stats 2022-10-28 23:04:38 +03:00
4c8f1c83c8 renderer_vulkan: Better error handling 2022-10-28 23:04:38 +03:00
98f6d697d8 renderer_vulkan: Allow direct allocation of images 2022-10-28 23:04:38 +03:00
4636735783 renderer_vulkan: Fix incorrect depth format detection
* Intel iGPUs don't support blit on all depth/stencil formats which caused issues since the runtime checks for this while the renderpass cache does not
2022-10-28 23:04:38 +03:00
8a2770bf83 renderer_vulkan: Actually minize state changes
* Keep track of the current state and only update it when needed. Previously games would set the same state over and over cluttering renderdoc logs
2022-10-28 23:04:37 +03:00
f1e09c1ea1 renderer_vulkan: Fix broken sync without timeline semaphores 2022-10-28 23:04:37 +03:00
2423e645f1 renderer_vulkan: Allocate descriptor sets during reinterpretation 2022-10-28 23:04:37 +03:00
0eaae31f9f renderer_vulkan: Enable logic ops and fix swapchain resizing 2022-10-28 23:04:37 +03:00
4f9b545296 renderer_vulkan: Clear stencil with renderpass
* Fixes outline retension in pokemon games
2022-10-28 23:04:37 +03:00
628d70e112 renderer_vulkan: Fix pipeline cache crashes 2022-10-28 23:04:37 +03:00
9991b9b12b renderer_vulkan: Optimize tiled format convertion + fix vertex buffer alignment
* Integrate format convertion to the morton copy function, removing the need for an intermediate copy and convertion pass. This should be beneficial for performance especially since most games use tiled textures

* Also bump vertex buffer size to avoid crashes with hardware shaders and provide correct offset on normal draws which fixes glitches in pokemon Y

* Reduce the local group size to 8 in the D24S8 compute shader which fixes graphical issues in the afformentioned pokemon games at native resolution

* Set LOD to 0 instead of 0.25 to fix another glitch in pokemon y
2022-10-28 23:04:37 +03:00
e4bcf73c5a renderer_opengl: Fix broken texture copy
* Resolves graphical bugs in Professor Layton vs Ace Attorney when using OpenGL
2022-10-28 23:04:37 +03:00
b693d205e4 renderer_vulkan: Pipeline cache fixes
* Delete cache file if found invalid

* Name it after the vendor/device ids so each physical devices gets a separate cache
2022-10-28 23:04:37 +03:00
91621ec202 video_core: Fix renderpass cache bug and introduce RGBA -> BGR converter 2022-10-28 23:04:37 +03:00
948f72d320 renderer_opengl: Specify precision in compute shader and add RGB5A1 converter
* Fixes OpenGLES crash
2022-10-28 23:04:37 +03:00
8c5b417486 renderer_vulkan: Complete hardware shader support
* With these changes all commercial games I tested work fine and get a massive performance boost
2022-10-28 23:04:37 +03:00
51685ee2db renderer_vulkan: Begin hardware shader support
* Still experimental and works only with homebrew
2022-10-28 23:04:37 +03:00
079e4aa205 citra: Fix build issues with MinGW and MSVC 2022-10-28 23:04:37 +03:00
48edfb891b renderer_vulkan: Fix warnings and cleanup 2022-10-28 23:04:37 +03:00
915406354c code: Run clang-format 2022-10-28 23:04:31 +03:00
6f3fc32a93 code: Address build issues 2022-10-28 23:03:18 +03:00
ebfa98d31d video_core: Re-implement format reinterpretation
* Same as before but D24S8 to RGBA8 is switched to a compute shader which should provide better throughput and is much simpler to implement in Vulkan
2022-10-28 23:03:18 +03:00
3095ee91a8 citra_qt: Add physical device selection dialog 2022-10-28 23:03:18 +03:00
fadeecfe6d code: Resolve unused variable warnings 2022-10-28 23:03:16 +03:00
2a1598036e renderer_opengl: Unbind unused framebuffer targets
* Fixes graphical glitches in many games for some reason
2022-10-28 23:02:23 +03:00
58f01112c5 renderer_opengl: Emulate texture copy with blit for now 2022-10-28 23:02:23 +03:00
7e7b3dc18c renderer_opengl: Address buffer overflow 2022-10-28 23:02:23 +03:00
d4a3f60575 video_core: Small code improvements 2022-10-28 23:02:23 +03:00
f8cbf783cb renderer_vulkan: Don't sample from mipmaps when using texture cubes
* Mipmaps for texture cubes are unimplemented in the rasterizer cache, so sampling from mipmaps will return nothing
2022-10-28 23:02:23 +03:00
dc0cddb7de citra_qt: Switch all strings to multiarg 2022-10-28 23:02:23 +03:00
9cef9d4c58 code: Address more compiler warnings 2022-10-28 23:02:23 +03:00
dada05801f citra_qt: Fix more warnings/deprecated functions 2022-10-28 23:02:23 +03:00
9bc71a3307 input_common: Small fix 2022-10-28 23:02:23 +03:00
98274273b1 citra_qt: Improve graphics API intergration
* Add renderer debug option which toggles debug output in OpenGL/validation layers in Vulkan

* Fix many warnings and replace deprecated Qt functionailty with newer alternatives
2022-10-28 23:02:23 +03:00
269db2bfb8 rasterizer_cache: Code cleanup
* Merge utils and types to a single header
2022-10-28 23:02:23 +03:00
d27c1c8606 texture_decode: Prefer std::memcpy where possible 2022-10-28 23:02:23 +03:00
891b4bff18 renderer_vulkan: Rework format handling
* This is a pretty large commit that aims to solve some issues with the current format system
* The instance now builds at application initialization an array of format traits for each pixel format
  that includes information such as blit/attachment/storage support and fallback formats
* The runtime doesn't ask the instance for formats but receives these traits and can dedice on its own what to build
  For now we do the same as before, we require both blit and attachment support

* Morton swizzling also sees many bug fixes. The previous code was very hacky and didn't work for partial
  texture updates. It was also inconsistent, as it would take a tiled_buffer and write to the middle of linear
* Now the functions have been greatly simplified and adjusted to work better with std::span. This fixes out of bounds
  errors and texture glitches (like the display in Mario Kart 7)
2022-10-28 23:02:23 +03:00
eeccdc02fc renderer_vulkan: Handle scheduler switches properly 2022-10-28 23:02:23 +03:00
29ee94c3f5 vk_platform: Fix wayland build 2022-10-28 23:02:23 +03:00
8936641841 renderer_vulkan: Rewrite stream buffer + other fixes
* Emulate blend color and clip planes correctly

* Don't hack the depth in the vertex shader, use VK_EXT_depth_clip_control for now to set the range to -1, 1

* Rewrite the stream buffer to remove flickering problems. The new implementation doesn't try to be smart about holding memory. It divides the allocation in SCHEDULER_COMMAND_COUNT buckets and automatically switches between them based on the current slot index
2022-10-28 23:02:23 +03:00
e1f6b88e7b vk_rasterizer: Bump vertex buffer size
* Helps with the stuttering, indicating the issue is with the vertex buffer somehow
2022-10-28 23:02:23 +03:00
64d809f06a pica_to_vk: Set cull mode correctly 2022-10-28 23:02:23 +03:00
ebd23026a0 renderer_vulkan: Minimize state changes
* Store current renderpass/pipelines and only rebind when they change

* Enable extended dynamic state support and only apply them when they change
2022-10-28 23:02:23 +03:00
6e1bfe9949 rasterizer_cache: Explicitely pass end_offset to swizzle functions
* This addresses overflow issues
2022-10-28 23:02:23 +03:00
854092ce4f renderer_vulkan: Implement partial color/depth clears 2022-10-28 23:02:23 +03:00
90d24caaf8 renderer_vulkan: Add second screen and remove renderpass breakage 2022-10-28 23:02:23 +03:00
65400936c7 renderer_vulkan: Improve task scheduler synchronization
* Use multiple semaphores for swapchain sync and improve the Submit API
2022-10-28 23:02:23 +03:00
3f9e5a2b42 renderer_vulkan: Use timeline semaphores if available 2022-10-28 23:02:23 +03:00
34ba320c3d renderer_vulkan: Pipeline cache fixes 2022-10-28 23:02:23 +03:00
ec9f1902f5 renderer_vulkan: Isolate surface creation to vk_platform.cpp
* Also cleanup the init code somewhat
2022-10-28 23:02:23 +03:00
c72a365d78 renderer_vulkan: Add ABGR -> RGBA byteswap
* Vulkan doesn't support VK_FORMAT_R8G8B8A8_UNORM_PACK32 unfortunately. Fixes graphical issues on the gpusprites demo
2022-10-28 23:02:23 +03:00
8f211613a3 externals: Trim down glslang build
* When the install option is turned on, glslang will override the install dir which causes SDL2 to fail
2022-10-28 23:02:23 +03:00
30885b72be common: math_util: Include <compare> 2022-10-28 23:02:23 +03:00
beb078a71b cmake: Lower cmake requirement to 3.14 2022-10-28 23:02:23 +03:00
a65f9ea5a8 renderer_vulkan: Address more validation errors and stop memory leakage
* The transition settings are temporary until I write a proper layout tracking system
2022-10-28 23:02:23 +03:00
f9c11eab96 renderer_vulkan: Fix some validation errors
* Temporarily add glm until I figure out how to fix the alignment
2022-10-28 23:02:23 +03:00
794f6e4a67 renderer_vulkan: Implement renderer and rasterizer classes
* Also WIP. Vulkan crashes when allocating command buffers, need to investigate...
2022-10-28 23:02:23 +03:00
c85731f3ae renderer_vulkan: Add experimental Vulkan renderer
* Stil extremelly WIP and missing the rasterizer/renderer classes
2022-10-28 23:02:23 +03:00
e1542cea84 externals: Add vulkan headers and vma 2022-10-28 23:02:23 +03:00
887ef51f04 rasterizer_cache: Refactor texture cube interface
* Reuse our Surface class instead of having a separate one, to avoid reimplementing stuff in the backend
2022-10-28 23:02:23 +03:00
d809687aeb gl_texture_runtime: Clean up texture upload/download code
* Improve readability and code clarity
2022-10-28 23:02:23 +03:00
96ec85a72e rasterizer_cache: Use Common::Rectangle everywhere
* Make a nice alias for it and use it instead of having Rect2D/Region2D. Makes the new design less intrusive to the current cache
2022-10-28 23:02:23 +03:00
0ed4d493ad rasterizer_cache: Make into template
* This is the final step, now RasterizerCache is compltely decoupled from OpenGL (technically not yet, but that's talking details). For now texture filtering and some GLES paths have been disabled and will be reimplemented in the following commits
2022-10-28 23:02:23 +03:00
78be1e7c17 rasterizer_cache: Use PBO staging buffer cache for texture uploads/downloads 2022-10-28 23:02:23 +03:00
2963682722 rasterizer_cache: Reorder methods 2022-10-28 23:02:23 +03:00
98eea4dcca rasterizer_cache: Remove remnants of cached_pages 2022-10-28 23:02:23 +03:00
98a4a18201 rasterizer_cache: Fix texture cube blitting
* The target was GL_TEXTURE_2D instead of GL_TEXTURE_CUBE_MAP_*
2022-10-28 23:02:23 +03:00
3619bd33b1 morton_swizzle: Implement texture formats in UNSWIZZLE_TABLE
* I can now remove that loop that has been messing with my OCD
2022-10-28 23:02:23 +03:00
fa870be263 morton_swizzle: Use tiled_buffer instead of reading data from g_memory
* It's much safer and removes hardcoded global state usage
2022-10-28 23:02:23 +03:00
3a6d19f51f rasterizer_accelerated: Zero intialize cached_pages
* Resolves random crashes because count takes random values
2022-10-28 23:02:23 +03:00
73d6a9d585 texture_runtime: Add staging buffer lock mechanism 2022-10-28 23:02:22 +03:00
5d48107dd6 cached_surface: Remove custom texture logic
* Makes things more complicated and is in the way. It's probably already
broken by recent changes, so I'll need to reimplement it anyway
2022-10-28 23:01:42 +03:00
a306931e1c renderer_opengl: Add driver class to report info/bugs 2022-10-28 23:01:42 +03:00
b3803c5002 rasterizer_cache: Add staging buffer cache for uploads/downloads
*  In addition bump context version to 4.4 to enforce ARB_buffer_storage and use EXT_buffer_storage for GLES which is support on many mobile devices
2022-10-28 23:01:42 +03:00
c412c116d8 rasterizer_cache: Improve TextureRuntime API
* This makes every operation more explicit and mimics more the Vulkan API
2022-10-28 23:01:42 +03:00
6ce4493e14 renderer_opengl: Encapsulate sync objects in OGLSync 2022-10-28 23:01:04 +03:00
d6e545932a code: Use std::numbers::pi 2022-10-28 23:01:04 +03:00
542bae4581 code: dodge PAGE_SIZE #define
Some header files, specifically for OSX and Musl libc define PAGE_SIZE to be a number
This is great except in citra we're using PAGE_SIZE as a variable

Specific example
`static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;`

PAGE_SIZE PAGE_BITS PAGE_MASK are all similar variables.
Simply deleted the underscores, and then added CITRA_ prefix
2022-10-28 23:01:02 +03:00
307154a06f morton_swizzle: Optimize and use std::span 2022-10-28 22:56:47 +03:00
725afe33ef morton_swizzle: Avoid buffer underflow
* Check the y coordinate before decrementing linear_buffer
2022-10-28 22:56:47 +03:00
bb58056ebe morton_swizzle: Move out of bounds texture check out of the decode loop
* Running relative expensive checks like this on a hot path causes small but measurable performance loss. Tested SMD wit this and it doesn't crash
2022-10-28 22:56:47 +03:00
f69a33574c rasterizer_cache: Use SurfaceType instead of Aspect
* It was doing pointless enum conversions when both enums described the same thing
2022-10-28 22:56:47 +03:00
1a48cf7e7d rasterizer_cache: Separate texture swizzling to utils 2022-10-28 22:56:47 +03:00
2833d94a3b rasterizer_cache: Remove OpenGL references from morton_swizzle 2022-10-28 22:56:47 +03:00
9b0aa5135e rasterizer_cache: microprofile: Rename OpenGL to RasterizerCache 2022-10-28 22:56:47 +03:00
9787efc7ee citra_qt: Forbid renderer change during runtime
* It's an endless source of problems and isn't usefull
2022-10-28 22:56:47 +03:00
623293d272 rasterizer_cache: Touch up MatchFlags comments 2022-10-28 22:56:47 +03:00
5ab5fdcc22 rasterizer_cache: Drop OpenGL postfix 2022-10-28 22:56:47 +03:00
1b1988a37a rasterizer_cache: Shorten filenames and general cleanup
* AllocateSurfaceTexture now takes the PixelFormat directly as FormatTuple is an OpenGL struct and will be moved there
2022-10-28 22:56:47 +03:00
f584d143ff video_core: Move UpdatePagesCachedCount to RasterizerAccelerated 2022-10-28 22:56:47 +03:00
56c679595f citra_qt: Prepare GUI for Vulkan support 2022-10-28 22:56:45 +03:00
264 changed files with 39920 additions and 8737 deletions

9
.gitmodules vendored
View File

@ -58,3 +58,12 @@
[submodule "sdl2"]
path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL
[submodule "vulkan-headers"]
path = externals/vulkan-headers
url = https://github.com/KhronosGroup/Vulkan-Headers
[submodule "glslang"]
path = externals/glslang
url = https://github.com/KhronosGroup/glslang
[submodule "glm"]
path = externals/glm
url = https://github.com/g-truc/glm

View File

@ -1,15 +1,15 @@
# CMake 3.12 required for 20 to be a valid value for CXX_STANDARD
cmake_minimum_required(VERSION 3.12)
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15)
# Don't override the warning flags in MSVC:
cmake_policy(SET CMP0092 NEW)
# Enforce new LTO setting
cmake_policy(SET CMP0069 NEW)
endif()
cmake_minimum_required(VERSION 3.14)
# Don't override the warning flags in MSVC:
cmake_policy(SET CMP0092 NEW)
# Enforce new LTO setting
cmake_policy(SET CMP0069 NEW)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
include(GNUInstallDirs)
include(CMakeDependentOption)
project(citra LANGUAGES C CXX ASM)

View File

@ -13,6 +13,6 @@
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="colorful">
<file>style.qss</file>
<file alias="style.qss">../default/style.qss</file>
</qresource>
</RCC>

View File

@ -1,4 +0,0 @@
/*
This file is intentionally left blank.
We do not want to apply any stylesheet for colorful, only icons.
*/

View File

@ -1,33 +1,22 @@
<RCC>
<qresource prefix="icons/default">
<file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="256x256/citra.png">icons/256x256/citra.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="default">
<file>style.qss</file>
</qresource>
</RCC>

13
dist/qt_themes/default/style.qss vendored Normal file
View File

@ -0,0 +1,13 @@
QPushButton#GraphicsAPIStatusBarButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
min-width: 60px;
min-height: 20px;
}
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}

View File

@ -522,13 +522,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
border-width: 1px;
border-color: #54575B;
border-style: solid;
padding: 6px 4px;
border: 1px solid #54575B;
border-radius: 2px;
padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
min-height: 13px;
background-color: #232629;
}
@ -1237,3 +1236,17 @@ QPlainTextEdit:disabled {
TouchScreenPreview {
qproperty-dotHighlightColor: #3daee9;
}
QPushButton#GraphicsAPIStatusBarButton {
color: #656565;
border: 1px solid transparent;
background-color: transparent;
padding: 0px 3px 0px 3px;
text-align: center;
min-width: 60px;
min-height: 20px;
}
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}

View File

@ -60,6 +60,16 @@ endif()
# Glad
add_subdirectory(glad)
# glslang
set(SKIP_GLSLANG_INSTALL ON)
set(ENABLE_GLSLANG_BINARIES OFF)
set(ENABLE_SPVREMAPPER OFF)
set(ENABLE_CTEST OFF)
add_subdirectory(glslang)
# glm
add_subdirectory(glm)
# inih
add_subdirectory(inih)
@ -154,3 +164,12 @@ if(ANDROID)
add_subdirectory(libyuv)
target_include_directories(yuv INTERFACE ./libyuv/include)
endif()
# VMA
add_library(vma INTERFACE)
target_include_directories(vma INTERFACE ./vma)
# vulkan-headers
add_library(vulkan-headers INTERFACE)
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
externals/glm vendored Submodule

Submodule externals/glm added at cc98465e35

1
externals/glslang vendored Submodule

Submodule externals/glslang added at c0cf8ad876

View File

@ -847,7 +847,7 @@ inline MicroProfileLogEntry MicroProfileMakeLogIndex(uint64_t nBegin, MicroProfi
MicroProfileLogEntry Entry = (nBegin<<62) | ((0x3fff&nToken)<<48) | (MP_LOG_TICK_MASK&nTick);
int t = MicroProfileLogType(Entry);
uint64_t nTimerIndex = MicroProfileLogTimerIndex(Entry);
MP_ASSERT(t == nBegin);
MP_ASSERT(static_cast<uint64_t>(t) == nBegin);
MP_ASSERT(nTimerIndex == (nToken&0x3fff));
return Entry;
@ -1579,10 +1579,10 @@ void MicroProfileFlip()
pFramePut->nFrameStartCpu = MP_TICK();
pFramePut->nFrameStartGpu = (uint32_t)MicroProfileGpuInsertTimeStamp();
if(pFrameNext->nFrameStartGpu != (uint64_t)-1)
if(static_cast<uint64_t>(pFrameNext->nFrameStartGpu) != (uint64_t)-1)
pFrameNext->nFrameStartGpu = MicroProfileGpuGetTimeStamp((uint32_t)pFrameNext->nFrameStartGpu);
if(pFrameCurrent->nFrameStartGpu == (uint64_t)-1)
if(static_cast<uint64_t>(pFrameCurrent->nFrameStartGpu) == (uint64_t)-1)
pFrameCurrent->nFrameStartGpu = pFrameNext->nFrameStartGpu + 1;
uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu;

View File

@ -354,7 +354,7 @@ void MicroProfileInitUI()
if(!bInitialized)
{
bInitialized = true;
memset(&g_MicroProfileUI, 0, sizeof(g_MicroProfileUI));
g_MicroProfileUI = {};
UI.nActiveMenu = UINT32_MAX;
UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f;
UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f;
@ -845,8 +845,8 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
MicroProfile& S = *MicroProfileGet();
MP_DEBUG_DUMP_RANGE();
int nY = nBaseY - UI.nOffsetY;
int64_t nNumBoxes = 0;
int64_t nNumLines = 0;
[[maybe_unused]] int64_t nNumBoxes = 0;
[[maybe_unused]] int64_t nNumLines = 0;
uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;
MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent];
@ -1988,7 +1988,7 @@ const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected)
else
{
nIndex = nIndex-1;
if(nIndex < UI.GroupMenuCount)
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
{
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
static char buffer[MICROPROFILE_NAME_MAX_LEN+32];
@ -2135,7 +2135,7 @@ const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected)
case 1: return "--";
default:
nIndex -= 2;
if(nIndex < UI.nCustomCount)
if(static_cast<uint32_t>(nIndex) < UI.nCustomCount)
{
return UI.Custom[nIndex].pName;
}
@ -2185,7 +2185,7 @@ void MicroProfileUIClickGroups(int nIndex)
else
{
nIndex -= 1;
if(nIndex < UI.GroupMenuCount)
if(static_cast<uint32_t>(nIndex) < UI.GroupMenuCount)
{
MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex];
if(Item.nIsCategory)

View File

@ -39,9 +39,9 @@ set(SDL_JOYSTICK ON CACHE BOOL "")
set(SDL_HAPTIC OFF CACHE BOOL "")
set(SDL_HIDAPI ON CACHE BOOL "")
set(SDL_POWER OFF CACHE BOOL "")
set(SDL_THREADS ON CACHE BOOL "")
set(SDL_TIMERS ON CACHE BOOL "")
set(SDL_FILE ON CACHE BOOL "")
set(SDL_THREADS ON CACHE BOOL "")
set(SDL_LOADSO ON CACHE BOOL "")
set(SDL_CPUINFO OFF CACHE BOOL "")
set(SDL_FILESYSTEM OFF CACHE BOOL "")

19670
externals/vma/vk_mem_alloc.h vendored Normal file

File diff suppressed because it is too large Load Diff

1
externals/vulkan-headers vendored Submodule

View File

@ -103,6 +103,7 @@ else()
if (MINGW)
add_definitions(-DMINGW_HAS_SECURE_API)
add_compile_options("-Wa,-mbig-obj")
if (COMPILE_WITH_DWARF)
add_compile_options("-gdwarf")
endif()

View File

@ -25,7 +25,7 @@
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "config.ini";
sdl2_config_loc = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@ -38,8 +38,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
FileUtil::CreateFullPath(location);
FileUtil::WriteStringToFile(true, location, default_contents);
Common::FS::CreateFullPath(location);
Common::FS::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);

View File

@ -35,7 +35,7 @@ std::vector<u8> GetSMDHData(std::string physical_name) {
std::string update_path = Service::AM::GetTitleContentPath(
Service::FS::MediaType::SDMC, program_id + 0x0000000E'00000000);
if (!FileUtil::Exists(update_path))
if (!Common::FS::Exists(update_path))
return original_smdh;
std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);

View File

@ -159,9 +159,9 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
log_filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(log_filter);
Log::AddBackend(std::make_unique<Log::LogcatBackend>());
FileUtil::CreateFullPath(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
Common::FS::CreateFullPath(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
Log::AddBackend(std::make_unique<Log::FileBackend>(
FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + LOG_FILE));
Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + LOG_FILE));
LOG_INFO(Frontend, "Logging backend initialised");
// Initialize misc classes

View File

@ -22,7 +22,6 @@
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/camera/factory.h"
#include "core/frontend/mic.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/savestate.h"
@ -155,7 +154,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
Config{};
// Replace with game-specific settings
u64 program_id{};
FileUtil::SetCurrentRomPath(filepath);
Common::FS::SetCurrentRomPath(filepath);
auto app_loader = Loader::GetLoader(filepath);
if (app_loader) {
app_loader->ReadProgramId(program_id);
@ -306,18 +305,18 @@ void Java_org_citra_citra_1emu_NativeLibrary_SwapScreens(JNIEnv* env, [[maybe_un
void Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring j_directory) {
FileUtil::SetCurrentDir(GetJString(env, j_directory));
Common::FS::SetCurrentDir(GetJString(env, j_directory));
}
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(
JNIEnv* env, [[maybe_unused]] jclass clazz) {
std::vector<std::string> games;
const FileUtil::DirectoryEntryCallable ScanDir =
const Common::FS::DirectoryEntryCallable ScanDir =
[&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) {
std::string path = directory + virtual_name;
if (FileUtil::IsDirectory(path)) {
if (Common::FS::IsDirectory(path)) {
path += '/';
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
Common::FS::ForeachDirectoryEntry(nullptr, path, ScanDir);
} else {
auto loader = Loader::GetLoader(path);
if (loader) {
@ -331,12 +330,12 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(
return true;
};
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
"Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
ScanDir(nullptr, "",
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"00000000000000000000000000000000/title/00040010");
jobjectArray jgames = env->NewObjectArray(static_cast<jsize>(games.size()),
env->FindClass("java/lang/String"), nullptr);

View File

@ -43,8 +43,8 @@ FuncDL<int(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, const uint8_
FuncDL<void(AVCodecParserContext*)> av_parser_close_dl;
bool InitFFmpegDL() {
std::string dll_path = FileUtil::GetUserPath(FileUtil::UserPath::DLLDir);
FileUtil::CreateDir(dll_path);
std::string dll_path = Common::FS::GetUserPath(Common::FS::UserPath::DLLDir);
Common::FS::CreateDir(dll_path);
std::wstring w_dll_path = Common::UTF8ToUTF16W(dll_path);
SetDllDirectoryW(w_dll_path.c_str());

View File

@ -7,17 +7,7 @@
#include <regex>
#include <string>
#include <thread>
// This needs to be included before getopt.h because the latter #defines symbols used by it
#include "common/microprofile.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#endif
#include "citra/config.h"
#include "citra/emu_window/emu_window_sdl2.h"
#include "citra/lodepng_image_interface.h"
@ -25,21 +15,17 @@
#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/dumping/backend.h"
#include "core/file_sys/cia_container.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/loader/loader.h"
#include "core/movie.h"
#include "core/settings.h"
#include "network/network.h"
@ -52,6 +38,11 @@
#endif
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
extern "C" {
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
@ -104,35 +95,35 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
break;
case Network::RoomMember::Error::CouldNotConnect:
LOG_ERROR(Network, "Error: Could not connect");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::NameCollision:
LOG_ERROR(
Network,
"You tried to use the same nickname as another user that is connected to the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::MacCollision:
LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
"connected to the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::ConsoleIdCollision:
LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::WrongPassword:
LOG_ERROR(Network, "Room replied with: Wrong password");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::WrongVersion:
LOG_ERROR(Network,
"You are using a different version than the room you are trying to connect to");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::RoomIsFull:
LOG_ERROR(Network, "The room is full");
exit(1);
std::exit(1);
break;
case Network::RoomMember::Error::HostKicked:
LOG_ERROR(Network, "You have been kicked by the host");
@ -140,6 +131,8 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
case Network::RoomMember::Error::HostBanned:
LOG_ERROR(Network, "You have been banned by the host");
break;
default:
LOG_ERROR(Network, "Unknown network error {}", error);
}
}
@ -177,8 +170,8 @@ static void InitializeLogging() {
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
FileUtil::CreateFullPath(log_dir);
const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@ -359,7 +352,7 @@ int main(int argc, char** argv) {
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
Frontend::ScopeAcquireContext scope(*emu_window);
const auto scope = emu_window->Acquire();
Core::System& system = Core::System::GetInstance();
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};

View File

@ -22,7 +22,7 @@
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini";
sdl2_config_loc = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "sdl2-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@ -35,8 +35,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
FileUtil::CreateFullPath(location);
FileUtil::WriteStringToFile(true, location, default_contents);
Common::FS::CreateFullPath(location);
Common::FS::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@ -109,7 +109,8 @@ void Config::ReadValues() {
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
// Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
Settings::values.graphics_api =
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 0));
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
#ifdef __APPLE__
@ -207,9 +208,9 @@ void Config::ReadValues() {
sdl2_config->GetBoolean("Data Storage", "use_custom_storage", false);
if (Settings::values.use_custom_storage) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
sdl2_config->GetString("Data Storage", "nand_directory", ""));
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir,
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
sdl2_config->GetString("Data Storage", "sdmc_directory", ""));
}

View File

@ -147,7 +147,8 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
SDL_SetMainReady();
if (Settings::values.use_gles) {
const bool is_opengles = Settings::values.graphics_api == Settings::GraphicsAPI::OpenGLES;
if (is_opengles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
@ -201,7 +202,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
exit(1);
}
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
auto gl_load_func = is_opengles ? gladLoadGLES2Loader : gladLoadGLLoader;
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());

View File

@ -260,9 +260,13 @@ endif()
create_target_directory_groups(citra-qt)
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia)
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (NOT WIN32)
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_compile_definitions(citra-qt PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.

View File

@ -54,12 +54,12 @@ QtKeyboardDialog::QtKeyboardDialog(QWidget* parent, QtKeyboard* keyboard_)
case ButtonConfig::None:
break;
}
connect(buttons, &QDialogButtonBox::accepted, this, [=] { Submit(); });
connect(buttons, &QDialogButtonBox::rejected, this, [=] {
connect(buttons, &QDialogButtonBox::accepted, this, [this] { Submit(); });
connect(buttons, &QDialogButtonBox::rejected, this, [this] {
button = QtKeyboard::cancel_id;
accept();
});
connect(buttons, &QDialogButtonBox::helpRequested, this, [=] {
connect(buttons, &QDialogButtonBox::helpRequested, this, [this] {
button = QtKeyboard::forgot_id;
accept();
});

View File

@ -6,10 +6,10 @@
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMessageBox>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_4_3_Core>
#include <QOpenGLExtraFunctions>
#include <fmt/format.h>
#include "citra_qt/bootmanager.h"
#include "citra_qt/main.h"
@ -17,56 +17,60 @@
#include "common/scm_rev.h"
#include "core/3ds.h"
#include "core/core.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/perf_stats.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
EmuThread::~EmuThread() = default;
static GMainWindow* GetMainWindow() {
for (QWidget* w : qApp->topLevelWidgets()) {
const auto widgets = qApp->topLevelWidgets();
for (QWidget* w : widgets) {
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
return main;
}
}
return nullptr;
}
void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread");
Frontend::ScopeAcquireContext scope(core_context);
const auto scope = core_context.Acquire();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
Core::System& system = Core::System::GetInstance();
system.Renderer().Rasterizer()->LoadDiskResources(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
emit HideLoadingScreen();
core_context.MakeCurrent();
if (Core::System::GetInstance().frame_limiter.IsFrameAdvancing()) {
if (system.frame_limiter.IsFrameAdvancing()) {
// Usually the loading screen is hidden after the first frame is drawn. In this case
// we hide it immediately as we need to wait for user input to start the emulation.
emit HideLoadingScreen();
Core::System::GetInstance().frame_limiter.WaitOnce();
system.frame_limiter.WaitOnce();
}
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step.
bool was_active = false;
Core::System& system = Core::System::GetInstance();
while (!stop_run) {
if (running) {
if (!was_active)
@ -111,87 +115,234 @@ void EmuThread::run() {
#endif
}
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
event_handler(event_handler) {
class OpenGLSharedContext : public Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
this->setFormat(format);
format.setVersion(4, 4);
format.setProfile(QSurfaceFormat::CoreProfile);
context->setShareContext(shared_context);
context->setScreen(this->screen());
context->setFormat(format);
context->create();
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
setSurfaceType(QWindow::OpenGLSurface);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
}
OpenGLWindow::~OpenGLWindow() {
context->doneCurrent();
}
void OpenGLWindow::Present() {
if (!isExposed())
return;
context->makeCurrent(this);
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(100);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
context->swapBuffers(this);
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
f->glFinish();
QWindow::requestUpdate();
}
bool OpenGLWindow::event(QEvent* event) {
switch (event->type()) {
case QEvent::UpdateRequest:
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = share_context->format();
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
~OpenGLSharedContext() {
context->doneCurrent();
}
void SwapBuffers() override {
context->swapBuffers(surface);
}
void MakeCurrent() override {
// We can't track the current state of the underlying context in this wrapper class because
// Qt may make the underlying context not current for one reason or another. In particular,
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
// Instead of always just making the context current (which does not have any caching to
// check if the underlying context is already current) we can check for the current context
// in the thread local data by calling `currentContext()` and checking if its ours.
if (QOpenGLContext::currentContext() != context.get()) {
context->makeCurrent(surface);
}
}
void DoneCurrent() override {
context->doneCurrent();
}
QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
};
class DummyContext : public Frontend::GraphicsContext {};
class RenderWidget : public QWidget {
public:
RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
}
virtual ~RenderWidget() = default;
virtual void Present() {}
void paintEvent(QPaintEvent* event) override {
Present();
return true;
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::FocusAboutToChange:
case QEvent::Enter:
case QEvent::Leave:
case QEvent::Wheel:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::InputMethodQuery:
case QEvent::TouchCancel:
return QCoreApplication::sendEvent(event_handler, event);
case QEvent::Drop:
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
return true;
case QEvent::DragEnter:
case QEvent::DragMove:
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
return true;
default:
return QWindow::event(event);
update();
}
void resizeEvent(QResizeEvent* ev) override {
render_window->resize(ev->size());
render_window->OnFramebufferSizeChanged();
}
void keyPressEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->PressKey(event->key());
}
void keyReleaseEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void mousePressEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchBeginEvent
const auto pos{event->pos()};
if (event->button() == Qt::LeftButton) {
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void mouseMoveEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchUpdateEvent
const auto pos{event->pos()};
const auto [x, y] = render_window->ScaleTouch(pos);
render_window->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void mouseReleaseEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchEndEvent
if (event->button() == Qt::LeftButton)
render_window->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
std::pair<unsigned, unsigned> GetSize() const {
return std::make_pair(width(), height());
}
QPaintEngine* paintEngine() const override {
return nullptr;
}
private:
GRenderWindow* render_window;
};
class OpenGLRenderWidget : public RenderWidget {
public:
explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
}
void SetContext(std::unique_ptr<OpenGLSharedContext>&& context_) {
context = std::move(context_);
}
void Present() override {
if (!isVisible()) {
return;
}
if (!Core::System::GetInstance().IsPoweredOn()) {
return;
}
context->MakeCurrent();
const auto f = context->GetShareContext()->extraFunctions();
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
VideoCore::g_renderer->TryPresent(100);
context->SwapBuffers();
f->glFinish();
}
private:
std::unique_ptr<OpenGLSharedContext> context{};
};
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
}
};
static Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
return Frontend::WindowSystemType::Windows;
else if (platform_name == QStringLiteral("xcb"))
return Frontend::WindowSystemType::X11;
else if (platform_name == QStringLiteral("wayland"))
return Frontend::WindowSystemType::Wayland;
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
return Frontend::WindowSystemType::Windows;
}
void OpenGLWindow::exposeEvent(QExposeEvent* event) {
QWindow::requestUpdate();
QWindow::exposeEvent(event);
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
// Our Win32 Qt external doesn't have the private API.
#if defined(WIN32) || defined(__APPLE__)
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
if (wsi.type == Frontend::WindowSystemType::Wayland)
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
else
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#endif
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
return wsi;
}
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
@ -218,11 +369,11 @@ GRenderWindow::~GRenderWindow() {
}
void GRenderWindow::MakeCurrent() {
core_context->MakeCurrent();
main_context->MakeCurrent();
}
void GRenderWindow::DoneCurrent() {
core_context->DoneCurrent();
main_context->DoneCurrent();
}
void GRenderWindow::PollEvents() {
@ -387,33 +538,70 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
OnFramebufferSizeChanged();
}
void GRenderWindow::InitRenderTarget() {
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api;
if (graphics_api == Settings::GraphicsAPI::OpenGL ||
graphics_api == Settings::GraphicsAPI::OpenGLES) {
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
// Bind the shared contexts to the main surface in case the backend wants to take over
// presentation
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
child_widget->windowHandle());
}
return std::make_unique<DummyContext>();
}
bool GRenderWindow::InitRenderTarget() {
ReleaseRenderTarget();
{
// Create a dummy render widget so that Qt
// places the render window at the correct position.
const RenderWidget dummy_widget{this};
}
first_frame = false;
GMainWindow* parent = GetMainWindow();
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
child_window->create();
child_widget = createWindowContainer(child_window, this);
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api;
switch (graphics_api) {
case Settings::GraphicsAPI::OpenGL:
case Settings::GraphicsAPI::OpenGLES:
if (!InitializeOpenGL()) {
return false;
}
break;
case Settings::GraphicsAPI::Vulkan:
if (!InitializeVulkan()) {
return false;
}
break;
}
// Update the Window System information with the new render target
window_info = GetWindowSystemInfo(child_widget->windowHandle());
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
layout()->addWidget(child_widget);
// Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1);
core_context = CreateSharedContext();
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
BackupGeometry();
return true;
}
void GRenderWindow::ReleaseRenderTarget() {
if (child_widget) {
layout()->removeWidget(child_widget);
delete child_widget;
child_widget->deleteLater();
child_widget = nullptr;
}
main_context.reset();
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@ -423,7 +611,7 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
VideoCore::RequestScreenshot(
screenshot_image.bits(),
[=] {
[this, &screenshot_path] {
const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
@ -438,6 +626,29 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
setMinimumSize(minimal_size.first, minimal_size.second);
}
bool GRenderWindow::InitializeOpenGL() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
main_context = context;
child->SetContext(
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
return true;
}
bool GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
return true;
}
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread;
}
@ -449,31 +660,3 @@ void GRenderWindow::OnEmulationStopping() {
void GRenderWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
}
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
return std::make_unique<GLContext>(QOpenGLContext::globalShareContext());
}
GLContext::GLContext(QOpenGLContext* shared_context)
: context(std::make_unique<QOpenGLContext>(shared_context->parent())),
surface(std::make_unique<QOffscreenSurface>(nullptr)) {
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(0);
context->setShareContext(shared_context);
context->setFormat(format);
context->create();
surface->setParent(shared_context->parent());
surface->setFormat(format);
surface->create();
}
void GLContext::MakeCurrent() {
context->makeCurrent(surface.get());
}
void GLContext::DoneCurrent() {
context->doneCurrent();
}

View File

@ -27,19 +27,6 @@ namespace VideoCore {
enum class LoadCallbackStage;
}
class GLContext : public Frontend::GraphicsContext {
public:
explicit GLContext(QOpenGLContext* shared_context);
void MakeCurrent() override;
void DoneCurrent() override;
private:
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> surface;
};
class EmuThread final : public QThread {
Q_OBJECT
@ -126,24 +113,6 @@ signals:
void HideLoadingScreen();
};
class OpenGLWindow : public QWindow {
Q_OBJECT
public:
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
~OpenGLWindow();
void Present();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
private:
std::unique_ptr<QOpenGLContext> context;
QWidget* event_handler;
};
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
Q_OBJECT
@ -179,13 +148,15 @@ public:
void focusOutEvent(QFocusEvent* event) override;
void InitRenderTarget();
bool InitRenderTarget();
/// Destroy the previous run's child_widget which should also destroy the child_window
void ReleaseRenderTarget();
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
public slots:
void OnEmulationStarting(EmuThread* emu_thread);
@ -205,29 +176,29 @@ signals:
void MouseActivity();
private:
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
std::unique_ptr<GraphicsContext> core_context;
QByteArray geometry;
/// Native window handle that backs this presentation widget
QWindow* child_window = nullptr;
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
/// put the child_window into a widget then add it to the layout. This child_widget can be
/// parented to GRenderWindow and use Qt's lifetime system
QWidget* child_widget = nullptr;
bool InitializeOpenGL();
bool InitializeVulkan();
EmuThread* emu_thread;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
// should instead be shared from
std::shared_ptr<Frontend::GraphicsContext> main_context;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
QByteArray geometry;
QWidget* child_widget = nullptr;
bool first_frame = false;
protected:

View File

@ -37,7 +37,7 @@ CheatDialog::CheatDialog(QWidget* parent)
connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
connect(ui->buttonSave, &QPushButton::clicked,
connect(ui->buttonSave, &QPushButton::clicked, this,
[this] { SaveCheat(ui->tableCheats->currentRow()); });
connect(ui->buttonDelete, &QPushButton::clicked, this, &CheatDialog::OnDeleteCheat);
@ -91,7 +91,7 @@ bool CheatDialog::SaveCheat(int row) {
}
// Check if the cheat lines are valid
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, QString::SkipEmptyParts);
auto code_lines = ui->textCode->toPlainText().split(QLatin1Char{'\n'}, Qt::SkipEmptyParts);
for (int i = 0; i < code_lines.size(); ++i) {
Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
if (cheat_line.valid)
@ -190,8 +190,9 @@ void CheatDialog::OnDeleteCheat() {
if (newly_created) {
newly_created = false;
} else {
Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow());
Core::System::GetInstance().CheatEngine().SaveCheatFile();
auto& cheat_engine = Core::System::GetInstance().CheatEngine();
cheat_engine.RemoveCheat(ui->tableCheats->currentRow());
cheat_engine.SaveCheatFile();
}
LoadCheats();

View File

@ -19,8 +19,8 @@
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
FileUtil::CreateFullPath(qt_config_loc);
qt_config_loc = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "qt-config.ini";
Common::FS::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload();
@ -312,8 +312,8 @@ void Config::ReadDataStorageValues() {
ReadSetting(QStringLiteral("sdmc_directory"), QStringLiteral("")).toString().toStdString();
if (Settings::values.use_custom_storage) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, nand_dir);
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, sdmc_dir);
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir, nand_dir);
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir, sdmc_dir);
}
qt_config->endGroup();
@ -327,6 +327,9 @@ void Config::ReadDebuggingValues() {
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
Settings::values.renderer_debug = ReadSetting(QStringLiteral("renderer_debug"), false).toBool();
Settings::values.dump_command_buffers =
ReadSetting(QStringLiteral("dump_command_buffers"), false).toBool();
qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Service::service_module_map) {
@ -478,6 +481,11 @@ void Config::ReadPathValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(
ReadSetting(QStringLiteral("graphics_api"), static_cast<u32>(Settings::GraphicsAPI::OpenGL))
.toUInt());
Settings::values.physical_device = ReadSetting(QStringLiteral("physical_device"), 0).toUInt();
Settings::values.async_command_recording = ReadSetting(QStringLiteral("async_command_recording"), true).toBool();
Settings::values.use_hw_renderer =
ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
@ -517,7 +525,7 @@ void Config::ReadRendererValues() {
void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (auto [name, group, shortcut] : default_hotkeys) {
for (const auto& [name, group, shortcut] : default_hotkeys) {
auto [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
@ -552,7 +560,7 @@ void Config::ReadSystemValues() {
// https://developers.google.com/media/vp9/live-encoding
const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QString{};
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QStringLiteral("");
void Config::ReadVideoDumpingValues() {
qt_config->beginGroup(QStringLiteral("VideoDumping"));
@ -867,10 +875,10 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
WriteSetting(QStringLiteral("use_custom_storage"), Settings::values.use_custom_storage, false);
WriteSetting(QStringLiteral("nand_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)),
QStringLiteral(""));
WriteSetting(QStringLiteral("sdmc_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)),
QStringLiteral(""));
qt_config->endGroup();
@ -883,6 +891,9 @@ void Config::SaveDebuggingValues() {
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteSetting(QStringLiteral("renderer_debug"), Settings::values.renderer_debug, false);
WriteSetting(QStringLiteral("dump_command_buffers"), Settings::values.dump_command_buffers,
false);
qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Settings::values.lle_modules) {
@ -991,6 +1002,10 @@ void Config::SavePathValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
WriteSetting(QStringLiteral("graphics_api"), static_cast<u32>(Settings::values.graphics_api),
static_cast<u32>(Settings::GraphicsAPI::OpenGL));
WriteSetting(QStringLiteral("physical_device"), Settings::values.physical_device, 0);
WriteSetting(QStringLiteral("async_command_recording"), Settings::values.async_command_recording, true);
WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
#ifdef __APPLE__
@ -1012,9 +1027,9 @@ void Config::SaveRendererValues() {
200);
// Cast to double because Qt's written float values are not human-readable
WriteSetting(QStringLiteral("bg_red"), (double)Settings::values.bg_red, 0.0);
WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0);
WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0);
WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
WriteSetting(QStringLiteral("texture_filter_name"),
QString::fromStdString(Settings::values.texture_filter_name),

View File

@ -9,8 +9,6 @@
#include <QMessageBox>
#include <QWidget>
#include "citra_qt/configuration/configure_camera.h"
#include "citra_qt/uisettings.h"
#include "core/core.h"
#include "core/frontend/camera/factory.h"
#include "core/frontend/camera/interface.h"
#include "core/hle/service/cam/cam.h"
@ -91,7 +89,7 @@ void ConfigureCamera::ConnectEvents() {
SetConfiguration();
});
connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked);
connect(ui->preview_button, &QPushButton::clicked, this, [=] { StartPreviewing(); });
connect(ui->preview_button, &QPushButton::clicked, this, [this] { StartPreviewing(); });
connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) {
ui->camera_file->setDisabled(state == Qt::Checked);
ui->toolButton->setDisabled(state == Qt::Checked);
@ -99,12 +97,11 @@ void ConfigureCamera::ConnectEvents() {
ui->camera_file->setText(QString{});
}
});
connect(ui->camera_file, &QLineEdit::textChanged, this, [=] { StopPreviewing(); });
connect(ui->system_camera,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[=] { StopPreviewing(); });
connect(ui->camera_flip, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, [=] { StopPreviewing(); });
connect(ui->camera_file, &QLineEdit::textChanged, this, [this] { StopPreviewing(); });
connect(ui->system_camera, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this] { StopPreviewing(); });
connect(ui->camera_flip, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this] { StopPreviewing(); });
}
void ConfigureCamera::UpdateCameraMode() {

View File

@ -4,16 +4,17 @@
#include <QDesktopServices>
#include <QUrl>
#include <QMessageBox>
#include "citra_qt/configuration/configure_debug.h"
#include "citra_qt/debugger/console.h"
#include "citra_qt/uisettings.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "qcheckbox.h"
#include "ui_configure_debug.h"
#include "video_core/renderer_vulkan/vk_instance.h"
ConfigureDebug::ConfigureDebug(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureDebug>()) {
@ -21,10 +22,44 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
SetConfiguration();
connect(ui->open_log_button, &QPushButton::clicked, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
QString path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) {
if (checked && Settings::values.graphics_api == Settings::GraphicsAPI::Vulkan) {
try {
Vulkan::Instance debug_inst{true};
} catch (vk::LayerNotPresentError& err) {
ui->toggle_renderer_debug->toggle();
QMessageBox::warning(
this, tr("Validation layer not available"),
tr("Unable to enable debug renderer because the layer "
"<strong>VK_LAYER_KHRONOS_validation</strong> is missing. "
"Please install the Vulkan SDK or the appropriate package of your distribution"));
}
}
});
connect(ui->toggle_dump_command_buffers, &QCheckBox::clicked, this, [this](bool checked) {
if (checked && Settings::values.graphics_api == Settings::GraphicsAPI::Vulkan) {
try {
Vulkan::Instance debug_inst{false, true};
} catch (vk::LayerNotPresentError& err) {
ui->toggle_dump_command_buffers->toggle();
QMessageBox::warning(
this, tr("Command buffer dumping not available"),
tr("Unable to enable command buffer dumping because the layer "
"<strong>VK_LAYER_LUNARG_api_dump</strong> is missing. "
"Please install the Vulkan SDK or the appropriate package of your distribution"));
}
}
});
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
ui->toggle_dump_command_buffers->setEnabled(!is_powered_on);
}
ConfigureDebug::~ConfigureDebug() = default;
@ -37,6 +72,8 @@ void ConfigureDebug::SetConfiguration() {
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug);
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers);
}
void ConfigureDebug::ApplyConfiguration() {
@ -49,6 +86,8 @@ void ConfigureDebug::ApplyConfiguration() {
filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(filter);
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
}
void ConfigureDebug::RetranslateUI() {

View File

@ -22,5 +22,6 @@ public:
void RetranslateUI();
void SetConfiguration();
private:
std::unique_ptr<Ui::ConfigureDebug> ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>443</width>
<height>300</height>
<width>454</width>
<height>356</height>
</rect>
</property>
<property name="windowTitle">
@ -114,11 +114,31 @@
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="toggle_cpu_jit">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable CPU JIT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_renderer_debug">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables debug reporting in the currently selected graphics API. Causes measurable performance loss, don't enable unless for debugging purposes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable debug renderer</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text">
<string>Dump command buffers</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -117,9 +117,9 @@ void ConfigureGeneral::SetConfiguration() {
QString screenshot_path = UISettings::values.screenshot_path;
if (screenshot_path.isEmpty()) {
screenshot_path =
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir));
screenshot_path.append(QStringLiteral("screenshots/"));
FileUtil::CreateFullPath(screenshot_path.toStdString());
Common::FS::CreateFullPath(screenshot_path.toStdString());
UISettings::values.screenshot_path = screenshot_path;
}
ui->screenshot_dir_path->setText(screenshot_path);
@ -134,7 +134,7 @@ void ConfigureGeneral::ResetDefaults() {
if (answer == QMessageBox::No)
return;
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini");
Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "qt-config.ini");
std::exit(0);
}

View File

@ -10,15 +10,28 @@
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
#include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_vulkan/vk_instance.h"
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
ui->setupUi(this);
DiscoverPhysicalDevices();
SetConfiguration();
ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked());
ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
const bool not_running = !Core::System::GetInstance().IsPoweredOn();
const bool hw_renderer_enabled = ui->toggle_hw_renderer->isChecked();
ui->toggle_hw_renderer->setEnabled(not_running);
ui->hw_renderer_group->setEnabled(hw_renderer_enabled && not_running);
ui->toggle_vsync_new->setEnabled(not_running);
ui->graphics_api_combo->setEnabled(not_running);
ui->toggle_shader_jit->setEnabled(not_running);
ui->toggle_disk_shader_cache->setEnabled(hw_renderer_enabled && not_running);
ui->toggle_async_recording->setEnabled(hw_renderer_enabled && not_running);
ui->physical_device_combo->setEnabled(not_running);
SetPhysicalDeviceComboVisibility(ui->graphics_api_combo->currentIndex());
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
auto checked = ui->toggle_hw_renderer->isChecked();
@ -31,7 +44,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
ui->toggle_hw_shader->isChecked());
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
auto checked = ui->toggle_hw_shader->isChecked();
const bool checked = ui->toggle_hw_shader->isChecked();
ui->hw_shader_group->setEnabled(checked);
ui->toggle_disk_shader_cache->setEnabled(checked);
});
@ -69,6 +82,9 @@ void ConfigureGraphics::SetConfiguration() {
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new);
ui->graphics_api_combo->setCurrentIndex(static_cast<int>(Settings::values.graphics_api));
ui->physical_device_combo->setCurrentIndex(static_cast<int>(Settings::values.physical_device));
ui->toggle_async_recording->setChecked(Settings::values.async_command_recording);
}
void ConfigureGraphics::ApplyConfiguration() {
@ -79,8 +95,30 @@ void ConfigureGraphics::ApplyConfiguration() {
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked();
Settings::values.graphics_api =
static_cast<Settings::GraphicsAPI>(ui->graphics_api_combo->currentIndex());
Settings::values.physical_device = static_cast<u16>(ui->physical_device_combo->currentIndex());
Settings::values.async_command_recording = ui->toggle_async_recording->isChecked();
}
void ConfigureGraphics::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGraphics::DiscoverPhysicalDevices() {
Vulkan::Instance instance{};
const auto physical_devices = instance.GetPhysicalDevices();
ui->physical_device_combo->clear();
for (const vk::PhysicalDevice& physical_device : physical_devices) {
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
ui->physical_device_combo->addItem(name);
}
}
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
const bool is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
ui->physical_device_label->setVisible(is_visible);
ui->physical_device_combo->setVisible(is_visible);
}

View File

@ -24,6 +24,11 @@ public:
void UpdateBackgroundColorButton(const QColor& color);
private:
void DiscoverPhysicalDevices();
void SetPhysicalDeviceComboVisibility(int index);
private:
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
};

View File

@ -20,6 +20,59 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="apiBox">
<property name="title">
<string>API Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="graphics_api_label">
<property name="text">
<string>Graphics API</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="graphics_api_combo">
<item>
<property name="text">
<string>OpenGL</string>
</property>
</item>
<item>
<property name="text">
<string>OpenGLES</string>
</property>
</item>
<item>
<property name="text">
<string>Vulkan</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="physical_device_label">
<property name="text">
<string>Physical device</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="physical_device_combo"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="rendererBox">
<property name="title">
@ -118,6 +171,16 @@
<string>Advanced</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_async_recording">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Offloads command buffer recording and fragment shader generation to a worker thread. Can improve performance especially on weaker systems. Disable if you notice better performance. If unsure leave it enabled,&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Async Command Recording</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_disk_shader_cache">
<property name="toolTip">

View File

@ -192,10 +192,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
if (!button_map[button_id])
continue;
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button_map[button_id], &QPushButton::clicked, [=]() {
connect(button_map[button_id], &QPushButton::clicked, [this, button_id]() {
HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) {
[this, button_id](const Common::ParamPackage& params) {
buttons_param[button_id] = params;
// If the user closes the dialog, the changes are reverted in
// `GMainWindow::OnConfigure()`
@ -204,16 +204,16 @@ ConfigureInput::ConfigureInput(QWidget* parent)
},
InputCommon::Polling::DeviceType::Button);
});
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
connect(button_map[button_id], &QPushButton::customContextMenuRequested, this,
[this, button_id](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
@ -230,27 +230,29 @@ ConfigureInput::ConfigureInput(QWidget* parent)
continue;
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
Qt::CustomContextMenu);
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, [=]() {
HandleClick(
analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Button);
});
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, this,
[this, analog_id, sub_button_id]() {
HandleClick(
analog_map_buttons[analog_id][sub_button_id],
[this, analog_id, sub_button_id](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Button);
});
connect(analog_map_buttons[analog_id][sub_button_id],
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
&QPushButton::customContextMenuRequested, this,
[this, analog_id, sub_button_id](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id],
@ -264,7 +266,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
menu_location));
});
}
connect(analog_map_stick[analog_id], &QPushButton::clicked, [=]() {
connect(analog_map_stick[analog_id], &QPushButton::clicked, this, [this, analog_id]() {
if (QMessageBox::information(
this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
@ -272,7 +274,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
HandleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) {
[this, analog_id](const Common::ParamPackage& params) {
analogs_param[analog_id] = params;
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
@ -280,29 +282,31 @@ ConfigureInput::ConfigureInput(QWidget* parent)
InputCommon::Polling::DeviceType::Analog);
}
});
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
const auto engine = analogs_param[analog_id].Get("engine", "");
if (engine == "sdl" || engine == "gcpad") {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Deadzone: %1%").arg(slider_value));
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
} else {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Modifier Scale: %1%").arg(slider_value));
analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
}
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, this,
[this, analog_id] {
const int slider_value =
analog_map_deadzone_and_modifier_slider[analog_id]->value();
const auto engine = analogs_param[analog_id].Get("engine", "");
if (engine == "sdl" || engine == "gcpad") {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Deadzone: %1%").arg(slider_value));
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
} else {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Modifier Scale: %1%").arg(slider_value));
analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
}
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
});
}
// The Circle Mod button is common for both the sticks, so update the modifier settings
// for both the sticks.
connect(ui->buttonCircleMod, &QPushButton::clicked, [=]() {
connect(ui->buttonCircleMod, &QPushButton::clicked, this, [this]() {
HandleClick(
ui->buttonCircleMod,
[=](const Common::ParamPackage& params) {
[this](const Common::ParamPackage& params) {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
SetAnalogButton(params, analogs_param[analog_id], "modifier");
@ -312,10 +316,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
},
InputCommon::Polling::DeviceType::Button);
});
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested,
connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested, this,
[&](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
context_menu.addAction(tr("Clear"), this, [&] {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
analogs_param[analog_id].Erase("modifier");
@ -325,7 +329,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), [&] {
context_menu.addAction(tr("Restore Default"), this, [&] {
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
@ -341,7 +345,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location));
});
connect(ui->buttonMotionTouch, &QPushButton::clicked, [this] {
connect(ui->buttonMotionTouch, &QPushButton::clicked, this, [this] {
QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
return motion_touch_dialog->exec();
});
@ -356,18 +360,17 @@ ConfigureInput::ConfigureInput(QWidget* parent)
connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile);
connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile);
connect(ui->profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this](int i) {
ApplyConfiguration();
Settings::SaveProfile(Settings::values.current_input_profile_index);
Settings::LoadProfile(i);
LoadConfiguration();
});
connect(ui->profile, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int i) {
ApplyConfiguration();
Settings::SaveProfile(Settings::values.current_input_profile_index);
Settings::LoadProfile(i);
LoadConfiguration();
});
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
@ -554,7 +557,7 @@ void ConfigureInput::AutoMap() {
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
return;
}
input_setter = [=](const Common::ParamPackage& params) {
input_setter = [this](const Common::ParamPackage& params) {
MapFromButton(params);
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());

View File

@ -46,6 +46,9 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
case CalibrationConfigurationJob::Status::Completed:
text = tr("Configuration completed!");
break;
default:
LOG_ERROR(Frontend, "Unknown calibration status {}", status);
break;
}
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
if (status == CalibrationConfigurationJob::Status::Completed) {
@ -99,9 +102,9 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
connect(poll_timer.get(), &QTimer::timeout, this, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
@ -202,7 +205,7 @@ void ConfigureMotionTouch::ConnectEvents() {
connect(ui->touch_provider,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[this]([[maybe_unused]] int index) { UpdateUiDisplay(); });
connect(ui->motion_controller_button, &QPushButton::clicked, [=]() {
connect(ui->motion_controller_button, &QPushButton::clicked, this, [this]() {
if (QMessageBox::information(this, tr("Information"),
tr("After pressing OK, press a button on the controller whose "
"motion you want to track."),
@ -210,7 +213,7 @@ void ConfigureMotionTouch::ConnectEvents() {
ui->motion_controller_button->setText(tr("[press button]"));
ui->motion_controller_button->setFocus();
input_setter = [=](const Common::ParamPackage& params) {
input_setter = [this](const Common::ParamPackage& params) {
guid = params.Get("guid", "0");
port = params.Get("port", 0);
};

View File

@ -16,33 +16,33 @@ ConfigureStorage::ConfigureStorage(QWidget* parent)
SetConfiguration();
connect(ui->open_nand_dir, &QPushButton::clicked, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
QString path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
connect(ui->change_nand_dir, &QPushButton::clicked, this, [this]() {
const QString dir_path = QFileDialog::getExistingDirectory(
this, tr("Select NAND Directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)),
QFileDialog::ShowDirsOnly);
if (!dir_path.isEmpty()) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, dir_path.toStdString());
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir, dir_path.toStdString());
SetConfiguration();
}
});
connect(ui->open_sdmc_dir, &QPushButton::clicked, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
QString path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
connect(ui->change_sdmc_dir, &QPushButton::clicked, this, [this]() {
const QString dir_path = QFileDialog::getExistingDirectory(
this, tr("Select SDMC Directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)),
QFileDialog::ShowDirsOnly);
if (!dir_path.isEmpty()) {
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, dir_path.toStdString());
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir, dir_path.toStdString());
SetConfiguration();
}
});
@ -61,13 +61,13 @@ ConfigureStorage::~ConfigureStorage() = default;
void ConfigureStorage::SetConfiguration() {
ui->nand_group->setVisible(Settings::values.use_custom_storage);
QString nand_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
QString nand_path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
ui->nand_dir_path->setText(nand_path);
ui->open_nand_dir->setEnabled(!nand_path.isEmpty());
ui->sdmc_group->setVisible(Settings::values.use_virtual_sd &&
Settings::values.use_custom_storage);
QString sdmc_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
QString sdmc_path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
ui->sdmc_dir_path->setText(sdmc_path);
ui->open_sdmc_dir->setEnabled(!sdmc_path.isEmpty());
@ -82,10 +82,10 @@ void ConfigureStorage::ApplyConfiguration() {
Settings::values.use_custom_storage = ui->toggle_custom_storage->isChecked();
if (!Settings::values.use_custom_storage) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
GetDefaultUserPath(FileUtil::UserPath::NANDDir));
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir,
GetDefaultUserPath(FileUtil::UserPath::SDMCDir));
Common::FS::UpdateUserPath(Common::FS::UserPath::NANDDir,
GetDefaultUserPath(Common::FS::UserPath::NANDDir));
Common::FS::UpdateUserPath(Common::FS::UserPath::SDMCDir,
GetDefaultUserPath(Common::FS::UserPath::SDMCDir));
}
}

View File

@ -508,7 +508,7 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
}
const auto pos = MapToDeviceCoords(event->x(), event->y());
if (pos) {
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x(), pos->y()));
} else {
coord_label->clear();
}
@ -568,7 +568,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
if (coord_label) {
coord_label->setText(
QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
QStringLiteral("X: %1, Y: %2").arg(device_coord->x(), device_coord->y()));
}
}
return true;

View File

@ -9,7 +9,6 @@
#include <QVBoxLayout>
#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
#include "citra_qt/debugger/graphics/graphics_breakpoints_p.h"
#include "common/assert.h"
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
: QAbstractListModel(parent), context_weak(debug_context),
@ -60,12 +59,15 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
}
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
if (!index.isValid())
return 0;
if (!index.isValid()) {
return {};
}
Qt::ItemFlags flags = Qt::ItemIsEnabled;
if (index.column() == 0)
if (index.column() == 0) {
flags |= Qt::ItemIsUserCheckable;
}
return flags;
}

View File

@ -14,7 +14,6 @@
#include <QTreeView>
#include <QVBoxLayout>
#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
#include "citra_qt/util/spinbox.h"
#include "citra_qt/util/util.h"
#include "common/vector_math.h"
#include "core/core.h"
@ -130,7 +129,7 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
COMMAND_IN_RANGE(command_id, texturing.texture1) ||
COMMAND_IN_RANGE(command_id, texturing.texture2)) {
unsigned texture_index;
[[maybe_unused]] u32 texture_index;
if (COMMAND_IN_RANGE(command_id, texturing.texture0)) {
texture_index = 0;
} else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) {

View File

@ -16,7 +16,6 @@
#include "citra_qt/util/spinbox.h"
#include "common/color.h"
#include "core/core.h"
#include "core/hw/gpu.h"
#include "core/memory.h"
#include "video_core/pica_state.h"
#include "video_core/regs_framebuffer.h"
@ -34,12 +33,15 @@ void SurfacePicture::mousePressEvent(QMouseEvent* event) {
if (!(event->buttons() & Qt::LeftButton))
return;
if (pixmap() == nullptr)
const QPixmap pixmap = this->pixmap(Qt::ReturnByValue);
if (pixmap.isNull()) {
return;
}
if (surface_widget)
surface_widget->Pick(event->x() * pixmap()->width() / width(),
event->y() * pixmap()->height() / height());
if (surface_widget) {
surface_widget->Pick(event->x() * pixmap.width() / width(),
event->y() * pixmap.height() / height());
}
}
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
@ -314,57 +316,46 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
case Format::RGBA8: {
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
}
case Format::RGB8: {
auto value = Color::DecodeRGB8(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
QString::number(value.b(), 'f', 2));
}
case Format::RGB5A1: {
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
}
case Format::RGB565: {
auto value = Color::DecodeRGB565(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2, Blue: %3")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
QString::number(value.b(), 'f', 2));
}
case Format::RGBA4: {
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2),
QString::number(value.b(), 'f', 2), QString::number(value.a(), 'f', 2));
}
case Format::IA8:
return QStringLiteral("Index: %1, Alpha: %2").arg(pixel[0]).arg(pixel[1]);
return QStringLiteral("Index: %1, Alpha: %2").arg(pixel[0], pixel[1]);
case Format::RG8: {
auto value = Color::DecodeRG8(pixel) / 255.0f;
return QStringLiteral("Red: %1, Green: %2")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2));
.arg(QString::number(value.r(), 'f', 2), QString::number(value.g(), 'f', 2));
}
case Format::I8:
return QStringLiteral("Index: %1").arg(*pixel);
case Format::A8:
return QStringLiteral("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
case Format::IA4:
return QStringLiteral("Index: %1, Alpha: %2")
.arg(*pixel & 0xF)
.arg((*pixel & 0xF0) >> 4);
return QStringLiteral("Index: %1, Alpha: %2").arg(*pixel & 0xF, (*pixel & 0xF0) >> 4);
case Format::I4: {
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
return QStringLiteral("Index: %1").arg(i);
@ -390,8 +381,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
case Format::X24S8: {
auto values = Color::DecodeD24S8(pixel);
return QStringLiteral("Depth: %1, Stencil: %2")
.arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4))
.arg(values[1]);
.arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4), values[1]);
}
case Format::Unknown:
return QStringLiteral("Unknown format");
@ -401,8 +391,8 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
};
QString nibbles;
for (unsigned i = 0; i < nibbles_per_pixel; i++) {
unsigned nibble_index = i;
for (u32 i = 0; i < nibbles_per_pixel; i++) {
u32 nibble_index = i;
if (nibble_mode) {
nibble_index += (offset % 2) ? 0 : 1;
}
@ -412,7 +402,7 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
}
surface_info_label->setText(
QStringLiteral("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
QStringLiteral("Raw: 0x%3\n(%4)").arg(nibbles, GetText(surface_format, pixel)));
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
}
@ -676,8 +666,8 @@ void GraphicsSurfaceWidget::SaveSurface() {
}
if (selected_filter == png_filter) {
const QPixmap* const pixmap = surface_picture_label->pixmap();
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
const QPixmap pixmap = surface_picture_label->pixmap(Qt::ReturnByValue);
ASSERT_MSG(!pixmap.isNull(), "No pixmap set");
QFile file{filename};
if (!file.open(QIODevice::WriteOnly)) {
@ -685,7 +675,7 @@ void GraphicsSurfaceWidget::SaveSurface() {
return;
}
if (!pixmap->save(&file, "PNG")) {
if (!pixmap.save(&file, "PNG")) {
QMessageBox::warning(this, tr("Error"),
tr("Failed to save surface data to file '%1'").arg(filename));
}

View File

@ -111,13 +111,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
}
};
const Instruction instr = par->info.code[index.row()];
const Instruction& instr = par->info.code[index.row()];
const OpCode opcode = instr.opcode;
const OpCode::Info opcode_info = opcode.GetInfo();
const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd
? instr.mad.operand_desc_id.Value()
: instr.common.operand_desc_id.Value();
const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern;
const SwizzlePattern& swizzle = par->info.swizzle_info[operand_desc_id].pattern;
// longest known instruction name: "setemit "
int kOpcodeColumnWidth = 8;
@ -407,8 +407,8 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(
static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
input_data_mapper->setMapping(input_data[i], i);
}
connect(input_data_mapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped),
this, &GraphicsVertexShaderWidget::OnInputAttributeChanged);
connect(input_data_mapper, &QSignalMapper::mappedInt, this,
&GraphicsVertexShaderWidget::OnInputAttributeChanged);
auto main_widget = new QWidget;
auto main_layout = new QVBoxLayout;
@ -514,8 +514,10 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
info.code.push_back({instr});
int num_attributes = shader_config.max_input_attribute_index + 1;
for (auto pattern : shader_setup.swizzle_data)
info.swizzle_info.push_back({pattern});
for (auto pattern : shader_setup.swizzle_data) {
const nihstro::SwizzleInfo swizzle_info = {.pattern = nihstro::SwizzlePattern{pattern}};
info.swizzle_info.push_back(swizzle_info);
}
u32 entry_point = Pica::g_state.regs.vs.main_offset;
info.labels.insert({entry_point, "main"});

View File

@ -57,6 +57,7 @@ QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record
return tr("HLE Unimplemented");
default:
UNREACHABLE();
return QLatin1String{};
}
}

View File

@ -160,7 +160,8 @@ void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* event) {
}
void MicroProfileWidget::wheelEvent(QWheelEvent* event) {
MicroProfileMousePosition(event->x() / x_scale, event->y() / y_scale, event->delta() / 120);
MicroProfileMousePosition(event->position().x() / x_scale, event->position().y() / y_scale,
event->angleDelta().y() / 120);
event->accept();
}

View File

@ -114,7 +114,7 @@ void OptionSetDialog::InitializeUI(const std::string& initial_value) {
ui->formatLabel->text().append(QStringLiteral("\n"));
}
ui->formatLabel->setText(
ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min).arg(option.max)));
ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min, option.max)));
}
// Decide and initialize layout

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <QApplication>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QHBoxLayout>
@ -31,6 +32,8 @@
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/hle/service/fs/archive.h"
#include "core/settings.h"
#include "qcursor.h"
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
: QObject(parent), gamelist{gamelist} {}
@ -169,8 +172,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
* @return true if the haystack contains all words of userinput
*/
static bool ContainsAllWords(const QString& haystack, const QString& userinput) {
const QStringList userinput_split =
userinput.split(QLatin1Char{' '}, QString::SplitBehavior::SkipEmptyParts);
const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
return std::all_of(userinput_split.begin(), userinput_split.end(),
[&haystack](const QString& s) { return haystack.contains(s); });
@ -463,6 +465,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
default:
break;
}
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
@ -476,30 +479,38 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
QAction* open_texture_load_location =
context_menu.addAction(tr("Open Custom Texture Location"));
QAction* open_mods_location = context_menu.addAction(tr("Open Mods Location"));
QMenu* shader_menu = context_menu.addMenu(tr("Disk Shader Cache"));
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
QAction* open_shader_cache_location = shader_menu->addAction(tr("Open Shader Cache Location"));
shader_menu->addSeparator();
QAction* delete_opengl_disk_shader_cache =
shader_menu->addAction(tr("Delete OpenGL Shader Cache"));
QAction* delete_vulkan_disk_shader_cache =
shader_menu->addAction(tr("Delete Vulkan Shader Cache"));
const bool is_application =
0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
open_save_location->setVisible(
is_application && FileUtil::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
std::string sdmc_dir = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
open_save_location->setEnabled(
is_application && Common::FS::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
sdmc_dir, program_id)));
if (extdata_id) {
open_extdata_location->setVisible(
open_extdata_location->setEnabled(
is_application &&
FileUtil::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
Common::FS::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
} else {
open_extdata_location->setVisible(false);
}
auto media_type = Service::AM::GetTitleMediaType(program_id);
open_application_location->setVisible(path.toStdString() ==
open_application_location->setEnabled(path.toStdString() ==
Service::AM::GetTitleContentPath(media_type, program_id));
open_update_location->setVisible(
is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
open_update_location->setEnabled(
is_application && Common::FS::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
program_id + 0xe00000000) +
"content/"));
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@ -511,44 +522,71 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
connect(open_save_location, &QAction::triggered, [this, program_id] {
connect(open_save_location, &QAction::triggered, this, [this, program_id] {
emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA);
});
connect(open_extdata_location, &QAction::triggered, [this, extdata_id] {
connect(open_extdata_location, &QAction::triggered, this, [this, extdata_id] {
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
});
connect(open_application_location, &QAction::triggered, [this, program_id] {
connect(open_application_location, &QAction::triggered, this, [this, program_id] {
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
});
connect(open_update_location, &QAction::triggered, [this, program_id] {
connect(open_update_location, &QAction::triggered, this, [this, program_id] {
emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA);
});
connect(open_texture_dump_location, &QAction::triggered, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
connect(open_texture_dump_location, &QAction::triggered, this, [this, program_id] {
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_DUMP);
}
});
connect(open_texture_load_location, &QAction::triggered, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
connect(open_texture_load_location, &QAction::triggered, this, [this, program_id] {
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
}
});
connect(open_mods_location, &QAction::triggered, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}mods/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
connect(open_texture_load_location, &QAction::triggered, this, [this, program_id] {
if (Common::FS::CreateFullPath(fmt::format("{}textures/{:016X}/",
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
}
});
connect(open_mods_location, &QAction::triggered, this, [this, program_id] {
if (Common::FS::CreateFullPath(fmt::format("{}mods/{:016X}/",
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
}
});
connect(dump_romfs, &QAction::triggered,
connect(dump_romfs, &QAction::triggered, this,
[this, path, program_id] { emit DumpRomFSRequested(path, program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
connect(navigate_to_gamedb_entry, &QAction::triggered, this, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
connect(open_shader_cache_location, &QAction::triggered, this, [this, program_id] {
if (Common::FS::CreateFullPath(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::SHADER_CACHE);
}
});
connect(delete_opengl_disk_shader_cache, &QAction::triggered, this, [program_id] {
const std::string_view cache_type =
Settings::values.separable_shader ? "separable" : "conventional";
const std::string path = fmt::format("{}opengl/precompiled/{}/{:016X}.bin",
Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir),
cache_type, program_id);
QFile file{QString::fromStdString(path)};
file.remove();
});
connect(delete_vulkan_disk_shader_cache, &QAction::triggered, this, [] {
const std::string path =
fmt::format("{}vulkan", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
QDir dir{QString::fromStdString(path)};
dir.removeRecursively();
});
};
void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
@ -561,11 +599,11 @@ void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
deep_scan->setCheckable(true);
deep_scan->setChecked(game_dir.deep_scan);
connect(deep_scan, &QAction::triggered, [this, &game_dir] {
connect(deep_scan, &QAction::triggered, this, [this, &game_dir] {
game_dir.deep_scan = !game_dir.deep_scan;
PopulateAsync(UISettings::values.game_dirs);
});
connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] {
connect(delete_dir, &QAction::triggered, this, [this, &game_dir, selected] {
UISettings::values.game_dirs.removeOne(game_dir);
item_model->invisibleRootItem()->removeRow(selected.row());
});
@ -583,7 +621,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
move_up->setEnabled(row > 0);
move_down->setEnabled(row < item_model->rowCount() - 2);
connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] {
connect(move_up, &QAction::triggered, this, [this, selected, row, game_dir_index] {
const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt();
// swap the items in the settings
std::swap(UISettings::values.game_dirs[game_dir_index],
@ -598,7 +636,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
});
connect(move_down, &QAction::triggered, [this, selected, row, game_dir_index] {
connect(move_down, &QAction::triggered, this, [this, selected, row, game_dir_index] {
const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt();
// swap the items in the settings
std::swap(UISettings::values.game_dirs[game_dir_index],
@ -613,7 +651,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
});
connect(open_directory_location, &QAction::triggered, [this, game_dir_index] {
connect(open_directory_location, &QAction::triggered, this, [this, game_dir_index] {
emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
});
}
@ -640,7 +678,7 @@ void GameList::LoadCompatibilityList() {
const QJsonDocument json = QJsonDocument::fromJson(content);
const QJsonArray arr = json.array();
for (const QJsonValue value : arr) {
for (const QJsonValue& value : arr) {
const QJsonObject game = value.toObject();
const QString compatibility_key = QStringLiteral("compatibility");
@ -652,7 +690,7 @@ void GameList::LoadCompatibilityList() {
const QString directory = game[QStringLiteral("directory")].toString();
const QJsonArray ids = game[QStringLiteral("releases")].toArray();
for (const QJsonValue id_ref : ids) {
for (const QJsonValue& id_ref : ids) {
const QJsonObject id_object = id_ref.toObject();
const QString id = id_object[QStringLiteral("id")].toString();

View File

@ -37,6 +37,7 @@ enum class GameListOpenTarget {
TEXTURE_DUMP = 4,
TEXTURE_LOAD = 5,
MODS = 6,
SHADER_CACHE = 7
};
class GameList : public QWidget {

View File

@ -43,7 +43,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
}
const std::string physical_name = directory + DIR_SEP + virtual_name;
const bool is_dir = FileUtil::IsDirectory(physical_name);
const bool is_dir = Common::FS::IsDirectory(physical_name);
if (!is_dir && HasSupportedFileExtension(physical_name)) {
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
if (!loader) {
@ -67,7 +67,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
if (!(program_id & ~0x00040000FFFFFFFF)) {
std::string update_path = Service::AM::GetTitleContentPath(
Service::FS::MediaType::SDMC, program_id | 0x0000000E00000000);
if (FileUtil::Exists(update_path)) {
if (Common::FS::Exists(update_path)) {
std::unique_ptr<Loader::AppLoader> update_loader =
Loader::GetLoader(update_path);
if (update_loader) {
@ -101,7 +101,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
new GameListItemRegion(smdh),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
new GameListItemSize(Common::FS::GetSize(physical_name)),
},
parent_dir);
@ -113,7 +113,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
return true;
};
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback);
}
void GameListWorker::run() {
@ -121,12 +121,12 @@ void GameListWorker::run() {
for (UISettings::GameDir& game_dir : game_dirs) {
if (game_dir.path == QStringLiteral("INSTALLED")) {
QString games_path =
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) +
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)) +
QStringLiteral("Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
QString demos_path =
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) +
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)) +
QStringLiteral(
"Nintendo "
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/"
@ -139,7 +139,7 @@ void GameListWorker::run() {
AddFstEntriesToGameList(demos_path.toStdString(), 2, game_list_dir);
} else if (game_dir.path == QStringLiteral("SYSTEM")) {
QString path =
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) +
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)) +
QStringLiteral("00000000000000000000000000000000/title/00040010");
watch_list.append(path);
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir);

View File

@ -11,18 +11,11 @@
#include <QFutureWatcher>
#include <QLabel>
#include <QMessageBox>
#include <QOpenGLFunctions_4_3_Core>
#include <QSysInfo>
#include <QtConcurrent/QtConcurrentRun>
#include <QtGui>
#include <QtWidgets>
#include <fmt/format.h>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include "citra_qt/aboutdialog.h"
#include "citra_qt/applets/mii_selector.h"
#include "citra_qt/applets/swkbd.h"
@ -62,12 +55,11 @@
#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#endif
@ -76,20 +68,23 @@
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/loader/loader.h"
#include "core/movie.h"
#include "core/savestate.h"
#include "core/settings.h"
#include "game_list_p.h"
#include "network/network_settings.h"
#include "ui_main.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef USE_DISCORD_PRESENCE
#include "citra_qt/discord_impl.h"
#endif
@ -143,8 +138,8 @@ static void InitializeLogging() {
log_filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(log_filter);
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
FileUtil::CreateFullPath(log_dir);
const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@ -152,8 +147,8 @@ static void InitializeLogging() {
}
GMainWindow::GMainWindow()
: config(std::make_unique<Config>()), emu_thread(nullptr),
ui(std::make_unique<Ui::MainWindow>()) {
: ui{std::make_unique<Ui::MainWindow>()}, config{std::make_unique<Config>()}, emu_thread{
nullptr} {
InitializeLogging();
Debugger::ToggleConsole();
Settings::LogSettings();
@ -263,7 +258,7 @@ void GMainWindow::InitializeWidgets() {
loading_screen = new LoadingScreen(this);
loading_screen->hide();
ui->horizontalLayout->addWidget(loading_screen);
connect(loading_screen, &LoadingScreen::Hidden, [&] {
connect(loading_screen, &LoadingScreen::Hidden, this, [&] {
loading_screen->Clear();
if (emulation_running) {
render_window->show();
@ -282,7 +277,6 @@ void GMainWindow::InitializeWidgets() {
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
message_label->setVisible(false);
message_label->setFrameStyle(QFrame::NoFrame);
message_label->setContentsMargins(4, 0, 4, 0);
message_label->setAlignment(Qt::AlignLeft);
@ -307,10 +301,28 @@ void GMainWindow::InitializeWidgets() {
label->setVisible(false);
label->setFrameStyle(QFrame::NoFrame);
label->setContentsMargins(4, 0, 4, 0);
statusBar()->addPermanentWidget(label, 0);
statusBar()->addPermanentWidget(label);
}
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
// Setup Graphics API button
graphics_api_button = new QPushButton();
graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton"));
graphics_api_button->setFocusPolicy(Qt::NoFocus);
UpdateAPIIndicator(false);
connect(graphics_api_button, &QPushButton::clicked, this, [this] {
if (emulation_running) {
return;
}
UpdateAPIIndicator(true);
});
statusBar()->insertPermanentWidget(0, graphics_api_button);
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
statusBar()->setVisible(true);
// Removes an ugly inner border from the status bar widgets under Linux
@ -432,13 +444,13 @@ void GMainWindow::InitializeSaveStateMenuActions() {
ui->menu_Save_State->addAction(actions_save_state[i]);
}
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, [this] {
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates();
if (newest_slot != 0) {
actions_load_state[newest_slot - 1]->trigger();
}
});
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, [this] {
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates();
actions_save_state[oldest_slot - 1]->trigger();
});
@ -674,7 +686,7 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
connect(game_list, &GameList::PopulatingCompleted,
connect(game_list, &GameList::PopulatingCompleted, this,
[this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
connect(this, &GMainWindow::EmulationStarting, render_window,
@ -765,7 +777,7 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui->action_Close_Movie, &QAction::triggered, this, &GMainWindow::OnCloseMovie);
connect(ui->action_Save_Movie, &QAction::triggered, this, &GMainWindow::OnSaveMovie);
connect(ui->action_Movie_Read_Only_Mode, &QAction::toggled, this,
[this](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); });
[](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); });
connect(ui->action_Enable_Frame_Advancing, &QAction::triggered, this, [this] {
if (emulation_running) {
Core::System::GetInstance().frame_limiter.SetFrameAdvancing(
@ -914,20 +926,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
render_window->InitRenderTarget();
Frontend::ScopeAcquireContext scope(*render_window);
const auto scope = render_window->Acquire();
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
"have the latest graphics driver.");
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
return false;
}
Core::System& system{Core::System::GetInstance()};
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
Core::System& system = Core::System::GetInstance();
const Core::System::ResultStatus result = system.Load(*render_window, filename.toStdString());
if (result != Core::System::ResultStatus::Success) {
switch (result) {
@ -976,7 +978,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorVideoCore:
QMessageBox::critical(
this, tr("Video Core Error"),
tr("An error has occurred. Please <a "
tr("An error has occurred during intialization of the video backend. Please <a "
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
"the "
"log</a> for more details. "
@ -991,10 +993,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
"proper drivers for your graphics card from the manufacturer's website."));
break;
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
break;
default:
QMessageBox::critical(
this, tr("Error while loading ROM!"),
@ -1206,7 +1204,6 @@ void GMainWindow::ShutdownGame() {
// Disable status bar updates
status_bar_update_timer.stop();
message_label->setVisible(false);
message_label_used_for_movie = false;
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
@ -1327,13 +1324,13 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
switch (target) {
case GameListOpenTarget::SAVE_DATA: {
open_target = "Save Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
std::string sdmc_dir = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, data_id);
break;
}
case GameListOpenTarget::EXT_DATA: {
open_target = "Extra Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
std::string sdmc_dir = Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir);
path = FileSys::GetExtDataPathFromId(sdmc_dir, data_id);
break;
}
@ -1343,26 +1340,35 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
break;
}
case GameListOpenTarget::UPDATE_DATA:
case GameListOpenTarget::UPDATE_DATA: {
open_target = "Update Data";
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
"content/";
break;
case GameListOpenTarget::TEXTURE_DUMP:
}
case GameListOpenTarget::TEXTURE_DUMP: {
open_target = "Dumped Textures";
path = fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), data_id);
Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), data_id);
break;
case GameListOpenTarget::TEXTURE_LOAD:
}
case GameListOpenTarget::TEXTURE_LOAD: {
open_target = "Custom Textures";
path = fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id);
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), data_id);
break;
case GameListOpenTarget::MODS:
}
case GameListOpenTarget::MODS: {
open_target = "Mods";
path = fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
path = fmt::format("{}mods/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
data_id);
break;
}
case GameListOpenTarget::SHADER_CACHE: {
open_target = "Shader Cache";
path = Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir);
break;
}
default:
LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
return;
@ -1404,13 +1410,13 @@ void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
dialog->setValue(0);
const auto base_path = fmt::format(
"{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
"{}romfs/{:016X}", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), program_id);
const auto update_path =
fmt::format("{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
fmt::format("{}romfs/{:016X}", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
program_id | 0x0004000e00000000);
using FutureWatcher = QFutureWatcher<std::pair<Loader::ResultStatus, Loader::ResultStatus>>;
auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished,
connect(future_watcher, &FutureWatcher::finished, this,
[this, dialog, base_path, update_path, future_watcher] {
dialog->hide();
const auto& [base, update] = future_watcher->result();
@ -1437,12 +1443,12 @@ void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("INSTALLED")) {
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
"Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
} else if (directory == QStringLiteral("SYSTEM")) {
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"00000000000000000000000000000000/title/00040010");
} else {
path = directory;
@ -1512,7 +1518,7 @@ void GMainWindow::InstallCIA(QStringList filepaths) {
const auto cia_progress = [&](std::size_t written, std::size_t total) {
emit UpdateProgress(written, total);
};
for (const auto current_path : filepaths) {
for (const auto& current_path : filepaths) {
status = Service::AM::InstallCIA(current_path.toStdString(), cia_progress);
emit CIAInstallReport(status, current_path);
}
@ -1550,6 +1556,10 @@ void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString
"before being used with Citra. A real 3DS is required.")
.arg(filename));
break;
case Service::AM::InstallStatus::ErrorFileNotFound:
QMessageBox::critical(this, tr("Unable to find File"),
tr("Could not find %1").arg(filename));
break;
}
}
@ -1727,6 +1737,8 @@ void GMainWindow::ToggleScreenLayout() {
case Settings::LayoutOption::SideScreen:
new_layout = Settings::LayoutOption::Default;
break;
default:
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
}
Settings::values.layout_option = new_layout;
@ -1796,6 +1808,7 @@ void GMainWindow::OnConfigure() {
} else {
setMouseTracking(false);
}
UpdateAPIIndicator(false);
} else {
Settings::values.input_profiles = old_input_profiles;
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
@ -1860,7 +1873,7 @@ void GMainWindow::OnRemoveAmiibo() {
void GMainWindow::OnOpenCitraFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir))));
}
void GMainWindow::OnToggleFilterBar() {
@ -1960,12 +1973,12 @@ void GMainWindow::OnSaveMovie() {
void GMainWindow::OnCaptureScreenshot() {
OnPauseGame();
QString path = UISettings::values.screenshot_path;
if (!FileUtil::IsDirectory(path.toStdString())) {
if (!FileUtil::CreateFullPath(path.toStdString())) {
if (!Common::FS::IsDirectory(path.toStdString())) {
if (!Common::FS::CreateFullPath(path.toStdString())) {
QMessageBox::information(this, tr("Invalid Screenshot Directory"),
tr("Cannot create specified screenshot directory. Screenshot "
"path is set back to its default value."));
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir));
path.append(QStringLiteral("screenshots/"));
UISettings::values.screenshot_path = path;
};
@ -1973,7 +1986,7 @@ void GMainWindow::OnCaptureScreenshot() {
const QString filename = game_title.remove(QRegularExpression(QStringLiteral("[\\/:?\"<>|]")));
const QString timestamp =
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
path.append(QStringLiteral("/%1_%2.png").arg(filename).arg(timestamp));
path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
OnStartGame();
}
@ -2048,7 +2061,7 @@ void GMainWindow::UpdateStatusBar() {
message_label_used_for_movie = true;
ui->action_Save_Movie->setEnabled(true);
} else if (play_mode == Core::Movie::PlayMode::Playing) {
message_label->setText(tr("Playing %1 / %2").arg(current).arg(total));
message_label->setText(tr("Playing %1 / %2").arg(current, total));
message_label->setVisible(true);
message_label_used_for_movie = true;
ui->action_Save_Movie->setEnabled(false);
@ -2107,6 +2120,26 @@ void GMainWindow::ShowMouseCursor() {
}
}
void GMainWindow::UpdateAPIIndicator(bool override) {
static std::array graphics_apis = {QStringLiteral("OPENGL"), QStringLiteral("OPENGLES"),
QStringLiteral("VULKAN")};
static std::array graphics_api_colors = {QStringLiteral("#00ccdd"), QStringLiteral("#ba2a8d"),
QStringLiteral("#91242a")};
u32 api_index = static_cast<u32>(Settings::values.graphics_api);
if (override) {
api_index = (api_index + 1) % graphics_apis.size();
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
}
const QString style_sheet = QStringLiteral("QPushButton { font-weight: bold; color: %0; }")
.arg(graphics_api_colors[api_index]);
graphics_api_button->setText(graphics_apis[api_index]);
graphics_api_button->setStyleSheet(style_sheet);
}
void GMainWindow::OnMouseActivity() {
ShowMouseCursor();
}
@ -2292,8 +2325,16 @@ void GMainWindow::UpdateUITheme() {
QStringList theme_paths(default_theme_paths);
if (is_default_theme || current_theme.isEmpty()) {
qApp->setStyleSheet({});
setStyleSheet({});
const QString theme_uri(QStringLiteral(":default/style.qss"));
QFile f(theme_uri);
if (f.open(QFile::ReadOnly | QFile::Text)) {
QTextStream ts(&f);
qApp->setStyleSheet(ts.readAll());
setStyleSheet(ts.readAll());
} else {
qApp->setStyleSheet({});
setStyleSheet({});
}
theme_paths.append(default_icons);
QIcon::setThemeName(default_icons);
} else {
@ -2440,16 +2481,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setSwapInterval(0);
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
QSurfaceFormat::setDefaultFormat(format);
#ifdef __APPLE__
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);

View File

@ -7,6 +7,7 @@
#include <array>
#include <memory>
#include <QMainWindow>
#include <QPushButton>
#include <QTimer>
#include <QTranslator>
#include "citra_qt/compatibility_list.h"
@ -234,6 +235,7 @@ private:
void InstallCIA(QStringList filepaths);
void HideMouseCursor();
void ShowMouseCursor();
void UpdateAPIIndicator(bool override);
std::unique_ptr<Ui::MainWindow> ui;
@ -248,6 +250,7 @@ private:
QLabel* emu_speed_label = nullptr;
QLabel* game_fps_label = nullptr;
QLabel* emu_frametime_label = nullptr;
QPushButton* graphics_api_button = nullptr;
QTimer status_bar_update_timer;
bool message_label_used_for_movie = false;

View File

@ -5,6 +5,7 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <QDialog>
#include <QSortFilterProxyModel>

View File

@ -7,7 +7,6 @@
#include <QIcon>
#include <QMessageBox>
#include <QStandardItemModel>
#include "citra_qt/game_list.h"
#include "citra_qt/multiplayer/client_room.h"
#include "citra_qt/multiplayer/direct_connect.h"
#include "citra_qt/multiplayer/host_room.h"
@ -16,7 +15,6 @@
#include "citra_qt/multiplayer/state.h"
#include "citra_qt/uisettings.h"
#include "citra_qt/util/clickable_label.h"
#include "common/announce_multiplayer_room.h"
#include "common/logging/log.h"
MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model,
@ -231,8 +229,9 @@ bool MultiplayerState::OnCloseRoom() {
if (room->GetState() != Network::Room::State::Open) {
return true;
}
// Save ban list
UISettings::values.ban_list = std::move(room->GetBanList());
UISettings::values.ban_list = room->GetBanList();
room->Destroy();
announce_multiplayer_session->Stop();

View File

@ -69,15 +69,27 @@ add_library(common STATIC
common_funcs.h
common_paths.h
common_types.h
concepts.h
construct.h
file_util.cpp
file_util.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
linear_disk_cache.h
logging/backend.cpp
logging/backend.h
logging/filter.cpp
logging/filter.h
logging/formatter.h
logging/log.h
logging/text_formatter.cpp
logging/text_formatter.h

View File

@ -59,6 +59,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
#endif // _MSC_VER ndef
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
a = a & b; \
return a; \
} \
constexpr type& operator^=(type& a, type b) noexcept { \
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.

35
src/common/concepts.h Normal file
View File

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <type_traits>
namespace Common {
// Check if type satisfies the ContiguousContainer named requirement.
template <typename T>
concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>;
// TODO: Replace with std::derived_from when the <concepts> header
// is available on all supported platforms.
template <typename Derived, typename Base>
concept DerivedFrom = requires {
std::is_base_of_v<Base, Derived>;
std::is_convertible_v<const volatile Derived*, const volatile Base*>;
};
// TODO: Replace with std::convertible_to when libc++ implements it.
template <typename From, typename To>
concept ConvertibleTo = std::is_convertible_v<From, To>;
// No equivalents in the stdlib
template <typename T>
concept IsArithmetic = std::is_arithmetic_v<T>;
template <typename T>
concept IsIntegral = std::is_integral_v<T>;
} // namespace Common

File diff suppressed because it is too large Load Diff

View File

@ -1,396 +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 <boost/serialization/split_member.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/wrapper.hpp>
#include "common/common_types.h"
#ifdef _MSC_VER
#include "common/string_util.h"
#endif
namespace FileUtil {
// User paths for GetUserPath
enum class UserPath {
CacheDir,
CheatsDir,
ConfigDir,
DLLDir,
DumpDir,
LoadDir,
LogDir,
NANDDir,
RootDir,
SDMCDir,
ShaderDir,
StatesDir,
SysDataDir,
UserDir,
};
// Replaces install-specific paths with standard placeholders, and back again
std::string SerializePath(const std::string& input, bool is_saving);
// A serializable path string
struct Path : public boost::serialization::wrapper_traits<const Path> {
std::string& str;
explicit Path(std::string& _str) : str(_str) {}
static const Path make(std::string& str) {
return Path(str);
}
template <class Archive>
void save(Archive& ar, const unsigned int) const {
auto s_path = SerializePath(str, true);
ar << s_path;
}
template <class Archive>
void load(Archive& ar, const unsigned int) const {
ar >> str;
str = SerializePath(str, false);
}
BOOST_SERIALIZATION_SPLIT_MEMBER();
friend class boost::serialization::access;
};
// 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;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& isDirectory;
ar& size;
ar& Path::make(physicalName);
ar& Path::make(virtualName);
ar& children;
}
friend class boost::serialization::access;
};
// 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);
/**
* Recursively searches through a FSTEntry for files, and stores them.
* @param directory The FSTEntry to start scanning from
* @param parent_entry FSTEntry vector where the results will be stored.
*/
void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output);
// 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);
void SetUserPath(const std::string& path = "");
void SetCurrentRomPath(const std::string& path);
// Returns a pointer to a string with a Citra data dir in the user's home
// directory. To be used in "multi-user" mode (that is, installed).
[[nodiscard]] const std::string& GetUserPath(UserPath path);
// Returns a pointer to a string with the default Citra data dir in the user's home
// directory.
[[nodiscard]] const std::string& GetDefaultUserPath(UserPath path);
// Update the Global Path with the new value
const void UpdateUserPath(UserPath path, const std::string& filename);
// 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);
// 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 citra to open the logs in shared write mode, so that the file
// isn't considered "locked" while citra 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 Close();
template <typename T>
std::size_t ReadArray(T* data, std::size_t length) {
static_assert(std::is_trivially_copyable_v<T>,
"Given array does not consist of trivially copyable objects");
std::size_t items_read = ReadImpl(data, length, sizeof(T));
if (items_read != length)
m_good = false;
return items_read;
}
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");
std::size_t items_written = WriteImpl(data, length, sizeof(T));
if (items_written != length)
m_good = false;
return items_written;
}
template <typename T>
std::size_t ReadBytes(T* data, std::size_t length) {
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;
}
// m_good is set to false when a read, write or other function fails
[[nodiscard]] bool IsGood() const {
return m_good;
}
[[nodiscard]] explicit operator bool() const {
return IsGood();
}
bool Seek(s64 off, int origin);
[[nodiscard]] u64 Tell() const;
[[nodiscard]] u64 GetSize() const;
bool Resize(u64 size);
bool Flush();
// clear error state
void Clear() {
m_good = true;
std::clearerr(m_file);
}
private:
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
bool Open();
std::FILE* m_file = nullptr;
bool m_good = true;
std::string filename;
std::string openmode;
u32 flags;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& Path::make(filename);
ar& openmode;
ar& flags;
u64 pos;
if (Archive::is_saving::value) {
pos = Tell();
}
ar& pos;
if (Archive::is_loading::value) {
Open();
Seek(pos, SEEK_SET);
}
}
friend class boost::serialization::access;
};
} // namespace FileUtil
// 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
}

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

@ -0,0 +1,415 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/file.h"
#include "common/fs/fs.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 (Exists(path) && !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) && !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;
#ifdef _WIN32
const auto flush_result = std::fflush(file) == 0;
#else
const auto flush_result = std::fflush(file) == 0;
#endif
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::Commit() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
#else
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
#endif
if (!commit_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return commit_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;
}
// Flush any unwritten buffered data into the file prior to retrieving the file size.
std::fflush(file);
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

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

@ -0,0 +1,459 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdio>
#include <filesystem>
#include <span>
#include <type_traits>
#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 regular 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 a file already exists at path, its contents will be erased.
* If a file does not exist at path, it creates and opens a new empty file for writing.
* If the filesystem object at path exists and is not a regular 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, it creates and opens a new empty file for appending.
* If the filesystem object at path exists and is not a regular 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 {
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);
~IOFile();
IOFile(const IOFile&) = delete;
IOFile& operator=(const IOFile&) = delete;
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>
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 container as defined by the concept IsContiguousContainer, 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 (IsContiguousContainer<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 IsContiguousContainer, 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 (IsContiguousContainer<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;
/**
* Attempts to flush any unwritten buffered data into the file.
*
* @returns True if the flush was successful, false otherwise.
*/
bool Flush() const;
/**
* Attempts to commit the file into the disk.
* Note that this is an expensive operation as this forces the operating system to write
* the contents of the file associated with the file descriptor into the disk.
*
* @returns True if the commit was successful, false otherwise.
*/
bool Commit() 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

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

@ -0,0 +1,624 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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 (Exists(path) && !IsFile(path)) {
LOG_ERROR(Common_Filesystem,
"Filesystem object at path={} exists and is not a regular 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;
// TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC.
for (const auto& entry : fs::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;
}
// TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator.
// recursive_directory_iterator throws an exception despite passing in a std::error_code.
if (entry.status().type() == fs::file_type::directory) {
return RemoveDirContentsRecursively(entry.path());
}
}
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;
// TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC.
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;
}
}
// TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator.
// recursive_directory_iterator throws an exception despite passing in a std::error_code.
if (entry.status().type() == fs::file_type::directory) {
IterateDirEntriesRecursively(entry.path(), callback, filter);
}
}
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

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

@ -0,0 +1,583 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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 regular file
* - Filesystem at path is read only
*
* @param path Filesystem path
*
* @returns True if file removal succeeds or file does not exist, false otherwise.
*/
bool RemoveFile(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
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 regular 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 exists and is not a regular file
* - The file is not open
*
* @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.
*/
bool RemoveDir(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
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.
*/
bool RemoveDirRecursively(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
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.
*/
bool RemoveDirContentsRecursively(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
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 regular file.
* A regular file is a file that stores text or binary data.
* It is not a directory, symlink, FIFO, socket, block device, or character device.
*
* @param path Filesystem path
*
* @returns True if a filesystem object at path is a regular 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

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

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// yuzu data directories
#define CITRA_DIR "citra"
#define PORTABLE_DIR "user"
// Sub-directories contained within a citra data directory
#define CONFIG_DIR "config"
#define CACHE_DIR "cache"
#define SDMC_DIR "sdmc"
#define NAND_DIR "nand"
#define SYSDATA_DIR "sysdata"
#define LOG_DIR "log"
#define CHEATS_DIR "cheats"
#define DLL_DIR "external_dlls"
#define SHADER_DIR "shaders"
#define DUMP_DIR "dump"
#define LOAD_DIR "load"
#define SHADER_DIR "shaders"
#define STATES_DIR "states"

View File

@ -0,0 +1,6 @@
#include "fs_serialize.h"
fs_serialize::fs_serialize()
{
}

View File

@ -0,0 +1,43 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <filesystem>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/wrapper.hpp>
namespace Common::FS {
// Replaces install-specific paths with standard placeholders, and back again
std::filesystem::path SerializePath(const std::filesystem::path& input, bool is_saving);
// A serializable path string
struct Path : public boost::serialization::wrapper_traits<const Path> {
std::filesystem::path& path;
explicit Path(std::filesystem::path& path) : path{path} {}
static const Path make(std::filesystem::path& path) {
return Path(path);
}
template <class Archive>
void save(Archive& ar, const unsigned int) const {
auto s_path = SerializePath(path, true);
ar << s_path;
}
template <class Archive>
void load(Archive& ar, const unsigned int) const {
ar >> path;
path = SerializePath(path, false);
}
BOOST_SERIALIZATION_SPLIT_MEMBER();
friend class boost::serialization::access;
};
} // namespace Common::FS

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

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include <filesystem>
#include "common/common_funcs.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

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

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#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()};
}
std::u8string BufferToU8String(std::span<const u8> buffer) {
return std::u8string{buffer.begin(), std::ranges::find(buffer, u8{0})};
}
std::u8string_view BufferToU8StringView(std::span<const u8> buffer) {
return std::u8string_view{reinterpret_cast<const char8_t*>(buffer.data())};
}
std::string ToUTF8String(std::u8string_view u8_string) {
return std::string{u8_string.begin(), u8_string.end()};
}
std::string BufferToUTF8String(std::span<const u8> buffer) {
return std::string{buffer.begin(), std::ranges::find(buffer, u8{0})};
}
std::string_view BufferToUTF8StringView(std::span<const u8> buffer) {
return std::string_view{reinterpret_cast<const char*>(buffer.data())};
}
std::string PathToUTF8String(const std::filesystem::path& path) {
return ToUTF8String(path.u8string());
}
} // namespace Common::FS

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

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <concepts>
#include <filesystem>
#include <span>
#include <string>
#include "common/common_types.h"
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);
/**
* Converts a buffer of bytes to a UTF8-encoded std::u8string.
* This converts from the start of the buffer until the first encountered null-terminator.
* If no null-terminator is found, this converts the entire buffer instead.
*
* @param buffer Buffer of bytes
*
* @returns UTF-8 encoded std::u8string.
*/
[[nodiscard]] std::u8string BufferToU8String(std::span<const u8> buffer);
/**
* Same as BufferToU8String, but returns a string view of the buffer.
*
* @param buffer Buffer of bytes
*
* @returns UTF-8 encoded std::u8string_view.
*/
[[nodiscard]] std::u8string_view BufferToU8StringView(std::span<const u8> buffer);
/**
* Converts a std::u8string or std::u8string_view to a UTF-8 encoded std::string.
*
* @param u8_string UTF-8 encoded u8string
*
* @returns UTF-8 encoded std::string.
*/
[[nodiscard]] std::string ToUTF8String(std::u8string_view u8_string);
/**
* Converts a buffer of bytes to a UTF8-encoded std::string.
* This converts from the start of the buffer until the first encountered null-terminator.
* If no null-terminator is found, this converts the entire buffer instead.
*
* @param buffer Buffer of bytes
*
* @returns UTF-8 encoded std::string.
*/
[[nodiscard]] std::string BufferToUTF8String(std::span<const u8> buffer);
/**
* Same as BufferToUTF8String, but returns a string view of the buffer.
*
* @param buffer Buffer of bytes
*
* @returns UTF-8 encoded std::string_view.
*/
[[nodiscard]] std::string_view BufferToUTF8StringView(std::span<const u8> buffer);
/**
* 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);
} // namespace Common::FS

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

@ -0,0 +1,428 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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
* UserPath enums to real filesystem paths.
* This class provides 2 functions: GetUserPathImpl and SetUserPathImpl.
* These are used by GetUserPath and SetUserPath respectively to get or modify
* the path mapped by the UserPath 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& GetUserPathImpl(UserPath user_path) {
return user_paths.at(user_path);
}
void SetUserPathImpl(UserPath user_path, const fs::path& new_path) {
user_paths.insert_or_assign(user_path, new_path);
}
private:
PathManagerImpl() {
fs::path user_path;
fs::path user_path_cache;
fs::path user_path_config;
#ifdef _WIN32
user_path = GetExeDirectory() / PORTABLE_DIR;
if (!IsDir(user_path)) {
user_path = GetAppDataRoamingDirectory() / CITRA_DIR;
}
user_path_cache = user_path / CACHE_DIR;
user_path_config = user_path / CONFIG_DIR;
#else
user_path = GetCurrentDir() / PORTABLE_DIR;
if (Exists(user_path) && IsDir(user_path)) {
user_path_cache = user_path / CACHE_DIR;
user_path_config = user_path / CONFIG_DIR;
} else {
user_path = GetDataDirectory("XDG_DATA_HOME") / CITRA_DIR;
user_path_cache = GetDataDirectory("XDG_CACHE_HOME") / CITRA_DIR;
user_path_config = GetDataDirectory("XDG_CONFIG_HOME") / CITRA_DIR;
}
#endif
GenerateUserPath(UserPath::UserDir, user_path);
GenerateUserPath(UserPath::CacheDir, user_path_cache);
GenerateUserPath(UserPath::ConfigDir, user_path_config);
GenerateUserPath(UserPath::CheatsDir, user_path / CHEATS_DIR);
GenerateUserPath(UserPath::DLLDir, user_path / DLL_DIR);
GenerateUserPath(UserPath::DumpDir, user_path / DUMP_DIR);
GenerateUserPath(UserPath::LoadDir, user_path / LOAD_DIR);
GenerateUserPath(UserPath::LogDir, user_path / LOG_DIR);
GenerateUserPath(UserPath::NANDDir, user_path / NAND_DIR);
GenerateUserPath(UserPath::SDMCDir, user_path / SDMC_DIR);
GenerateUserPath(UserPath::SysDataDir, user_path / SYSDATA_DIR);
GenerateUserPath(UserPath::StatesDir, user_path / SYSDATA_DIR);
GenerateUserPath(UserPath::ShaderDir, user_path / STATES_DIR);
}
~PathManagerImpl() = default;
void GenerateUserPath(UserPath user_path, const fs::path& new_path) {
void(FS::CreateDir(new_path));
SetUserPathImpl(user_path, new_path);
}
std::unordered_map<UserPath, fs::path> user_paths;
};
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& GetUserPath(UserPath user_path) {
return PathManagerImpl::GetInstance().GetUserPathImpl(user_path);
}
std::string GetUserPathString(UserPath user_path) {
return PathToUTF8String(GetUserPath(user_path));
}
void SetUserPath(UserPath user_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().SetUserPathImpl(user_path, new_path);
}
#ifdef _WIN32
fs::path GetExeDirectory() {
wchar_t exe_path[MAX_PATH];
if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH) == 0) {
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

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

@ -0,0 +1,302 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
#include "common/fs/fs_util.h"
namespace Common::FS {
enum class UserPath {
CacheDir,
CheatsDir,
ConfigDir,
DLLDir,
DumpDir,
LoadDir,
LogDir,
NANDDir,
RootDir,
SDMCDir,
ShaderDir,
StatesDir,
SysDataDir,
UserDir,
};
/**
* 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 UserPath enum.
*
* @param user_path UserPath enum
*
* @returns The filesystem path associated with the UserPath enum.
*/
[[nodiscard]] const std::filesystem::path& GetUserPath(UserPath user_path);
/**
* Gets the filesystem path associated with the UserPath enum as a UTF-8 encoded std::string.
*
* @param user_path UserPath enum
*
* @returns The filesystem path associated with the UserPath enum as a UTF-8 encoded std::string.
*/
[[nodiscard]] std::string GetUserPathString(UserPath user_path);
/**
* Sets a new filesystem path associated with the User enum.
* If the filesystem object at new_path is not a directory, this function will not do anything.
*
* @param user_path User enum
* @param new_path New filesystem path
*/
void SetUserPath(UserPath user_path, const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path>
void SetUserPath(UserPath user_path, const Path& new_path) {
if constexpr (IsChar<typename Path::value_type>) {
SetUserPath(user_path, ToU8String(new_path));
} else {
SetUserPath(user_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\User\Documents\save.bin" becomes {"C:", "Users", "User", "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

@ -4,6 +4,7 @@
#pragma once
#include <concepts>
#include <cstddef>
#include <cstring>
#include "common/cityhash.h"
@ -41,6 +42,13 @@ inline u64 HashCombine(std::size_t& seed, const u64 hash) {
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename T>
struct IdentityHash {
T operator()(const T& value) const {
return value;
}
};
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
template <typename T>
struct HashableStruct {

View File

@ -12,12 +12,10 @@
#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"
@ -144,17 +142,16 @@ void LogcatBackend::Write(const Entry& entry) {
PrintMessageToLogcat(entry);
}
FileBackend::FileBackend(const std::string& filename) : bytes_written(0) {
if (FileUtil::Exists(filename + ".old.txt")) {
FileUtil::Delete(filename + ".old.txt");
}
if (FileUtil::Exists(filename)) {
FileUtil::Rename(filename, filename + ".old.txt");
}
FileBackend::FileBackend(const std::filesystem::path& filename) : bytes_written(0) {
auto old_filename = filename;
old_filename += ".old.txt";
// _SH_DENYWR allows read only access to the file for other programs.
// It is #defined to 0 on other platforms
file = FileUtil::IOFile(filename, "w", _SH_DENYWR);
// Existence checks are done within the functions themselves.
// We don't particularly care if these succeed or not.
static_cast<void>(Common::FS::RemoveFile(old_filename));
static_cast<void>(Common::FS::RenameFile(filename, old_filename));
file = Common::FS::IOFile(filename, Common::FS::FileAccessMode::Write, Common::FS::FileType::TextFile);
}
void FileBackend::Write(const Entry& entry) {
@ -235,6 +232,7 @@ void DebuggerBackend::Write(const Entry& entry) {
CLS(Render) \
SUB(Render, Software) \
SUB(Render, OpenGL) \
SUB(Render, Vulkan) \
CLS(Audio) \
SUB(Audio, DSP) \
SUB(Audio, Sink) \

View File

@ -5,11 +5,11 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include "common/file_util.h"
#include "common/logging/filter.h"
#include "common/fs/file.h"
#include "common/logging/log.h"
namespace Log {
@ -101,7 +101,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";
@ -114,7 +114,7 @@ public:
void Write(const Entry& entry) override;
private:
FileUtil::IOFile file;
Common::FS::IOFile file;
std::size_t bytes_written;
};

View File

@ -8,6 +8,7 @@
#include <array>
#include "common/common_types.h"
#include "common/logging/formatter.h"
namespace Log {
// trims up to and including the last of ../, ..\, src/, src\ in a string
@ -102,6 +103,7 @@ enum class Class : ClassType {
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
Render_Vulkan, ///< Vulkan backend
Audio, ///< Audio emulation
Audio_DSP, ///< The HLE and LLE implementations of the DSP
Audio_Sink, ///< Emulator audio output backend

View File

@ -4,13 +4,12 @@
#pragma once
#include <compare>
#include <cstdlib>
#include <type_traits>
namespace Common {
constexpr float PI = 3.14159265f;
template <class T>
struct Rectangle {
T left{};
@ -23,19 +22,31 @@ struct Rectangle {
constexpr Rectangle(T left, T top, T right, T bottom)
: left(left), top(top), right(right), bottom(bottom) {}
[[nodiscard]] T GetWidth() const {
constexpr auto operator<=>(const Rectangle&) const = default;
constexpr void operator*=(const T value) {
left *= value;
top *= value;
right *= value;
bottom *= value;
}
[[nodiscard]] constexpr Rectangle operator*(const T value) const {
return Rectangle{left * value, top * value, right * value, bottom * value};
}
[[nodiscard]] constexpr T GetWidth() const {
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
}
[[nodiscard]] T GetHeight() const {
[[nodiscard]] constexpr T GetHeight() const {
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
}
[[nodiscard]] Rectangle<T> TranslateX(const T x) const {
[[nodiscard]] constexpr Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};
}
[[nodiscard]] Rectangle<T> TranslateY(const T y) const {
[[nodiscard]] constexpr Rectangle<T> TranslateY(const T y) const {
return Rectangle{left, top + y, right, bottom + y};
}
[[nodiscard]] Rectangle<T> Scale(const float s) const {
[[nodiscard]] constexpr Rectangle<T> Scale(const float s) const {
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
static_cast<T>(top + GetHeight() * s)};
}

View File

@ -3,8 +3,8 @@
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <span>
#include <vector>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
@ -65,8 +65,11 @@ private:
BOOST_CLASS_EXPORT_KEY(BufferMem);
/// A managed reference to host-side memory. Fast enough to be used everywhere instead of u8*
/// Supports serialization.
/**
* A managed reference to host-side memory.
* Fast enough to be used everywhere instead of u8*
* Supports serialization.
*/
class MemoryRef {
public:
MemoryRef() = default;
@ -75,35 +78,52 @@ public:
: backing_mem(std::move(backing_mem_)), offset(0) {
Init();
}
MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
: backing_mem(std::move(backing_mem_)), offset(offset_) {
ASSERT(offset <= backing_mem->GetSize());
Init();
}
explicit operator bool() const {
return cptr != nullptr;
}
operator u8*() {
return cptr;
}
u8* GetPtr() {
return cptr;
}
operator const u8*() const {
return cptr;
}
u8* GetPtr() {
return cptr;
}
const u8* GetPtr() const {
return cptr;
}
auto GetWriteBytes(std::size_t size) {
return std::span{reinterpret_cast<std::byte*>(cptr), size > csize ? csize : size};
}
auto GetReadBytes(std::size_t size) const {
return std::span{reinterpret_cast<const std::byte*>(cptr), size > csize ? csize : size};
}
std::size_t GetSize() const {
return csize;
}
MemoryRef& operator+=(u32 offset_by) {
ASSERT(offset_by < csize);
offset += offset_by;
Init();
return *this;
}
MemoryRef operator+(u32 offset_by) const {
ASSERT(offset_by < csize);
return MemoryRef(backing_mem, offset + offset_by);

View File

@ -23,12 +23,3 @@ typedef void* HANDLE;
#include <microprofile.h>
#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0)
// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with
// identifiers we use.
#ifdef PAGE_SIZE
#undef PAGE_SIZE
#endif
#ifdef PAGE_MASK
#undef PAGE_MASK
#endif

View File

@ -110,8 +110,6 @@ add_library(core STATIC
frontend/input.h
frontend/mic.cpp
frontend/mic.h
frontend/scope_acquire_context.cpp
frontend/scope_acquire_context.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
hle/applets/applet.cpp

View File

@ -261,6 +261,8 @@ u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) const {
default:
UNREACHABLE_MSG("Unknown CP15 register: {}", reg);
}
return 0;
}
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {

View File

@ -849,17 +849,13 @@ static int InterpreterTranslateBlock(ARMul_State* cpu, std::size_t& bb_start, u3
// Save start addr of basicblock in CreamCache
ARM_INST_PTR inst_base = nullptr;
TransExtData ret = TransExtData::NON_BRANCH;
int size = 0; // instruction size of basic block
bb_start = trans_cache_buf_top;
u32 phys_addr = addr;
u32 pc_start = cpu->Reg[15];
while (ret == TransExtData::NON_BRANCH) {
unsigned int inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base);
size++;
u32 inst_size = InterpreterTranslateInstruction(cpu, phys_addr, inst_base);
phys_addr += inst_size;
if ((phys_addr & 0xfff) == 0) {
@ -972,7 +968,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
// GCC and Clang have a C++ extension to support a lookup table of labels. Otherwise, fallback to a
// clunky switch statement.
#if defined __GNUC__ || defined __clang__
#if defined __GNUC__ || (defined __clang__ && !defined _MSC_VER)
#define GOTO_NEXT_INST \
GDB_BP_CHECK; \
if (num_instrs >= cpu->NumInstrsToExecute) \

View File

@ -1218,7 +1218,7 @@ u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
u32 except;
char type;
[[maybe_unused]] char type;
type = (fop->flags & OP_SD) ? 's' : 'd';
if (op == FOP_EXT)

View File

@ -1242,7 +1242,7 @@ u32 vfp_single_cpdo(ARMul_State* state, u32 inst, u32 fpscr) {
for (vecitr = 0; vecitr <= veclen; vecitr += 1 << FPSCR_LENGTH_BIT) {
s32 m = vfp_get_float(state, sm);
u32 except;
char type;
[[maybe_unused]] char type;
type = (fop->flags & OP_DD) ? 'd' : 's';
if (op == FOP_EXT)

View File

@ -47,7 +47,7 @@ void CheatEngine::AddCheat(const std::shared_ptr<CheatBase>& cheat) {
void CheatEngine::RemoveCheat(int index) {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
if (index < 0 || index >= cheats_list.size()) {
if (index < 0 || index >= static_cast<int>(cheats_list.size())) {
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
return;
}
@ -56,7 +56,7 @@ void CheatEngine::RemoveCheat(int index) {
void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat) {
std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
if (index < 0 || index >= cheats_list.size()) {
if (index < 0 || index >= static_cast<int>(cheats_list.size())) {
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
return;
}
@ -64,12 +64,12 @@ void CheatEngine::UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_c
}
void CheatEngine::SaveCheatFile() const {
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
const std::string cheat_dir = Common::FS::GetUserPath(Common::FS::UserPath::CheatsDir);
const std::string filepath = fmt::format(
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
if (!FileUtil::IsDirectory(cheat_dir)) {
FileUtil::CreateDir(cheat_dir);
if (!Common::FS::IsDirectory(cheat_dir)) {
Common::FS::CreateDir(cheat_dir);
}
std::ofstream file;
@ -84,15 +84,15 @@ void CheatEngine::SaveCheatFile() const {
}
void CheatEngine::LoadCheatFile() {
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
const std::string cheat_dir = Common::FS::GetUserPath(Common::FS::UserPath::CheatsDir);
const std::string filepath = fmt::format(
"{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id);
if (!FileUtil::IsDirectory(cheat_dir)) {
FileUtil::CreateDir(cheat_dir);
if (!Common::FS::IsDirectory(cheat_dir)) {
Common::FS::CreateDir(cheat_dir);
}
if (!FileUtil::Exists(filepath))
if (!Common::FS::Exists(filepath))
return;
auto gateway_cheats = GatewayCheat::LoadFile(filepath);

View File

@ -248,7 +248,7 @@ System::ResultStatus System::SingleStep() {
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
FileUtil::SetCurrentRomPath(filepath);
Common::FS::SetCurrentRomPath(filepath);
app_loader = Loader::GetLoader(filepath);
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
@ -314,8 +314,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
if (Settings::values.custom_textures) {
const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id;
FileUtil::CreateFullPath(fmt::format(
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id));
Common::FS::CreateFullPath(fmt::format(
"{}textures/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), program_id));
custom_tex_cache->FindCustomTextures(program_id);
}
if (Settings::values.preload_textures) {
@ -424,8 +424,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
switch (result) {
case VideoCore::ResultStatus::ErrorGenericDrivers:
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
case VideoCore::ResultStatus::ErrorBelowGL43:
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
default:
return ResultStatus::ErrorVideoCore;
}

View File

@ -88,8 +88,6 @@ public:
ErrorVideoCore, ///< Error in the video core
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
/// generic drivers installed
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
/// OpenGL 4.3 or higher
ErrorSavestate, ///< Error saving or loading
ShutdownRequested, ///< Emulated program requested a system shutdown
ErrorUnknown ///< Any other error

View File

@ -301,10 +301,6 @@ private:
std::vector<std::shared_ptr<Timer>> timers;
Timer* current_timer = nullptr;
// Stores a scaling for the internal clockspeed. Changing this number results in
// under/overclocking the guest cpu
double cpu_clock_scale = 1.0;
// When true, the event queue can't be modified. Used while deserializing to workaround
// destructor side effects.
bool event_queue_locked = false;

View File

@ -45,14 +45,14 @@ void CustomTexCache::FindCustomTextures(u64 program_id) {
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
const std::string load_path = fmt::format(
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id);
"{}textures/{:016X}/", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), program_id);
if (FileUtil::Exists(load_path)) {
FileUtil::FSTEntry texture_dir;
std::vector<FileUtil::FSTEntry> textures;
if (Common::FS::Exists(load_path)) {
Common::FS::FSTEntry texture_dir;
std::vector<Common::FS::FSTEntry> textures;
// 64 nested folders should be plenty for most cases
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
Common::FS::ScanDirectoryTree(load_path, texture_dir, 64);
Common::FS::GetAllFilesFromNestedEntries(texture_dir, textures);
for (const auto& file : textures) {
if (file.isDirectory)

View File

@ -412,7 +412,7 @@ bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout&
InitializeFFmpegLibraries();
if (!FileUtil::CreateFullPath(path)) {
if (!Common::FS::CreateFullPath(path)) {
return false;
}

View File

@ -30,7 +30,7 @@ namespace FileSys {
*/
class FixSizeDiskFile : public DiskFile {
public:
FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode,
FixSizeDiskFile(Common::FS::IOFile&& file, const Mode& mode,
std::unique_ptr<DelayGenerator> delay_generator_)
: DiskFile(std::move(file), mode, std::move(delay_generator_)) {
size = GetSize();
@ -144,7 +144,7 @@ public:
break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, "r+b");
Common::FS::IOFile file(full_path, "r+b");
if (!file.IsOpen()) {
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
return ERROR_FILE_NOT_FOUND;
@ -248,7 +248,7 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) {
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
u64 program_id) {
std::string fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + "user/";
if (!FileUtil::Exists(fullpath)) {
if (!Common::FS::Exists(fullpath)) {
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
if (!shared) {
@ -270,12 +270,12 @@ ResultCode ArchiveFactory_ExtSaveData::Format(const Path& path,
// These folders are always created with the ExtSaveData
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
FileUtil::CreateFullPath(user_path);
FileUtil::CreateFullPath(boss_path);
Common::FS::CreateFullPath(user_path);
Common::FS::CreateFullPath(boss_path);
// Write the format metadata
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata";
FileUtil::IOFile file(metadata_path, "wb");
Common::FS::IOFile file(metadata_path, "wb");
if (!file.IsOpen()) {
// TODO(Subv): Find the correct error code
@ -289,7 +289,7 @@ ResultCode ArchiveFactory_ExtSaveData::Format(const Path& path,
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
u64 program_id) const {
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
FileUtil::IOFile file(metadata_path, "rb");
Common::FS::IOFile file(metadata_path, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
@ -305,7 +305,7 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Pat
void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, const u8* icon_data,
std::size_t icon_size) {
std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path);
FileUtil::IOFile icon_file(game_path + "icon", "wb");
Common::FS::IOFile icon_file(game_path + "icon", "wb");
icon_file.WriteBytes(icon_data, icon_size);
}

View File

@ -97,14 +97,14 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa
return ERROR_NOT_FOUND;
} else {
// Create the file
FileUtil::CreateEmptyFile(full_path);
Common::FS::CreateEmptyFile(full_path);
}
break;
case PathParser::FileFound:
break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
Common::FS::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
if (!file.IsOpen()) {
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
return ERROR_NOT_FOUND;
@ -141,7 +141,7 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const {
break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
if (Common::FS::Delete(full_path)) {
return RESULT_SUCCESS;
}
@ -168,7 +168,7 @@ ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path)
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
if (FileUtil::Rename(src_path_full, dest_path_full)) {
if (Common::FS::Rename(src_path_full, dest_path_full)) {
return RESULT_SUCCESS;
}
@ -218,12 +218,12 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
}
ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
return DeleteDirectoryHelper(path, mount_point, Common::FS::Delete);
}
ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
return DeleteDirectoryHelper(
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
path, mount_point, [](const std::string& p) { return Common::FS::DeleteDirRecursively(p); });
}
ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
@ -255,11 +255,11 @@ ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
}
if (size == 0) {
FileUtil::CreateEmptyFile(full_path);
Common::FS::CreateEmptyFile(full_path);
return RESULT_SUCCESS;
}
FileUtil::IOFile file(full_path, "wb");
Common::FS::IOFile file(full_path, "wb");
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
// We do this by seeking to the right size, then writing a single null byte.
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
@ -297,7 +297,7 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
break; // Expected 'success' case
}
if (FileUtil::CreateDir(mount_point + path.AsString())) {
if (Common::FS::CreateDir(mount_point + path.AsString())) {
return RESULT_SUCCESS;
}
@ -325,7 +325,7 @@ ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_p
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
if (FileUtil::Rename(src_path_full, dest_path_full)) {
if (Common::FS::Rename(src_path_full, dest_path_full)) {
return RESULT_SUCCESS;
}
@ -382,7 +382,7 @@ bool ArchiveFactory_SDMC::Initialize() {
return false;
}
if (!FileUtil::CreateFullPath(sdmc_directory)) {
if (!Common::FS::CreateFullPath(sdmc_directory)) {
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
return false;
}

View File

@ -69,7 +69,7 @@ bool ArchiveFactory_SDMCWriteOnly::Initialize() {
return false;
}
if (!FileUtil::CreateFullPath(sdmc_directory)) {
if (!Common::FS::CreateFullPath(sdmc_directory)) {
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
return false;
}

View File

@ -45,7 +45,7 @@ ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_direc
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) {
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
if (!FileUtil::Exists(concrete_mount_point)) {
if (!Common::FS::Exists(concrete_mount_point)) {
// When a SaveData archive is created for the first time, it is not yet formatted and the
// save file/directory structure expected by the game has not yet been initialized.
// Returning the NotFormatted error code will signal the game to provision the SaveData
@ -60,12 +60,12 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 pr
ResultCode ArchiveSource_SDSaveData::Format(u64 program_id,
const FileSys::ArchiveFormatInfo& format_info) {
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
FileUtil::DeleteDirRecursively(concrete_mount_point);
FileUtil::CreateFullPath(concrete_mount_point);
Common::FS::DeleteDirRecursively(concrete_mount_point);
Common::FS::CreateFullPath(concrete_mount_point);
// Write the format metadata
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
FileUtil::IOFile file(metadata_path, "wb");
Common::FS::IOFile file(metadata_path, "wb");
if (file.IsOpen()) {
file.WriteBytes(&format_info, sizeof(format_info));
@ -76,7 +76,7 @@ ResultCode ArchiveSource_SDSaveData::Format(u64 program_id,
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const {
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
FileUtil::IOFile file(metadata_path, "rb");
Common::FS::IOFile file(metadata_path, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Service_FS, "Could not open metadata information for archive");

View File

@ -58,7 +58,7 @@ ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string&
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path,
u64 program_id) {
std::string fullpath = GetSystemSaveDataPath(base_path, path);
if (!FileUtil::Exists(fullpath)) {
if (!Common::FS::Exists(fullpath)) {
// TODO(Subv): Check error code, this one is probably wrong
return ERR_NOT_FORMATTED;
}
@ -70,8 +70,8 @@ ResultCode ArchiveFactory_SystemSaveData::Format(const Path& path,
const FileSys::ArchiveFormatInfo& format_info,
u64 program_id) {
std::string fullpath = GetSystemSaveDataPath(base_path, path);
FileUtil::DeleteDirRecursively(fullpath);
FileUtil::CreateFullPath(fullpath);
Common::FS::DeleteDirRecursively(fullpath);
Common::FS::CreateFullPath(fullpath);
return RESULT_SUCCESS;
}

View File

@ -66,7 +66,7 @@ Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
}
Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
FileUtil::IOFile file(filepath, "rb");
Common::FS::IOFile file(filepath, "rb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;

View File

@ -58,7 +58,7 @@ bool DiskFile::Close() const {
////////////////////////////////////////////////////////////////////////////////////////////////////
DiskDirectory::DiskDirectory(const std::string& path) {
directory.size = FileUtil::ScanDirectoryTree(path, directory);
directory.size = Common::FS::ScanDirectoryTree(path, directory);
directory.isDirectory = true;
children_iterator = directory.children.begin();
}
@ -67,7 +67,7 @@ u32 DiskDirectory::Read(const u32 count, Entry* entries) {
u32 entries_read = 0;
while (entries_read < count && children_iterator != directory.children.cend()) {
const FileUtil::FSTEntry& file = *children_iterator;
const Common::FS::FSTEntry& file = *children_iterator;
const std::string& filename = file.virtualName;
Entry& entry = entries[entries_read];
@ -80,7 +80,7 @@ u32 DiskDirectory::Read(const u32 count, Entry* entries) {
break;
}
FileUtil::SplitFilename83(filename, entry.short_name, entry.extension);
Common::FS::SplitFilename83(filename, entry.short_name, entry.extension);
entry.is_directory = file.isDirectory;
entry.is_hidden = (filename[0] == '.');

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